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

# Didit API Errors: Status Codes, Bodies and Fixes

> Every Didit API error: HTTP status codes, response body shapes, stable error codes, and which failures are safe to retry. Covers 401, 403, 409, 429 and 5xx.

The Didit API reports a failure with an HTTP status code and, in almost every case, a JSON body.
This page lists what each status means, what the body looks like, which `code` values you can branch on, and which failures are safe to retry.
Rely on the status first and on `code` when it is present, because the text of a message can change.

## Status codes

| Status | What it means | What to do | Retry? |
| - | - | - | - |
| `400` | The request is invalid: a field failed validation, or an image or document could not be read | Fix what the body names | After fixing it |
| `401` | The key you sent is wrong, malformed, revoked or expired | Check `DIDIT_API_KEY`, or create a new key | After fixing it |
| `402` | The check could not be paid for, so it did not run | Top up your balance | After topping up |
| `403` | No key was sent, the key has no access to this resource or action, the request came from outside the key's IP list, or the balance cannot cover the call | Read the body, then see [API key errors](#api-key-errors) and [Not enough credits](#not-enough-credits) | After fixing it |
| `404` | The resource does not exist, or a session is outside the key's workflows or statuses | Check the id and the key's limits | No |
| `409` | The request conflicts with the current state, for example a reused `Idempotency-Key` or a step that already ran | Read `code` and reload the state | Depends on `code` |
| `410` | The resource has expired, for example a session or an invitation | Create a new one | No |
| `415` | The request body is not JSON | Send `Content-Type: application/json` | After fixing it |
| `422` | A wallet address fits more than one blockchain, so nothing was screened or billed | Resend with `blockchain` set to one of `candidate_chains` | After fixing it |
| `429` | A rate limit or a quota was reached | Wait for `Retry-After`; see [Rate limiting](https://docs.didit.me/integration/rate-limiting.md) | Yes, after waiting |
| `500` | An unexpected error on Didit's side | Retry with backoff, and contact support if it continues | See [Retries](#retries-and-idempotency) |
| `502` | A service Didit depends on returned an error | Read `code` | See [Retries](#retries-and-idempotency) |
| `503` | A service Didit depends on is temporarily unavailable | Read `code` and try again | See [Retries](#retries-and-idempotency) |

## Error bodies

Error bodies do not share one envelope, so read them defensively.
The shape depends on the endpoint family:

| Shape | Where you see it | Example |
| - | - | - |
| `detail` | Most errors, including authentication, not found and rate limits | `{"detail": "You do not have permission to perform this action."}` |
| `detail` and `code` | Errors with a stable code | `{"detail": "This application was deleted.", "code": "application_deleted"}` |
| Field errors | Input validation | `{"workflow_id": ["This field is required."]}` |
| `non_field_errors` | Validation across fields | `{"non_field_errors": ["..."]}` |
| A JSON array | Some validation errors, such as a simulate call | `["Scenario can only be armed before the verification starts."]` |
| `error` | Most standalone APIs | `{"error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me"}` |
| `error` and `code` | Image errors on standalone APIs | `{"error": "...", "code": "invalid_image"}` |
| `error` as a code, with `detail` | Digital ID wallet verification | `{"error": "insufficient_credits", "detail": "..."}` |

A `500` may have no JSON body at all, so handle a body that does not parse.

```javascript theme={null}
const raw = await response.text();
let body = {};
try {
  body = JSON.parse(raw);
} catch {
  // A 5xx response may not be JSON: rely on response.status.
}
const code = body.code ?? null;
const message = body.detail ?? body.error ?? (Array.isArray(body) ? body[0] : raw);
```

## API key errors

| What you sent | Status | `detail` |
| - | - | - |
| No `x-api-key` header | `403` | `You do not have permission to perform this action.` |
| A wrong, malformed or revoked key | `401` | `Authentication credentials were not provided or are invalid.` |
| An expired key | `401` | `This API key has expired.` |
| A key used from outside its IP list | `403` | `This API key can't be used from this IP address.` |
| A scoped key without access to this resource or action | `403` | `This API key has no access to this endpoint.` |
| A key limited to some workflows or statuses, on an endpoint it can't use | `403` | `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.` |
| A key limited to some workflows or statuses, on a session outside those limits | `404` | `Session not found.` |
| A key whose application was just deleted | `403` | `This application was deleted.`, with `"code": "application_deleted"` |

For a few minutes after an application is deleted the API answers with `application_deleted`, and after that it refuses the key like any wrong key.
Do not retry: the application and its keys are gone.

See [API authentication](https://docs.didit.me/getting-started/api-authentication.md) and [API keys](https://docs.didit.me/console/api-keys.md) for how keys and their limits work.

## Not enough credits

Each API family reports a low balance in its own way:

| Call | Status | Body |
| - | - | - |
| `POST /v3/session/` | `400` | `{"detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me"}` |
| Most standalone APIs | `403` | `{"error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me"}` |
| `POST /v3/bank/account-check/` | `402` | `{"detail": "...", "code": "insufficient_credits"}` |
| Digital ID wallet verification | `403` | `{"error": "insufficient_credits", "detail": "..."}` |
| Transactions, Travel Rule and address validation | `403` | `{"detail": "...", "code": "insufficient_credits"}` |

On a contract, a session create answers `403` with `"code": "contract_billing_suspended"` while paid services are paused for your organization.
[Sandbox applications](https://docs.didit.me/integration/sandbox-testing.md) are never billed, so they skip these checks.

## Error codes

These `code` values are stable, so you can branch on them.
Most sit next to `detail`; on Digital ID wallet verification the code is the value of `error`.

### Access and billing

| Code | Status | Meaning |
| - | - | - |
| `insufficient_credits` | `402`, `403` | The balance cannot cover the call |
| `contract_billing_suspended` | `403` | Paid services are paused for this organization |
| `contract_rate_missing` | `402`, `403` | The contract has no rate for this check |
| `application_deleted` | `403` | The key's application was deleted |
| `organization_deleted` | `403` | The key's organization was deleted |
| `event_monitoring_disabled` | `403` | Event ingestion is not switched on for this application |
| `networks_not_entitled` | `403` | Fraud networks are not enabled for this organization |

### Requests and idempotency

| Code | Status | Meaning |
| - | - | - |
| `invalid_image` | `400` | The image could not be used |
| `return_url_invalid` | `400` | The return URL is not allowed |
| `idempotency_key_invalid` | `400` | The `Idempotency-Key` is longer than 255 characters |
| `idempotency_key_reused` | `409` | The key was already used with a different body |
| `idempotency_key_in_progress` | `409` | The first request with this key is still running; wait for `Retry-After` |
| `verification_in_progress` | `409` | The user is completing this verification right now, so it cannot be cancelled; wait for `Retry-After` |
| `feature_override_not_pending` | `409` | The override already applied or was cancelled |

### Bank, phone and risk checks

| Code | Status | Meaning |
| - | - | - |
| `invalid_account_input` | `400` | The account details are not valid |
| `country_not_covered` | `404` | The check does not cover this country or this kind of account |
| `bank_not_covered` | `404` | The check does not reach this bank |
| `bank_verification_not_available` | `404` | Bank Verification is not available yet |
| `account_check_unavailable` | `503` | The check is temporarily unavailable and the request was not charged |
| `phone_provider_unavailable` | `502` | Phone verification is temporarily unavailable; nothing is billed and no attempt is used |
| `risk_lookup_unavailable` | `502` | The risk lookup is temporarily unavailable and the request was not charged |

### Business verification (KYB)

| Code | Status | Meaning |
| - | - | - |
| `kyb_registry_provider_unavailable` | `502` | The company registry is temporarily unavailable |
| `kyb_registry_country_unsupported` | `400` | The registry search does not cover this country |
| `kyb_registry_search_limit_reached` | `429` | The application reached its limit of 600 paid searches an hour |
| `kyb_session_not_writable` | `409` | The session is finished |
| `kyb_step_mismatch` | `409` | The session is on another step; `next_step` names it |
| `kyb_no_party_drafts` | `409` | There are no draft parties to submit |
| `kyb_parties_already_submitted` | `409` | The parties were already submitted |
| `kyb_party_kyc_session_not_deleted` | `409` | The party's verification is still live, so it cannot be regenerated |

### Digital ID wallets

| Code | Status | Meaning |
| - | - | - |
| `wallet_unavailable` | `403` | This wallet is not available to your application |
| `wallet_provider_unavailable` | `502` | The wallet could not start, and nothing was created |
| `wallet_not_configured` | `503` | This environment cannot reach the wallet |

### Reports and cases

| Code | Status | Meaning |
| - | - | - |
| `report_deletion_pending` | `409` | The report is being deleted; repeat the deletion to finish erasing its files |
| `report_storage_unavailable` | `503` | The report's files could not be erased; repeat the deletion |
| `wallet_address_deletion_incomplete` | `503` | A stored file could not be deleted, so the deletion stopped; try again |
| `case_request_not_waiting` | `409` | The request no longer waits on the customer, so there is nothing to remind them of |
| `case_request_reminder_too_soon` | `429` | A reminder was sent recently; `retry_at` says when you can send the next one |

### Electronic signatures

| Code | Status | Meaning |
| - | - | - |
| `signature_not_found` | `404` | The signature does not exist |
| `signature_field_not_found` | `404` | The signature field does not exist |
| `signature_not_signed` | `404`, `409` | The document has not been signed |

On session create, the document problems below arrive as the value of the offending field with status `400`, for example `{"sign": "signature_document_changed"}`:

| Value | Meaning |
| - | - |
| `signature_document_changed` | The document differs from the one sent first |
| `signature_document_not_supported` | The document format is not supported |
| `signature_document_conversion_failed` | The document could not be converted |
| `field_anchor_not_found` | A field's text anchor was not found in the document |
| `field_value_missing` | A field has no value |

### Webhooks

| Code | Status | Meaning |
| - | - | - |
| `webhook_url_missing` | `400` | The delivery has no recorded URL, so nothing is sent |
| `invalid_webhook_payload` | `400` | The webhook data is not in a valid format |
| `webhook_resend_error` | `400` | The stored delivery could not be signed again, or the attempt could not be completed |

## Retries and idempotency

Some failures are safe to repeat and some are not:

* **`429`:** wait for the `Retry-After` header, then retry.
* **`502` and `503`:** retry with backoff when the endpoint's reference says nothing was billed, as it does for phone, email, bank and risk checks.
* **`502` on `POST /v3/database-validation/`:** read each `validation_errors` entry first, because this status alone does not mean a temporary failure.
* **`503` on a deletion:** repeat the same request until it returns `200`.
* **Other `4xx`:** fix the request first, because the same request fails the same way.

`POST /v3/aml/` and Digital ID wallet verification accept an `Idempotency-Key` header of up to 255 characters.
Repeating a request with the same key and body replays the stored response, so the work and the charge run once.
The same key with a different body answers `409`, and while the first request is still running the wallet endpoints answer `409` with `Retry-After`.

`POST /v3/session/` has no idempotency key, so if a create times out, list your sessions by `vendor_data` before creating another one.

## Related

* [Rate limiting](https://docs.didit.me/integration/rate-limiting.md): limits, headers and the `429` body.
* [API authentication](https://docs.didit.me/getting-started/api-authentication.md): how to send your key.
* [Sandbox testing](https://docs.didit.me/integration/sandbox-testing.md): reproduce outcomes without real data or billing.
* [Webhooks](https://docs.didit.me/integration/webhooks.md): delivery, retries and order.


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