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. |
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. |
WITHDRAWALS_BLOCKED | 403 | Withdrawals are currently disabled for the account. |
INVALID_PIX_KEY | 400 | The destination PIX key is not a recognizable CPF, CNPJ, e-mail, phone, or random key. |
INVALID_WITHDRAWAL_RECIPIENT | 403 | The PIX key does not belong to a permitted recipient (for CNPJ accounts, the key must match the company name or registered partners). |
PIX_KEY_VALIDATION_FAILED | 400 | The PIX key could not be validated. |
CREDITOR_DOCUMENT_UNAVAILABLE | 403 | Your account’s CNPJ (business) or owner CPF (individual) is not on file. Complete KYB/KYC before withdrawing. |
VERIFICATION_REQUIRED | 403 | Account verification is required before withdrawals can be processed. |
Server errors
| Code | Status | Description |
|---|---|---|
INTERNAL_ERROR | 500 | Unexpected server error. Retry with exponential backoff. |
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();
}

