telnesstech

Rate limiting

The request limits on the Seamless OS API, and how to retry when you reach one.

The Seamless OS API limits how many requests one API key can send. The limits sit well above what an integration needs, so they catch a runaway caller and leave normal traffic alone.

How it works

The limits are high. A normal integration never reaches one. You do not have to pace your requests to stay under a limit during ordinary work.

The limits protect every tenant. One caller that sends too many requests degrades the service for everybody. We find that pattern and limit it. We do not restrict legitimate traffic.

The window resets on its own. A short spike in your traffic has no lasting effect on your integration.

When limits apply

Each limit applies per API key. The limits catch these callers:

  • A runaway script, or an infinite loop.
  • A bulk operation that sends every request at once.
  • A request volume far above your normal business pattern.

Rate limit exceeded

When you reach a limit, the API answers 429 Too Many Requests with a Retry-After header. The header gives the wait in seconds:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
  "message": "Rate limit exceeded",
  "code": "RATE_LIMIT_EXCEEDED",
  "hint": "Wait before retrying or reduce request frequency"
}

Best practices

Obey Retry-After. When the API answers 429, wait the number of seconds in the header before you send the request again.

Use exponential backoff. If a response carries no Retry-After header, back off exponentially and add jitter. The jitter prevents a thundering herd.

Send bulk work in batches. Process a bulk operation in batches with a delay between them. Do not send every request at the same time.

Cache what does not change. A cached response is one request that you do not send.

Example retry logic

async function makeRequestWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status !== 429) {
      return response;
    }

    if (attempt === maxRetries) {
      throw new Error('Rate limit exceeded after max retries');
    }

    // Use Retry-After header if provided, otherwise exponential backoff with jitter
    const retryAfter = response.headers.get('Retry-After');
    const backoff = Math.pow(2, attempt) * 1000;
    const delay = retryAfter ? parseInt(retryAfter) * 1000 : backoff + Math.random() * backoff;

    await new Promise((resolve) => setTimeout(resolve, delay));
  }
}