Handle Shopify API rate limits for both REST (leaky bucket) and GraphQL (calculated query cost). Use when hitting 429 errors, implementing retry logic, or optimizing API request throughput. Trigger with phrases like "shopify rate limit", "shopify throttling", "shopify 429", "shopify THROTTLED", "shopify query cost", "shopify backoff".
Shopify uses two distinct rate limiting systems: leaky bucket for REST and calculated query cost for GraphQL. This skill covers both with real header values and response shapes.
@shopify/shopify-api libraryREST Admin API -- Leaky Bucket:
| Plan | Bucket Size | Leak Rate |
|---|---|---|
| Standard | 40 requests | 2/second |
| Shopify Plus | 80 requests | 4/second |
The X-Shopify-Shop-Api-Call-Limit header shows your bucket state (e.g., 32/40 means 32 of 40 slots used). When full, you get HTTP 429 with header.
Retry-AfterGraphQL Admin API -- Calculated Query Cost:
| Plan | Max Available | Restore Rate |
|---|---|---|
| Standard | 1,000 points | 50 points/second |
| Shopify Plus | 2,000 points | 100 points/second |
Every GraphQL response includes cost info in extensions.cost with requestedQueryCost (worst-case estimate), actualQueryCost (real cost, often much lower), and throttleStatus (available points and restore rate). When currentlyAvailable drops to 0, you get THROTTLED.
Client-side rate limiter that tracks the query cost bucket and pre-emptively waits before sending requests that would be throttled. Updates available points from each response's throttleStatus.
See Cost-Aware Rate Limiter for the complete ShopifyRateLimiter class.
Generic retry wrapper handling both REST 429 responses and GraphQL THROTTLED errors. Uses Retry-After header when available, otherwise exponential backoff with jitter (max 30s).
See Retry with Backoff for the complete implementation.
Prune unused fields and lower first: page sizes to reduce requestedQueryCost. A query dropping from first: 250 to first: 50 with fewer nested fields can go from ~5,500 to ~112 cost.
See Query Cost Reduction for before/after examples and the debug curl command.
| Scenario | REST Indicator | GraphQL Indicator |
|---|---|---|
| Approaching limit | X-Shopify-Shop-Api-Call-Limit: 38/40 | currentlyAvailable < 100 |
| At limit | HTTP 429 + Retry-After: 2.0 | errors[0].extensions.code: "THROTTLED" |
| Recovering | Wait for Retry-After seconds | Wait for restoreRate to refill |
For large data exports, use Shopify's bulk query API which bypasses rate limits entirely:
import PQueue from "p-queue";
const BULK_QUERY = `
mutation bulkOperationRunQuery($query: String!) {
bulkOperationRunQuery(query: $query) {
bulkOperation { id status url }
userErrors { field message }
}
}
`;
await client.request(BULK_QUERY, {
variables: {
query: `{
products {
edges {
node {
id title
variants { edges { node { id sku price } } }
}
}
}
}`,
},
});