Skip to main content

Response shape

On success the body is the data object. There is no envelope — the HTTP status tells you it succeeded.
Success — 201 from create
Every error uses one five-key envelope — for every status code and every error path, including validation, 429, 503 and the Api-Version rejection.
409
All field, questionnaire and custom-field errors arrive together, one entry each, and nothing is created. Duplicate email (ERRCUS_1600), duplicate phone (ERRCUS_1601), restricted country (ERRCUS_1605) and a reused share token are checked afterwards, one at a time, and only once the fields are valid — on create and on update:
400 — two problems
Branch on errors[].code, never on the message. Every onboarding error has its own frozen ERRCUS_* code — one per field and per reason — listed below. Codes are never renumbered, reused or removed; new ones are only added. There is no name field any more, and no per-status exceptions — 429 and 503 use the same envelope as everything else.

errors[].field

The offending input, as a single machine-mappable token — email, company.phone, beneficiaries[0].dob, customFields.whatsappNumber, userType, status, Api-Version, userId, idDocType, document, subjectType, subjectId, uboRef, beneficiaries[0].types. A questionnaire item uses the full path — questionnaire.sections.profileDetails.items.occupation.value. A missing block names the block (company, company.address, beneficiaries), and a cross-beneficiary rule (share total, no UBO) names beneficiaries. Only errors with no single input (a malformed questionnaire structure, a restricted country) carry field: null.

Message wording

Most messages are field-prefixed and lower-cased to match the field token:

Status codes

A 503 on create is safe to retry with the same body — Endl reconciles rather than creating a duplicate.

Conflicts

A duplicate email on a normal create is a 400, ERRCUS_1600. The 409 variant, ERRCUS_1602, is only the concurrent-retry race.

Verification lifecycle

status can also take these five values — the only ones beyond the eight above: Treat unknown values as in progress. The list filter ?status= accepts only the original eight. For an individual, the customer is submitted for review automatically once every required document step is uploaded — the photo ID, the selfie and the proof of address (a UTILITY_BILL). Until then each upload answers SUBMITTED with a missing list. For a business, Endl submits the business after its first company document, once every owner’s documents are complete — so upload both company groups before the owners’ last document. A business keeps its current status (normally INITIATED) until then, and never returns SUBMITTED.

Locks on completion

The lock is not the same for the two customer types. The questionnaire is not in either list because it is never editable: it is set on create. On update a non-empty questionnaire object is 400 ERRCUS_1303, an empty or non-object one is 400 ERRCUS_1301, and null is ignored — before or after completion.
If you have read that “database-only fields stay editable” after completion, that is no longer true for an individual. currency and custom fields lock with everything else.

Onboarding error codes

Every onboarding error carries its own ERRCUS_* code in errors[].code. The HTTP status, message and field are the same as they would otherwise be — for beneficiaries the code is per field and the index stays in field, so ERRCUS_1206 comes with field: "beneficiaries[2].email".

Individual customer

Business company

Business beneficiaries

Questionnaire and custom fields

Question keys come from the verification level, so these codes are per reason, and field names the exact question — for example questionnaire.sections.profileDetails.items.occupation.value.

Documents

The document country uses ERRCUS_1012 (sent but blank) and ERRCUS_1013 (invalid). Omitted entirely, it is 400 ERRCORE_1001 (country is required).

Request level

Onboarding business rules

Verification-provider errors

Errors from the verification step that you can act on keep their own ERRCOMP_* code, message and HTTP status, rather than collapsing into a generic 500:
409
Other provider failures are a 500 ERRCORE_1000. The 500 message is free text and may include internal detail; don’t parse it. A verification-service failure returns Service temporarily unavailable, please try again (503 ERRCORE_1008 on a timeout).

Codes that are not onboarding-specific

These keep their ERRCORE_*, ERRREF_* or ERRCOMP_* code: