Error Codes
API errors are returned with a consistent standard error response. Raw bank error codes (bank transaction/result codes) are not surfaced to the customer; you only see the stable codes below.
Standard error response
Section titled “Standard error response”{ "error": { "code": "terminal_not_allowed", "message": "Terminal is not allowed for this partner.", "traceId": "trc_9f12ab" }}| Field | Description |
|---|---|
code |
Stable, machine-readable error code (catalog below). |
message |
Human-readable description. |
traceId |
A trace identifier to share in support requests. |
HTTP status mapping
Section titled “HTTP status mapping”- Validation and business-rule violations →
4xx(e.g.,400,409,422). - Authentication failure →
401; authorization or access denial →403. - The requested resource was not found or is outside your access scope →
404. - Rate limit exceeded →
429(the response may carry aRetry-Afterheader telling you how long to wait). - The platform is temporarily at its capacity ceiling →
503(the response carries aRetry-Afterheader; don’t confuse this with429— see the code catalog below). - Every error response carries an
error.codeand atraceId.
Code catalog
Section titled “Code catalog”| Code | HTTP | Meaning |
|---|---|---|
validation_error |
400 |
The request body failed validation (a missing or invalid field). |
duplicate_partner_tx_code |
409 |
An active/successful payment already exists with the same partnerTxCode. |
terminal_not_allowed |
403 |
The terminal does not belong to the partner or is inactive. |
partner_not_active |
403 |
The partner account is not active (suspended or closed); new payments and partner-initiated refunds are blocked. |
amount_limit_exceeded |
422 |
The amount is outside the allowed min/max range. Not currently enforced (reserved — today only positivity is validated; a negative or 0 amount is rejected with 400 validation_error). |
installment_not_allowed |
422 |
The card is not eligible for installments, the terminal has no installment permission, the card’s brand is known but this brand × installment count is not offered at your terminal, or a payment link’s installment restriction was violated. See the remedy under installment mismatch. |
payment_not_found |
404 |
The requested payment was not found or does not belong to this partner. |
payment_not_refundable |
422 |
The payment is not in a refundable state. |
refund_amount_exceeds_remaining |
422 |
The requested refund amount exceeds the remaining refundable balance. |
refund_not_yet_available |
422 |
Same-day partial refund: available after day-end. |
refund_in_progress |
409 |
A pending refund already exists for this payment (single-flight), or this partnerRefundCode was already used (duplicate code). |
refund_not_found |
404 |
The requested refund was not found or does not belong to this partner. |
direct_refund_not_allowed |
422 |
This payment was made on a direct-collection (direct) terminal; a credit refund is not possible — only a same-day full void is available. |
payment_link_not_found |
404 |
The requested payment link was not found or does not belong to this partner. |
payment_link_already_paid |
409 |
The payment link is already completed. (Rarely, the same error is returned if your cancellation races with a payment completing at that exact moment.) |
payment_link_expired |
409 |
The payment link’s window has expired; it can no longer be cancelled. |
payment_link_cancelled |
409 |
The payment link was already cancelled. |
rate_limited |
429 |
Too many requests: the rate limit was exceeded. Wait a while and retry. |
service_overloaded |
503 |
The platform is temporarily at its capacity ceiling; your request was rejected in a controlled way. The response carries a Retry-After header (in seconds); wait that long and retry with exponential backoff. ⚠ Don’t confuse this with rate_limited (429): rate_limited is a business rule specific to your API key (you sent more requests than allowed); service_overloaded is the platform’s overall capacity signal — it has nothing to do with your API key or your limit. |
Installment mismatch (installment_not_allowed)
Section titled “Installment mismatch (installment_not_allowed)”installment_not_allowed can occur for four distinct reasons:
- The card is not eligible for installments (installment sales are supported only for certain cards).
- Your terminal’s effective installment set (which may be narrowed on your account; see Installment Options) does not cover this installment count.
- The card’s brand is known, but this brand × installment count is not offered at your terminal. Installment Options tells you exactly which installment counts are offered: every count missing from that list produces this error.
- The
allowedInstallmentCountsyou sent when creating a payment link is empty, or is not a subset of your terminal’s effective installment set.
When it occurs: reason 2 does not depend on the card’s brand, so it is evaluated in the POST /v1/payments call itself, at create time. Reasons 1 and 3 require the card’s brand, so they only surface during the Card Handoff’s 3D relay. Reason 4 is evaluated at create time on POST /v1/payment-links.
The remedy is the same for reasons 1-3: the installment count is fixed when you create the payment (POST /v1/payments) and cannot be changed on the same payment. If you get this error, create a new cardless payment with a different installment count; the same payment can only be retried with a different card — the installment count stays fixed. ⚠ Note: reasons 1 and 3 surface during the 3D relay, so each attempt draws down this payment’s card attempt allowance; once it runs out the payment is locked to failed (card_attempt_limit_exceeded) and the paymentRef can no longer be used. In other words, “retry with a different card” is not unlimited. See Payment Flow — card attempt limit. To see this coming ahead of time, query which installment counts and commission rates the card qualifies for on your checkout using Installment Options. For reason 4, the remedy is to resend allowedInstallmentCounts as a subset of your terminal’s effective installment set (this restriction cannot be changed once the link is created).
Handoff errors
Section titled “Handoff errors”The codes below occur during Card Handoff. For payment_ref_invalid / payment_ref_expired / payment_prepare_already_started / card_attempt_limit_exceeded, the remedy is the same: create a new cardless payment (POST /v1/payments), then start the handoff again with the new paymentRef/handoffUrl it returns. virtual_pos_inactive is different: your terminal’s bank connection is temporarily inactive, so creating a new payment reproduces the same error — wait until it is resolved or contact support. virtual_pos_card_type_not_supported is different too, and it depends on the card: creating a new payment with the same card reproduces the same error — ask the buyer for another card.
| Code | HTTP | Meaning |
|---|---|---|
payment_ref_invalid |
404 |
The paymentRef is invalid/unknown. |
payment_ref_expired |
404 |
The paymentRef has expired. |
payment_prepare_already_started |
409 |
3D has already been started for this paymentRef (single-use). |
card_attempt_limit_exceeded |
429 |
The allowed number of card attempts for this paymentRef was exceeded; the payment is locked to failed. In your retry logic, treat this as a trigger to create a new cardless payment, not to retry with a different card on the same payment. |
virtual_pos_inactive |
422 |
The bank connection (virtual POS) backing this terminal is currently inactive; a new payment cannot be prepared. In-flight (already started) transactions are unaffected. |
virtual_pos_card_type_not_supported |
422 |
The bank connection (virtual POS) backing this terminal does not support this card type; the payment cannot be prepared. In practice you see this most often with foreign-issued cards. The payment stays initiated, but the attempt draws down the card attempt allowance. To see it coming: Installment Options — options comes back empty for an unsupported card type. |