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

# Error Codes

> Every error code the Partner API V2 can return, with its HTTP status, meaning, and the endpoints that return it.

This page lists every `code` the Partner API V2 can return today. The error tables on each endpoint page show the codes that endpoint returns most often; this page is the complete list.

## Error response format

Every error response has this shape:

```json theme={null}
{
  "success": false,
  "message": "Invalid User Input",
  "code": "INVALID_USER_INPUT_ERROR",
  "errors": [
    { "field": "email", "message": "\"email\" must be a valid email" }
  ]
}
```

| Field | Type | Description |
| - | - | - |
| `success` | boolean | Always `false` on an error. |
| `code` | string | Machine-readable error code. Branch on this field. |
| `message` | string | Human-readable description. For logging and debugging only: the wording can change at any time and is not part of the contract. |
| `errors` | array | Only on validation errors (`INVALID_USER_INPUT_ERROR`, `UNPROCESSABLE_ENTITY`, and a few others). One `{ field, message }` entry per invalid field. |

## Handling errors

* **Branch on `code`, never on `message`.** Messages are meant for people and change without notice.
* **Use the HTTP status as the fallback.** If you get a code you don't recognize, handle it by its status family: `4xx` means fix the request (don't retry it unchanged), `429` and `503` mean retry later with backoff, and `5xx` means retry with backoff and contact support if it persists.
* **Expect new codes.** We add codes as the API grows. An unknown code must never break your integration.
* **Some statuses will change.** A few codes are returned with an HTTP status that doesn't match their meaning (see [Known inconsistencies](#known-inconsistencies)). We will correct them only in a future API version, announced in advance, so you can rely on the values below until then.
* **Send an `Idempotency-Key`** on the endpoints that support it, so you can retry safely after a timeout or a `5xx`. See [Idempotency](https://docs.capa.fi/docs/idempotency.md).

## General

Any endpoint can return these.

| Code | HTTP status | Meaning | Typical endpoints |
| - | - | - | - |
| `UNAUTHORIZED` | 401 | The `partner-api-key` header is missing, malformed, or invalid; or the `userId` in the request does not belong to your partner account. | All |
| `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded. The response includes a `Retry-After` header with the number of seconds to wait. | All |
| `INVALID_USER_INPUT_ERROR` | 400 | The request failed validation: a field is missing, has the wrong type or format, or is not allowed. `errors[]` lists each field. Also returned with **422** when an `Idempotency-Key` is reused with a different body. | All |
| `UNPROCESSABLE_ENTITY` | 422 | The request is well-formed but breaks a business rule, for example a KYB submission for an individual user. `errors[]` may list the fields involved. | Users, KYB, Associated persons |
| `BAD_REQUEST` | 400 | Generic request error, used by many checks (a disabled currency, a quote of the wrong type, an invalid `Idempotency-Key`, conflicting `userId` values, and more). Read `message` to see which. Returned with **409** when a user has already signed their documents. | Most |
| `REQUIRED_USER_ID_ERROR` | 400 | The endpoint needs a `userId` (in the path, query, or body) and none was given. | On-ramp, Off-ramp, Cross-ramp |
| `NOT_FOUND_ERROR` | 404 | The resource doesn't exist or belongs to another partner. Also returned for unknown routes and for features not enabled for your account. | All |
| `FORBIDDEN` | 403 | The operation isn't allowed for your partner account, for example a corridor not enabled for you, or a quote created by another partner. | Ramps, Cross-ramp |
| `CONFLICT` | 409 | The resource already exists or was changed concurrently, or a request with the same `Idempotency-Key` is still in progress. | Ramps, Cross-ramp, KYB, Associated persons, Virtual accounts |
| `MISSING_USER` | 401 | The user named in the request could not be resolved. | Virtual accounts, Create bank account |
| `EXTERNAL_PROVIDER_ERROR` | 502 | An upstream provider (identity verification or banking) failed. Retry later. | KYC, KYB, Virtual accounts, Document signature |
| `INTERNAL_SERVER_ERROR` | 500 | Unexpected server error. Retry with backoff. Also returned with **400** for a malformed JSON body (fix the body; don't retry it unchanged) and for a temporary idempotency-store failure (retry), and with **503** when country restrictions can't be checked. | All |

## Users, KYC and KYB

| Code | HTTP status | Meaning | Typical endpoints |
| - | - | - | - |
| `USER_NOT_VERIFIED_ERROR` | 403 | The user hasn't completed KYC/KYB (for the country of the operation). | Ramps, Cross-ramp, Document signature, Associated person signatures |
| `USER_TERMS_NOT_ACCEPTED_ERROR` | 403 | The user hasn't accepted the Terms and Conditions and Privacy Notice. Only when T\&C enforcement is enabled for your account. | Ramps, Cross-ramp |
| `SIGNATURE_REQUIREMENT_NOT_MET` | 403 | A required signature is still outstanding (for example a beneficial owner's form). Complete it, then retry. | Cross-ramp, Cross-ramp invoice upload |
| `RESTRICTED_COUNTRY` | 403 | Operations for this country are not supported. | KYC/KYB verification links, Create user KYC, KYB, Update user |
| `INVALID_VERIFICATION_TYPE_FOR_USER` | 400 | The verification type doesn't match the user type: KYC is for `INDIVIDUAL` users and KYB for `BUSINESS` users. | KYC verification link, KYB verification link |
| `PARTNER_SELF_USER_VERIFICATION_NOT_ALLOWED` | 400 | The user is your own partner account's user, which can't be verified through this endpoint. | KYC/KYB verification links |
| `EMAIL_NOT_FOUND` | 400 | The user doesn't sign in with an email address, which this verification requires. | Create user KYC |
| `SELFIE_VALIDATION_FAILED` | 400 | The selfie file is invalid. | Create user KYC |
| `SELFIE_DOWNLOAD_FAILED` | 400 | The selfie file couldn't be downloaded from the URL provided. | Create user KYC |
| `FRONT_ID_VALIDATION_FAILED` | 400 | The ID front image is invalid. | Create user KYC |
| `FRONT_ID_DOWNLOAD_FAILED` | 400 | The ID front image couldn't be downloaded from the URL provided. | Create user KYC |
| `BACK_ID_VALIDATION_FAILED` | 400 | The ID back image is invalid. | Create user KYC |
| `BACK_ID_DOWNLOAD_FAILED` | 400 | The ID back image couldn't be downloaded from the URL provided. | Create user KYC |
| `DOCUMENT_VALIDATION_FAILED` | 400 | A supporting document is invalid. | Create user KYC |
| `DOCUMENT_DOWNLOAD_FAILED` | 400 | A supporting document couldn't be downloaded from the URL provided. | Create user KYC |
| `IDENTITY_VERIFICATION_SESSION_LINK_FAILED` | 500 | The verification session couldn't be linked to the user. Retry. | `POST /kyc/send-verification` (deprecated) |

## Bank accounts

| Code | HTTP status | Meaning | Typical endpoints |
| - | - | - | - |
| `USER_BANK_INFO_NOT_FOUND` | 404 | The bank account doesn't exist or is disabled. | Off-ramp, Cross-ramp |
| `USER_BANK_INFO_ACCESS_DENIED` | 403 | The bank account belongs to another user or partner. | Off-ramp, Cross-ramp, Delete bank account |
| `USER_BANK_INFO_CREATION_FAILED` | 400 | The bank account couldn't be registered, for example because the user isn't verified for that country or the bank details were rejected. | Create bank account, Off-ramp, Cross-ramp, On-ramp |
| `UNSUPPORTED_BANK` | 400 | This bank isn't supported. | Create bank account, Off-ramp |
| `MISSING_IDENTITY_DOCUMENT_NUMBER` | 400 | The user's verified identity has no document number, which the bank account requires. | On-ramp, Cross-ramp |

## Wallets

| Code | HTTP status | Meaning | Typical endpoints |
| - | - | - | - |
| `UserOnRampWalletDuplicateError` | 409 | The user already has a wallet with this address and VM type. | Create wallet |
| `UserOnRampWalletDisabledError` | 403 | The wallet has been disabled after risk screening and can't receive funds. | On-ramp |

## Quotes and transactions

| Code | HTTP status | Meaning | Typical endpoints |
| - | - | - | - |
| `QUOTE_EXPIRED` | 400 | The quote has expired or was already used. Request a new quote. | On-ramp, Off-ramp, Cross-ramp |
| `INVALID_FIAT_AMOUNT_ERROR` | 400 | The fiat amount is outside the allowed range. `message` gives the limits. | Quotes, On-ramp, Off-ramp, Cross-ramp |
| `INVALID_TOKEN_AMOUNT_ERROR` | 400 | The token amount is outside the allowed range. `message` gives the limits. | Quotes, On-ramp, Off-ramp, Cross-ramp |
| `INVALID_TOKEN_OPERATION_ERROR` | 400 | The token can't be used for this operation (OTC supports stablecoins only). | On-ramp, Off-ramp |
| `PARTNER_NOT_OWNER_OF_TRANSACTION_ERROR` | 403 | The transaction belongs to another partner. | Cancel transaction, Execute off-ramp |
| `INVALID_TRANSACTION_STATE_TRANSITION` | 409 | The transaction's current status doesn't allow this action. | Cancel transaction |
| `STEP_EXECUTION_ERROR` | 400 | A provider step needed to create the transaction couldn't run, for example because no liquidity was available. Retry later. | Off-ramp, Cross-ramp |

## Cross-ramp

| Code | HTTP status | Meaning | Typical endpoints |
| - | - | - | - |
| `INVALID_COMMERCIAL_INVOICE` | 400 | The invoice is empty, too large, not a PDF, encrypted or unreadable, or has 0 or more than 500 pages. | Off-ramp, Cross-ramp, Cross-ramp bulk, Invoice uploads |
| `COMMERCIAL_INVOICE_UPLOAD_FAILED` | 400 | Storing the invoice failed. Retry. | Off-ramp, Cross-ramp, Invoice uploads |
| `COMMERCIAL_INVOICE_PERSIST_FAILED` | 400 | Saving the invoice to the transaction failed. Retry. | Off-ramp, Cross-ramp |
| `CROSS_RAMP_NOT_AWAITING_INVOICE` | 409 | The cross-ramp is no longer waiting for an invoice (it was cancelled, expired or failed). | Invoice uploads |
| `CROSS_RAMP_BULK_ITEMS_NOT_FOUND` | 404 | One or more `transactionId`s are not part of this bulk. | Bulk invoice upload |
| `CROSS_RAMP_BULK_INVOICE_BUDGET_EXCEEDED` | 400 | The bulk's invoices would exceed 24 MiB in total. | Cross-ramp bulk, Invoice uploads |
| `CROSS_RAMP_BULK_INVOICE_PAGES_EXCEEDED` | 400 | The bulk's invoices would exceed 500 pages in total. | Cross-ramp bulk, Invoice uploads |
| `CROSS_RAMP_BULK_INVOICE_BUSY` | 503 | Another invoice of the same bulk is being processed. Retry in a second or two. | Invoice uploads |
| `CROSS_RAMP_BULK_INVOICE_SIZE_UNAVAILABLE` | 503 | The size of an invoice already stored for the bulk can't be read yet. Retry with backoff. | Single invoice upload |
| `INTERNAL_ERROR` | — | Only inside the `results` of a bulk invoice upload (HTTP 200): that invoice failed for an unexpected reason. Retry it. | Bulk invoice upload |

## Webhooks

| Code | HTTP status | Meaning | Typical endpoints |
| - | - | - | - |
| `INVALID_STATUS_FOR_RETRY` | 422 | Only notifications in `FAILED` or `NOT_SENT` status can be retried. | Retry webhook notification |
| `MANUAL_RETRY_LIMIT_EXCEEDED` | 422 | The notification already used its 3 manual retries. | Retry webhook notification |
| `WEBHOOK_URL_NOT_CONFIGURED` | 422 | You have no webhook URL configured, so the notification can't be sent. | Retry webhook notification |
| `MANUAL_RETRY_RATE_LIMIT_EXCEEDED` | 429 | Too many manual retries in the last minute. Wait and try again. | Retry webhook notification |
| `CONCURRENT_RETRY_CONFLICT` | 409 | The notification was updated at the same time by another request. Retry. | Retry webhook notification |

## Known inconsistencies

These behaviors are not ideal, but integrations may already depend on them, so they stay as they are until a future API version:

* **`INTERNAL_SERVER_ERROR` with a 4xx status.** A malformed JSON body returns `400` with `INTERNAL_SERVER_ERROR`; fix the body before retrying. A temporary failure of the idempotency store also returns `400` with this code; retry it. No response field tells these two apart, and `message` is not part of the contract: the idempotency-store case only happens when you sent an `Idempotency-Key`, so if your body is valid JSON, retry with the same key.
* **Server-side failures with a 400 status.** `STEP_EXECUTION_ERROR`, `COMMERCIAL_INVOICE_UPLOAD_FAILED` and `COMMERCIAL_INVOICE_PERSIST_FAILED` are returned with `400` although they are not caused by the request. They can be retried.
* **Status doesn't match the code.** `BAD_REQUEST` comes with `409` when a user has already signed their documents, and `INVALID_USER_INPUT_ERROR` comes with `422` when an `Idempotency-Key` is reused with a different body.
* **Two codes are PascalCase:** `UserOnRampWalletDuplicateError` and `UserOnRampWalletDisabledError`.
* **Generic codes.** `BAD_REQUEST`, `FORBIDDEN` and `NOT_FOUND_ERROR` cover several different conditions; the cause is only in `message`. Treat them by status, and log `message` for support.
* **`/kyc` endpoints.** `GET /api/partner/v2/kyc/details` (and the deprecated `POST /kyc/verification-link` and `POST /kyc/send-verification`) use an older error format: `errors[]` is never returned, and some errors come without `code`. Unexpected server errors (`500`), rate limiting (`429`) and malformed requests (`400`), and on `send-verification` also file upload errors (`400`, `413`), have no `code`; use the HTTP status for those.


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