Errors
Read the RFC 9457 problem envelope, branch on stable codes, and use request IDs to get support help fast.
Errors use application/problem+json following RFC 9457. The HTTP status tells you the class; the code tells you what happened, and it is stable within an API version.
Media type
application/problem+json
RFC 9457
Contract
code
detail and title can be reworded
Status
Tells you the class
The code separates what a 403 means
Correlation
requestId, or X-Request-ID
Log it with every failure
Explanation page
type under /developers/problems
Explains each documented code
The envelope
{
"type": "https://docs.ulsav.com/developers/problems/api_key_forbidden",
"title": "Forbidden",
"status": 403,
"code": "api_key_forbidden",
"detail": "this API key lacks the required capability",
"requestId": "9f2c4e7a1b8d4c6e9a0f3b5d7c1e2a48",
"errors": []
}| Field | Meaning |
|---|---|
type | Absolute link to the explanation page for this code |
title | HTTP reason phrase, for humans |
status | The same status as the HTTP response |
code | Stable machine identifier; branch on this |
detail | Human-readable explanation; wording may change |
requestId | Correlation identifier; omitted when unavailable |
errors | Array of field-level details; empty for non-validation problems |
type points at /developers/problems/<code>, for example /developers/problems/api_key_forbidden. The problem catalogue documents the codes that have an explanation page, with their usual status and what to do about it.
Branch on code, never detail
detail and title are for logs and people. They can be reworded or made more specific without a version bump. code is the contract.
const problem = await response.json();
switch (problem.code) {
case "api_key_expired":
case "api_key_revoked":
// Stop retrying and provision a new key.
break;
case "api_key_scope_denied":
// The key is valid but scoped to another resource.
break;
case "rate_limited":
// Wait for Retry-After, then retry.
break;
}The same distinction applies to status codes. A 403 can mean a missing capability, a wrong Scope, a suspended Organization, or an operation that does not accept API keys. The code separates them.
Codes you will meet early:
| Code | Meaning |
|---|---|
api_key_invalid, api_key_expired, api_key_revoked | The key itself is unusable |
api_key_forbidden | The key lacks the capability, the operation does not accept keys, or key management was attempted with a key |
api_key_scope_denied | The key's Scope does not cover the target |
capability_denied | A human session lacks the required capability |
invalid_cursor | The cursor is malformed, from another collection, or stale |
idempotency_conflict | An Idempotency-Key was reused with different details |
idempotency_key_conflict | A webhook replay key already belongs to another delivery |
rate_limited | Back off for Retry-After |
internal_error | Retry with backoff, then report it with the request ID |
Framework-level rejections reuse request_invalid with whatever status they return, so a 401 or 409 can carry it instead of a domain code.
Validation errors
A request that fails schema validation returns 422 request_invalid with a populated errors array. Each entry carries a message and a location such as body.items[3].tags, and may echo the offending value, so you can map it to a form field without parsing prose.
{
"type": "https://docs.ulsav.com/developers/problems/request_invalid",
"title": "Unprocessable Entity",
"status": 422,
"code": "request_invalid",
"detail": "validation failed",
"requestId": "3c7d1a9e5b2f4c8d0a6e1f3b9c5d7a24",
"errors": [
{
"message": "expected length >= 1",
"location": "body.name"
}
]
}Send the request ID
When you contact support, include the requestId from the problem body or the X-Request-ID response header, the operation you called, and the approximate time. Do not send the API key or a bearer token.
Questions
Should I branch on detail or on code?
On code. detail and title are for logs and people and can be reworded or made more specific without a version bump, while code is the contract.
Why did my request return a 403?
A 403 can mean a missing capability, a wrong Scope, a suspended Organization, or an operation that does not accept API keys. The code separates them.
Where does the type field point?
To the explanation page for that code under /developers/problems, for example /developers/problems/api_key_forbidden.
What should I send support with a failing request?
The requestId from the problem body or the X-Request-ID header, the operation you called, and the approximate time. Do not send the API key or a bearer token.