Rate limits, errors and versioning
The operational contract: limits and their headers, idempotent retries, the error catalogue, and how the API versions.
Rate limits
Every key may make 25 requests per second, enforced as a token bucket that refills continuously, so short bursts above the rate are fine as long as the average holds. Every /v1 response reports where you stand:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | The bucket size (25) |
X-RateLimit-Remaining | Requests left right now |
X-RateLimit-Reset | Unix seconds until the bucket is full again |
Past the limit you get a 429 with Retry-After in seconds. Honour it and back off; the bucket refills within a second under any normal load. Limits are per key, so a busy integration can have a key of its own.
Idempotent retries
Send an Idempotency-Key header (any string up to 255 characters) with a POST and the API replays the first response for 24 hours to any retry carrying the same key, marked with Idempotent-Replayed: true. Reusing a key with a different request is a 422, never a silent overwrite. Keys are scoped to your API key, so two systems using plain counters never collide.
Use it on anything that must not happen twice: creating sales, partners, or payout batches on a network you do not trust to deliver your response.
Errors
Every error is the same shape:
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "limit must be an integer between 1 and 100",
"param": "limit"
}
}param names the offending parameter when there is one. The nine types:
| Type | Status | When |
|---|---|---|
authentication_error | 401 | Missing, malformed, unknown or revoked key |
permission_error | 403 | The key lacks the scope; the message names the one it needed |
invalid_request_error | 400, 422 | A parameter or body that does not parse or validate; a reused idempotency key |
not_found_error | 404 | No such object in your organization (resource_missing) |
conflict_error | 409 | The write contradicts current state |
rate_limit_error | 429 | Over 25 requests per second |
plan_error | 402 | The account's plan has lapsed; the key is paused, nothing is deleted |
not_implemented_error | 501 | A documented capability not switched on for this deployment |
api_error | 500 | Our fault; safe to retry with the same idempotency key |
An id that exists but belongs to another organization returns the same 404 as one that never existed.
Versioning
Every response carries X-Rail-Version with a date (currently 2026-08-21). Additive changes, new fields, new endpoints, new enum values, do not bump it, so build parsers that ignore what they do not recognise. A breaking change becomes a new version, announced ahead of time, with the old behaviour kept for existing keys.
Live and test mode
Every response carries X-Rail-Livemode (true or false) from the key that made the request. Test keys see the same organization; assert on the header in your staging environment and a production key in staging fails loudly instead of quietly polluting live data.