Rate limits
The daily caps, the 429 answer and its headers, and what is never limited.
The limits are generous on purpose. A normal shop never meets them. A looping script or a runaway AI app does within minutes.
Storefront API
No Storefront API operation has an application limit today. If one is added, it will say so on the operation, and it will answer as described below.
Merchant API
There are two kinds of limit. Both are counted per UTC day, and reset at 00:00 UTC, which is 02:00 in South Africa.
Per store, per day, for everyone
These count against the store, not the person or the app.
| Allowance | Per day | Operations |
|---|---|---|
aiGenerations | 300 | draftProduct, generateProductCopy, generateImage, designForMe, buildPageWithAi, startStudioMake |
emailSends | 100 | sendCampaign, sendTestEmail, remindBaskets, remindToPay. One per call, whatever the number of recipients |
whatsappSends | 500 | sendWhatsAppMessage |
A call that is then refused or fails gives its place back. Bulk product imports have a daily cap too (20 runs a store), but it is applied in the console, not on this API.
Per connected AI app, per day
These apply only to a connection made through Merchant OAuth. The ShopEazy app's own sign-in has none.
| Allowance | Per day |
|---|---|
addPhotos | 200 photos |
bookCollection | 50 courier bookings |
Credits are separate. A connection also has a monthly credit limit that the merchant sets, which answers 403 over_credit_limit, not 429.
The 429 answer
Every limit answers 429 rate_limited:
HTTP/1.1 429 Too Many Requests
Retry-After: 21600
RateLimit-Limit: 300
RateLimit-Remaining: 0
RateLimit-Reset: 21600
X-Request-Id: 7c9e6679-7425-40de-944b-e07fc1f90ae7{
"error": {
"code": "rate_limited",
"message": "This store has used its 300 AI generations for today. They come back at midnight UTC (02:00 in South Africa).",
"requestId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"details": { "limit": 300, "remaining": 0, "retryAfter": 21600 }
}
}| Header | Meaning |
|---|---|
Retry-After | Seconds to wait before you try again |
RateLimit-Limit | The cap for the window. Left out when a limit cannot name its cap |
RateLimit-Remaining | Calls left in the window. 0 when you are refused |
RateLimit-Reset | Seconds until the window resets. The same number as Retry-After |
error.details carries limit, remaining and retryAfter. For a daily cap, retryAfter is the wait until the next 00:00 UTC.
A refused call did nothing and used nothing. Send the same call again after the wait, with the same Idempotency-Key if it has one.
On the Storefront API a browser may read these headers, as the API exposes them.
What is never limited
Payment-provider callbacks, webhooks and /health are never rate limited. A provider that gets a 429 retries or gives up, which would lose or delay a payment.