Idempotency, rate limits and errors
The parts of the Supplier API that keep an unattended integration honest: how to retry a write without shipping a parcel twice, how much you can send, and what every error looks like.
Request and response conventions
Success envelope. Every 2xx response wraps the result:
{ "statusCode": 200, "message": "Success", "data": { ... }, "timestamp": "2026-09-07T09:01:20.963Z" }message is "Created" on a 201 and "Success" otherwise. The shapes in these guides describe data.
Validation is strict. A request body or query string containing a property the endpoint does not know is a 400, with the message property <name> should not exist. Numbers in a JSON body must be JSON numbers, not strings. Path ids must be UUIDs; anything else answers 400 Validation failed (uuid is expected).
Ids are scoped to you. A purchase order, webhook subscription or delivery that belongs to someone else answers the same 404 as one that never existed.
Idempotency-Key
A write that times out on your side may or may not have been applied on Orbit's. For POST /api/supplier-api/v1/orders/{id}/shipments, the one call where a blind retry could ship the same parcel twice, send an Idempotency-Key header and retry freely.
POST /api/supplier-api/v1/orders/3d5f7e9c-1b0a-4c8e-9c4e-2b1a7f3d4c8e/shipments HTTP/1.1
Idempotency-Key: wms-despatch-88213
Content-Type: application/json| Rule | Behaviour |
|---|---|
| Format | 1 to 255 printable ASCII characters. Anything else is a 400. Use something your system already has, such as the despatch or parcel id. |
| Scope | Per API key. Keys from different systems, or different suppliers, can never collide. |
| First request | Runs normally. Its response is stored for 24 hours. |
| Same key again within 24 hours | The stored response is returned unchanged, with the header Idempotency-Replayed: true, and nothing is written again. |
| Same key while the first is still running | 409 A request with this Idempotency-Key is still being processed. Wait and retry. |
| The first request failed | Nothing is stored. A retry with the same key runs for real, so a validation error corrected and resent goes through. |
Two things to design around:
- The body is not compared. A replay with the same key and a different body returns the first response. Never reuse a key for a different parcel; derive it from the parcel, not from a counter you might reset.
- Only the shipments endpoint reads the header. The tracking endpoint is safe to retry without it because an unchanged row is
skipped, acknowledging is only accepted once, and stock updates set absolute levels. Webhook creation is guarded by the one-per-topic rule.
Rate limits
Limits are per key, in fixed one-minute windows aligned to the clock:
| Requests | Limit |
|---|---|
Reads (GET) | 600 per minute |
Writes (POST, PATCH, DELETE) | 300 per minute |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (the window's end, as a Unix timestamp in seconds). Past the limit you receive:
{
"statusCode": 429,
"timestamp": "2026-09-07T09:31:02.410Z",
"path": "/api/supplier-api/v1/stock/bulk",
"method": "POST",
"message": "Rate limit exceeded. Max 300 requests per minute for Supplier API endpoints.",
"retryAfter": 14
}with a matching Retry-After header in seconds. Sleep for that long and continue. Bulk endpoints exist so the limit rarely matters: 500 stock rows or 500 tracking rows per call, 100 lines per shipment.
Three smaller ceilings apply on top:
- Webhook test pings: 10 a minute per key.
- Redeliveries: 30 a minute per subscription.
- A platform-wide limit of 600 requests a minute per client IP address, in front of everything. Its
429is a plain message without theX-RateLimit-*headers. If several systems call Orbit from behind one NAT address, spread their schedules so they do not burst together.
Errors
Errors other than 401 share one shape. message is a string for a rule violation and an array of strings for validation failures, one per problem. Extra fields such as retryAfter appear when they apply.
{
"statusCode": 400,
"timestamp": "2026-09-07T09:33:15.020Z",
"path": "/api/supplier-api/v1/orders/3d5f7e9c-1b0a-4c8e-9c4e-2b1a7f3d4c8e/shipments",
"method": "POST",
"message": [ "lines must contain at least 1 elements" ]
}A 401 is shorter, with no path:
{ "statusCode": 401, "error": "unauthorized", "message": "Invalid or expired token", "timestamp": "2026-09-07T09:33:15.020Z" }| Status | When | What to do |
|---|---|---|
400 | Validation, an unknown property, a non-UUID id, or a business rule such as over-shipping, acknowledging a non-sent order, an unknown product id in a stock batch, a badly formed Idempotency-Key. | Fix the request. Do not retry as-is. |
401 No API key provided | The Authorization header is missing or is not Bearer …. | Fix the header. |
401 Invalid or expired token | The key is unknown, revoked, or past its expiry. The message is deliberately the same for all three. | Stop and alert. Retrying will not help. |
403 Missing required scopes: … | The key is valid but lacks the scope this endpoint needs. The message names it. | Mint a key with the scope. Scopes cannot be added to a live key. |
404 | The id does not exist for this supplier: order, line item on this order, subscription, delivery. | Treat as gone. |
409 | Idempotency request still running; a subscription for this topic already exists; a redelivery on a paused, disabled or dead-key subscription; more than 10 active keys. | Wait or resolve the state, then retry. |
429 | A limit above. | Honour Retry-After. |
500 | An unhandled error. The body carries a requestId. | Retry with backoff; quote the requestId if you contact support. |
A retry policy that fits
429and500: retry with exponential backoff, usingRetry-Afterwhen present.- Network timeouts on a shipment: retry with the same
Idempotency-Key. - Network timeouts on anything else: retry as-is; the other writes are safe to repeat.
400,401,403,404: do not retry. Log, alert, move to the next item so one bad row never stalls a batch.
Next steps
- The endpoints these rules apply to: Purchase orders and shipments, Stock updates, Webhooks.
- Field-level schemas: Supplier API Reference.