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