Rate limits
Every authenticated request counts toward 500 per minute per API key (per user for signed-in dashboard sessions). Operations that create quotes, uploads and orders, or move money, also count toward 60 per minute. Public sign-up, catalog and shipping endpoints are limited per IP address. Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; past a limit the answer is 429 rate_limited with a Retry-After header.
The limits
Limits are counted in fixed windows. Each operation lists the limits it counts toward in x-ratelimit (limit requests per window_seconds, per API key or user, or per IP address); these are the defaults.
| Applies to | Limit | Counted per |
|---|---|---|
| Every authenticated request | 500 per minute | API key, or user for dashboard sessions |
| Creating quotes, uploads and orders, cancelling orders, top-ups, card setup and withdrawals (on top of the above) | 60 per minute | API key, or user |
Integrator sign-up (create_integrator_account) | 10 per hour | IP address |
Resending the confirmation email (resend_integrator_confirmation) | 5 per hour | IP address |
| Catalog and shipping reads (materials, categories, shipping rates and quotes) | 120 per minute | IP address |
get_health and the OpenAPI document are not limited.
Every limited response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds) for the limit closest to being reached. Past a limit the API answers 429 with code rate_limited, a Retry-After header in seconds, and details with limit, window_seconds, reset_at and retry_after_seconds. A refused request does not count. A request replayed with the same Idempotency-Key counts like any other request.
Quotes, uploads, orders and payments
These operations start slicing, create files or move money, so they also count toward a tighter limit per API key (or signed-in user). Each request still counts toward the general limit too.
- POSTQuote a model file
/v2/quotes - POSTCreate an order
/v2/orders - POSTCancel an order
/v2/orders/{id}/cancel - POSTOrder prints of your quotes
/v2/integrator/orders - POSTCancel an order
/v2/integrator/orders/{id}/cancel - POSTBuy stock batches
/v2/stock-purchases - POSTStart adding a card
/v2/cards/setup-sessions - POSTTop up your wallet
/v2/top-ups - POSTRequest a withdrawal
/v2/withdrawals - POSTStart a file upload
/v2/uploads
Public endpoints
Endpoints that need no key are limited per IP address:
- POSTSign up as an integrator
10 per hour - POSTResend the confirmation email
5 per hour - GETGet the shipping rate card
120 per minute - GETGet a shipping price
120 per minute - GETList catalog materials
120 per minute - GETGet a catalog material
120 per minute - GETList product categories
120 per minute - GETGet a product category
120 per minute
Handling a 429
Watch X-RateLimit-Remaining and slow down before it reaches 0. When a request is refused, wait for the number of seconds in Retry-After, then send it again. Retry a write with the same Idempotency-Key, so it never runs twice.
async function callWithRetry(url, init, attempts = 5) {
for (let attempt = 1; ; attempt++) {
const res = await fetch(url, init);
if (res.status !== 429 || attempt === attempts) return res;
// Retry-After is in seconds; wait at least that long.
const seconds = Number(res.headers.get('retry-after') ?? 1);
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
}{
"status_code": 429,
"error": "Too Many Requests",
"code": "rate_limited",
"message": "Too many requests with this API key: the limit is 500 per minute. Retry after 42 seconds (at 2026-10-22T10:01:00.000Z).",
"details": {
"limit": 500,
"window_seconds": 60,
"reset_at": "2026-10-22T10:01:00.000Z",
"retry_after_seconds": 42,
"policy": "per_credential",
"request_id": "req_6Yb2kQe0VxLr4TnPz8sWa1Mc"
}
}