API Docs
API ReferenceErrors

Errors

Understanding and handling errors from the Uprails API.

Error Response Format

When an error occurs, the API returns a JSON response with an error object containing the error type, code, and a human-readable message.

{
  "error": {
    "type": "invalid_request",
    "code": "IR_04",
    "message": "Invalid value for field: amount"
  }
}

HTTP Status Codes

StatusDescription
200Request succeeded
201Resource created successfully
400Bad request — invalid parameters or missing required fields
401Unauthorized — invalid or missing API key
403Forbidden — insufficient permissions
404Not found — resource doesn't exist
409Conflict — resource already exists or action conflicts
422Unprocessable entity — request understood but cannot be processed
429Too many requests — rate limit exceeded
500Internal server error — something went wrong on our end
502Bad gateway — upstream service error
503Service unavailable — temporary outage

Error Types

authentication_error

Issues with API authentication. Check that your API key is correct and has the required permissions.

invalid_request

The request was malformed or contained invalid parameters. Check the request body and parameters.

processing_error

An error occurred while processing the payment. This may include card declines or issues with the payment method.

rate_limit_error

Too many requests were made in a short period. Wait and retry with exponential backoff.

api_error

An unexpected error occurred on our servers. These are rare and usually temporary. Retry with exponential backoff.

Common Error Codes

CodeTypeDescription
AU_01authentication_errorInvalid API key provided
AU_02authentication_errorAPI key has been revoked
IR_01invalid_requestMissing required field
IR_04invalid_requestInvalid field value
IR_05invalid_requestResource not found
PE_01processing_errorCard declined
PE_02processing_errorInsufficient funds
PE_03processing_errorExpired card
CE_01connector_errorProcessor unavailable
RL_01rate_limit_errorRate limit exceeded

Payment Failure Codes

error.code and unified_code are different things. error.code (IR_04, HE_00) means the API rejected your request — you get a 4xx/5xx and no payment is attempted. unified_code (UE_1001) means a payment was attempted and declined — you get a 200 with status: "failed".

Every card network and acquirer describes the same decline differently. A payment refused for insufficient funds may come back as INSUFFICIENT_FUNDS, 51, or NotSufficientFunds depending on which acquirer handled it.

unified_code normalises these into one vocabulary, so you can write your decline handling once and it keeps working when we add an acquirer. It appears on the payment object, in /payments/list, and in webhooks.

Response shape

{
  "payment_id": "pay_kQdeYLEIhIk62GZomSsE",
  "status": "failed",
  "unified_code": "UE_1003",
  "unified_message": "The card number is invalid",
  "error_code": "DECLINED",
  "error_message": "Resubmit with alternative payment details",
  "error_details": {
    "unified_details": {
      "category": "UE_1003",
      "message": "The card number is invalid"
    },
    "issuer_details": {
      "code": "14",
      "message": "Invalid card number",
      "network_details": { "name": "Visa" }
    },
    "connector_details": {
      "code": "DECLINED",
      "message": "Resubmit with alternative payment details",
      "reason": "Failed: Invalid card number"
    }
  }
}

unified_code and unified_message are what you should branch on and, where appropriate, show to your customer. error_code, error_message and error_details.connector_details carry the acquirer's own wording — useful when raising a support ticket with us, but they vary by acquirer and are not a stable integration surface.

Do not match on error_message. It is the acquirer's text and changes without notice — "Resubmit with alternative payment details" is an instruction to us, not something to show a cardholder. Branch on unified_code.

Code families

The first digit tells you what kind of failure it was, which is usually enough to decide what to do. Match on the family when you want broad handling, on the exact code when you want to tailor the message.

FamilyMeaningRetry?
UE_1xxxCard or issuer declinedNo — the customer needs a different card (except UE_1018, see below)
UE_2xxxNot eligible, or stopped by risk rulesNo — except UE_2002, see below
UE_3xxxTemporary availability problemYes, with backoff
UE_4xxxSomething wrong in the requestNo — fix the request first
UE_5xxxFailed during 3-D Secure authenticationSometimes — see below
UE_9000Unrecognised failureTreat as terminal

UE_5xxx is about authentication, not the card. These payments failed during 3-D Secure and never reached the issuer for authorisation, so the card itself may be perfectly good. A customer who abandoned the challenge (UE_5003) can usually just try again.

UE_2002 is retryable. The issuer has asked for authentication under PSD2 rather than declining the card. Retrying the same payment with 3-D Secure will often succeed — treat it as a prompt to authenticate, not as a refusal.

UE_1018 is not a decline. The acquirer reported a duplicate, which means the original attempt may have succeeded. Check the status of the earlier payment before retrying or refunding — treating it as a plain failure risks charging the customer twice.

UE_1xxx — Card and issuer declines

The issuer refused the payment. The card details or the account are the problem.

CodeMessage
UE_1001Insufficient funds
UE_1002The card has expired
UE_1003The card number is invalid
UE_1004The security code is incorrect
UE_1005The PIN is incorrect
UE_1006The card was reported lost or stolen
UE_1007The card was declined by the issuer
UE_1008This card is not supported
UE_1009The card is restricted
UE_1010This transaction is not permitted for this card
UE_1011The transaction exceeds the card's limit
UE_1012The transaction was declined for security reasons
UE_1013Declined, please contact your card issuer
UE_1014No record of this card was found
UE_1015The card is not active
UE_1016This account cannot be used for this payment
UE_1017The cardholder has cancelled this payment
UE_1018This payment may have already been processed
UE_1099The payment was declined

UE_2xxx — Eligibility and risk

The payment was stopped by eligibility or risk rules rather than by a decline on the card itself. UE_2002 is the exception: there the issuer is asking for authentication.

CodeMessage
UE_2001This payment method is currently unavailable
UE_2002Additional authentication is required
UE_2003The payment was blocked
UE_2004This currency or country is not supported

UE_3xxx — Availability

A temporary problem reaching the acquirer or issuer. Safe to retry later.

CodeMessage
UE_3001The payment service is temporarily unavailable, please try again later
UE_3002The payment could not be processed, please try again
UE_3003The payment timed out, please try again

UE_4xxx — Request data

Something in the request was missing, malformed or no longer valid.

CodeMessage
UE_4001Required payment information is missing or invalid
UE_4002The security code is not the expected length
UE_4003The payment session has expired, please start again
UE_4004The transaction is in an invalid state

UE_5xxx — Authentication (3DS)

The payment failed during 3-D Secure, before the issuer was asked to authorise it.

CodeMessage
UE_5001Card authentication failed
UE_5002Authentication was declined by the issuer
UE_5003Authentication was not completed
UE_5004The cardholder could not be verified
UE_5005The authentication service is unavailable
UE_5006Too many authentication attempts
UE_5007The device could not be recognised
UE_5008The authentication response was invalid
UE_5009No response from the authentication service
UE_5010The card is not enrolled for authentication
UE_5011Authentication timed out
UE_5012This authentication method is not supported
UE_5013The card has expired
UE_5014The card number is invalid
UE_5015The transaction is not valid for authentication
UE_5016No record of this card was found
UE_5017Authentication was declined for security reasons
UE_5018The card was reported lost or stolen
UE_5019Authentication is not permitted for this card
UE_5020Required authentication data is missing or invalid
UE_5021The authentication session has expired

Legacy codes

Acquirers we have not yet migrated still return these coarser codes. Unlike the codes above, a legacy code does not imply a single message — UE_1000 alone covers insufficient funds, an expired card, an incorrect security code and several others.

CodeMeaningExample messages
UE_1000Card or payment-method problem"Insufficient funds", "The card has expired", "The card number is incorrect"
UE_2000Not eligible, blocked, or 3DS required"3D Secure authentication is required for this card", "This currency is not supported"
UE_3000Temporary problem"The payment could not be completed, please try again or use a different payment method"
UE_4000Request data problem"Required payment information is missing or invalid", "The amount is below the minimum allowed"

If you receive a legacy code, branch on the code and show unified_message to your customer — do not assume the message from the code. These are being replaced by the granular codes above as each acquirer is migrated, so handling written against UE_1xxxUE_5xxx will continue to work as more move across.

Fallback

CodeMessage
UE_9000Something went wrong

Handling declines

const payment = await response.json();if (payment.status === 'failed') {const code = payment.unified_code;// Branch on the family for broad handlingswitch (code?.slice(0, 4)) {  case 'UE_3':    // Temporary — safe to retry with backoff    return scheduleRetry(payment.payment_id);  case 'UE_5':    // Authentication failed; the card may still be fine    return promptRetryAuthentication(payment.unified_message);  case 'UE_4':    // Our request was wrong — do not retry unchanged    return logIntegrationError(payment);  default:    // UE_1xxx / UE_2xxx — ask for another payment method    return showToCustomer(payment.unified_message);}}

Handling Errors

Here's an example of how to handle errors in your code:

try {const response = await fetch('https://api.uprails.com/payments', {  method: 'POST',  headers: {    'Content-Type': 'application/json',    'api-key': 'snd_YOUR_API_KEY'  },  body: JSON.stringify({    amount: 1000,    currency: 'USD',    profile_id: 'YOUR_PROFILE_ID'  })});const data = await response.json();if (!response.ok) {  const { error } = data;  switch (error.type) {    case 'authentication_error':      console.error('Authentication failed:', error.message);      break;    case 'invalid_request':      console.error('Invalid request:', error.message);      break;    case 'processing_error':      console.error('Payment failed:', error.message);      break;    case 'rate_limit_error':      console.error('Rate limited, retrying...');      break;    default:      console.error('An error occurred:', error.message);  }  return;}console.log('Payment succeeded:', data.payment_id);} catch (networkError) {console.error('Network error:', networkError);}

Retry Strategy

For transient errors (5xx status codes and rate limits), implement exponential backoff with jitter:

async function retryWithBackoff(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;

      // Only retry on 5xx errors and rate limits
      if (error.status < 500 && error.status !== 429) throw error;

      // Exponential backoff with jitter
      const delay = Math.min(1000 * Math.pow(2, i) + Math.random() * 1000, 30000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Retrying payment creation: Reuse the same payment_id on every retry. If the retry returns HE_01 ("payment already exists"), the original request was received — call GET /payments/{payment_id} to read its true status instead of treating it as a failure.