Error Codes Reference

Every error code the Crypto as a Service API returns, the HTTP status behind it, and how to debug a failed call.

Errors are predictable. Every failure—a missing field, a revoked key, an upstream outage—comes back in one envelope with the same shape. Branch on the HTTP status for the category and on code for the exact reason; treat message as a hint to log or show, never something to parse.

Error Response Format

Every error returns success: false, an errors array, and meta. Each error carries a stable code, a message, and, when it points at your input, a source with a pointer (body field), parameter (path or query parameter), or header. See API conventions for the full rules.

{
  "success": false,
  "errors": [
    {
      "code": "VALIDATION_ERROR",
      "message": "address should not be empty",
      "source": { "pointer": "/address" }
    },
    {
      "code": "VALIDATION_ERROR",
      "message": "amount must be a positive decimal string, e.g. \"0.01000000\"",
      "source": { "pointer": "/amount" }
    }
  ],
  "meta": {
    "timestamp": "2026-10-12T14:03:11Z",
    "version": "v1",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}

HTTP Status Codes

StatusMeaningWhen it occurs
200OKRequest succeeded
201CreatedResource created
204No ContentSucceeded with no body (deprecated DELETE aliases)
207Multi-StatusBulk request with per-item results (bulk user tokens)
400Bad RequestA business rule rejected the request
401UnauthorizedMissing, invalid, revoked, or expired credential
403ForbiddenValid credential that can't perform this action
404Not FoundResource doesn't exist or doesn't belong to you
409ConflictDuplicate resource, or the same idempotency key still running
413Payload Too LargeRequest body too large
415Unsupported Media TypePOST or PUT without Content-Type: application/json
422Unprocessable EntityValidation failed, illegal state change, or idempotency key reused
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error
502Bad GatewayAn upstream service returned an error
503Service UnavailableService temporarily unavailable
504Gateway TimeoutAn upstream service didn't answer in time

Generic Codes

Any endpoint can return these. When an error has no more specific code, the code follows the HTTP status.

CodeHTTP StatusCause
BAD_REQUEST400A business rule rejected the request; read message
UNAUTHORIZED401Missing, invalid, revoked, or expired x-api-key or Bearer token
FORBIDDEN403Valid credential without access to this action
NOT_FOUND404Resource not found or not yours
CONFLICT409The resource already exists
VALIDATION_ERROR422A field, parameter, or header failed validation; source points at it
INVALID_STATE_TRANSITION422The action isn't allowed in the resource's current status
RATE_LIMIT_EXCEEDED429Too many requests; wait retry-after seconds
PAYLOAD_TOO_LARGE413Request body exceeds the size limit
UNSUPPORTED_MEDIA_TYPE415Set Content-Type: application/json
INTERNAL_SERVER_ERROR500Unexpected error—include trace_id when reporting
BAD_GATEWAY502An upstream service returned an error
SERVICE_UNAVAILABLE503Service temporarily unavailable
GATEWAY_TIMEOUT504An upstream service didn't answer in time
METHOD_NOT_ALLOWED405The route doesn't accept this HTTP method
NOT_ACCEPTABLE406The requested response format isn't available
REQUEST_TIMEOUT408The request took too long to arrive
NOT_IMPLEMENTED501The operation isn't available
UNKNOWN_ERRORother 4xxAny other client error; read message

Transitional codes

For one release, some 404 and 409 errors without a specific code still derive it from the message: USER_NOT_FOUND, DEPOSIT_NOT_FOUND, CLIENT_NOT_FOUND, EMAIL_ALREADY_EXISTS, and PHONE_ALREADY_EXISTS. Don't build new logic on them; they'll be replaced by the specific codes below.


Idempotency Errors

CodeHTTP StatusCause
VALIDATION_ERROR422x-idempotency-key isn't a UUID (source.header)
IDEMPOTENCY_IN_PROGRESS409A request with this key is still running; retry shortly
IDEMPOTENCY_KEY_REUSED422The key was already used with a different body; send a new key

See Idempotency.


Authentication Errors

CodeHTTP StatusCause
UNAUTHORIZED401Missing, invalid, revoked, or expired credential, including a user token past its 24-hour lifetime; on x-api-key and user-token routes source.header names the header (SX Customers session errors carry no source)
INVALID_CREDENTIALS401Login rejected: wrong email or password
INVALID_REFRESH_TOKEN401The refresh token is invalid, expired, or already used
FORBIDDEN403The credential can't perform this action

A 401 means fix the header or get a fresh token. A 403 means the credential is valid but lacks reach—use the credential the endpoint expects. See Authentication.


User Errors

CodeHTTP StatusCause
NOT_FOUND404No user with that user_id under your customer (source.parameter: user_id)
USER_NOT_FOUND404The user referenced by a wallet or account call doesn't exist
EMAIL_ALREADY_IN_USE409Another user under your customer has this email (source.pointer: /email)
PHONE_ALREADY_IN_USE409Another user under your customer has this phone number
EXTERNAL_ID_ALREADY_IN_USE409Another user under your customer has this external_id
USER_ID_ALREADY_IN_USE409A user with this user_id already exists
INVALID_STATE_TRANSITION422The user can't get a token in its current status (only verified, active users can)
DUPLICATE_ITEM207 (per item)Bulk tokens: the same user_id appears earlier in the batch

Wallet and Address Errors

CodeHTTP StatusCause
WALLET_NOT_FOUND404No such wallet under your customer, or (with a user token) under that user
ADDRESS_NOT_FOUND404The wallet has no address for that currency
WALLET_NAME_ALREADY_EXISTS400The user already has a wallet with this name

Transfer Errors

CodeHTTP StatusCause
TRANSFER_NOT_FOUND404No such transfer on this wallet
SAME_WALLET_TRANSFER400Source and destination wallet are the same
CROSS_CUSTOMER_TRANSFER400Both wallets must belong to the same customer
INSUFFICIENT_BALANCE400The source wallet's available balance is too low
INVALID_VERIFICATION_CODE400Wrong verification code
VERIFICATION_CODE_EXPIRED400The verification code expired
VERIFICATION_BLOCKED403Too many wrong codes; the transfer is BLOCKED
ACCOUNT_FROZEN403The account is frozen
INVALID_STATE_TRANSITION422The transfer's status doesn't allow this action

Withdrawal Errors

CodeHTTP StatusCause
WITHDRAWAL_NOT_FOUND404No such withdrawal for this user under your customer
VALIDATION_ERROR422Invalid field, for example a network that doesn't match the currency (source.pointer: /network)
INSUFFICIENT_BALANCE400The user's available balance is too low
INVALID_VERIFICATION_CODE400Wrong verification code
VERIFICATION_CODE_EXPIRED400The verification code expired
VERIFICATION_BLOCKED403Too many wrong codes; the withdrawal is BLOCKED
INVALID_STATE_TRANSITION422The withdrawal's status doesn't allow this action
SERVICE_UNAVAILABLE503Crypto withdrawals aren't available for your account yet

Trading Errors

CodeHTTP StatusCause
WALLET_NOT_FOUND404The wallet in the path isn't yours
QUOTE_NOT_FOUND404No such quote for this wallet
QUOTE_EXPIRED400The quote expired—request a new one
ORDER_NOT_FOUND404No such order for this wallet
INVALID_STATE_TRANSITION422The quote was already executed
BAD_GATEWAY502The trading venue couldn't price or execute the request
QUOTE_PERSISTENCE_FAILED502The quote couldn't be recorded—request a new one

A quote the venue rejects at execution doesn't fail the call: the order comes back 201 with status: "FAILED" and status_reason.code: "QUOTE_REJECTED". If the venue's answer is lost, the order comes back 201 with status: "PENDING" and status_reason.code: "EXECUTION_OUTCOME_UNKNOWN": the quote may have executed, so don't retry it; follow the order until it settles.


Customer Errors

CodeHTTP StatusCause
CUSTOMER_NOT_FOUND404No such customer for your session, or it was deleted
ACCOUNT_OWNER_REQUIRED403Only the account holder's session can delete the customer or issue or revoke credentials; a team member's session can't
CUSTOMER_ALREADY_EXISTS409The customer is already registered (source.pointer: /customer_id)
CREDENTIAL_NOT_FOUND404No such credential on this customer
OWNER_CREDENTIAL_NOT_FOUND404The customer has no owner credential yet
CREDENTIAL_ISSUANCE_FAILED502The credential couldn't be issued upstream; retry
EMAIL_POLLER_NOT_FOUND404The customer has no email poller
WEBHOOK_NOT_FOUND404The customer has no webhook configured
INVALID_STATE_TRANSITION422The poller is already in the state you asked for
VAULT_NOT_CONFIGURED404The customer's vault isn't configured yet
TREASURY_WALLET_NOT_CONFIGURED404The customer has no treasury wallet yet

Debugging Tips

Trace IDs

Every response carries a trace_id in meta and in the x-trace-id response header. Log it for every failed request and include it when you report an issue—it's the fastest way for support to find the exact call.

Pass your own x-trace-id (32 lowercase hex characters) to stitch a request to your application logs. The API keeps it and echoes it back; a hyphenated UUID is compacted, and any other value is replaced with a fresh id instead of failing the request.

curl -H "x-trace-id: 4bf92f3577b34da6a3ce929d0e0e4736" \
  -H "x-api-key: YOUR_API_KEY" \
  "https://api.sandbox.sovera.io/sovx/v1/users"

Common Troubleshooting

  1. 401 Unauthorized—Check the x-api-key or Authorization: Bearer header, and mint a fresh user token if it's older than 24 hours.
  2. 403 Forbidden—Use the credential the endpoint expects.
  3. 404 Not Found—Verify the id and that it belongs to your customer (and, with a user token, to that user).
  4. 409 Conflict—A resource with that email, phone, or id already exists, or the same idempotency key is still running.
  5. 422 Unprocessable Entity—Read source to find the field, parameter, or header that failed, or check the resource's status for INVALID_STATE_TRANSITION.
  6. 429 Too Many Requests—Wait the retry-after seconds, then back off exponentially:
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (error.response?.status === 429) {
        const retryAfter = Number(error.response.headers['retry-after'] ?? 2 ** i);
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
      } else {
        throw error;
      }
    }
  }
  throw new Error('Max retries exceeded');
}
  1. 502 / 504—An upstream service had an issue. Retry after a short delay with the same x-idempotency-key.

Error Handling Example

import axios from 'axios';

async function handleApiCall() {
  try {
    const response = await axios.get('https://api.sandbox.sovera.io/sovx/v1/users', {
      headers: { 'x-api-key': 'YOUR_API_KEY' }
    });
    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      const apiError = error.response?.data;
      const { code, message, source } = apiError?.errors?.[0] ?? {};
      const traceId = apiError?.meta?.trace_id;

      console.error(`API Error [${code}]: ${message} ${JSON.stringify(source ?? {})} (trace: ${traceId})`);

      switch (code) {
        case 'UNAUTHORIZED':
          // Re-authenticate or mint a fresh user token
          break;
        case 'RATE_LIMIT_EXCEEDED':
          // Wait retry-after seconds and retry
          break;
        case 'QUOTE_EXPIRED':
          // Request a new quote and retry
          break;
        default:
          throw error;
      }
    }
    throw error;
  }
}

Support

Hit persistent errors? Contact support with:

  • Trace ID from the error response
  • Timestamp of the request
  • Endpoint and HTTP method called
  • Request body / parameters (redact sensitive values)
  • Email: [email protected]

On this page