Error codes
The envelope
Section titled “The envelope”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. |
Authentication and signing
Section titled “Authentication and signing”| 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. |
Chains and availability
Section titled “Chains and availability”| 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. |
Withdrawals and security
Section titled “Withdrawals and security”| 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. |
Sandbox faucet
Section titled “Sandbox faucet”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. |
A note on warnings
Section titled “A note on warnings”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.