Error response format
All errors follow a consistent structure:{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": {
"amount": "Must be greater than 0.01"
}
}
}
| Field | Type | Description |
|---|---|---|
error.code | string | Machine-readable error code. |
error.message | string | Human-readable description. |
error.details | object | Additional context (optional). |
Most errors use the nested envelope above. A few low-level guards return a flat envelope instead — notably the idempotency middleware (In the flat shape,
IDEMPOTENCY_KEY_REQUIRED, IDEMPOTENCY_PAYLOAD_MISMATCH, IDEMPOTENCY_CONFLICT):{
"statusCode": 400,
"error": "Bad Request",
"message": "x-idempotency-key header is required for this operation",
"code": "IDEMPOTENCY_KEY_REQUIRED"
}
error is a string (the HTTP reason phrase), not an object, and the machine-readable code lives in the top-level code field. To handle both shapes, read the code defensively: body.error?.code ?? body.code.HTTP status codes
| Status | Meaning |
|---|---|
200 | Success. |
201 | Resource created. |
400 | Bad request (validation error, missing fields). |
401 | Unauthorized (missing, invalid or revoked API key). |
403 | Forbidden (insufficient permissions). |
404 | Resource not found. |
409 | Conflict (idempotency key mismatch). |
429 | Rate limit exceeded. |
500 | Internal server error. |
503 | Service unavailable. |
Error codes
Authentication errors
| Code | Status | Description |
|---|---|---|
API_KEY_REQUIRED | 401 | No Authorization: Bearer header supplied. |
INVALID_API_KEY | 401 | The key is unknown, revoked, expired, or its environment does not match the request. |
FORBIDDEN | 403 | The key is valid but lacks the required scope, or the request came from an IP outside the key’s allowlist. |
IP_NOT_ALLOWED | 403 | The request came from an IP address outside the key’s IP allowlist. |
Validation errors
| Code | Status | Description |
|---|---|---|
VALIDATION_ERROR | 400 | Request body or query parameters failed validation. Check details for specifics. |
Idempotency errors
| Code | Status | Description |
|---|---|---|
IDEMPOTENCY_KEY_REQUIRED | 400 | POST endpoints that require an X-Idempotency-Key header. |
IDEMPOTENCY_PAYLOAD_MISMATCH | 409 | The same idempotency key was used with a different request body, within the 24-hour window. |
IDEMPOTENCY_CONFLICT | 409 | The request is still being processed from a previous attempt. |
Rate limiting errors
| Code | Status | Description |
|---|---|---|
RATE_LIMIT_EXCEEDED | 429 | Too many requests. Check response headers for reset timing. |
Resource errors
| Code | Status | Description |
|---|---|---|
NOT_FOUND | 404 | The requested resource does not exist or does not belong to your account. |
Payment errors
| Code | Status | Description |
|---|---|---|
PAYMENT_CREATION_BLOCKED | 403 | The account is currently not allowed to create payments (KYB pending, suspended, or the requested method is disabled). |
AMOUNT_REQUIRED | 400 | A direct amount is required when no productId is supplied. |
CUSTOMER_DATA_REQUIRED | 400 | The selected payment method requires customer data and none was provided. |
CARD_HOLDER_INFO_REQUIRED | 400 | Direct card payments require card.holderInfo (postal code and address number). |
CARD_REMOTE_IP_REQUIRED | 400 | Direct card payments require card.remoteIp (the payer’s IP address). |
INSTALLMENTS_NOT_SUPPORTED | 400 | Card installments are not currently supported; send installments: 1 or omit. |
PROVIDER_TEMPORARILY_UNAVAILABLE | 502 | A downstream payment processor is unavailable. Retry with backoff. |
Refund errors
| Code | Status | Description |
|---|---|---|
INVALID_STATUS | 400 | The payment is not in a refundable status. |
PARTIAL_REFUND_NOT_SUPPORTED | 400 | Only full refunds are currently supported. Send no amount or send the full remaining refundable amount. |
ALREADY_REFUNDED | 400 | The payment has already been fully refunded. |
REFUND_IN_PROGRESS | 409 | A refund is already being processed for this payment. |
REFUND_NOT_SUPPORTED | 400 | Refunds are not supported for this payment method or processing route. |
INSUFFICIENT_BALANCE_FOR_REFUND | 400 | Your available balance is not sufficient to issue this refund. |
Withdrawal errors
| Code | Status | Description |
|---|---|---|
WITHDRAWAL_BELOW_MINIMUM | 400 | The amount is below the minimum withdrawal value. |
INSUFFICIENT_BALANCE | 400 | Your available balance is not sufficient. |
WITHDRAWAL_LIMIT_EXCEEDED | 400 | The amount exceeds the remaining daily or nightly limit, or a per-transaction cap. |
WITHDRAWAL_BELOW_PROVIDER_MINIMUM | 400 | The amount is below the minimum the routing PIX provider accepts. |
WITHDRAWALS_BLOCKED | 403 | Withdrawals are currently disabled for the account. |
INVALID_PIX_KEY | 400 | The destination PIX key is not a well-formed CPF, CNPJ, e-mail, phone, or random key. Format only — key existence is resolved by the network after acceptance. |
INVALID_RECIPIENT_DOCUMENT | 400 | destination.recipientDocument is not a valid CPF or CNPJ once punctuation is stripped. |
RECIPIENT_DOCUMENT_MISMATCH | 400 | destination.recipientDocument disagrees with a CPF or CNPJ destination.pixKey. |
WITHDRAWAL_RECIPIENT_DOCUMENT_MISMATCH | 400 | The network rejected the payout: the creditor document does not match the PIX key holder. |
WITHDRAWAL_PIX_KEY_BLOCKED | 400 | This PIX key was recently rejected by the network for the document used. |
BLOCKLIST_DENIED | 400 | The destination is blocked by platform risk policy. |
CREDITOR_DOCUMENT_UNAVAILABLE | 403 | The creditor document fell through to your own tax id and it is not on file. Complete KYB/KYC, or send destination.recipientDocument. |
CREDITOR_DOCUMENT_SOURCE_NOT_SUPPORTED | 400 | creditorDocumentSource was sent to POST /v1/withdrawals. The field is dashboard-only and is rejected rather than ignored. Send destination.recipientDocument instead. |
CREDITOR_DOCUMENT_UNREADABLE | 500 | Your tax id is on file but could not be read. No money moved, and retrying will not help — this is a platform-side fault. Send destination.recipientDocument to unblock, or contact support. |
VERIFICATION_REQUIRED | 403 | Account verification is required before withdrawals can be processed. |
SANDBOX_COMPANY_PRODUCTION_BLOCKED | 403 | A production request was made by an account that is still sandbox-only. |
WITHDRAWAL_PROVIDER_UNAVAILABLE | 503 | No PIX provider could accept the payout. No money moved; retry with a new idempotency key. |
WITHDRAWAL_TEMPORARILY_UNAVAILABLE | 503 | The request was abandoned before reaching the network. No money moved. |
WITHDRAWAL_PROCESSING_FAILED | 503 | The withdrawal could not be recorded. No money moved. |
INVALID_WITHDRAWAL_RECIPIENT and PIX_KEY_VALIDATION_FAILED were removed
along with the PIX key ownership policy. Neither code can be returned by the
API any more.Server errors
| Code | Status | Description |
|---|---|---|
INTERNAL_ERROR | 500 | Unexpected server error. Retry with exponential backoff. |
CREDITOR_DOCUMENT_UNREADABLE | 500 | Withdrawal-specific. Unlike INTERNAL_ERROR, this one is deterministic: retrying the same request will fail the same way. |
SERVICE_UNAVAILABLE | 503 | The service is temporarily unavailable. Retry after a short delay. |
Handling errors
async function createPayment(data) {
const response = await fetch("https://api.yuvexpay.com/v1/payments", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.YUVEX_API_KEY}`,
"Content-Type": "application/json",
"X-Idempotency-Key": generateIdempotencyKey(),
},
body: JSON.stringify(data),
});
if (!response.ok) {
const { error } = await response.json();
switch (response.status) {
case 400:
console.error("Validation error:", error.details);
break;
case 401:
case 403:
throw new Error(`Auth failed: ${error.code} — ${error.message}`);
case 409:
console.warn("Duplicate request detected");
break;
case 429:
const retryAfter = response.headers.get("X-RateLimit-Reset");
await sleep(retryAfter * 1000);
return createPayment(data);
default:
throw new Error(`API error: ${error.message}`);
}
}
return response.json();
}

