The API enforces two rate limit buckets. Both are keyed by store, not by token, not by IP, and not by user.
| Bucket | Limit | Applies to |
|---|---|---|
| General | 60 requests per minute | Every /v1 endpoint |
| Payments | 30 requests per minute | POST /v1/payments/charge and POST /v1/orders/{order_id}/upsell |
Because the key is the store, all of a store’s tokens share one budget. Issuing a second token to a second integration does not buy more throughput; it splits the same 60/minute between them.
The two buckets stack
The two payment endpoints sit inside the general group and carry the stricter payment throttle, so a single charge consumes one slot in both buckets.
In practice:
- 30 charges in a minute exhausts the payment bucket and leaves 30 general requests available for reads.
- 60 reads in a minute exhausts the general bucket and blocks charges too, even though the payment bucket still has room.
Budget for the sum, not for either limit alone.
Windows
Each bucket is a fixed one-minute window. Once the window rolls over, the full allowance is restored at once — there is no leaky-bucket smoothing and no burst credit. Rather than pacing off the clock, read Retry-After from the 429 and sleep for exactly that long.
Headers
Every response — successful or not — carries the current bucket state:
| Header | Present on | Meaning |
|---|---|---|
X-RateLimit-Limit |
All responses | Requests allowed in the current window |
X-RateLimit-Remaining |
All responses | Requests left in the current window |
Retry-After |
429 only |
Seconds until the window resets |
X-RateLimit-Reset |
429 only |
Unix timestamp when the window resets |
On the two payment endpoints the headers report whichever bucket is more restrictive at that moment, so a charge response typically shows X-RateLimit-Limit: 30. Treat the headers as advisory for the request you just made rather than as a full picture of both buckets.
The 429 response
Exceeding either bucket returns 429 Too Many Requests:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 37
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1786425600{
"message": "Too Many Attempts."
}That is the whole body. There is no error key, no error code, and no detail about which bucket you hit — the only signal is X-RateLimit-Limit, which tells you whether it was the 60 or the 30 bucket.
Important: This JSON body only appears when the request carried
Accept: application/json. Without that header you get an HTML error page with a429status. See Errors.
Some published FirearmCart material describes a rate_limit_exceeded error envelope with a retry_after field in the body. No such envelope exists. The 429 body is exactly the single-key object above, and the retry delay is only ever in the Retry-After header.
Handling 429 in your client
#!/usr/bin/env bash
# Retry once on 429, honouring Retry-After.
response=$(curl -s -D /tmp/h -o /tmp/b -w '%{http_code}' \
"https://api.firearmcart.com/v1/orders" \
-H "Authorization: Bearer $FIREARMCART_TOKEN" \
-H "Accept: application/json")
if [ "$response" = "429" ]; then
delay=$(grep -i '^retry-after:' /tmp/h | tr -d '\r' | awk '{print $2}')
sleep "${delay:-60}"
# ... reissue the request
fiRules that keep integrations healthy:
- Always honour
Retry-After. Retrying sooner just burns another slot and extends the window. - Never retry a 429 on a payment endpoint blindly.
POST /v1/payments/chargehas no idempotency key, so a retry that races the original can double-charge.POST /v1/orders/{order_id}/upsellrequires anidempotency_keyand is safe to retry with the same key. - Page with the largest
per_pageyou can use.per_page=100costs one request instead of four. See Pagination. - Cache the slow-moving reads.
GET /v1/shippingandGET /v1/taxationreturn small, rarely-changing payloads and are not paginated; fetch them on a schedule rather than per checkout.
Other buckets
The Theme Dev MCP server at https://api.firearmcart.com/mcp/themes uses its own, more generous limit of 240 requests per minute per store, because agent sessions make many small tool calls. It does not draw on the /v1 budget, and the /v1 budget does not draw on it — exhausting one leaves the other untouched. See MCP rate limits for the unauthenticated key and how requests map to tool calls.
Storefront and webhook traffic are limited separately and never consume your API budget.
Related documentation
- Errors — every failure shape, per status code
- Pagination — fetching more per request
- Authentication — the store-scoped token model that defines the key
- Theme Dev MCP server — the 240/min bucket in context

