The Yuno error envelope, the real codes services return, and a sane retry strategy. Verified against the production codebase.
Every Yuno API error returns the same compact JSON envelope. Parse the code for programmatic handling and surface the messages array to your support tools.
{ "code": "CUSTOMER_ID_DUPLICATED", "messages": [ "A customer with this merchant_customer_id already exists for this account." ]}
Field
Type
Description
code
string
Stable, machine readable identifier in SCREAMING_SNAKE_CASE. Branch on this in your code.
messages
array of string
Human readable details. Always an array, even when there is only one entry. Validation errors put one entry per failed field, formatted "fieldName message".
There is no error wrapper, no singular message, no type, no details. The body is exactly the two top level fields above.
Yuno sets x-trace-id on every response. When you open a support ticket, attach the response body and the x-trace-id header so the team can pull the trace.
The shared utility libraries map common HTTP statuses to a default code. Service code may return a more specific business code for the same status (see the next table).
HTTP
Default code
Meaning
400
BAD_REQUEST
Generic bad request fallback when no business code applies.
400
VALIDATION_ERROR
Request payload failed schema or constraint validation.
400
INVALID_REQUEST
Request was rejected by the resource’s business rules.
401
UNAUTHORIZED
Missing or invalid public-api-key / private-secret-key.
403
FORBIDDEN
Caller authenticated but is not allowed to perform the action.
404
NOT_FOUND
The route does not exist, or the framework catch all could not match the resource.
405
METHOD_NOT_ALLOWED
The HTTP verb is not supported on this path. (Kotlin services emit UNSUPPORTED_METHOD.)
408
REQUEST_TIMEOUT
Request timed out before the service could respond.
413
REQUEST_ENTITY_TOO_LARGE
Request body exceeds the service limit.
415
UNSUPPORTED_MEDIA_TYPE
Content-Type is missing or not JSON.
429
TOO_MANY_REQUESTS
Edge throttling kicked in. Back off and retry.
500
INTERNAL_ERROR
Unhandled server side error. Safe to retry idempotent operations.
502
BAD_GATEWAY
Upstream provider was unreachable or returned a malformed response.
503
SERVICE_UNAVAILABLE
Yuno service is overloaded or in maintenance.
Yuno does not currently use HTTP 422. Schema and constraint failures arrive as 400 with code VALIDATION_ERROR (Kotlin services) or BAD_REQUEST (Go services). Most “not found” business cases (such as CUSTOMER_NOT_FOUND, RECIPIENT_NOT_FOUND) also arrive as 400 with a descriptive code, not as 404.
These come from the shared exception handler in the Kotlin services and apply to checkout sessions, recipients, payouts, and any service that uses the shared library.
code
HTTP
Trigger
VALIDATION_ERROR
400
A required field is missing, an enum value is wrong, or a JSON field has the wrong type. The messages array lists each field failure.
MISSING_HEADER
400
A required header (e.g., public-api-key or private-secret-key) was not sent.
MISSING_PARAMETER
400
A required query parameter was not sent.
INVALID_DATE_TYPE
400
A date field could not be parsed in the expected ISO format.
ILLEGAL_ARGUMENT
400
An argument violates a business invariant outside JSON validation.
Errors that originate at a downstream payment provider are surfaced under codes prefixed with PROVIDER_. Examples include PROVIDER_INVALID_CREDENTIALS, PROVIDER_INVALID_REQUEST, PROVIDER_PAYMENT_NOT_FOUND, PROVIDER_INVALID_AMOUNT, PROVIDER_COUNTRY_NOT_SUPPORTED, and PROVIDER_CURRENCY_NOT_ALLOWED. The provider’s raw response is reflected through messages. See Provider errors for the full mapping.
Validation failures from Kotlin services package every failed field into the messages array. The shape is always { code, messages }, never a structured details object.
{ "code": "VALIDATION_ERROR", "messages": [ "amount must be greater than 0", "country must not be blank", "merchant_order_id must not be blank" ]}
When you display these to internal users (operators, support), surface every entry in messages so they can act on all failures at once.
For non idempotent writes, the safe pattern is to set a unique business key in the request body (merchant_customer_id, merchant_order_id, merchant_recipient_id). Yuno’s database constraints reject duplicates, so a retry that finds the same key returns a clear 400 rather than creating a duplicate. The one endpoint that supports X-Idempotency-Key directly is POST /v1/subscriptions. See Avoiding duplicates.
Retry transient failures only. Do not retry codes that mean “your request is wrong”. Automated retries on those waste budget and add noise.
Retry
Do not retry
429 TOO_MANY_REQUESTS
400 BAD_REQUEST and 400 VALIDATION_ERROR
500 INTERNAL_ERROR
400 INVALID_REQUEST
502 BAD_GATEWAY
401 UNAUTHORIZED
503 SERVICE_UNAVAILABLE
403 FORBIDDEN
504 Gateway Timeout
404 NOT_FOUND
408 REQUEST_TIMEOUT
409 CONCURRENT_MODIFICATION (re fetch first, then retry)
Use exponential backoff with full jitter, starting at 1 s and capped at 30 s. When Retry-After is present on a 429, honor it as the floor for the wait.
Branch on code, never on messages. The strings in messages are subject to copy edits and localization. The code is the contract.
Set a unique business key on every non idempotent write (merchant_customer_id, merchant_order_id, merchant_recipient_id). On POST /v1/subscriptions you can also pass X-Idempotency-Key.
Log the full envelope plus the x-trace-id response header for every failed request. Without x-trace-id, support cannot find your request in our traces.
Treat PROVIDER_* codes as upstream signals. They indicate the failure happened at the provider, not at Yuno. Inspect messages for the provider’s raw text.
Alert on 5xx rate, not on individual 5xx events. Provider blips are normal. Sustained spikes are not.