> ## Documentation Index
> Fetch the complete documentation index at: https://docs.samsa.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> Per-key request limits, per-organization concurrency caps, the headers to watch, and how to back off.

The Samsa API protects shared capacity with two independent limits: a **per-key
request rate** and a **per-organization concurrent-job cap**. Both return
[`429`](/guides/errors#rate_limited) with headers that tell you when to retry.

<Info>
  These limits are defaults and are **subject to change**. If your integration needs
  a higher ceiling, contact [support@samsa.ai](mailto:support@samsa.ai).
</Info>

## Per-key request rate

Each API key may make up to **60 requests per minute**, measured as a sliding
60-second window. Exceeding it returns `429` with `code: "rate_limited"`.

Successful responses and rate-limit `429`s carry the current window state (the
`Retry-After` header is added on `429`s only):

```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 41
X-RateLimit-Reset: 1782043260
```

| Header                  | Meaning                                                            |
| ----------------------- | ------------------------------------------------------------------ |
| `X-RateLimit-Limit`     | Requests allowed per window (default `60`).                        |
| `X-RateLimit-Remaining` | Requests left in the current window.                               |
| `X-RateLimit-Reset`     | Unix timestamp (seconds) when the window resets.                   |
| `Retry-After`           | Seconds to wait before retrying. **Sent on `429` responses only.** |

Read `X-RateLimit-Remaining` on successful responses to slow down *before* you hit
the limit.

## Per-organization concurrency cap

Independently of request rate, an organization may have at most **5 concurrent
in-flight jobs** — generation, edit, video, and model-creation jobs that are still
`pending` or `processing`, counted across every key in the organization. Submitting
another job while at the cap returns `429` with `code: "too_many_active_jobs"` and a
`Retry-After` header:

```json 429 Too Many Requests theme={null}
{
  "error": {
    "type": "rate_limit_error",
    "code": "too_many_active_jobs",
    "message": "Too many active jobs for this organization (5/5). Wait for in-flight jobs to finish before submitting more.",
    "request_id": "req_801c9a3b7d2d5f4a"
  }
}
```

This cap protects processing capacity, so it is keyed to the whole organization, not a
single key. Wait for in-flight jobs to reach a terminal status — poll their `GET`
endpoint, or subscribe to a [webhook](/guides/webhooks) — before submitting more.

## An example 429

A `429` from the per-key window includes both the `Retry-After` and `X-RateLimit-*`
headers:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1782043260
Content-Type: application/json

{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "API rate limit exceeded. Slow down and retry after the rate-limit window resets.",
    "request_id": "req_5f4a801c9a3b7d2d"
  }
}
```

## Handling 429s

<Steps>
  <Step title="Honor Retry-After">
    When a `429` includes a `Retry-After` header, wait at least that many seconds
    before retrying. It is the authoritative signal.
  </Step>

  <Step title="Back off exponentially">
    For repeated `429`s, increase the delay between attempts (for example
    1s, 2s, 4s, 8s…), capped at a sensible maximum, with a little random jitter to
    avoid thundering-herd retries.
  </Step>

  <Step title="Stay under the limit proactively">
    Watch `X-RateLimit-Remaining` and throttle client-side before you hit `0`. For
    the concurrency cap, bound how many jobs you keep in flight at once.
  </Step>
</Steps>

Below is a minimal retry loop that honors `Retry-After` and falls back to exponential
backoff.

<CodeGroup>
  ```python Python theme={null}
  import time
  import requests


  def request_with_retry(method, url, *, headers, max_retries=5, **kwargs):
      for attempt in range(max_retries + 1):
          resp = requests.request(method, url, headers=headers, **kwargs)
          if resp.status_code != 429 or attempt == max_retries:
              return resp
          retry_after = resp.headers.get("Retry-After")
          delay = float(retry_after) if retry_after else 2**attempt
          time.sleep(delay)
      return resp
  ```

  ```typescript TypeScript theme={null}
  async function requestWithRetry(
    url: string,
    init: RequestInit,
    maxRetries = 5,
  ): Promise<Response> {
    for (let attempt = 0; ; attempt++) {
      const resp = await fetch(url, init);
      if (resp.status !== 429 || attempt === maxRetries) return resp;
      const retryAfter = resp.headers.get("Retry-After");
      const delayMs = retryAfter ? Number(retryAfter) * 1000 : 2 ** attempt * 1000;
      await new Promise((r) => setTimeout(r, delayMs));
    }
  }
  ```
</CodeGroup>

## Outer per-IP protection

Beyond these per-key and per-organization limits, Samsa applies a coarse **per-IP**
protection layer at the network edge (shared with the rest of the platform). It is a
backstop against abusive traffic, not a limit you tune per integration — well-behaved
server-side clients that respect the limits above will not encounter it. If you route
many organizations through a single egress IP and see unexpected throttling, contact
[support@samsa.ai](mailto:support@samsa.ai).
