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. |
502 | Temporarily unavailable (PROVIDER_TEMPORARILY_UNAVAILABLE). |
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 or suspended). |
PAYMENT_METHOD_NOT_ENABLED | 403 | A method in methods is not enabled for your account, and details.method names it. If card payments are enabled for your account but card acceptance is temporarily unavailable, a request gets this error only when CARD is its only method; with other methods, the payment is created without CARD. See Card payments. |
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_PAYMENTS_NOT_AVAILABLE_VIA_API | 400 | Card payments are not accepted through the API. Returned for a card object, for CARD in methods in headless mode (mode omitted or "headless"), and for any of these fields at the top level of the body: deviceInfo, remoteIp, holderInfo, authorizeOnly, creditCard, creditCardHolderInfo, creditCardToken, installmentCount, installmentValue, totalValue. It takes precedence over validation and authentication errors, so the request gets this 400 even without a valid API key, and the card data is not stored or logged. The message is always Pagamentos com cartão não são aceitos pela API. Use o checkout hospedado: crie o pagamento com mode "hosted" e envie o pagador para o checkoutUrl. Create the payment with mode: "hosted" and send the payer to checkoutUrl: see Card payments. |
SPLIT_RECIPIENT_LIMIT_EXCEEDED | 400 | splits carries more entries than the recipient limit configured for your account (three by default). |
SPLIT_SELF_RECIPIENT | 400 | A split names your own company as the recipient. |
SPLIT_ORIGINATOR_NOT_APPROVED | 403 | Your own company has not completed KYC (and KYB, for a business), so it cannot originate split payments. |
SPLIT_AMOUNT_EXCEEDS_NET | 400 | The splits total more than netAmount. The message names the overflow; nothing is clamped. |
SPLIT_RELATIONSHIP_PENDING | 400 | The recipient has not accepted split payments from your company yet. This is the single answer for every recipient that cannot receive a split yet, apart from a pair that declined or was revoked — both need an invitation your company already sent — so it reads the same whether or not the company ID exists and cannot be used to probe for company IDs. |
SPLIT_RELATIONSHIP_REQUEST_LIMIT | 400 | Your company already has the maximum number of split requests awaiting an answer (20 by default, per environment). Wait for answers before naming new recipients. |
SPLIT_RELATIONSHIP_REVOKED | 400 | The relationship was revoked, by either side. Revocation is forward-only: payments created before it still settle. |
SPLIT_RELATIONSHIP_DECLINED | 400 | The recipient refused your company’s invitation to receive splits. details.recipients names the company IDs that declined. Send a new invitation from the dashboard to ask again. |
SPLIT_RECIPIENT_INACTIVE | 400 | A recipient that had accepted the relationship is no longer active. |
SPLIT_RECIPIENT_NOT_APPROVED | 400 | A recipient that had accepted the relationship no longer meets KYC (and KYB, for a business). Approval is checked when the payment is created. |
SPLIT_ENVIRONMENT_MISMATCH | 400 | A recipient that had accepted the relationship is in the other environment. |
SPLIT_FEE_CAP_EXCEEDED | 400 | A split to an approved split platform is above its fee cap. details.recipientCompanyId names the platform and details.feeCap the cap applied, in percent of amount. A PERCENTAGE split must be at most the cap, and a FIXED one at most amount × cap / 100. |
SPLIT_PLATFORM_SUSPENDED | 400 | The recipient is a split platform your company is connected to with a fee cap, and its approval was withdrawn. details.recipientCompanyId names it. New payments cannot split to it while it is not approved. |
SPLIT_PLATFORM_KEY_INVALID | 403 | The key sent in X-Split-Platform-Key was refused: unknown, revoked or expired, from the other environment, without payments:write, used from outside its IP allowlist, or from the same company as the request. Every reason gets this same answer, with no details. |
SPLIT_PARTNER_LIMIT_EXCEEDED | 400 | Connecting a split platform would take your company past its limit of active partners (three by default). details.limit carries the limit. The API returns it only on a platform connection: for dashboard invites the limit is enforced in the dashboard. |
PROVIDER_REJECTED | 400 | The payment was refused. The message is generic and does not say why. Ask the payer to check their details or use another payment method. |
PROVIDER_TEMPORARILY_UNAVAILABLE | 502, 503 | The service is temporarily unavailable. Retry later. On a boleto payment, a 502 means the outcome is not known yet and is replayed for the same X-Idempotency-Key: see Boleto errors before retrying. On a PIX payment, the payment is recorded as CANCELLED: retry with a new externalId. |
CARD_HOLDER_INFO_REQUIRED, CARD_REMOTE_IP_REQUIRED and
INSTALLMENTS_NOT_SUPPORTED were removed along with the card object of
POST /v1/payments. None of these codes can be returned by the API any more.Refund errors
| Code | Status | Description |
|---|---|---|
INVALID_STATUS | 400 | The payment is not in a refundable status. |
PARTIAL_REFUND_NOT_SUPPORTED | 400 | A partial refund needs a settled payment and a processing route that supports partial refunds; this payment fails one of the two. Send no amount, or the full remaining refundable amount. |
ALREADY_REFUNDED | 400 | The payment has already been fully refunded. |
REFUND_ALREADY_PERFORMED | 400 | The payment was already partially refunded. A payment can be refunded once. |
REFUND_IN_PROGRESS | 400 | A refund is already being processed for this payment. |
REFUND_NOT_SUPPORTED | 400 | Refunds are not supported for this payment method or processing route. |
REFUND_REQUIRES_MANUAL_REVIEW | 400 | The payment needs a manual review before it can be refunded. Contact support. |
INSUFFICIENT_BALANCE_FOR_REFUND | 400 | Your available balance is not sufficient to issue this refund. On a payment with splits, every party is debited in proportion to what it received and this check covers the originating company’s share only — a recipient can go negative and never blocks the 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 of the PIX route that sends the payout. |
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 route 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();
}

