How to Optimize Shopify API Rate Limits When Developing Apps
Avoiding 429 errors is just the starting point. Strategically optimizing Shopify API rate limits is essential to ensure your app runs efficiently, scales reliably, and is built to handle future growth.
This becomes especially important when you’re developing a public app as you need to take into account different scenarios and use cases that could potentially overwhelm the app because of too many requests.
While it’s important to implement a safety net to ensure the app never runs into 429 errors, optimizing the rate limit for maximum performance requires some math and thoughtful strategy.
In this article, we’ll briefly discuss:
- API rate limits in Shopify
- Why those limits exist in the first place
- Strategies to handle and optimize Shopify API rate limits
Introduction to Shopify API Rate Limits
For starters, API rate limits define how many requests your app is allowed to make to Shopify’s servers within a specific time frame.
If your app exceeds that limit and you send more requests than allowed, you run into “429 Too Many Requests” errors.
This is how Shopify maintains the performance and stability of their infrastructure. Without these limits in place, any app with no algorithm for responsibly managing API requests could easily overwhelm Shopify’s servers, leading to downtime.
These rate limits encourage developers to:
- Use responsible coding best practices to optimize the use of resources
- Build efficient, scalable, and respectful applications that can coexist smoothly within Shopify’s ecosystem
- Build for long-term success, not short-term hacks
So what exactly are Shopify’s API rate limits?
Shopify enforces different rate limiting systems depending on the type of API you’re using, primarily the REST Admin API and the GraphQL Admin API.
REST Admin API: Request-Based Rate Limiting
The REST API uses a leaky bucket algorithm to manage traffic. Here’s how it works:
- Each app/store combination is given a bucket of 40 requests.
- This bucket “leaks” at a rate of 2 requests per second.
- For Shopify Plus stores, the bucket size increases to 80 and the leak rate to 4 requests per second.
If your app fills the bucket (i.e., makes 40 requests quickly), any additional request will trigger a 429 Too Many Requests error. You’ll need to wait for the bucket to drain before retrying.
Every REST response includes the header:
X-Shopify-Shop-Api-Call-Limit: 10/40
This means you’ve used 10 out of 40 available requests.
GraphQL Admin API: Points-Based Rate Limiting
The GraphQL API uses a calculated query cost model, where:
- Every query has a “cost” based on its complexity.
- Apps are given a bucket of 1,000 points.
- Points restore at a rate of 50 per second.
- Fetching data or queries cost just 1 point, while mutations like creating, updating or deleting data cost 100 points each.
This system allows for much more flexibility and efficiency.
With the granular control GraphQL provides, you can avoid overfetching and request the specific data points you need.
Every GraphQL response includes cost data in the extensions field:
{
"extensions": {
"cost": {
"requestedQueryCost": 15,
"actualQueryCost": 12,
"throttleStatus": {
"maximumAvailable": 1000,
"currentlyAvailable": 988,
"restoreRate": 50
}
}
}
}
In this post, we’ll only talk about optimizing GraphQL rate limits as Shopify has marked REST API as a legacy API and they are phasing out its support.
6 Strategies for Optimizing Shopify API Rate Limits
Once you understand how Shopify’s rate limits work, the next step is to design your app in a way that avoids bottlenecks, maintains reliability under load, and takes full advantage of available capacity without triggering errors.
Here are four proven strategies for optimizing API usage, particularly when working with the GraphQL Admin API.
1. Use Cost Estimation and Throttling
The simplest strategy for optimizing Shopify API rate limits is to use your full quota efficiently while throttling only when necessary. This ensures your app performs at its best without hitting the dreaded 429 Too Many Requests errors.
Using your available limit to its fullest is crucial for performance. If your system waits for each API request to complete before starting the next one (i.e., synchronous execution), you’re unlikely to hit the limit, but you’re also underutilizing the quota, which leads to slower processing and a sluggish user experience.
Instead, you want to fire asynchronous requests in parallel to consume available points more quickly. However, this aggressive approach introduces the risk of exceeding your available points.
That’s why you need to implement a safety net, that is, you monitor the remaining available points (currently Available), and when it drops below a predefined threshold, you pause execution briefly to let the bucket refill.
How to Implement This Strategy
Here’s a conceptual implementation using Node.js or any modern JavaScript runtime (like in a worker or serverless function).
- Define Your Safety Threshold
This is the buffer before you stop making requests.
const SAFETY_THRESHOLD = 100; // Pause if points drop below this
- Track the Available Points Dynamically
After each GraphQL request, read the throttleStatus:
const {
currentlyAvailable,
restoreRate
} = response.extensions.cost.throttleStatus;
- Throttle If Too Close to the Limit
This logic ensures you only pause when you’re running out of points.
if (currentlyAvailable < SAFETY_THRESHOLD) {
const waitTime = Math.ceil((SAFETY_THRESHOLD - currentlyAvailable) / restoreRate);
console.log(`Approaching rate limit, sleeping for ${waitTime} seconds...`);
await new Promise(resolve => setTimeout(resolve, waitTime * 1000));}
- Fire Asynchronous Requests While Monitoring Load
Here’s a basic loop that processes a batch of requests:
async function processBatch(products) {
for (const product of products) {
// Run requests in parallel up to a safe concurrency limit
await Promise.allSettled(
products.slice(0, 5).map(product => sendGraphQLRequest(product))
);
}
}
You can increase concurrency as long as you’re tracking rate limits and pausing if needed. With this strategy, you get maximum throughput and efficiency.
However, it has its downsides too:
- Overlapping requests can exceed limits due to unpredictable query costs, causing 429 errors.
- Inconsistent performance due to sleep-based throttling that introduces variable response times.
- Blocking behavior pauses delay processing, especially in high-volume workflows.
- Harder to scale as it requires per-store tracking and coordination to avoid flooding.
This strategy works best for simple use cases. For more complex apps and functionality, consider one of the following strategies.
2. Use Background Jobs with Backoff Control
Shopify’s rate limits are per store. Each store gets its own independent GraphQL quota (1,000 points, restoring at 50 points/sec). So, if one store (say, Store A) hits its API limit, it doesn’t affect your ability to call APIs for Store B or C.
Now imagine you have 30 background workers processing API tasks for hundreds of stores. Without intelligent control, all workers might get blocked just because one store runs out of credits. That’s inefficient.
Here is how this strategy works:
- If a store hits its API limit, we pause jobs for that store only.
- Meanwhile, workers continue processing jobs for other stores that still have API bandwidth.
How to Implement This Strategy
- Track Backoff State on the Store
Add a field to your Shop model that stores the time until which, that store should pause API calls.
# db migration
add_column :shops, :api_backoff_untill, :datetime
- Add Backoff Logic to the Model
Create a concern to encapsulate reusable logic:
# app/models/concerns/api_backoff.rb
module ApiBackoff
extend ActiveSupport::Concern
REST_CREDITS_THRESHOLD = 10
REST_WAIT_TIME = 20.seconds
GRAPHQL_CREDITS_THRESHOLD = 100
def api_backoff?
return false unless api_backoff_untill
api_backoff_untill > Time.current
end
def check_rest_rate_limit
if ShopifyAPI.credit_left < REST_CREDITS_THRESHOLD
update!(api_backoff_untill: REST_WAIT_TIME.from_now)
end
end
def check_graphql_rate_limit(response)
return unless graphql_response.points_maxed?(threshold: GRAPHQL_CREDITS_THRESHOLD)
backoff_seconds = (graphql_response.points_limit - GRAPHQL_CREDITS_THRESHOLD) / graphql_response.points_restore_rate
update!(api_backoff_untill: backoff_seconds.seconds.from_now)
end
end
Include this module in your Shop model:
# app/models/shop.rb
class Shop < ApplicationRecord
include ApiBackoff
end
3. Apply Backoff in Background Jobs
Modify your jobs to respect the backoff window:
# app/jobs/graphql_api_job.rb
class GraphqlApiJob < ApplicationJob
def perform(shop)
if shop.api_backoff?
self.class.set(wait: shop.api_backoff_untill).perform_later(*arguments)
return
end
shop.with_shopify_session do
response = GetProduct.call(id: "...")
product = response.data
# do something with product
end
shop.check_graphql_rate_limit(response)
end
end
Downsides of the Backoff Job Strategy
- No pre-query estimation:
It reacts after requests are made, not before so a costly query might still slip through. - Outdated database values:
In high-concurrency environments, the backoff field can become stale between fetch and execution. - Rescheduling adds latency:
Pausing and requeuing adds delay, especially if many shops are throttled at once.
When to Use This Strategy
This strategy is ideal for:
- Apps connected to many stores.
- High-volume operations where parallelism is required.
- Scenarios that need per-store API isolation for stability and efficiency.
It’s an excellent fit for scaling apps like bulk editors, auto-sync tools, or order processing pipelines across a large merchant base.
3. Use Bulk Operations to Avoid Rate Limits Altogether
The most powerful way to avoid Shopify API rate limits is to not hit them at all, that is, by using Bulk Operations.
Bulk Operations let you perform large-scale data fetching or mutations outside the constraints of per-request rate limits. Shopify handles these in the background, meaning your app just needs to initiate the request and wait for results.
Why This Strategy Works
Instead of fetching 10,000 products using paginated GraphQL queries (which could consume thousands of points), you can:
- Trigger a Bulk Query job that runs server-side in Shopify
- Wait for it to complete
- Download the full dataset from a provided JSONL file URL
Similarly, for mutations:
- You upload a CSV or JSONL file with mutation parameters
- Shopify executes them internally in bulk
This offloads the heavy lifting to Shopify’s infrastructure freeing your app from rate-limit worries.
How Bulk Operations Work
For Queries:
- Submit a GraphQL bulk query
- Poll for status or subscribe to a webhook
- Once complete, download and process the JSONL result file
For Mutations:
- Create a staged upload
- Upload your JSONL mutation payload
- Submit a bulk mutation operation referencing the file
- Monitor status and handle results
Downsides of Bulk Operations
- Complex setup: Especially for mutations, you need to handle file staging, async uploads, and format strictness.
- One operation at a time: Only one active bulk query and one bulk mutation per store which may limit use cases with overlapping data syncs.
- Limited mutation support: Not all GraphQL mutations are available for bulk use e.g., some order-related actions aren’t supported.
When to Use This Strategy
Bulk Operations are ideal for:
- Exporting or importing thousands of records at once
- Initial data syncs (e.g. on app install)
- Apps that generate reports, run analytics, or manage catalogs in bulk
It’s the best fit when you prioritize performance and scale over real-time interaction.
Your app will benefit the most when paired with strategies like async processing, polling, or webhooks to track job status.
4. Optimize Data Retrieval to Lower Query Costs
Shopify’s GraphQL API charges based on the complexity of your query, not just the number of requests. That means bloated queries, even if technically valid, consume more API points than necessary. One of the most efficient ways to avoid hitting rate limits is to only request the fields you need, avoiding overfetching.
How to Implement
When building GraphQL queries:
- Avoid requesting full objects when only a few fields are needed.
- Use fragments and pagination to request data in lightweight, modular blocks.
- Profile your queries using the extensions.cost object returned by Shopify.
query {
product(id: "gid://shopify/Product/123") {
id
title
status
}
}
This query costs less than fetching the entire product object with all associated media, metafields, variants, etc.
Downsides
- Requires discipline during development especially when using generated code or 3rd-party SDKs that default to overfetching.
- Developers must stay aware of Shopify schema changes to maintain optimal queries.
5. Implement Smart Caching to Eliminate Redundant API Calls
Why hit the Shopify API for the same data multiple times when it hasn’t changed? Caching frequently accessed responses reduces pressure on your rate limits and improves app response time. The more effective your caching strategy, the less you rely on the API for common lookups.
By caching frequently accessed data (especially read-heavy endpoints), you:
- Decrease API load.
- Improve response times.
- Minimize the risk of hitting rate limits.
How to Implement
- Identify non-volatile endpoints (e.g. product info, shop settings, policy pages).
- Cache GraphQL responses at the request level using a query string or hash key.
- Use a cache layer (memory store, Redis, CDN, etc.) to store responses temporarily.
- Set custom TTLs (time-to-live) based on how often data changes.
# Example: Cache product queries for 10 minutes
# Example: Cache product queries for 10 minutes
cache:
rules:
- match: /graphql/product
ttl: 600 # seconds
When using this strategy, make sure to use conditional invalidation rules (e.g., clear cache when product updates occur).
Also, avoid caching sensitive or real-time data like inventory levels or checkout state.
Cached responses reduce total API calls, improve speed, and protect against rate-limiting during peak load.
6. Regulate Request Rates with Priority Queuing
Instead of firing API requests as they come which can result in spikes that blow past your rate limits you can smooth out traffic by queuing and prioritizing jobs. High-priority tasks (like checkout-related actions) get executed immediately, while non-urgent operations (like syncing metafields) wait their turn.
How to Implement
- Use a priority queue system to group API calls into high, medium, and low importance.
- Defer or batch non-urgent jobs like metafield updates, SEO syncs, or tag cleanups.
- Ensure only a few jobs per store run concurrently to avoid per-store rate collisions.
{
"priority": "high",
"request": {
"query": "...",
"variables": {}
}
}
Downsides
- Misclassification of priorities can lead to unnecessary delays.
- Queue systems require careful tuning to avoid starvation or over-throttling.
Wrapping Up
Shopify’s API rate limits aren’t just a constraint they’re an opportunity. With the right approach, you can build apps that are not only efficient and scalable but resilient under real-world pressure. Whether you’re handling thousands of background jobs, syncing product data across hundreds of stores, or building an app to support high-volume merchants, strategic optimization is key.
At Codup, we’ve helped Shopify app developers and B2B brands navigate these challenges through performance-first architecture, smart job queues, and tailored rate limit strategies. If you’re planning to launch or scale a public or custom Shopify app, working with a team that understands both the technical and business nuances makes all the difference.
Frequently Asked Questions
If your app exceeds Shopify’s API rate limits, you’ll receive a 429 Too Many Requests error. This means your app has hit the threshold of allowed calls and must wait before sending more. Ignoring this can lead to app performance issues or throttling.
For REST APIs, check the X-Shopify-Shop-Api-Call-Limit
header in your response.
For GraphQL APIs, use the extensions.cost.throttleStatus
object in the response, which shows current usage, restore rate, and remaining points.
GraphQL Admin API is better for optimization. It uses a points-based system, allowing more granular control and efficiency. REST API is being phased out and is less flexible for high-volume operations.
Not entirely, but you can work around them using strategies like:
-
Bulk Operations for large-scale data jobs
-
Caching frequent requests
-
Background job queuing with backoff logic
These help minimize risk of hitting rate limits.
Contributors
-
Calister Maloney
writer
Content Lead