---
title: Rate limiting
description: 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
```
```json
{
  "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
```javascript
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));
  }
}
```
