Errors
Error body, common codes and when to retry.
Error body
Errors are sent with Content-Type: application/problem+json (RFC 7807). Make sure your HTTP
client parses that content type as JSON.
{
"type": "https://errors.tucopramp.xyz/invalid-api-key",
"title": "Invalid api key",
"status": 401,
"code": "invalid_api_key",
"detail": "Invalid or unknown API key.",
"request_id": "3b0f6c2e-1d4a-4f7e-9c8b-2a1e0d9f8c7b"
}
| Field | Meaning |
|---|---|
code | Machine-readable code in snake_case. Branch on this field only. |
status | The HTTP status. |
detail | A sentence for people. It can change at any time. |
title, type | Derived from code. Do not fetch type; it is an identifier, not a page. |
request_id | Include it when you contact TuCOP about a request. |
issues | On some validation errors: a list of { path, code } for the fields that failed. |
Some errors add fields of their own: amount_below_minimum includes minimum,
pay_limit_exceeded includes check, invalid_document includes reason, and
product_not_enabled includes category and required.
Every platform endpoint (see API conventions) answers errors only in this shape.
This page lists the codes that any endpoint can answer. Codes specific to one product are listed
on its page: Parties and sessions, Destinations,
Payments (which also covers GET /rates), Withdrawals,
Deposits and
Pay from your own payments provider account.
Legacy error bodies
A few P2P responses predate this shape. TuCOP Wallet and other clients depend on them, so they stay as they are. New endpoints never use them.
| Endpoint | Body | Read |
|---|---|---|
POST /p2p/offramp/orders, POST /p2p/onramp/orders: business errors (consent_required, amount_limit_exceeded, ...) | { "code", "error_code", "message", "details" } as application/json | code (error_code holds the same value) |
The same two endpoints: Idempotency-Key reused with a different body | 409 with { "error_code": "idempotency_key_reuse", "message" } as application/json | error_code |
These bodies are stored with the idempotency key so a retry replays the same answer, which is
why they are not in the envelope. On platform endpoints the same reuse case is
422 idempotency_key_reuse in the envelope.
Common codes
Authentication and limits
| Code | Status | When |
|---|---|---|
invalid_api_key | 401 | Missing, unknown, malformed, expired or revoked key, a key with a wrong checksum, a key for the other environment, or a paused consumer. |
api_key_conflict | 400 | X-TuCOPRamp-Key and Authorization: Bearer carry two different keys. Send one. |
api_key_ip_not_allowed | 403 | The key is limited to IP ranges and the request comes from another address. |
api_key_scope_missing | 403 | A read-only key sent a request that changes something (POST, PATCH, PUT, DELETE). Use a full-access key. |
api_key_origin_not_allowed | 403 | An older tcr_<env>_pk_ key limited to browser origins, used from another origin or with none. |
product_not_enabled | 403 | The route needs a service TuCOP has not enabled for your consumer. required lists the services that would open it (any one is enough) and category says which kind they are (on_off_ramp or p2p). GET /consumer lists yours. See Services. |
rate_limited_api_key | 429 | Over the key's own per-minute limit. See Retry-After. |
rate_limited_consumer | 429 | Over one of your consumer's budgets: 1,000 platform requests per minute shared by all your keys, 120 per minute on GET /rates, or the session code and sign-in budgets. See Rate limits and Retry-After. |
rate_limited_email | 429 | Too many codes or code checks for one email address. See Retry-After. |
rate_limited_party_session | 429 | Too many POST /parties with one party session. See Retry-After. |
rate_limited_wallet | 429 | Over a per-wallet limit: 10 session nonces per minute for one address, or the P2P per-wallet budget. See Retry-After. |
rate_limited_ip | 429 | Over a per-IP limit: requests without a valid key from one address, or, for a trusted relay, the /sessions limits for one end user's X-TuCOPRamp-Client-IP. See Retry-After. |
signature_invalid | 401 | Wallet headers missing or malformed, or the signature does not match the message. |
signature_expired | 401 | X-Wallet-Timestamp is more than 5 minutes from the server clock. |
Requests
| Code | Status | When |
|---|---|---|
invalid_json | 400 | The body is not valid JSON. |
invalid_body | 400 | The JSON body failed validation. See issues. |
invalid_query | 400 | A query parameter failed validation. |
invalid_api_version | 400 | A TuCOPRamp-Version header was sent with an unknown value. The header is not needed: stop sending it. See Versioning. |
not_found | 404 | No such route. |
payload_too_large | 413 | The body is over 256 KB. |
unsupported_encoding | 415 | The body uses a content encoding or charset the API does not read. |
internal_error | 500 | Unexpected server error. |
Idempotency on platform endpoints
| Code | Status | When |
|---|---|---|
idempotency_key_required | 400 | POST or PATCH without an Idempotency-Key header. |
idempotency_key_invalid | 400 | The key is longer than 255 characters or has spaces or non-ASCII characters. |
idempotency_in_progress | 409 | A request with the same key is still running. See Retry-After. |
idempotency_key_reuse | 422 | The same key was sent with a different body, method or path. |
See idempotency.
Payments provider
| Code | Status | When |
|---|---|---|
provider_unavailable | 502 | The payments provider could not be reached or failed. Retry with backoff and the same Idempotency-Key. |
provider_rejected | 422 | The payments provider refused to create a withdrawal address or a deposit account. |
rate_unavailable | 502 | The payments provider has no exchange rate right now. Retry. |
provider_error | 502 | POST /payments: the payments provider did not return deposit instructions. |
Party sessions
Every endpoint that acts for a party (/parties, /destinations, /quotes,
/payments, /limits, /withdrawals, /deposits) can answer these.
| Code | Status | When |
|---|---|---|
party_session_required | 401 | X-TuCOPRamp-Party-Session is missing on an endpoint that acts for a party, and TuCOP has not linked a payments provider account to your integrator. POST /parties always needs a session. |
party_session_invalid | 401 | The party session is unknown, expired, revoked or was opened for another consumer. |
party_required | 403 | The party session has no party linked to your app yet. |
party_not_linked | 403 | The party withdrew its consent to your app. |
integrator_not_linked | 409 | No session was sent, and the payments provider account TuCOP set for your integrator is not linked to it yet. |
Sign-in, onboarding and identity codes (invalid_code, invalid_document,
party_identity_not_proven, ...) are in Parties and sessions. Codes for
first-party keys and for integrators that pay from their own payments provider account
(first_party_not_available, destination_limit_reached, integrator_customer_mismatch) are in
Destinations and
Pay from your own payments provider account.
When to retry
5xx: retry with backoff (for example 1, 2, 4 and 8 seconds). Requests with anIdempotency-Keyare safe to retry with the same key.409 idempotency_in_progress: wait forRetry-Afterseconds, then retry with the same key.429: wait forRetry-Afterseconds, then retry.401 signature_expired: sign again with a fresh timestamp and retry once.401 signature_invalid: do not retry. Your message or body hash differs from what the server computed.- Other
4xx: do not retry. Fix the request.