Every error returns a consistent JSON envelope with a stable machine-readable code you can branch on:
{
  "error": {
    "code": "scope_not_consented",
    "message": "scope 'crew:documents:read' is not consented for this crew member",
    "scope": "crew:documents:read"
  }
}
Some codes add fields (scope, retry_after_seconds). Branch on code, not on message. Every response also carries an X-Request-Id header — include it when contacting support.

Catalogue

CodeHTTPMeaning
invalid_api_key401Missing, malformed, or unknown API key.
invalid_signature401Bad/missing HMAC signature, stale timestamp, or replay.
api_not_enabled403Your plan does not include API access.
scope_not_granted403Your plan does not grant the required scope.
scope_not_consented403The crew member hasn’t consented to that scope for you.
not_found404The vessel/crew isn’t attributed to you (or doesn’t exist).
partner_rate_limited429You exceeded your rate-limit budget.
partner_mode_mismatch403A test-mode key was used against a live environment.
Malformed request bodies (failing schema validation) return FastAPI’s standard 422 response rather than this envelope.