TidePay

Errors

How the Tidepay API reports failures, and how to handle them.

Tidepay uses conventional HTTP response codes to indicate the success or failure of an API request. In general:

  • Codes in the 2xx range indicate success.
  • Codes in the 4xx range indicate an error that failed given the information provided — a missing required field, a not-found resource, a conflicting idempotency key, or too many requests.
  • Codes in the 5xx range indicate something went wrong on Tidepay's end.

Error shape

Every error response is the same flat shape — a single human-readable error string, nothing else:

{ "error": "Invalid input" }

No error-code taxonomy

Unlike some APIs, Tidepay does not return a machine-readable code or type field on errors — the HTTP status code is the only thing you should branch your handling logic on. Treat error as a string for logging and display, not for switch-style program logic; its exact wording can change without that being a breaking change.

Success responses (2xx) return the resource itself directly, not wrapped in an envelope — error only ever appears on a failure.

HTTP status code summary

StatusMeaningWhen it happens
200OKRequest succeeded, or an idempotency key matched an existing resource — see Idempotent creates.
201CreatedA new resource was created.
400Bad RequestThe request body or query parameters failed validation, or violated a business rule (e.g. subscribing to a one-time pricing option).
401UnauthorizedThe X-API-Key header is missing, or the key doesn't match any merchant. See below — this single status covers every authentication failure.
403ForbiddenThe key is valid but not allowed to call this specific endpoint — currently only POST /sandbox/charge, which requires a test key.
404Not FoundThe requested resource doesn't exist, or exists but belongs to a different merchant or environment (live/sandbox) than the one your key authenticated as.
409ConflictThe request conflicts with existing data — a reused idempotency key that doesn't cleanly replay, a unique-field collision (e.g. a duplicate offer identifier), or an operation that would leave a resource in an unsafe state (e.g. deleting a settlement wallet that still has an address registered).
429Too Many RequestsYou've exceeded a consumption limit. See Consumption limits.
500Server ErrorSomething went wrong on Tidepay's end. See below — not every 500 is guaranteed to have a JSON body.

No other status codes are returned by the /v1 API today — there is no 402, 422, or 503.

Authentication errors are undifferentiated

Every one of these produces the exact same 401 { "error": "Unauthorized" }, with no way to tell them apart from the response alone:

  • The X-API-Key header is missing entirely.
  • The key doesn't match any merchant's live or test key.
  • The key matches, but the merchant account is suspended.

This is deliberate — a more specific error here (e.g. "this key was valid but the account is suspended") would let an attacker enumerate valid-but-suspended keys. If you're getting an unexpected 401, double-check the header name (X-API-Key, not Authorization) and that you're sending the raw key exactly as shown when it was generated — see Authentication.

Validation errors report one problem at a time

When a request body or query string fails validation, the response is the first validation problem found, not an exhaustive list:

{
  "error": "Minimum amount is $1.00 USD (or equivalent in the chosen currency)"
}

If a payload has multiple invalid fields, fixing the one reported and resubmitting may surface a second, previously-unreported problem. There's no way to request the full list of issues in one round trip today — validate against the documented request schema client-side first if you want to avoid multiple round trips.

Not Found vs. cross-tenant access

A 404 is also what you get if a resource id is real but belongs to someone else's account, or to the other environment (a sandbox id fetched with a live key, or vice versa) — Tidepay never reveals that a resource exists but isn't yours by returning a different status like 403. If you're confident an id is correct and still see 404, check that you're using the same key (live vs. test) that created it.

Conflict (409) responses

409 covers two distinct situations, both about data collisions rather than malformed input:

Idempotency key reused in a way that can't cleanly replay — e.g. the same idempotencyKey on POST /payments was already used by a different merchant:

{ "error": "idempotencyKey already used" }

A clean replay (same key, same merchant, same mode) is not an error at all — it returns the original resource with 200. See Idempotent creates.

A unique-field collision — creating an offer with an identifier that's already taken, or a customer with a walletAddress that's already registered:

{ "error": "An offer with this identifier already exists" }
{ "error": "Customer with this wallet already exists" }

Deleting a resource still referenced elsewhere also fails with 409 — resolve the reference first, or deactivate instead of deleting where that's available (PATCH { "active": false } on offers):

{ "error": "Offer has active subscriptions and cannot be deleted. Deactivate it instead." }

Deleting a settlement wallet that already has an address registered is never allowed, not just while something references it — see Settlement Wallets:

{ "error": "This wallet has an address registered and cannot be deleted — register a different address instead (POST /v1/wallets), which safely replaces it in place." }

Server errors (500)

Tidepay has no catch-all error handler that guarantees every failure comes back as { "error": "..." }. Most 500s you'll see are narrow, explicit cases — e.g. an unexpected database failure right after a write:

{ "error": "Failed to create offer" }

But a truly unexpected failure (a database outage, an on-chain RPC provider being down mid-request) can surface as a generic framework-level 500 with no JSON body at all, rather than Tidepay's usual error shape. Don't assume response.json() will always succeed on a 500 — check the status code first, and fall back to the raw response text if parsing fails.

500s are rare and, unlike 4xxs, are never your integration's fault — retrying with backoff is reasonable; a 500 is never a signal to change what you're sending.

Related pages

Consumption limits covers 429 in full detail (headers, per-endpoint limits, backoff guidance). Webhooks covers a separate failure mode — your own endpoint failing to receive a webhook delivery, not an API request failing.

On this page