Errors
Quick summary
Start with the HTTP code, then the domain error code. Error bodies differ in some cases between Worker, Axum, and SDK.
A denied security operation must remain a denial; handle 401, 403, 409, and NOT_AVAILABLE explicitly instead of treating them as success.
Error bodies carry {code, message} where the backend implements it (optionally nested under error); internal reasons are never exposed. The authoritative three-column matrix (Axum be/ vs Worker vs SDK) is contract matrix §2 — this page is the operational digest.
HTTP → meaning
| Status | Typical codes | Meaning |
|---|---|---|
400 | BAD_JSON/BAD_MSGPACK/BAD_PROTOBUF (Axum codecs), VALIDATION, INVALID_PUBLIC_JWK (SDK) | Malformed body or field |
401 | UNAUTHORIZED | Auth/proof/binding/replay/body-hash failure — one opaque code for every auth check on the Worker and SDK |
402 | NOT_LIVE_PAYMENT, PAYMENT_PROVIDER_UNAVAILABLE | Payment rail refused by configuration (live disabled / provider unimplemented) |
403 | FORBIDDEN, PRODUCT_MISMATCH, REVOKED, SUSPENDED, EXPIRED, INVALID_STATUS, DEVICE_LIMIT, CAPABILITY_INVALID (SDK), RESOURCE_FORBIDDEN (SDK), NOT_FOUND (Worker activation denial) | Gate/ownership denial |
404 | NOT_FOUND | Missing resource — also used to hide cross-tenant rows (fail closed) |
409 | CONFLICT, DEVICE_LIMIT (Axum activations), CAPABILITY_CONSUMED, IDEMPOTENCY_CONFLICT, ALREADY_REFUNDED | State conflict / duplicate consumption |
413 | PAYLOAD_TOO_LARGE (Worker) / BODY_TOO_LARGE (SDK) | Body over limit |
429 | bare status | Global RPM limiter; /auth/login adds a 5-fail → 5-min lock |
500 | TX, INTERNAL, CONFIG (SDK) | Storage/internal failure, misconfig — always fail closed |
503 | PAYMENT_PROVIDER_UNAVAILABLE | Provider adapter absent (Portone) or unreachable |
Deliberate Worker↔SDK deltas
These are documented, not bugs — read code + HTTP status together:
VALIDATION(Worker) vsINVALID_PUBLIC_JWK(SDK) — both400.FORBIDDEN(Worker) vsCAPABILITY_INVALID(SDK) — both403.403 NOT_FOUND(Worker activation on unknown key) vs404 NOT_FOUND(SDK) — the Worker deliberately returns 403 for license-key denials.PAYLOAD_TOO_LARGEvsBODY_TOO_LARGE— both413.- unknown license
status→INVALID_STATUS(Worker/Axum) vsREVOKED(SDK — unknown status is never trusted). - On
/licenses/execute/capabilityguard denials, any non-INVALID_STATUSverdict collapses to403 FORBIDDEN; the specific code survives only in thelicense_blockedsecurity event.
Web-client surface
@yetdapay/license-web forwards {code} verbatim into LicenseError.code with status = actual HTTP status. @yetdapay/sdk raises YetdaPayError with the same contract.
CLI/MCP surface
The yp CLI maps HTTP outcomes onto its own contract — 401/403 on auth-required commands → exit 3 with the login hint; other non-2xx → exit 1. MCP tool errors keep upstream {code,message} verbatim.