Errors
Every error is an RFC 9457 problem-details document. Branch on the stable `code`, not on prose.
The API returns errors as RFC 9457 problem-details documents, served with
Content-Type: application/problem+json; charset=utf-8. Every error — a
validation failure, a missing key, a rate limit — uses the same envelope.
The envelope
{
"type": "/errors/invalid_request",
"title": "Invalid request",
"status": 400,
"detail": "Title is required.",
"code": "invalid_request",
"details": { }
}| Member | Meaning |
|---|---|
type | A URI reference identifying the problem type, e.g. /errors/not_found. |
title | A short, stable, human-readable summary of the problem type. |
status | The HTTP status code, as a number. |
detail | A human-readable explanation specific to this occurrence. |
code | Extension member — the stable, machine-branchable error code. |
details | Extension member, optional — a validation-error tree (present on 400s). |
Branch on code, not on title or detail. code is a stable contract;
the human-readable strings may change. type mirrors code as /errors/<code>.
Internal (500) errors never leak details: detail is always
"Something went wrong." and the real cause is logged server-side only.
Status codes
| Status | code | When |
|---|---|---|
400 | invalid_request | Body/query failed validation (carries a details tree), or a malformed pagination cursor. |
401 | unauthorized | Missing or invalid API key / not signed in. |
402 | plan_limit_exceeded | Well-formed and authorized, but the org's plan doesn't allow it (quota hit or gated feature) — upgrade, don't retry. |
403 | forbidden | The key lacks the required scope, or the key has expired. |
404 | not_found | The resource isn't in this gallery, or you can't see it. Also hides other galleries' ids. |
409 | conflict | A conflict, e.g. a slug collision or an Idempotency-Key reused on a different endpoint. |
429 | rate_limited | Over the rate limit — carries Retry-After and the RateLimit headers. See Rate limits. |
500 | internal | An unexpected server error. detail is generic. |
503 | service_unavailable | The rate limiter had to run but couldn't (fails closed); carries Retry-After: 5. |
Validation errors
A 400 invalid_request from a body or query-string validation failure includes
a details tree describing which fields failed and why, so you can map errors
back to your input:
{
"type": "/errors/invalid_request",
"title": "Invalid request",
"status": 400,
"detail": "Invalid request body.",
"code": "invalid_request",
"details": {
"title": { "_errors": ["String must contain at most 200 character(s)"] }
}
}Handling errors
- Inspect
statusfor the class of failure andcodefor the specific reason. - Retry only
429(afterRetry-After) and503; treat4xxother than429as a request you must fix, not retry. - When retrying a write, send an
Idempotency-Keyso a retry can't create the same resource twice. - Never parse
detail/titlefor control flow — they're for humans and logs.