> ## 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.

# Errors and lifecycle

> The flat response shape, every status code, and how verification status moves

## Response shape

On success the body **is** the data object. There is no envelope — the HTTP status
tells you it succeeded.

```json Success — 201 from create theme={null}
{
  "userId": "310d2801-9260-4279-81db-f2e13edf7f47",
  "userType": "INDIVIDUAL",
  "status": "INITIATED",
  "sumsubApplicantId": "6aa198eccff9e09439ab321c"
}
```

On failure you get a flat object naming the problem: `code` is the HTTP status,
`message` the specific reason, and `name` the operation that failed.

```json One problem theme={null}
{
  "code": "409",
  "message": "Cannot delete a customer that holds a balance",
  "name": "PartnerOnboardingDelete failed to process"
}
```

When several things are wrong at once they arrive together, one entry each, and
nothing is created.

```json Several problems theme={null}
{
  "errors": [
    { "code": "400", "message": "firstName must be at most 100 characters", "name": "PartnerOnboardingCreate failed to process" },
    { "code": "400", "message": "country must be an ISO 3166-1 alpha-3 code", "name": "PartnerOnboardingCreate failed to process" }
  ]
}
```

<Warning>
  **Two exceptions to the flat shape.** A `503` returns the `errors[]` form, and a
  `429` keeps the older envelope — `{ data, code, message, status, errors }`. Parse
  defensively: check for `errors` first, then fall back to the flat fields.
</Warning>

## Status codes

| Code  | Meaning                                                                                                                                     |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `201` | Customer created or imported                                                                                                                |
| `200` | Success — get, list, update, kyc-link, document upload, delete                                                                              |
| `400` | Validation failed. Nothing is written                                                                                                       |
| `401` | Missing or invalid `API-KEY`                                                                                                                |
| `403` | Key lacks `onboarding`, customer belongs to another partner, or [the country is restricted](/api-reference/onboarding/country-restrictions) |
| `404` | Customer or owner not found — including one that was deleted                                                                                |
| `405` | Wrong HTTP method for the path                                                                                                              |
| `409` | Conflict — see below                                                                                                                        |
| `413` | A document exceeds the 2 MB per-file limit                                                                                                  |
| `415` | Non-JSON content type on a JSON endpoint                                                                                                    |
| `429` | Rate limit exceeded                                                                                                                         |
| `500` | Downstream failure. The whole operation is rolled back — retry                                                                              |
| `503` | Downstream timed out. Retryable                                                                                                             |

<Tip>
  A `503` on create is **safe to retry with the same body** — Endl reconciles rather
  than creating a duplicate.
</Tip>

### The three conflicts

`409` means one of exactly three things:

<AccordionGroup>
  <Accordion title="A KYC link for an already-verified customer" icon="link-slash">
    `This customer's KYC is already completed; a new verification link is not needed`
  </Accordion>

  <Accordion title="Changing a locked identity field after completion" icon="lock">
    `This customer's KYC is completed; verified identity fields can no longer be changed`
  </Accordion>

  <Accordion title="Deleting a customer that holds a balance" icon="wallet">
    `Cannot delete a customer that holds a balance` — the amount is never disclosed. Clear the balance first.
  </Accordion>
</AccordionGroup>

## Verification lifecycle

| Status      | Meaning                                       |
| ----------- | --------------------------------------------- |
| `INITIATED` | Created; documents not yet submitted          |
| `PENDING`   | Documents submitted; verification in progress |
| `COMPLETED` | Approved                                      |
| `REJECTED`  | Rejected                                      |

For an **individual**, uploading the photo ID auto-submits and moves the customer
to `PENDING`. For a **business**, submission waits until the company documents
*and* every owner's documents are uploaded.

### Identity locks on completion

Once a customer reaches `COMPLETED`, the fields the provider verified can no
longer be changed — `firstName`, `lastName`, `phone`, `dateOfBirth`, `country`,
`nationality`, `gender`, `placeOfBirth`, `tin`, `taxResidenceCountry` and the
address. Touching any of them returns `409` and nothing is written.

Database-only fields (`currency`, `countryId`, `currencyId`) stay editable.

## Validation reference

| Case                                         | HTTP  | Message                                                                                 |
| -------------------------------------------- | ----- | --------------------------------------------------------------------------------------- |
| Email missing or malformed                   | `400` | `email is required` / `email must be a valid email address`                             |
| First or last name missing                   | `400` | `firstName is required`                                                                 |
| Name over 100 characters                     | `400` | `firstName must be at most 100 characters`                                              |
| Country not ISO alpha-3                      | `400` | `country must be an ISO 3166-1 alpha-3 code (e.g. ARE)`                                 |
| Date of birth bad, future, or under 18       | `400` | `Date of birth must be a valid date in DD-MM-YYYY format`                               |
| Gender outside `M`/`F`                       | `400` | `gender must be one of M, F`                                                            |
| Bad questionnaire option                     | `400` | `profileDetails.occupation must be one of the allowed options (got 'X')`                |
| Missing questionnaire item                   | `400` | `profileDetails.expectedMonthlyVolume is required`                                      |
| Missing custom field                         | `400` | `custom field 'whatsappNumber' is required`                                             |
| Unknown custom field                         | `400` | `unknown custom field 'foo'`                                                            |
| Duplicate email                              | `400` | `Duplicate email`                                                                       |
| No beneficiaries on a business               | `400` | `At least one beneficiary is required`                                                  |
| `shareSize` out of range or summing over 100 | `400` | `beneficiaries[0].shareSize must be between 0 and 100`                                  |
| File is not really an image or PDF           | `400` | `Document content is not a valid JPEG, PNG, or PDF`                                     |
| Bad `idDocType` for an individual            | `400` | `idDocType must be one of PASSPORT, ID_CARD, DRIVERS_LICENSE, RESIDENCE_PERMIT, SELFIE` |
| Malformed share token                        | `400` | `shareToken is not a valid share token`                                                 |
| Restricted country                           | `403` | `Onboarding is not permitted for this country`                                          |
| Wrong HTTP method                            | `405` | `HTTP method PUT is not supported for this endpoint`                                    |
| File over 2 MB                               | `413` | `File exceeds the maximum upload size of 2 MB`                                          |
| Wrong content type                           | `415` | `Content type text/plain is not supported; use application/json`                        |
| Rate limited                                 | `429` | `Rate limit exceeded, please retry later`                                               |
| Downstream timeout                           | `503` | `Service temporarily unavailable, please try again`                                     |

<Note>
  A few messages read generically — a reused share token returns plain
  `Bad Request`, and a missing customer returns `Not found`. The status code plus
  `name` tell you which operation failed.
</Note>
