PUT /dapi/v1/client/kyb, GET /dapi/v1/client/kyb and POST /dapi/v1/client/kyb/complete all require X-Hevn-Account, and document upload accepts the same header. Your own access token is the only credential any of this needs. Examples use the client from HTTP client.
Send X-Hevn-Account: cl_… with your own access token to act for one client. On a resource route
like GET /banks the header is optional: omit it and the call acts on your integrator account
instead. The singular /client* routes require it — the header is the only thing that names the
account — and /clients and /escrow* refuse it.
One document, written until it is complete
The client’s verification data is a single JSON document. YouPUT the whole thing, HEVN answers with what is still missing, you fix those paths and PUT again. There is no form to build, no order to respect and no second readiness rule: the missing[] list is produced by the same gate that decides whether submission succeeds.
The write is idempotent by construction. Every person carries a key you choose, HEVN derives that person’s id from it, and the same body written twice touches the same records instead of creating a second Jane Doe. Idempotency-Key is accepted and unnecessary here — a PUT is already safe to repeat.
What the document describes
Three things, and a reviewer reads all three together:- The company — who it is on paper, where it is registered, what it sells, and where its money comes from and goes.
- Its people — the humans who own it, control it, direct it or sign for it. A reviewer needs a name, a date of birth, an address, a phone number and a tax residence for each one.
- Its papers — files, each filed into a named slot, proving the two above.
company alone leaves the people and the documents exactly as they were, so the writes below can be three separate calls or one.
1
Write the company
Send what you know. Required values, their accepted enum members and the exact spelling of each field live in the KYB field reference. The example below is the required core; the document also takes some forty optional fields a reviewer often asks for later —
identityDocuments, operatingCountries, highRiskActivities, hasPep, isRegulated and the per-method volume split among them. Filling the ones you already hold turns most RFIs into no RFI at all.200 OK:ready: false is not an error. The write succeeded; the document is incomplete.2
Add the people
people[] is the client’s owners, directors and signers. Give each one a key — any stable string of your own, 1 to 64 characters from A-Z a-z 0-9 . _ : -. Re-sending the same key updates the same person.people replaces the roster: a person you created and then omit is removed. Omitting the people key entirely leaves the roster alone, and "people": [] empties it. A person somebody added outside the flat document is never deleted — a PUT that would drop one answers 409 kyb_roster_conflict with details.entityIds.3
Upload the papers
Upload a file once, then file its id into a slot. Re-filing the same upload in the same slot is a no-op. A different upload in an occupied slot replaces what was there. An unknown slot answers
POST /dapi/v1/documents takes JSON, with the file base64-encoded in content and the slot in slot. Uploads act on a client, so send X-Hevn-Account here too.201 Created answers { "id": "doc_1a7Kd2", "slot": "certificate_of_incorporation", "fileName": "incorporation.pdf" }. The same bytes in the same slot answer the first id with 200 rather than storing a second copy, so a retried upload never forks a document. A decoded file over 20 MB answers 413 payload_too_large, and content that is not base64 answers 422 validation_failed naming content.File the id into a slot on the next write — company papers under the document’s own documents[], personal papers under the person’s:422 document_type_unsupported with the accepted list in details.allowed and the path in details.field, refused before anything is written. An upload id that is not this client’s answers 404 document_not_found with details.uploadId.4
Read the document back
GET /dapi/v1/client/kyb returns readiness, the application if one is filed, and the whole document projected back, so you can diff your own state against HEVN’s:ready: true means completion will succeed. Nothing else does — the list is deliberately stricter than the minimum a reviewer would accept, so that a document that reads ready never fails at the gate.The document block is projected from what HEVN holds, not echoed from what you sent, which makes it the thing to diff against your own record. It is also where people[].id becomes stable: index positions in missing[] refer to this array, in this order.5
Complete the submission
201 Created, Location: /dapi/v1/client/kyb:200 with Idempotency-Replayed: true and the same application id. Completing an incomplete document answers 422 kyb_incomplete with the same details.missing list the document read returns.Track the application
PollGET /dapi/v1/client/kyb. There is no webhook; kybStatus on the client row carries the same value if you are already reading that.
The application block is the review’s own record:
pending lasts is the reviewer’s, not the API’s. A clean US or EU company with all five company papers and one UBO is usually decided the same day; an unusual jurisdiction, a missing register extract or a person HEVN cannot place takes longer and normally arrives as an RFI rather than a rejection.
Answer an RFI
An RFI is the same loop, once more. Readapplication.comment, write the correction into the document, and complete the submission again.
The correction is a normal write. A replacement paper is a new upload filed into the slot it belongs in — a new upload id in an occupied slot replaces what was there, so there is nothing to delete first. A wrong date of birth is a PUT with that person’s key and the right value. Then complete: attemptNo increments, comment is replaced by the next reviewer’s, and kybStatus returns to pending.
Two refusals to expect at completion time:
Fill a document’s fields by hand
Each entry indetails.documents reads <slot>.<key> — certificate_of_incorporation.registration_number — or <person>: <slot>.<key> for a person’s paper. Write the values onto the upload the slot holds:
address.street_address. A key the slot has no place for answers 422 validation_failed naming fields, and a slot with no field schema at all answers 422 document_type_unsupported. The response is the document record.
Two phone numbers, two jobs
The phone on the account and the phone in the document are different fields with different readers, and mixing them up is the single most common reason a rail refuses to open.
Set the account phone at creation and this never comes up.
Complex ownership
The flat document covers a company owned directly by people — one subject, its papers, and the humans above it. A structure with holding companies in the chain is written through the entity-graph routes instead; the two models coexist, because the document only owns the people it created. When a gap belongs to an entity the document does not own,missing[] reports it as graph.entities.<id>.<field> rather than a people[…] path. Contact HEVN before you build against the graph routes.
Next: Virtual accounts
Check a rail’s requirements, open it, and read the requisites the client’s payers wire to.