Skip to content

Errors

Every non-2xx response carries the same JSON envelope:

{
"error": {
"code": "scope_required",
"message": "Missing required scope",
"details": { "scope": "photos:write" }
}
}
FieldTypePurpose
codestringMachine-readable identifier. Branch on this in your code.
messagestringHuman-readable explanation. Don’t branch on this — wording may change.
detailsobject | nullOptional structured context for the error (missing scope, validation issues, …).
HTTPcodeWhen
400validation_failedRequest body or query failed schema validation. details lists per-field issues.
400invalid_cursorPagination cursor expired or malformed. Restart pagination without cursor.
401unauthorizedMissing, malformed, or unknown API key.
403scope_requiredKey valid but lacks the scope the endpoint requires. details.scope names the missing one.
403ip_not_whitelistedSource IP not in the key’s whitelist.
404not_foundResource does not exist, or the key cannot see it (multi-tenant isolation).
409conflictOperation conflicts with current state (e.g. tag with that name already exists).
413payload_too_largeUpload or request body exceeds the per-endpoint limit.
415unsupported_mediaContent-Type not accepted by this endpoint.
429rate_limitedRequest budget exhausted. See Rate limits.
403tariff_not_supportedCustomer’s subscription tier does not include this feature. Upgrade the plan or remove the call. Not retryable.
500internal_errorUnexpected server failure. Safe to retry with backoff; if persistent, contact support with the requestId.

5xx responses include an X-Request-Id header. Include it when reporting issues — it lets us trace the failure end-to-end in seconds rather than minutes.

HTTP/1.1 500 Internal Server Error
X-Request-Id: req_01HXYZ123ABC
Content-Type: application/json
{ "error": { "code": "internal_error", "message": "Something went wrong" } }

validation_failed returns per-field detail in details.issues:

{
"error": {
"code": "validation_failed",
"message": "Request body failed validation",
"details": {
"issues": [
{ "path": "name", "message": "Required" },
{ "path": "scopes", "message": "Must contain at least one scope" }
]
}
}
}

path is the dotted JSON path inside the request body or ?query= for query parameters.

  • Branch on code, not message. Messages are tuned for humans and may change. Codes are part of the API contract.
  • Don’t retry 4xx. A bad request stays bad. Only 429 and transient 5xx make sense to retry.
  • Log the requestId from 5xx responses. It’s the fastest path to a fix when something goes wrong.