API fundamentals
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/1.1 429 Too Many Requests
Retry-After: 1Wait 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.
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 instead of polling. The
order.createdwebhook reaches you within seconds, while polling the API every minute consumes your limit on every store. - For periodic sync, use
updated_afterto fetch only what changed. - Spread heavy jobs across stores over time, rather than hitting every store at once at midnight.