Skip to content

Rate Limits ​

The InvoisX API implements rate limiting to ensure fair usage and system stability.

Default Limits ​

Limit TypeLimitWindow
API Requests60 requestsPer minute
Document SubmissionsBased on planPer month

Rate Limit Headers ​

Every API response includes rate limit information:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1704067200
HeaderDescription
X-RateLimit-LimitMaximum requests allowed
X-RateLimit-RemainingRequests remaining in window
X-RateLimit-ResetUnix timestamp when limit resets

Rate Limit Exceeded ​

When you exceed the rate limit:

Response (HTTP 429):

json
{
  "success": false,
  "message": "Too many requests. Please try again in 60 seconds.",
  "error_code": "RATE_LIMIT_EXCEEDED"
}

Headers:

Retry-After: 60
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704067200

Handling Rate Limits ​

JavaScript ​

javascript
async function apiWithRetry(method, endpoint, data, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await api(method, endpoint, data);
    } catch (error) {
      if (error.response?.status === 429) {
        const retryAfter = error.response.headers['retry-after'] || 60;
        console.log(`Rate limited. Retrying in ${retryAfter}s...`);
        await new Promise(r => setTimeout(r, retryAfter * 1000));
      } else {
        throw error;
      }
    }
  }
  throw new Error('Max retries exceeded');
}

Python ​

python
import time

def api_with_retry(method, endpoint, data=None, max_retries=3):
    for attempt in range(max_retries):
        try:
            return api(method, endpoint, data)
        except RateLimitError as e:
            retry_after = e.retry_after or 60
            print(f'Rate limited. Retrying in {retry_after}s...')
            time.sleep(retry_after)
    raise Exception('Max retries exceeded')

PHP ​

php
function apiWithRetry($api, $method, $endpoint, $data = null, $maxRetries = 3) {
    for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
        $response = $api->$method($endpoint, $data);

        if ($response->status() !== 429) {
            return $response;
        }

        $retryAfter = $response->header('Retry-After') ?: 60;
        echo "Rate limited. Retrying in {$retryAfter}s...\n";
        sleep($retryAfter);
    }

    throw new Exception('Max retries exceeded');
}

Document Quota ​

In addition to API rate limits, document submissions are limited by your subscription plan:

PlanMonthly Quota
Starter100 documents
Professional500 documents
Business2,000 documents
EnterpriseUnlimited

Checking Quota ​

The submission endpoint returns quota information:

json
{
  "success": false,
  "message": "Document quota exceeded. You have 0 documents remaining out of 100.",
  "error_code": "QUOTA_EXCEEDED",
  "errors": {
    "quota": {
      "used": 100,
      "limit": 100,
      "remaining": 0,
      "resets_at": "2024-02-01T00:00:00+08:00"
    }
  }
}

Best Practices ​

1. Implement Exponential Backoff ​

javascript
async function apiWithBackoff(method, endpoint, data) {
  const delays = [1000, 2000, 4000, 8000];

  for (let i = 0; i < delays.length; i++) {
    try {
      return await api(method, endpoint, data);
    } catch (error) {
      if (error.response?.status === 429 && i < delays.length - 1) {
        await new Promise(r => setTimeout(r, delays[i]));
      } else {
        throw error;
      }
    }
  }
}

2. Queue Requests ​

For bulk operations, queue requests to stay within limits:

javascript
class RequestQueue {
  constructor(requestsPerMinute = 60) {
    this.queue = [];
    this.processing = false;
    this.delay = (60 / requestsPerMinute) * 1000;
  }

  async add(fn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ fn, resolve, reject });
      this.process();
    });
  }

  async process() {
    if (this.processing) return;
    this.processing = true;

    while (this.queue.length > 0) {
      const { fn, resolve, reject } = this.queue.shift();
      try {
        resolve(await fn());
      } catch (e) {
        reject(e);
      }
      await new Promise(r => setTimeout(r, this.delay));
    }

    this.processing = false;
  }
}

// Usage
const queue = new RequestQueue(50); // 50 requests per minute

const results = await Promise.all(
  documentIds.map(id =>
    queue.add(() => api('POST', `/documents/${id}/validate`))
  )
);

3. Cache Reference Data ​

Reference data endpoints (states, countries, etc.) rarely change. Cache them:

javascript
let statesCache = null;
let statesCacheTime = 0;
const CACHE_TTL = 3600000; // 1 hour

async function getStates() {
  const now = Date.now();
  if (!statesCache || now - statesCacheTime > CACHE_TTL) {
    statesCache = await api('GET', '/states');
    statesCacheTime = now;
  }
  return statesCache;
}

4. Batch Operations ​

Use bulk endpoints where available:

javascript
// Instead of:
for (const id of documentIds) {
  await api('POST', `/documents/${id}/submit`);
}

// Use:
await api('POST', '/documents/submit-bulk', {
  document_ids: documentIds
});

Need Higher Limits? ​

Contact us for custom rate limits and enterprise pricing:

InvoisX - Malaysia's Leading e-Invoice Platform