---
title: Rate limits
description: Per-tier request budgets, the response headers, and how the SDK handles 429s.
section: Reference
order: 2
---
Every API key is rate-limited per organization tier with a 1-hour sliding window. The SDK auto-retries short waits; longer waits raise so your code can decide.

## Per-tier limits

| Tier | Requests / hour |
| --- | --- |
| Free | 1,000 |
| Core | 5,000 |
| Pro | 20,000 |
| Enterprise | 100,000 |

## Response headers

Every successful response carries the current state of the window. Read them off the underlying response if you're tracking your own consumption — most users can ignore them and let the SDK retry automatically.

| Header | Meaning |
| --- | --- |
| `X-RateLimit-Limit` | Cap for the current window |
| `X-RateLimit-Remaining` | Calls left in the current window |
| `X-RateLimit-Reset` | Unix timestamp when the window resets |
| `Retry-After` | (429 only) seconds until the next call may succeed |

## What counts

One HTTP request → one count. Payload size doesn't matter. Streaming downloads (image / model / export blobs) count as a single request regardless of size.

Bulk operations are designed to keep counts low — prefer `client.batch.move()` over N `client.images.update()` calls, and prefer the [workflows](/docs/workflows.md) over hand-rolled loops.

## SDK auto-retry

`RateLimitError` carries a `retry_after` attribute. The SDK waits and retries automatically when the response includes a `Retry-After` header **and** the wait is at most 120 seconds. Anything longer raises immediately so your code can back off, queue, or fail.

```python
from pictograph.exceptions import RateLimitError
import time

try:
    client.datasets.list(limit=1000)
except RateLimitError as e:
    print(f"Hit cap; retry in {e.retry_after}s")
    time.sleep(e.retry_after)
    # …then retry. The SDK won't auto-recover for waits >120s.
```

The `pictograph` CLI inherits the same behaviour. On a long wait it prints the retry estimate to stderr so you can Ctrl-C if you don't want to wait.

## Spreading bursty load

If your workload is bursty (nightly imports, large auto-annotation runs), pace it across the hour:

```python
from time import sleep

for batch in batches:
    process(batch)
    sleep(0.5)  # ~7,200 req/hr ceiling — comfortably under Core
```

For sustained workloads above your tier, the right move is to upgrade — retrying harder doesn't increase your share.

## See also

- [Error handling](/docs/error-handling.md) — the full exception hierarchy
- [Credits](/docs/api-reference/credits.md) — paid-operation budgeting