Errors
HTTP status codes, per-operation result statuses, and how to handle them.
HTTP status codes
A 200 response does not guarantee every resource was reserved —
it means the request was processed. Check the status field on each result item.
| Status | Meaning | Action |
|---|---|---|
200 |
Request processed. Check per-item status fields. |
Inspect each result item's status. |
400 |
Malformed request: empty body, missing required field, or invalid value (e.g. ttlSeconds ≤ 0). |
Fix the request body. Error message is in the response string. |
401 |
Missing or invalid X-Api-Key. |
Check the header name and key value. Retrieve a fresh key from the dashboard. |
403 |
Unreserve: one or more requests supplied an invalid or missing release token. Body contains per-item results. | Provide the correct releaseToken from the original reserve call. |
404 |
Resource definition not found (delete endpoint). | Check the resource name. Use GET /api/v1/Resource/definitions to list registered names. |
429 |
Rate limit or tier capacity exceeded. | Respect Retry-After header. Check upgradeRequired and limitType in the body. |
503 |
Endpoint not available in the current store mode (e.g. tenant endpoints in InMemory mode). | Configure a database-backed store type (Sqlite or Postgres). |
Error response body
Non-200 responses carry a structured JSON body:
{
"error": "Daily operation limit reached (10000). Resets at midnight UTC.",
"tier": "Free",
"limit": 10000,
"retryAfter": 86394,
"upgradeRequired": true
}
upgradeRequired: true means the limit is tier-based and can be raised by upgrading.
Reserve result statuses
| Status | Success? | Meaning |
|---|---|---|
ACQUIRED | ✓ | Resource reserved. releaseToken is set. |
ALREADY_HAD | ✓ | Requester already holds this resource. Idempotent — existing token returned. |
ALREADY_HELD | ✗ | A different requester holds the resource. Retry later. |
NOT_AVAILABLE | ✗ | Capacity resource has no free slots. |
NOT_VALID | ✗ | Resource is not registered (only for resources that require registration). |
Unreserve result statuses
| Status | Success? | Meaning |
|---|---|---|
RELINQUISHED | ✓ | Resource released. released lists all freed physical resources. |
NOT_RESERVED | ✓ | Resource was already free. Safe and idempotent. |
DOESNT_OWN_RESERVATION | ✗ | The requester does not hold this resource. |
Rate limiting
Rate limits are enforced per {tenantId}:{tier}:{read|write}.
Write operations — Reserve and Unreserve calls — each count as one op toward your tier's ops/min limit.
A typical reserve-then-release cycle uses two ops.
Read operations (GET, HEAD, and POST .../check) use a separate bucket at 5× the write limit
(e.g. Pro write limit = 5,000/min; read limit = 25,000/min).
Read limits are an operational guard and are not part of the subscription SLA.
When a rate limit is hit, the server returns 429 with a Retry-After
header giving the number of seconds to wait. For per-minute limits, this is typically a few
seconds; for daily limits, it is the seconds until midnight UTC.
HTTP/1.1 429 Too Many Requests
Retry-After: 86394
{
"error": "Daily operation limit reached (10000). Resets at midnight UTC.",
"tier": "Free",
"limit": 10000,
"retryAfter": 86394,
"upgradeRequired": true
}
Concurrent reservation limit
Each tier has a cap on the total number of simultaneously held reservations.
If this limit is reached, new reserve calls return 429 with
"limitType": "ConcurrentReservations". Release existing reservations
or upgrade your tier to increase the limit.
| Tier | Concurrent reservations |
|---|---|
| Free | 500 |
| Starter | 10,000 |
| Pro | 100,000 |
| Enterprise | 1,000,000 |
Retrying safely
Reserve and unreserve are both safe to retry on network failures:
- Reserve is idempotent for the same requester + resource combination — re-reserving returns
ALREADY_HADwith the existing token - Unreserve on an already-free resource returns
NOT_RESERVED(success) — safe to retry - Unreserve with a wrong token returns
403— do not retry without the correct token