Skip to content

Error codes

Every error is the same flat shape. No nesting, no details.

{ "error": "Insufficient balance. Available: 10.000000, Requested: 50.000000", "code": "INSUFFICIENT_BALANCE" }

Branch on code. Never on error. The error string is human text for your logs and your support screens; it can be reworded at any time and is not part of the contract. code is.

Most failures carry a generic code, not a named one

Section titled “Most failures carry a generic code, not a named one”

This is the part a catalogue usually hides, so it goes first.

The API raises 326 errors that carry no business code, against 46 named public codes. Those 326 fall through to one of the generic codes below, chosen from the HTTP status alone. In practice you will meet VALIDATION_ERROR and NOT_FOUND far more often than anything specific.

So: write your error handling around the generic codes, and treat the named ones as refinements. An integration that only handles named codes will be surprised by the common case.

Code HTTP What it means What to do
VALIDATION_ERROR 400 The request failed validation and no more specific reason was attached. Read error. Fix the request. Not retryable unchanged.
UNAUTHORIZED 401 Authentication failed and no more specific reason was attached. Check your credentials and signing. Not retryable unchanged.
FORBIDDEN 403 Authenticated, but not allowed to do this. Check scopes and workspace state. Human action.
NOT_FOUND 404 The resource does not exist, or is not yours. Check the id. Terminal for that id.
CONFLICT 409 The request conflicts with current state. Re-read the resource, then decide.
UNPROCESSABLE 422 Well-formed, but rejected by a business rule with no specific code. Read error. Fix the input or the state. Not retryable unchanged.
INTERNAL_ERROR 500 An unexpected server error. Details are logged on our side, never returned. Retryable. Retry with backoff; if it persists, contact [email protected] with the timestamp.
DATABASE_ERROR 500 A database error. The underlying detail is never returned. Retryable. Same as above.
Code HTTP Cause What to do
MISSING_AUTH_HEADERS 401 One of the three X-CowriePay-* headers is absent. Send all three. See the Authentication guide.
INVALID_SIGNATURE 401 The HMAC does not match the signing string. Log the exact signing string; confirm you hashed the raw body and included /v2 and the query string.
TIMESTAMP_EXPIRED 401 The timestamp is outside the accepted clock skew. Sync your clock (NTP), then retry. Retryable once fixed.
REPLAY_DETECTED 401 The same signature was presented twice inside the replay window. Use a fresh timestamp per request. Never re-send a signed request verbatim.
INVALID_API_KEY 401 Unknown or revoked key. Check the key id. Human action.
API_KEY_EXPIRED 401 The key passed its expiry date. Mint a new key. Human action.
IP_NOT_ALLOWED 403 The caller IP is not on the key’s IP allowlist. Add the IP, or call from an allowed one. Human action.
INSUFFICIENT_SCOPE 403 The key lacks the scope this endpoint requires. Mint a key with the scope. Human action.
WORKSPACE_INACTIVE 401 The workspace is suspended or closed. Contact support. Terminal until resolved.

Request shape, idempotency and rate limits

Section titled “Request shape, idempotency and rate limits”
Code HTTP Cause What to do
INVALID_JSON 400 The body is not valid JSON. Fix the serialisation.
PAYLOAD_TOO_LARGE 413 The body exceeds 50kb. Send less.
UNSUPPORTED_MEDIA_TYPE 415 The body charset or encoding is not supported. Send UTF-8 JSON.
IDEMPOTENCY_KEY_INVALID 400 The Idempotency-Key header is not 1-255 characters of A-Z a-z 0-9 _ -. Fix the key format.
IDEMPOTENCY_CONFLICT 409 A request with this key is still in flight. Retryable. Wait and retry with the SAME key.
IDEMPOTENCY_MISMATCH 422 The same key was reused with a different body. Use a new key, or send the original body. Not retryable unchanged.
RATE_LIMITED 429 Too many requests. Retryable. Back off and retry.
Code HTTP Cause What to do
CHAIN_NOT_ENABLED 422 The chain is not open for new operations. Chains are closed by default. Call List the chains open for new operations and offer only those.
FEATURE_NOT_AVAILABLE 422 The feature is not enabled for your workspace. Contact support if you expected access. Human action.
Code HTTP Cause What to do
INSUFFICIENT_BALANCE 422 Available balance is below the requested amount. Fund the workspace, or request less.
INVALID_DESTINATION_ADDRESS 400 The address is not valid for the chain. Validate before submitting.
DESTINATION_IS_INTERNAL 422 The destination is a CowriePay-managed address. On-chain transfers between CowriePay addresses are not allowed. Withdraw to an external address.
WITHDRAWALS_FROZEN 422 Withdrawals are frozen for this workspace. Contact support. Terminal until resolved.
WITHDRAWAL_NOT_CANCELLABLE 422 The withdrawal is past a cancellable state. Nothing to do; it is already processing or settled. Terminal.
WORKSPACE_NOT_FOUND 422 The workspace does not exist. Check your key.
SYSTEM_WORKSPACE_PROTECTED 422 The target is a protected internal workspace. You should not see this; contact support if you do.

These only occur in the Sandbox, on the faucet endpoint. The largest named group, because the faucet validates a lot before it dispenses.

Code HTTP Cause What to do
FAUCET_ONLY_SANDBOX 403 Called with a live key. Use a cpk_test_ key.
FAUCET_NOT_SANDBOX 422 The target wallet is not a sandbox wallet. Use a testnet wallet.
FAUCET_ADDRESS_NOT_FOUND 422 The address is not a wallet of yours. Create the wallet first.
FAUCET_INVALID_ASSET 400 The asset is not dispensable on that chain. Check the asset and chain pair.
FAUCET_INVALID_AMOUNT 400 The amount is not a valid number, or is out of range. Fix the amount.
FAUCET_AMOUNT_OVER_CAP 422 Above the per-drip cap. Ask for less, or drip twice.
FAUCET_NO_ASSETS 400 No assets were requested. Send at least one.
FAUCET_DUPLICATE_ASSET 422 The same asset appears twice in one request. De-duplicate.
FAUCET_TOO_MANY_ITEMS 422 Too many assets in one request. Split the request.
FAUCET_RATE_LIMITED 422 Too many drips in the window. Retryable. Wait and retry.
FAUCET_NATIVE_REQUIRES_DEPOSIT 422 A native drip requires an existing token deposit on that wallet. Drip the token first.
FAUCET_NATIVE_BUDGET_EXCEEDED 422 The workspace native budget for the window is spent. Retryable later. Wait for the window to roll.
FAUCET_DISPENSER_EXHAUSTED 422 The shared dispenser wallet is empty. Not your fault. Contact support; we refill it.

Some 200 responses carry a warnings[] array with its own codes, for example when a deposit is below the sweep minimum. Those are not errors: the request succeeded. They are documented with the endpoints that emit them, not here, so that this page stays a list of things that failed.