> ## Documentation Index
> Fetch the complete documentation index at: https://docs.primevault.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Error Codes

PrimeVault error responses combine an HTTP status with a human-readable message and, for known failures, a machine-readable custom code. The HTTP status remains authoritative for the broad failure class; the custom code enables precise handling inside that class.

## Error response format

```json theme={null}
{
  "message": "Vault has insufficient balance to complete the transaction",
  "code": "4003"
}
```

| Field | Required | Description |
| - | - | - |
| `message` | Yes | Human-readable diagnostic text. Suitable for logs and operator-facing messages after sensitive values are removed. |
| `code` | No | Stable machine-readable PrimeVault error code for a known failure. Generic validation failures can use `"400"`, and a few low-level failures may omit it. |

<Tip>
  Branch program logic on `code` when a documented code exists. Use `message` as diagnostic context, not as a stable identifier.
</Tip>

## Handling order

<Steps>
  <Step>
    Inspect the HTTP status and typed SDK error class.
  </Step>

  <Step>
    Read the custom `errorCode` when present.
  </Step>

  <Step>
    Apply handling for that documented code.
  </Step>

  <Step>
    Fall back to the HTTP status and message when no custom code is available.
  </Step>

  <Step>
    Preserve the original status and code in structured logs, without logging credentials or sensitive request data.
  </Step>
</Steps>

## SDK examples

<Tabs>
  <Tab title="TypeScript / JavaScript">
    The SDK error carries `message`, `errorCode`, `status`, and `responseText`.

    ```typescript theme={null}
    import {
      BadRequestError,
      TooManyRequestsError,
    } from "@primevault/js-api-sdk";

    try {
      await apiClient.createTransferTransaction({
        source,
        destination,
        amount,
        asset,
        chain,
      });
    } catch (error) {
      if (error instanceof BadRequestError) {
        switch (error.errorCode) {
          case "4003":
            // Insufficient balance.
            break;
          case "4011":
            // Compliance or OFAC restriction.
            break;
          case "4013":
            // Invalid transaction parameters.
            break;
          default:
            console.error(error.status, error.message);
        }
      } else if (error instanceof TooManyRequestsError) {
        // Retry with bounded exponential backoff and jitter.
      }
    }
    ```
  </Tab>

  <Tab title="Python">
    The Python SDK error exposes `response_text` and `code`.

    ```python theme={null}
    from primevault_python_sdk.base_api_client import BadRequestError

    try:
        api_client.create_transfer_transaction(request)
    except BadRequestError as error:
        if error.code == "4003":
            # Insufficient balance.
            ...
        else:
            # Fall back to the HTTP class and diagnostic message.
            print(error.response_text)
    ```
  </Tab>
</Tabs>

## Retry safety

* Do not retry a known validation, permission, compliance, or insufficient-balance error unchanged.
* Retry `429` and transient `5xx` responses only with bounded backoff. See [Status Codes](/api-basics/status-codes) for retry guidance by status.

## Transaction error codes

Transaction endpoints return the following custom codes, typically with a 400 status. Use the Safe to retry? column to decide whether to fix the request, wait for an external change, or stop.

| Code | Name | When it happens | Example message | Safe to retry? |
| - | - | - | - | - |
| `4001` | `ON_CHAIN_VALIDATION_ERROR` | The chain/RPC rejected the built transaction | RPC error text | Yes — fix input, resend |
| `4002` | `TXN_MESSAGE_VALIDATION_ERROR` | Invalid EIP-712 / typed data (contract call, WalletConnect) | validation text | Yes |
| `4003` | `TOKEN_BALANCE_LOW` | Insufficient asset balance in the vault | Vault has insufficient balance to complete the transaction | Yes — after funding |
| `4004` | `GAS_TOKEN_BALANCE_LOW` | Insufficient native token for gas (source or fee-payer vault) | Vault has insufficient native token balance to complete the transaction | Yes — after funding |
| `4005` | `DESTINATION_ERROR` | Missing/invalid contact, vault, or address; bad address format | `Invalid destination address: <addr> for chain: <chain>` | Yes — after fixing |
| `4006` | `POLICY_CONFLICT` | Blocked by policy / no matching / ambiguous policy | Transaction blocked by policy. | Yes — until policy changes |
| `4011` | `COMPLIANCE_BLOCKED` | OFAC / sanctioned destination address | Cannot add OFAC sanctioned address | No — change the destination |
| `4012` | `CHAIN_NOT_ENABLED` | The chain is not enabled for your organization | `<chain> transactions are not enabled at this time` | Yes — after enabling the chain |
| `4013` | `INVALID_TXN_PARAMS` | Invalid category, unsupported gas parameter, or missing field | Invalid transaction category, should be TRANSFER, SWAP or CONTRACT\_CALL | Yes — fix the request parameters |

### Safe retries with externalId

Send a stable, unique `externalId` for each logical payment and reuse the **same** value on every retry of that payment. `externalId` is unique per customer, so:

* If the transaction was never created, the retry creates it.
* If it was already created, the retry is safely rejected as a duplicate (A record with the same information already exists), so a second payment is impossible.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.