Skip to main content

The one rule

Branch on error.code. Every failure is a typed string code in error.code, never a bare HTTP number. The HTTP status is a coarse hint for proxies and logs. The contract lives in the string. Two failures can share status 422 and need opposite handling, so a switch on the number routes you wrong. A switch on error.code routes you right.
When a code carries a valid set (supported currencies, supported credentials, candidate programs), it lives in error.details. Read details instead of hard-coding the set. The list is per-integration and changes as your enablement changes; the runtime source of truth is GET /capabilities.

The complete table

This is the complete error.code enum the API returns today. The set grows additively, so treat an unrecognized code as a non-retryable failure: surface it, log it, and check GET /openapi for the current set.

The codes that carry a valid set

Four codes return the answer in details. Read it instead of guessing.
shortfall_minor, available_actual_minor, and available_promo_minor, all minor integers. The credential is not consumed, so the customer can top up the shortfall_minor and you can retry the same payment.
supported: the array of ISO-4217 currency codes with an active provider for this integration.
candidates: the array of wallet_program_id values the credential resolves to. Re-send the call with one of them in wallet_program_id.
The supported credential types for this integration. The authoritative live source is GET /capabilities.

INSUFFICIENT_FUNDS is a recovery path, not a dead end

Model INSUFFICIENT_FUNDS as a recovery, never a bare decline. The credential is not consumed on this failure. A failed debit leaves the OTP retryable and the QR nonce unspent, so the same payment succeeds after a reload.

Read the shortfall

details.shortfall_minor is the exact gap. available_actual_minor and available_promo_minor are the current balance split.

Top up the gap

Credit the wallet by at least shortfall_minor. Confirm the top-up against the provider settlement reference with POST /topup/confirm, which credits the cashable balance and accrues any reload bonus in one atomic operation.

Retry the original payment

Retry with a NEW idempotency key (the first attempt’s key replays the failure). The credential was never consumed, so it still resolves.
A standalone top-up entry point is not yet available. Today, confirm a top-up against a provider settlement reference with POST /topup/confirm. Read GET /capabilities for the operations enabled for your integration.

Retrying safely

Two failures are safe to retry with the SAME Idempotency-Key, and the distinction matters for money.

INTERNAL_SERVER_ERROR (500)

Retry with the SAME key. If the original call committed, you get a byte-identical replay with meta.idempotency_replayed: true. If it did not, the retry executes. Either way, no double-debit.

RATE_LIMITED (429)

Back off for the seconds in Retry-After, then retry with the SAME key. The same-key rule still protects you against a double-debit if an earlier attempt slipped through.
Everything else needs a NEW key. A VALIDATION_ERROR, INSUFFICIENT_FUNDS, or IDEMPOTENCY_KEY_REUSED means the request as sent will never succeed. Fix the request, then retry under a fresh Idempotency-Key. Reusing the old key with a changed payload returns IDEMPOTENCY_KEY_REUSED.
IDEMPOTENCY_KEY_REUSED is a key-generation bug on your side, not a transient error. A key must be stable for one logical operation and unique across different ones. Never retry it; regenerate the key for the corrected request.

Branching in code

Switch on the string, handle the carriers of a valid set, and treat the two retry-safe codes apart from the rest.
This list is stable for the codes above, and the contract grows additively. Treat an unrecognized error.code as a non-retryable failure: surface it, log it, and check GET /openapi for the current set rather than crashing.

Where to go next

Idempotency & Errors

The conceptual model behind the same-key replay rule and the typed-error envelope.

Conventions

The request and response envelope, headers, and the minor-units money rule.

Authentication

Resolving INVALID_API_KEY and FORBIDDEN: keys, the terminal JWT, and tenant scope.

Money: Actual vs Promotional

The balance split that INSUFFICIENT_FUNDS.details reports.