# Rate limits

How many requests your app can send, and how to handle 429 and 503 without losing work.

## Limits

Each request passes through several limits, and the first one exceeded returns 429 `RATE_LIMITED`:

| Limit | Capacity | Refill |
|---|---|---|
| Your app on one store, all requests | 20 requests | 5 per second |
| Your app on one store, writes | 5 requests | 1 per second |
| Your app across all stores | 100 requests | 50 per second |
| One store, across all apps | 40 requests | 20 per second |
| Per access token | 600 per minute | |
| Per IP address | 1200 per minute on the API, 600 on `/oauth/*` | |

Limits use a token bucket: you can send a burst up to the capacity once, then the bucket refills at the refill rate. An app that sends 4 requests per second to one store never sees a 429.

## Handling 429

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 1
```

Wait for the number of seconds in `Retry-After`, then resend the same request. For writes, resend with the same `Idempotency-Key` so the effect is not applied twice.

```js
async function call(url, init, attempt = 0) {
  const response = await fetch(url, init)
  if ((response.status === 429 || response.status === 503) && attempt < 5) {
    const wait = Number(response.headers.get('retry-after') ?? 1) * 1000 * 2 ** attempt
    await new Promise(resolve => setTimeout(resolve, wait + Math.random() * 250))
    return call(url, init, attempt + 1)
  }
  return response
}
```

## Handling 503

| Code | Meaning | What to do |
|---|---|---|
| `STORE_MIGRATING` | The store is moving between servers | Retry after a few seconds |
| `POLICY_UNAVAILABLE` | The app policy could not be verified right now | Retry after a second |
| `APPS_DISABLED` | Reads or writes are temporarily disabled by LARM | Retry later, without rapid retries |
| `APPS_SCOPE_RUNTIME_NOT_READY` | This resource is not enabled on this merchant's store yet | Do not retry immediately; retry after a few hours |

## Best practices

- Subscribe to [webhooks](/guides/webhooks) instead of polling. The `order.created` webhook reaches you within seconds, while polling the API every minute consumes your limit on every store.
- For periodic sync, use `updated_after` to fetch only what changed.
- Spread heavy jobs across stores over time, rather than hitting every store at once at midnight.
