Skip to main content

Error Reference

All API errors return a JSON body with an errors array. Each element carries a detail string and, on some endpoints, a machine-readable code. Validation failures list one element per field.

The normative definition is §10 of the protocol specification.


The error that arrives as a success​

Routing, limits and anti-fraud do not refuse a payment with an HTTP error. They create the transaction and mark it rejected, so the response is 201 Created with a perfectly ordinary body:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "rejected",
"error_description": "terminal not found"
}

Code that checks only the status code books unpaid orders as paid. Read status on every response. rejected and failed mean no money moved; charged is the only settled outcome.


Error response format​

{
"errors": [
{ "code": "flow_not_continuable", "detail": "status_poll" }
]
}

A few endpoints predating this format answer with {"error": "…", "message": "…"} instead. Accept both shapes.


HTTP status codes​

CodeMeaningCommon cause
400Bad RequestMalformed JSON, missing required fields — and, on some endpoints, a missing x-organization-id header
401UnauthorizedMissing or invalid credentials

Handle 400 and 401 the same way when they carry no field pointers: both mean the request was not authenticated, and which one you get depends on the endpoint. | 403 | Forbidden | Valid credentials but no permission for this resource | | 404 | Not Found | Transaction or resource with given ID does not exist | | 409 | Conflict | The transaction cannot move as requested from its current state | | 422 | Unprocessable Entity | Validation failed — one errors element per field | | 429 | Too Many Requests | Rate limit exceeded | | 500 | Internal Server Error | Platform error — contact support with transaction ID |


Transaction error codes​

These appear in error_description within webhook payloads and transaction status responses:

CodeMeaningRecommended action
insufficient_fundsCard declined: insufficient fundsAsk payer to use another card
card_declinedCard declined by issuerAsk payer to contact their bank
expired_cardCard expiry date has passedAsk payer to update card details
invalid_card_numberCard number is invalidCheck card number format
invalid_cvvCVV is incorrectAsk payer to re-enter CVV
do_not_honourGeneric bank declineAsk payer to contact their bank
lost_cardCard reported lostDo not retry
stolen_cardCard reported stolenDo not retry
restricted_cardCard cannot be used for this transactionAsk payer to use another card
transaction_limit_exceededAmount exceeds card/account limitsAsk payer to use another payment method
3ds_failed3D Secure authentication failedAsk payer to retry or use another card
3ds_timeout3D Secure timed outRetry the transaction
provider_unavailablePayment provider is temporarily unavailableRetry after a delay
routing_failedNo provider found for this transactionContact your platform operator
duplicate_txidtxid already exists for this terminalUse a unique txid per transaction
currency_not_supportedCurrency not supported by this terminalCheck terminal configuration
amount_too_lowAmount below minimum for this providerIncrease transaction amount
amount_too_highAmount above maximum for this providerSplit into smaller transactions
antifraud_rejectBlocked by anti-fraud rulesContact your platform operator

Validation errors (422)​

Validation errors list one errors element per problem, each with a detail string naming the field:

{
"errors": [
{ "detail": "amount: can't be blank" },
{ "detail": "currency: is invalid" }
]
}

Handling errors in your code​

Retry strategy​

Error typeRetry?Notes
provider_unavailable✅ YesWait 30–60 seconds, max 3 retries
3ds_timeout✅ YesRetry once immediately
insufficient_funds❌ NoShow alternative payment method
card_declined❌ NoShow alternative payment method
lost_card, stolen_card❌ NeverDo not retry or re-present
duplicate_txid❌ NoGenerate a new unique txid
routing_failed❌ NoContact your platform operator

Example error handling (Python)​

import requests

response = requests.post(
'https://4pay.online/api/v1/transactions',
headers={
'x-api-key': 'YOUR_API_KEY',
'x-organization-id': '00000000-1111-2222-3333-444444444444',
'Content-Type': 'application/json',
},
json={'params': {'type': 'payment', 'amount': '1000', 'currency': 'USD',
'txid': 'order-1', 'env': 'prod'}}
)

if response.status_code == 422:
for err in response.json().get('errors', []):
print(err.get('detail'))
elif response.status_code == 409:
print("The transaction cannot move as requested from its current state")
elif response.status_code >= 500:
print("Platform error — retry later or contact support")
else:
tx = response.json()
print(f"Transaction created: {tx['id']}")

Contact support​

For persistent errors or unexpected 500 responses, contact: