Registry API v1
Errors
Every error uses the same envelope, so a single handler can cover the whole API.
Error envelope
Shape
{
"error": {
"code": "invalid_query",
"message": "One or more query parameters are invalid.",
"issues": [{ "path": "limit", "message": "Number must be less than or equal to 100" }]
}
}code is stable and safe to branch on; message is human-readable and may be reworded. Some codes add extra keys: issues (validation), fields (protected fields), limit and retry_after_seconds (rate limiting).
Authentication and permissions
| Status | Code | How to handle |
|---|---|---|
| 401 | missing_api_key | Add the Authorization or x-api-key header. |
| 401 | invalid_api_key | Check the key value; do not retry automatically. |
| 401 | revoked_api_key | The key was revoked. Request a new one. |
| 401 | expired_api_key | The key passed its expiry date. |
| 403 | organisation_inactive | Contact Collect24; API access is not active. |
| 403 | missing_scope | Your key lacks the required scope. |
| 403 | not_permitted | The artwork is outside your organisation's profile. |
| 403 | no_write_target | No gallery or collection profile is linked to your key. |
Requests and validation
| Status | Code | How to handle |
|---|---|---|
| 400 | invalid_query | Unknown or out-of-range query parameter. Fix the client. |
| 400 | invalid_json | The body was not valid JSON. |
| 404 | artwork_not_found | Unknown id, or the work left the public projection. |
| 404 | artist_not_found | Unknown artist slug or id. |
| 404 | gallery_not_found | Unknown or non-public gallery profile. |
| 404 | museum_not_found | Unknown or non-public museum profile. |
| 422 | invalid_body | Field-level validation failed; see issues. |
| 422 | protected_field | Remove the listed Collect24-managed fields. |
Rate limiting, idempotency and server errors
| Status | Code | How to handle |
|---|---|---|
| 429 | rate_limited | Wait for Retry-After / retry_after_seconds, then retry. Back off, do not hammer. |
| 409 | idempotency_key_reused | Same key, different body. Use a new key. |
| 409 | idempotent_request_in_progress | The first call is still running; retry after a short pause. |
| 500 | internal_error | Retry with exponential backoff; contact us if it persists. |
Retry policy
Retry 429 and 5xx with exponential backoff and jitter. Do not retry 400, 401, 403, 404 or 422: those need a change on your side. When retrying a POST, reuse the same Idempotency-Key so a duplicate record can never be created.
Handling errors
const response = await fetch(url, { headers });
if (response.status === 429) {
const wait = Number(response.headers.get("retry-after") ?? 30);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
// retry
}
if (!response.ok) {
const { error } = await response.json();
switch (error.code) {
case "missing_scope":
case "not_permitted":
throw new Error(`Permission problem: ${error.message}`);
case "protected_field":
throw new Error(`Remove these fields: ${error.fields.join(", ")}`);
default:
throw new Error(`${error.code}: ${error.message}`);
}
}
