# Rate limits and retries

[Open in the interactive reference](https://docs.api.lensing.ai/guides/limits)

Requests are rate-limited per client IP address, across all endpoints, and every client behind the same address shares the limit. A client over the limit is refused with `429` until its request rate falls back below the limit.

The limit is not part of the contract and can change, so do not build against a number. Keep concurrency modest, and prefer one paged walk to many parallel requests.

A `429` comes from the WAF in front of the API, not from an endpoint. It may carry a `Retry-After` header naming a fixed number of seconds to wait, and a JSON body with `title`, `status` and `detail` fields — this is rolling out environment by environment, so do not assume either is present. Honor `Retry-After` when it is present — it is a fixed value, not a countdown of time actually left in the window — and otherwise retry after a delay that doubles with each attempt. Add random jitter so that clients do not retry in step, and give up after a few attempts.

### Retrying other failures

| Status | What to do |
| --- | --- |
| `429` | Back off and retry, as above. |
| `409 IDEMPOTENCY_KEY_CONFLICT` | Wait, then retry with the same key. |
| `500` | Retry once after a short wait, and retry a write only with the same `Idempotency-Key`. A `500` can also mean the API was briefly unavailable while your environment was being changed, which passes on its own. |
| `400, 401, 403, 404, 422` | Do not retry unchanged: the same request fails the same way. |

While your environment is being updated, a request waits up to 45 seconds for that to finish before it runs or fails. Set client timeouts longer than that.

## Examples

**A 429, where the WAF sends one**

```
HTTP/1.1 429 Too Many Requests
Retry-After: <seconds>
Content-Type: application/json

{ "title": "Too Many Requests", "status": 429,
  "detail": "…" }
```

**Backing off, in Python**

```
import random, time

def send_with_backoff(send, attempts=6):
    delay = 1.0
    for attempt in range(attempts):
        response = send()
        if response.status_code != 429 or attempt == attempts - 1:
            return response
        retry_after = response.headers.get('Retry-After')
        wait = float(retry_after) if retry_after else delay + random.uniform(0, delay)
        time.sleep(wait)
        delay *= 2
```
