Essentials
Errors
Errors use standard HTTP status codes and a JSON body with a stable code you can branch on, a human-readable message, the offending field where there is one, and the request_id.
{
"code": "insufficient_funds",
"message": "The source wallet's available balance is too low for this transfer.",
"field": "amount",
"request_id": "req_01JA8ZC4QK7N2Y5W3V0T9S8R6P"
}Reading an error
- Branch on
code, never onmessage. Messages are for people and can change. - Validation failures (
422 validation_failed) list every field that failed indetails. - Conflicts with an existing object name it in
existing_id. A retried user create, for example, tells you which user already has that reference. - A breached limit is described in
limit, with its name, amount and when it resets. - Unknown codes. New codes can be added; treat one you don't recognise by its HTTP status.
- Messages never contain your API key or personal data.
Error codes
| Code | Status | Meaning |
|---|---|---|
invalid_request | 400 | Malformed JSON, unknown parameter or bad cursor |
idempotency_key_missing | 400 | POST without Idempotency-Key |
unauthorized | 401 | Missing, invalid or revoked key |
key_environment_mismatch | 401 | Sandbox key on production or the reverse |
forbidden | 403 | Key scope does not allow the action |
sandbox_only | 403 | Sandbox endpoint called with a production key |
user_not_approved | 403 | KYC or terms status is not approved |
user_deactivated | 403 | User is deactivated |
not_found | 404 | Object does not exist for this integrator |
idempotency_key_reused | 409 | Same key, different body |
idempotency_request_in_progress | 409 | Same key, original request still running |
external_reference_exists | 409 | field is external_reference; existing_id names the user |
wallet_exists | 409 | User already has a wallet in this asset |
user_has_balance | 409 | Deactivation refused while a wallet holds funds |
user_has_open_transfers | 409 | Deactivation refused while a transfer is open |
transfer_not_cancelable | 409 | Transfer has posted or is final |
payout_account_in_use | 409 | A pending off-ramp targets this account |
invalid_state | 409 | Object is not in a state that allows the action |
validation_failed | 422 | One or more fields invalid; see details |
invalid_amount_precision | 422 | More decimal places than the asset allows |
amount_below_minimum | 422 | Below the route minimum |
amount_above_maximum | 422 | Above the route maximum |
insufficient_funds | 422 | Available balance too low |
amount_exceeds_cashable | 422 | Off-ramp above the purchased points still held; earned points cannot be cashed out |
same_source_and_destination | 422 | Source and destination wallet are the same |
asset_mismatch | 422 | Wallets hold different assets, and this is not a conversion |
price_risk_not_acknowledged | 422 | NdeipiCoin wallet created without acknowledge_price_risk: true |
quote_required | 422 | Conversion without a quote_id |
quote_mismatch | 422 | Quote's assets, wallets or amount differ from the transfer |
quote_expired | 422 | Quote's expires_at has passed; request a new one |
quote_used | 409 | Quote already settled a conversion |
insufficient_liquidity | 422 | Ndeipi's NdeipiCoin stock, or the treasury backing new points, is too low for this size; try a smaller amount later |
unsupported_route | 422 | No route for this source and destination |
limit_exceeded | 422 | Per-user or per-transfer limit; limit names it |
sanctions_match | 422 | Payout beneficiary failed screening |
rate_limited | 429 | Over the key's rate limit |
provider_unavailable | 503 | Ramp provider down; request refused |
internal_error | 500 | Unexpected server error |
Request IDs
Every response, success or error, carries a Request-Id header that matches Ndeipi's audit record. Include it when you contact Ndeipi about a request.