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

# Accounts errors

> Every error the accounts endpoints raise, what causes it, and whether to retry

Every accounts error returns the same five-key envelope, so you only need one
parser. **Branch on `errors[0].code`, not on the message text** — the codes are
frozen and will never be renumbered or reused, while messages may be reworded.

```json theme={null}
{
  "code": 409,
  "message": "Account is deactivated. Reactivate it before using it",
  "status": "ERROR",
  "data": null,
  "errors": [
    {
      "code": "ERRACC_1002",
      "message": "Account is deactivated. Reactivate it before using it",
      "field": "accountId"
    }
  ]
}
```

<Warning>
  **Errors are wrapped; successes are not.** A successful accounts response is the
  bare object — there is no `data` key to unwrap. Only the error path uses this
  envelope. See [Errors and the response envelope](/api-reference/errors).
</Warning>

When the failure is about one specific input, `errors[0].field` names it, so you
can bind the message straight to a form field. When it is not about an input —
authentication, rate limits, a provider outage — it is `null` rather than filled
with prose.

## What to do about each status

| You get | It means | Do this |
| - | - | - |
| `400` | Something in your request is wrong | Fix it and resend. **Never retry unchanged.** |
| `401` | Key missing, wrong, or its partner is inactive | Check the `API-KEY` header. Do not retry. |
| `403` | Valid key, but not allowed | Check the key has `accounts`, and that the customer is yours. |
| `404` | The customer or account does not exist | Do not retry. Re-check the reference id. |
| `409` | Conflicts with current state | Read the code: already open, deactivated, or an unsupported provider. |
| `429` | Too many requests | Back off. The window is one minute. |
| `502` / `503` | A provider is unavailable | **Safe to retry** with backoff — nothing changed. |
| `500` | Unexpected server error | Report it with the `X-Request-ID` response header. |

## Every code

| HTTP | Code | `field` | Message | When | Endpoints |
| - | - | - | - | - | - |
| `400` | `ERRACC_1000` | `userId` | User id is required | The `{userId}` path segment is blank or whitespace. | All |
| `400` | `ERRCORE_1001` | `currency / accountName / displayName` | Validation Failed | A request-body constraint failed. One `errors[]` entry per failed field. | Open account |
| `400` | `ERRACC_1009` | `accountCategory` | Unsupported account category. Expected one of: FIAT, CRYPTO, ONRAMP | `accountCategory` is not `FIAT` or `ONRAMP`. | Open account |
| `400` | `ERRACC_1005` | `status` | Unsupported status '…'. Expected one of: ACTIVE, PENDING, DEACTIVATED, CLOSED, FROZEN, UNDER\_REVIEW, DORMANT | An unknown value was passed in `?status=`. | List accounts |
| `400` | `ERRREF_1000` | `accountId` | '…' is not a valid account id | `{accountId}` is blank, malformed, or not an `acct_…` reference. | Get, Balance, Activity, Activate, Deactivate |
| `400` | `ERRREF_1000` | `walletId` | '…' is not a valid wallet id | `walletId` is missing, blank, or not a `wlt_…` reference. | Open account |
| `400` | `ERRCORE_1001` | `Api-Version` | The 'Api-Version' header is required · Invalid 'Api-Version' format · Unsupported 'Api-Version': … | The version header is missing, malformed, or names a version the server does not support. | All |
| `400` | `ERRCORE_1001` | `null` | The request path is not a valid URL | The raw path contains a doubled slash, an encoded slash, a backslash or a control character. | All |
| `401` | `ERRCORE_1004` | `null` | Partner API key required · Invalid API key · Associated partner is not active or not found | The `Api-Key` header is missing, does not match an active credential, or its partner is inactive. | All |
| `403` | `ERRCORE_1005` | `null` | Insufficient permissions | The key is valid but lacks the `accounts` permission. | All |
| `403` | `ERRCORE_1005` | `null` | Access denied — User does not belong to this partner | The customer exists but belongs to a different partner. | All |
| `403` | `ERRCORE_1005` | `null` | Source IP not allowed for this API key (observed: …) | The calling IP is outside the allow-list configured on the key. | All |
| `404` | `ERRREF_1001` | `walletId` | Wallet 'wlt\_…' not found | A well-formed wallet reference that resolves to no wallet for this customer. | Open account |
| `404` | `ERRREF_1001` | `userId` | User with ID 'cus\_…' not found | `{userId}` resolves to nothing, or the customer is soft-deleted. | All |
| `404` | `ERRREF_1001` | `accountId` | Account 'acct\_…' not found | A well-formed account reference that resolves to no row. | Get, Balance, Activity, Activate, Deactivate |
| `404` | `ERRACC_1001` | `accountId` | Account not found | The account reference resolved, but no such account belongs to *this* customer. | Balance, Activate, Deactivate |
| `404` | `ERRCORE_1002` | `null` | User not found | Relayed from the core service when the resolved user is missing there — a data race, rarely seen. | All |
| `405` | `ERRCORE_1001` | `null` | HTTP … is not supported for this endpoint | Wrong verb on a real path, e.g. `PUT` on Get Account. Note the code: a 405 carries the *validation* code `ERRCORE_1001`, not a method-specific one. Key off the HTTP status here. | All |
| `409` | `ERRACC_1002` | `accountId` | Account is deactivated. Reactivate it before using it | Get Account was called on a deactivated account. Use List to see it, or Activate to bring it back. | Get account |
| `409` | `ERRACC_1003` | `null` | Balance is not available for this account's provider | The account sits with a provider that has no balance integration. | Get balance |
| `409` | `ERRACC_1003` | `null` | Activation is not supported for this account's provider | The account is AED, or sits with a provider that has no lifecycle integration. **AED accounts cannot be activated or deactivated.** | Activate, Deactivate |
| `409` | `ERRACC_1006` | `null` | An account already exists for this currency | The customer already has an active or pending account in that currency. | Open account |
| `409` | `ERRACC_1018` | `currency` | Account limit reached for this currency | The customer already holds **10 accounts in that currency**, counting active and pending accounts combined. | Open account |
| `415` | `ERRCORE_1001` | `null` | Content type … is not supported; use application/json | A non-JSON `Content-Type` on Open Account. Same quirk as 405 — the code is `ERRCORE_1001`. | Open account |
| `429` | `ERRCORE_1006` | `null` | Rate limit exceeded, please retry later | The per-API-key limit was exhausted. Carries `X-RateLimit-Limit` and `X-RateLimit-Remaining`. **Safe to retry.** | All |
| `500` | `ERRCORE_1000` | `null` | An error occurred while processing your request | An unexpected server error. Quote the `X-Request-ID` when reporting it. | All |
| `502` | `ERRACC_1011` | `null` | The balance could not be retrieved. Please try again in a moment | The provider answered but returned no balance. **Safe to retry.** | Get balance |
| `502` | `ERRACC_1004` | `null` | The status change could not be accepted. Please try again in a moment | The change could not be queued. Nothing was applied, so it is **safe to retry**. | Activate, Deactivate |
| `503` | `ERRACC_1012` | `null` | The account provider is temporarily unavailable. Please try again later | The provider threw or timed out. **Safe to retry.** | Get balance |

<Warning>
  **Two codes cover "you cannot open another account in this currency".**
  `ERRACC_1018` is the ceiling — 10 per currency, active and pending combined.
  `ERRACC_1006` reports that an account already exists for the currency. Match on
  `errors[0].code` and handle **both**, rather than assuming only one of them can
  reach you.
</Warning>

<Note>
  **A `405` and a `415` both carry `ERRCORE_1001`** — the validation code — rather
  than a method- or media-specific one. Key off the HTTP status for those two.
</Note>

<Warning>
  **A malformed id is a `400`, not a `404`.** `ERRREF_1000` means the reference did
  not parse as an `acct_…` or `wlt_…` id at all. `ERRREF_1001` means it parsed
  correctly but resolves to nothing. Different fixes, so they are different codes.
</Warning>

## Worked examples

### 400 · validation

One entry per failed constraint. The envelope message is always the literal `Validation Failed`, and `field` names the real property.

```json 400 theme={null}
{
  "code": 400,
  "message": "Validation Failed",
  "status": "ERROR",
  "data": null,
  "errors": [
    { "code": "ERRCORE_1001", "message": "Currency is mandatory!", "field": "currency" },
    { "code": "ERRCORE_1001", "message": "Account Name is mandatory!", "field": "accountName" },
    { "code": "ERRCORE_1001", "message": "Display name must not exceed 50 characters", "field": "displayName" }
  ]
}
```

### 400 · business rule

A domain error carries its own `ERRACC_*` code and names the offending parameter.

```json 400 theme={null}
{
  "code": 400,
  "message": "Unsupported account category. Expected one of: FIAT, CRYPTO, ONRAMP",
  "status": "ERROR",
  "data": null,
  "errors": [
    {
      "code": "ERRACC_1009",
      "message": "Unsupported account category. Expected one of: FIAT, CRYPTO, ONRAMP",
      "field": "accountCategory"
    }
  ]
}
```

### 400 · bad reference

A malformed id is a 400, not a 404. A well-formed id that resolves to nothing is the 404.

```json 400 theme={null}
{
  "code": 400,
  "message": "'zxsdf' is not a valid account id",
  "status": "ERROR",
  "data": null,
  "errors": [
    { "code": "ERRREF_1000", "message": "'zxsdf' is not a valid account id", "field": "accountId" }
  ]
}
```

### 401 · auth

Raised before routing. `field` is always null on a 401.

```json 401 theme={null}
{
  "code": 401,
  "message": "Partner API key required",
  "status": "ERROR",
  "data": null,
  "errors": [
    { "code": "ERRCORE_1004", "message": "Partner API key required", "field": null }
  ]
}
```

### 403 · permission

Either the key lacks the `accounts` permission, or the customer belongs to another partner. `field` is always null.

```json 403 theme={null}
{
  "code": 403,
  "message": "Insufficient permissions",
  "status": "ERROR",
  "data": null,
  "errors": [
    { "code": "ERRCORE_1005", "message": "Forbidden", "field": null }
  ]
}
```

### 404 · not found

Customer and account both report through this shape, distinguished by `field`.

```json 404 theme={null}
{
  "code": 404,
  "message": "User with ID 'cus_missing' not found",
  "status": "ERROR",
  "data": null,
  "errors": [
    { "code": "ERRREF_1001", "message": "User with ID 'cus_missing' not found", "field": "userId" }
  ]
}
```

### 409 · conflict

Three distinct causes: a duplicate account on open, a deactivated account on get, and an operation the provider does not support.

```json 409 theme={null}
{
  "code": 409,
  "message": "Account is deactivated. Reactivate it before using it",
  "status": "ERROR",
  "data": null,
  "errors": [
    {
      "code": "ERRACC_1002",
      "message": "Account is deactivated. Reactivate it before using it",
      "field": "accountId"
    }
  ]
}
```

### 429 · rate limit

Read `X-RateLimit-Limit` and `X-RateLimit-Remaining` from the response headers. The window is one minute and rejection is immediate — back off rather than retrying tightly.

```json 429 theme={null}
{
  "code": 429,
  "message": "Rate limit exceeded, please retry later",
  "status": "ERROR",
  "data": null,
  "errors": [
    { "code": "ERRCORE_1006", "message": "Rate limit exceeded, please retry later", "field": null }
  ]
}
```

### 500 · server error

Quote the `X-Request-ID` response header when you report one.

```json 500 theme={null}
{
  "code": 500,
  "message": "An error occurred while processing your request",
  "status": "ERROR",
  "data": null,
  "errors": [
    { "code": "ERRCORE_1000", "message": "Unknown Error", "field": null }
  ]
}
```

### 502 / 503 · retryable

Both are safe to retry with backoff — no state was changed.

```json 502 / 503 theme={null}
{
  "code": 503,
  "message": "The account provider is temporarily unavailable. Please try again later",
  "status": "ERROR",
  "data": null,
  "errors": [
    {
      "code": "ERRACC_1012",
      "message": "The account provider is temporarily unavailable. Please try again later",
      "field": null
    }
  ]
}
```

## Retrying safely

| Codes | Retry? |
| - | - |
| `ERRACC_1011` — the balance could not be retrieved | **Yes**, after a moment. The provider answered but returned no balance. |
| `ERRACC_1012` — the provider is temporarily unavailable | **Yes**, with backoff. |
| `ERRACC_1004` — the status change could not be accepted | **Yes.** Nothing was applied. |
| `ERRCORE_1006` — the rate limit | **Yes**, after the window. One minute. |
| `ERRCORE_1000` — an unexpected server error | Once, then report it with the `X-Request-ID`. |
| Everything else `4xx` | **No.** Fix the request or the credential. |

The full cross-service catalogue, including the other `ERR` modules, is on
[Error codes](/api-reference/error-codes).
