Errors

RFC 7807 problem details, common status codes, and how to interpret error bodies.

All non-2xx responses are served as application/problem+json and follow RFC 7807 Problem Details for HTTP APIs.

Envelope

{
  "type": "https://api.benjipays.com/problems/<slug>",
  "title": "<short title>",
  "status": 401,
  "detail": "<human-readable explanation>",
  "instance": "/v2/<path>",
  "request_id": "5e3e7c8e-1a04-4f7e-9c1f-9e0c5b9b1a23",
  "correlation_id": "trace-abc-123"
}
FieldTypeDescription
typestring (URI)Identifies the problem class. Stable per error category.
titlestringShort, human-readable summary of the class.
statusintegerHTTP status code.
detailstringExplanation specific to this occurrence.
instancestring (URI)URI identifying this specific failure.
request_idstringEchoed X-Request-ID — included whenever the middleware has set it.
correlation_idstringEchoed X-Correlation-ID — included whenever the middleware has set it.

Both request_id and correlation_id are always set by the middleware, so a single ID will identify a failure across your logs, the API's logs, and any support ticket. See Request Tracing.

Common statuses

StatusWhen
400 Bad RequestValidation error. May include an errors[] extension listing the offending fields.
401 UnauthorizedMissing, invalid, or expired credentials; or organization API access is not active (disabled or missing). See Authentication.
403 ForbiddenWrong actor type, missing OAuth scope, insufficient mapping permission for the API key owner, or free trial ended without billing enabled.
404 Not FoundUnknown route or tenant-scoped resource not visible to the caller.
409 ConflictIdempotency-key body conflict, or a domain conflict. See Idempotency.
429 Too Many RequestsRate limit exceeded, or an in-flight idempotent request with the same key. Includes Retry-After. See Rate Limits.
500 Internal Server ErrorUnexpected failure. Safe to retry idempotent requests.

Examples

400 with field-level details

{
  "type": "https://api.benjipays.com/problems/validation",
  "title": "Validation error",
  "status": 400,
  "detail": "Request body failed validation",
  "instance": "/v2/partners",
  "errors": [
    { "path": ["body", "externalId"], "message": "Required" }
  ],
  "request_id": "9c6d…",
  "correlation_id": "trace-abc-123"
}

401 unauthorized

HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
{
  "type": "https://api.benjipays.com/problems/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Invalid or missing authentication token",
  "instance": "/v2/whoami"
}

403 forbidden — wrong actor

{
  "type": "https://api.benjipays.com/problems/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "Access denied. Required actor type: partner",
  "instance": "/v2/partners/accounts"
}

403 forbidden — missing scope

{
  "type": "https://api.benjipays.com/problems/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "Insufficient scope",
  "instance": "/v2/partners/accounts"
}

403 forbidden — free trial ended without billing

Organization-scoped merchant routes (/v2/invoices, /v2/gateways, etc.) return this when the organization's free trial has ended and billing is not enabled:

{
  "type": "https://api.benjipays.com/problems/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "Free trial has ended and billing is not enabled",
  "instance": "/v2/gateways"
}

429 too many requests

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 1
{
  "type": "https://api.benjipays.com/problems/too_many_requests",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit exceeded. Please retry after the specified time.",
  "instance": "/v2/whoami",
  "retry_after_seconds": 1
}

Best practices

  • Parse application/problem+json responses by their status and type — don't string-match on detail.
  • Distinguish 401 (re-authenticate) from 403 (you can't do this with these credentials).
  • Use request_id / correlation_id from the body when filing support tickets.
  • Retry 429 only after honoring Retry-After. Retry 500 only on idempotent operations.
  • Treat unknown problem type URIs as if they were just the HTTP status — the platform may add new categories.