Every non-2xx response returns a machine-readable JSON envelope. Never parse error text — key off error.code.
Error envelope
{
"error": {
"code": "payment_declined",
"message": "The card issuer declined the charge.",
"type": "provider_error",
"doc_url": "https://asset-avenue-platform.lovable.app/docs/errors.html#payment_declined",
"request_id": "req_01HR2X8YQ4T5B0G6ZP",
"param": "payment_method",
"retryable": false
}
}
| Field | Description |
|---|---|
code | Stable machine identifier. Safe to switch on. |
message | Human message. Not localized. Do not display raw to end users. |
type | High-level category (see below). |
request_id | Include in support tickets. Also returned as X-Request-Id header. |
param | Present when a specific field caused the error. |
retryable | true if the client MAY retry with backoff. |
Error types
| Type | HTTP | Meaning |
|---|---|---|
authentication_error | 401 | Missing/invalid API key or OAuth token. |
authorization_error | 403 | Key is valid but lacks scope, or IP not allow-listed. |
invalid_request_error | 400 / 404 / 409 | Malformed payload, missing field, or unknown resource. |
idempotency_error | 409 | Idempotency key reused with a different payload. |
rate_limit_error | 429 | Client exceeded its quota. Respect Retry-After. |
provider_error | 402 / 502 | Underlying provider (Stripe, Bridge, Compose, Monerium) declined or failed. |
compliance_error | 451 | Blocked by AML, sanctions, KYC, or risk rules. |
api_error | 500 / 503 | QashX-side failure. Retryable with backoff. |
Code reference
| Code | Type | Retryable | Notes |
|---|---|---|---|
invalid_api_key | authentication_error | No | Rotate via /admin/developers. |
expired_api_key | authentication_error | No | Key past expires_at. Provision a new one. |
ip_not_allowed | authorization_error | No | Caller IP outside key ip_allowlist. |
insufficient_scope | authorization_error | No | Key lacks the required scope (e.g. payments:write). |
missing_parameter | invalid_request_error | No | param names the missing field. |
invalid_parameter | invalid_request_error | No | Field failed validation. |
resource_not_found | invalid_request_error | No | Unknown ID or reference. |
idempotency_conflict | idempotency_error | No | Same key, different body. See idempotency. |
payment_declined | provider_error | No | Issuer declined. Show generic message to payer. |
insufficient_funds | provider_error | No | Source balance too low. |
provider_unavailable | provider_error | Yes | Retry with exponential backoff. |
rate_limited | rate_limit_error | Yes | Honor Retry-After header. |
aml_blocked | compliance_error | No | Sanctions/AML hit. Do not retry. |
kyc_required | compliance_error | No | Payer must complete verification. |
internal_error | api_error | Yes | Retry with backoff; include request_id in support tickets. |
service_unavailable | api_error | Yes | Planned maintenance or overload. |
Retry guidance
Only retry when error.retryable === true or on network/timeout errors. Use exponential backoff with jitter:
attempt 1 → wait 500 ms
attempt 2 → wait 1 s
attempt 3 → wait 2 s
attempt 4 → wait 4 s
attempt 5 → wait 8 s (cap; then fail)
Always send the same Idempotency-Key on retries of POST/PUT/DELETE so the operation is not duplicated.
Never retry 4xx without inspecting retryable. Retrying a payment_declined in a loop is treated as abuse and can trip rate limits.