Skip to main content
A robust integration handles errors gracefully: it distinguishes problems it can fix automatically from those that require merchant action, surfaces useful messages to users, and avoids hammering the API during outages.

Error response format

All Yuno API errors return a JSON body with two fields:
{
  "code": "INVALID_REQUEST",
  "messages": [
    "customer.document.document_number is required for PIX payments"
  ]
}
  • code — a machine-readable string identifying the error class. Log this value when opening support tickets.
  • messages — a human-readable array describing what went wrong. There may be multiple messages when several fields fail validation simultaneously.

HTTP status codes

StatusMeaning in YunoRetryable?
200 / 201Request succeeded
400 Bad RequestInvalid request schema, missing required fields, or malformed valuesNo — fix the request
401 UnauthorizedMissing or invalid authentication credentialsNo — fix credentials
403 ForbiddenAuthenticated but not permitted (e.g., payment method not enabled for merchant)No — contact Yuno
404 Not FoundResource does not exist or endpoint path is incorrectNo — fix the request
409 ConflictConcurrent request with the same idempotency keyNo — wait and check status
429 Too Many RequestsRate limit exceededYes — with backoff
500 Internal Server ErrorUnexpected server-side errorYes — with backoff
504 Gateway TimeoutUpstream timeout; request may or may not have been processedYes — use idempotency key

Retryable vs. non-retryable errors

Non-retryable errors (4xx)

4xx errors indicate a problem with your request or account configuration. Retrying without changing anything will produce the same error.
  • 400 — Inspect messages for the exact field or value that failed validation. Fix the request payload before retrying.
  • 401 — Verify that public-api-key, private-secret-key, and account-code headers are present and correct for the target environment (sandbox vs. production).
  • 403 — The merchant account lacks permission for the requested operation. Check Dashboard → Connections to confirm the payment method is enabled, or contact Yuno support.

Retryable errors (429 and 5xx)

These errors indicate transient conditions. Retry with exponential backoff:
  • 429 — Back off and retry. The response may include a Retry-After header indicating how long to wait.
  • 500 / 504 — The server encountered an error or timed out. Because the idempotency key is not stored on 5xx responses, it is safe to retry with the same key. Wait at least 1 second before the first retry, then double the interval on each subsequent attempt (e.g., 1s → 2s → 4s → 8s), up to a maximum of around 30 seconds.

Error handling pattern

async function createPayment(payload, idempotencyKey) {
  const MAX_RETRIES = 3;
  let attempt = 0;
  let delayMs = 1000;

  while (attempt < MAX_RETRIES) {
    try {
      const response = await fetch('https://api-sandbox.y.uno/v1/payments', {
        method: 'POST',
        headers: {
          'public-api-key': process.env.YUNO_PUBLIC_API_KEY,
          'private-secret-key': process.env.YUNO_PRIVATE_SECRET_KEY,
          'account-code': process.env.YUNO_ACCOUNT_CODE,
          'X-Idempotency-Key': idempotencyKey,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(payload),
      });

      const data = await response.json();

      if (response.ok) {
        return data;
      }

      const { code, messages } = data;

      // Non-retryable: surface error to caller immediately
      if (response.status >= 400 && response.status < 500) {
        console.error(`[Yuno] Non-retryable error ${response.status}:`, code, messages);
        throw new Error(`Payment failed: ${code}`);
      }

      // Retryable: log and back off
      console.warn(`[Yuno] Retryable error ${response.status} (attempt ${attempt + 1}):`, code);
      attempt++;
      await new Promise(resolve => setTimeout(resolve, delayMs));
      delayMs *= 2;

    } catch (err) {
      if (err.message.startsWith('Payment failed:')) throw err;
      // Network-level error (no response received) — safe to retry with same key
      attempt++;
      await new Promise(resolve => setTimeout(resolve, delayMs));
      delayMs *= 2;
    }
  }

  throw new Error('Payment request failed after maximum retries');
}

Payment status vs. API errors

A 200 API response does not mean the payment succeeded. It means the API request was valid and accepted. The payment itself may still be DECLINED or PENDING.Always check the payment_status field in the response body to determine the actual outcome of the transaction, and configure webhooks to receive asynchronous status updates.
For example, a 200 response body may contain:
{
  "id": "pay_abc123",
  "payment_status": "DECLINED",
  "provider_code": "51",
  "provider_message": "Insufficient funds"
}
In this case, provider_code and provider_message contain the downstream reason. These are distinct from the top-level API error code and messages fields.