Ulsav Docs

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": []
}
FieldMeaning
typeAbsolute link to the explanation page for this code
titleHTTP reason phrase, for humans
statusThe same status as the HTTP response
codeStable machine identifier; branch on this
detailHuman-readable explanation; wording may change
requestIdCorrelation identifier; omitted when unavailable
errorsArray 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:

CodeMeaning
api_key_invalid, api_key_expired, api_key_revokedThe key itself is unusable
api_key_forbiddenThe key lacks the capability, the operation does not accept keys, or key management was attempted with a key
api_key_scope_deniedThe key's Scope does not cover the target
capability_deniedA human session lacks the required capability
invalid_cursorThe cursor is malformed, from another collection, or stale
idempotency_conflictAn Idempotency-Key was reused with different details
idempotency_key_conflictA webhook replay key already belongs to another delivery
rate_limitedBack off for Retry-After
internal_errorRetry 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.

On this page