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

> ## Agent Instructions
> HEVN is payments software: an API and app over a Base smart wallet. HEVN is not a bank and holds no key to the wallet. Virtual accounts, fiat payins and fiat payouts are provided by partner banks, which hold fiat while it is in transit. Self-serve (the General tab): the person who controls the email signs for the wallet. Developers integrate /dapi/v1 in one of two ways. HEVN Connect (recommended, the HEVN Connect tab): the integrator's customer creates or links a HEVN account in a HEVN-hosted sign-in window on the integrator's site, the integrator's backend reads the linked account and books its payouts, and the account holder approves each payout in that window. External wallets (the External wallets tab, enabled per integrator): the integrator creates a client through the API, registers a wallet address the customer holds as an external account, and requests virtual accounts that deliver to that address. The customer's own wallet sends each transfer that funds a payout. The developer key signs for the integrator's wallet and its sub accounts and cannot sign for a client's wallet.
> Use HEVN's terms as written. A product is the region a client requests. A rail is the bank route HEVN picks for the product. A virtual account (bnk_) is the record a partner bank opens for a client. Account details are what a payer needs to send a bank transfer, returned in a field named requisites. A client (cl_) is a customer's account: linked to the integrator through Sign in with HEVN (POST /dapi/v1/connect/sessions, then POST /dapi/v1/clients/link), or created with POST /dapi/v1/clients for an external-wallets integration. An external account (acc_, kind external) is a wallet address the client holds and signs for. A sub account (acc_, kind sub) is a further wallet of the integrator's own account on Base, spent with the developer key. Write 'sub account' as two words. A contact is a saved destination. A beneficiary is the party that receives a fiat payout. Money leaving an account is a payout, whether it goes to a bank account or to a wallet.
> Do not name a bank, payment institution, card issuer, identity-verification vendor or stablecoin issuer. Say 'the partner bank'. Do not describe HEVN as licensed, regulated or insured.
> HEVN publishes no fee rates, spreads or minimums. Prices come from a quote on a specific payment. Direct pricing questions to HEVN and do not invent numbers.
> The API is at /dapi/v1 and takes a developer-key session plus an X-Hevn-Account header. Developer keys have an IP allowlist and no scopes. Remote transfers are at /api/v1/remote_spend_limit and take a remote transfer key (rsl_) in X-Api-Key, bounded by an onchain spend permission the account holder grants. The two credentials are separate and neither works on the other API. The HEVN CLI is discontinued.

# Clients and verification

> Read and update a client, then write, submit and track a business client's KYB document.

A client is a customer's account that your integrator account acts for, one per customer. Read it with `GET /dapi/v1/client`, then verify a business client with `PUT /dapi/v1/client/kyb`.

| Integration kind | How the client is created | Returns |
| - | - | - |
| [HEVN Connect](https://docs.gethevn.com/connect/overview.md) | The customer signs in, then you call `POST /dapi/v1/clients/link` with the code ([Sign in with HEVN](https://docs.gethevn.com/connect/sign-in-with-hevn.md)) | `200` with the client, usually `ready` |
| [External wallets](https://docs.gethevn.com/external-wallets/overview.md) | You call `POST /dapi/v1/clients` with the company's `name` and `email` | `202` with the client in `provisioning` and a `pollUrl` |

Both routes return the `cl_…` id. Send it as `X-Hevn-Account` to read the client with `GET /dapi/v1/client`.

```json POST /dapi/v1/clients/link 200 theme={null}
{
  "id": "cl_7YQ2Kf3mN8",
  "status": "ready",
  "email": "ops@northwind.example",
  "baseSmartWallet": "0x2f1c9a3b7e5d41c8a06f2d9b4e7c15839af0d6b2",
  "kybStatus": "notStarted",
  "createdAt": "2026-09-17T10:04:11Z",
  "pollUrl": "/dapi/v1/client"
}
```

The fields are described on [Connect an existing account](https://docs.gethevn.com/api-reference/accounts/connect-an-existing-account.md) and [Create a client](https://docs.gethevn.com/api-reference/accounts/create-a-client.md). A field with no value is omitted. A client created with `POST /dapi/v1/clients` has no wallet at HEVN, so it has no `baseSmartWallet`.

A linked individual has no KYB progress. Poll `verifiedAt` after they [verify their identity](https://docs.gethevn.com/connect/sign-in-with-hevn.md#verify-an-individual). `POST /dapi/v1/clients` creates companies.

Which routes take `X-Hevn-Account` is covered in [Acting as a client](https://docs.gethevn.com/developers/authentication.md#acting-as-a-client). For who signs a client's payouts, see [Who can spend](https://docs.gethevn.com/developers/overview.md#who-can-spend).

## Statuses

| `status` | Meaning | Next |
| - | - | - |
| `provisioning` | A creation request is open, or the client has no deployed Base smart wallet and no active external account yet. | Poll `GET /dapi/v1/client`. For a created client, register an [external account](https://docs.gethevn.com/external-wallets/external-accounts.md). |
| `ready` | The account and its KYB subject exist, and the client has a deployed Base smart wallet or an active external account. | [Write the KYB document](#write-the-document). |
| `failed` | The client was not created. `failure` has `code`, `message` and `retriable: false`. | Read `failure.code`: `provisioning_failed`, `request_rejected` or `request_canceled`. |

A linked client is usually `ready` when you exchange the code. HEVN reviews a `POST /dapi/v1/clients` request unless your integrator agreement waives the review, so a created client can stay `provisioning` for longer. Until its account exists, routes that take the new id, other than `GET /dapi/v1/client`, return `404 account_not_found`. `GET /dapi/v1/client` returns the client in each status, and an unknown id returns `404 client_not_found`. `poll_until` is defined in [HTTP client](https://docs.gethevn.com/developers/reference/client.md).

```bash cURL theme={null}
curl "$HEVN_API/client" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: cl_7YQ2Kf3mN8"
```

```python Python theme={null}
client = hevn.acting_as(client_id)
ready = poll_until(
    lambda: client.get("/client"),
    lambda c: c["status"] != "provisioning",
)
```

```javascript Node theme={null}
const client = hevn.actingAs(clientId);
const ready = await pollUntil(
  () => client.get("/client"),
  (c) => c.status !== "provisioning",
);
```

```go Go theme={null}
client := api.ActingAs(clientID)
ready, err := hevn.PollUntil(func() (hevn.Payload, error) {
	return client.Get("/client")
}, func(c hevn.Payload) bool { return c.Str("status") != "provisioning" }, 2*time.Minute)
```

## List clients

`GET /dapi/v1/clients` lists your clients: the accounts linked to you, the clients you created and the creation requests still open. Use it to recover a lost id. Filters are on [List clients](https://docs.gethevn.com/api-reference/accounts/list-clients.md), and paging follows [Pagination](https://docs.gethevn.com/developers/conventions.md#pagination).

```bash cURL theme={null}
curl "$HEVN_API/clients?status=ready" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN"
```

```python Python theme={null}
mine = hevn.get("/clients", {"status": "ready"})["items"]
```

```javascript Node theme={null}
const { items: mine } = await hevn.get("/clients", { status: "ready" });
```

```go Go theme={null}
page, err := api.Get("/clients", hevn.Query{"status": "ready"})
mine := page.List("items")
```

## Update phone and address

`PATCH /dapi/v1/client` writes the phone and the postal address. The fields are on [Update a client profile](https://docs.gethevn.com/api-reference/accounts/update-a-client-profile.md).

```bash cURL theme={null}
curl -X PATCH "$HEVN_API/client" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: cl_7YQ2Kf3mN8" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+13125550142"}'
```

```python Python theme={null}
patched = hevn.acting_as(client_id).patch("/client", {"phone": "+13125550142"})
```

```javascript Node theme={null}
const patched = await hevn.actingAs(clientId).patch("/client", { phone: "+13125550142" });
```

```go Go theme={null}
patched, err := api.ActingAs(clientID).Patch("/client", hevn.Body{"phone": "+13125550142"})
```

Changing a locked field returns `409 profile_locked` with `details.fields`.

### Phone numbers

A company's virtual account requires a phone number. Either phone satisfies the check: `company.phone` in the KYB document, or the account phone written with `PATCH /dapi/v1/client`.

## Write the document

A business client is verified from one KYB document that you write, submit for review and correct on request. Write it with `PUT /dapi/v1/client/kyb` until the response returns `ready: true`. Then call `POST /dapi/v1/client/kyb/complete`.

The KYB and document routes are client-scoped, so send `X-Hevn-Account`. The flow is the same for a linked business and for a created company. An individual client verifies in the [sign-in window](https://docs.gethevn.com/connect/sign-in-with-hevn.md#verify-an-individual).

The document has the sections `company`, `people[]` and `documents[]`. Field types, enum values and slot names are on [Provide applicant information](https://docs.gethevn.com/api-reference/verification/provide-applicant-information.md).

**1. Write the company**

The example has the required fields. `company.legalName` must be unique across HEVN. A US `taxId` in a format other than `XX-XXXXXXX` counts as missing.

Also send the optional fields you hold, such as `identityDocuments`, `operatingCountries`, `highRiskActivities`, `hasPep`, `isRegulated` and the per-method volume split. Optional fields are not listed in `missing[]`, but a reviewer often asks for them in an RFI.

```json theme={null}
{
  "company": {
    "legalName": "Northwind Trading Ltd",
    "country": "US",
    "taxId": "88-1234567",
    "phone": "+13125550142",
    "email": "compliance@northwind.example",
    "address": {
      "streetAddress": "401 N Michigan Ave",
      "city": "Chicago",
      "state": "US-IL",
      "zip": "60601",
      "country": "US"
    },
    "entityType": "llc",
    "expectedMonthlyVolumeUsd": "250000",
    "businessDescription": "Wholesale and retail distribution of industrial fasteners.",
    "businessType": "retail",
    "sourceOfFunds": "business_revenue",
    "purposeOfFunds": "vendor_payments",
    "flowOfFunds": "Customers pay us by wire; we pay suppliers in EUR."
  }
}
```

```bash cURL theme={null}
curl -X PUT "$HEVN_API/client/kyb" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Content-Type: application/json" \
  --data @document.json
```

```python Python theme={null}
client = hevn.acting_as(CLIENT_ID)
readiness = client.put("/client/kyb", document)
```

```javascript Node theme={null}
const client = hevn.actingAs(clientId);
const readiness = await client.put("/client/kyb", document);
```

```go Go theme={null}
client := api.ActingAs(clientID)
readiness, err := client.Put("/client/kyb", document)
```

```json Response 200 theme={null}
{
  "ready": false,
  "missing": ["people", "documents.certificate_of_incorporation", "…"],
  "people": []
}
```

`ready: false` means the write succeeded. Read each `missing[]` entry as described in [Missing paths](#missing-paths).

**2. Add the people**

`people[]` lists the client's owners, directors and signers. Give each person a stable `key` and at least one role: `ownershipPercentage`, `hasControl`, `isDirector` or `isSigner`.

```json theme={null}
{
  "people": [
    {
      "key": "jane",
      "firstName": "Jane",
      "lastName": "Doe",
      "dateOfBirth": "1984-02-11",
      "email": "jane@northwind.example",
      "phone": "+13125550188",
      "address": {
        "streetAddress": "1120 W Fulton Market",
        "city": "Chicago",
        "state": "US-IL",
        "zip": "60607",
        "country": "US"
      },
      "taxIdCountry": "US",
      "taxId": "123-45-6789",
      "ownershipPercentage": "100",
      "hasControl": true,
      "isDirector": true,
      "isSigner": true
    }
  ]
}
```

```json Response 200 theme={null}
{
  "ready": false,
  "missing": ["people[0].documents.proof_of_address", "…"],
  "people": [{ "key": "jane", "id": "7f1e6b2c-90a4-4d31-9b5e-2c08d7a41f63" }]
}
```

Sending `people` replaces the roster you created, so a person you omit is removed. `"people": []` removes them all. For a holding company in the chain, see [Complex ownership](#complex-ownership).

**3. Upload the files**

Upload each file with `POST /dapi/v1/documents`, base64-encoded in `content`, with the target slot in `slot`.

```bash cURL theme={null}
curl -X POST "$HEVN_API/documents" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Content-Type: application/json" \
  -d "{\"slot\":\"certificate_of_incorporation\",\"fileName\":\"incorporation.pdf\",
       \"content\":\"$(base64 < incorporation.pdf | tr -d '\n')\"}"
```

```python Python theme={null}
import base64, pathlib

upload = client.post("/documents", {
    "slot": "certificate_of_incorporation",
    "fileName": "incorporation.pdf",
    "content": base64.b64encode(pathlib.Path("incorporation.pdf").read_bytes()).decode(),
})
```

```javascript Node theme={null}
const upload = await client.post("/documents", {
  slot: "certificate_of_incorporation",
  fileName: "incorporation.pdf",
  content: readFileSync("incorporation.pdf").toString("base64"),
});
```

```go Go theme={null}
file, err := os.ReadFile("incorporation.pdf")
upload, err := client.Post("/documents", hevn.Body{
	"slot":     "certificate_of_incorporation",
	"fileName": "incorporation.pdf",
	"content":  base64.StdEncoding.EncodeToString(file),
})
```

Responses and limits are on [Upload a document](https://docs.gethevn.com/api-reference/documents/upload-a-document.md). File each returned `id` into a slot on the next `PUT`. Company files go in the top-level `documents[]` and a person's files in that person's `documents[]`.

```json theme={null}
{
  "documents": [{ "slot": "certificate_of_incorporation", "uploadId": "doc_1a7Kd2" }],
  "people": [
    { "key": "jane", "documents": [{ "slot": "proof_of_address", "uploadId": "doc_5b3Qm9" }] }
  ]
}
```

An unknown slot returns `422 document_type_unsupported` with `details.field`. An `uploadId` of another client returns `404 document_not_found` with `details.uploadId`.

Required slots:

| Slot | Required when | Content |
| - | - | - |
| `certificate_of_incorporation` | Always | Registrar's proof that the company exists. |
| `corporate_structure` | Always | Ownership chart, down to natural persons. |
| `director_structure` | Always | List of directors. |
| `proof_of_address` | Always | Recent document showing the registered address. |
| `source_of_funds` | Always | Evidence for the `sourceOfFunds` value. |
| `tax_identification_document` | `company.country` is `US` | IRS letter that assigned the EIN. |
| `vat_registration` | `company.country` is `AE` | FTA VAT/TRN registration certificate. |
| `regulatory_license` | `company.isRegulated` is `true` | The licence the company holds from its regulator. |
| `proof_of_address` in `people[].documents` | For each person | Recent document showing where the person lives. |

A reviewer can request any other slot in an RFI.

**4. Complete the submission**

Call `POST /dapi/v1/client/kyb/complete` with no body. It is the only call that starts a review.

```bash cURL theme={null}
curl -X POST "$HEVN_API/client/kyb/complete" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_CLIENT_ID"
```

```python Python theme={null}
application = client.post("/client/kyb/complete")
```

```javascript Node theme={null}
const application = await client.post("/client/kyb/complete");
```

```go Go theme={null}
application, err := client.Post("/client/kyb/complete", nil)
```

```json Response 201 theme={null}
{
  "applicationId": "kyb_9F4tRb",
  "status": "pending",
  "attemptNo": 1,
  "pollUrl": "/dapi/v1/client/kyb"
}
```

Replays and refusals are on [Complete the verification submission](https://docs.gethevn.com/api-reference/verification/complete-the-verification-submission.md).

### Missing paths

Each `missing[]` entry is a path into the document. Set the value at that path and write again.

| Path | Meaning |
| - | - |
| `company.flowOfFunds` | A company field is empty. |
| `company.address.zip` | A required address field is empty. |
| `people` | The roster is empty. Add at least one person. |
| `people[0].taxIdCountry` | A field on the first person is empty. |
| `people[0].documents.proof_of_address` | That person's slot is empty. |
| `documents.certificate_of_incorporation` | A company slot is empty. |
| `graph.entities.<uuid>.phone` | A field is empty on an entity reached through a holding company. See [Complex ownership](#complex-ownership). |

The index in `people[0]` is the position in `document.people[]` of `GET /dapi/v1/client/kyb`. It can differ from the order you sent.

### Complex ownership

The flat document describes one company held by people. Set `company.hasIntermediateEntityOwnership` to `true` when a holding company stands in the chain.

Write such a chain through HEVN's entity-graph routes. They are outside this API and available on request.

A flat write leaves entities added through those routes in place. A write that would remove one returns `409 kyb_roster_conflict` with `details.entityIds`.

## Track the application

`GET /dapi/v1/client/kyb` returns readiness, the application if one is filed, and the stored document. Poll it as described in [polling](https://docs.gethevn.com/developers/conventions.md#status-codes).

```json theme={null}
{
  "ready": true,
  "missing": [],
  "status": "rfi",
  "application": {
    "id": "kyb_9F4tRb",
    "attemptNo": 1,
    "status": "rfi",
    "comment": "The proof of address for Jane Doe is older than three months.",
    "submittedAt": "2026-09-17T10:31:04Z"
  },
  "document": {
    "company": { "legalName": "Northwind Trading Ltd", "country": "US", "…": "…" },
    "people": [{ "key": "jane", "id": "7f1e6b2c-…", "…": "…" }],
    "documents": [{ "slot": "certificate_of_incorporation", "uploadId": "doc_1a7Kd2" }]
  }
}
```

| `status` | Meaning | Next |
| - | - | - |
| `notStarted` | No application is filed. | Complete the document, then the submission. |
| `pending` | The application is filed and under review. | Poll. Timing is set by the reviewer. |
| `approved` | The application was accepted. | [Request virtual accounts](https://docs.gethevn.com/developers/virtual-accounts.md). |
| `rfi` | The reviewer asked for something. | [Answer the RFI](#answer-an-rfi). |
| `rejected` | The application was refused. | Read `application.comment`. Complete again only after you change the document. |

In the sandbox, set the status with [`POST /dapi/v1/sandbox/kyb/status`](https://docs.gethevn.com/developers/sandbox#decide-a-kyb-application).

## Answer an RFI

1. Read `application.comment`.
2. Correct the document with `PUT /dapi/v1/client/kyb`. To replace a file, file a new upload into the same slot. To correct a person, send that person's `key` and the new value.
3. Call `POST /dapi/v1/client/kyb/complete` again. `attemptNo` increments, `kybStatus` returns to `pending`, and the next review replaces `comment`.

When the reviewer could not read a value on a file, `comment` names the file and the value. Upload a legible copy, or write the value onto the upload.

### Fill a document's fields by hand

Call `PUT /dapi/v1/documents/{documentId}/content` with the upload id from the `document` block of `GET /dapi/v1/client/kyb`. Company uploads are in `documents[]` and a person's are in `people[].documents[]`.

```bash cURL theme={null}
curl -X PUT "$HEVN_API/documents/doc_1a7Kd2/content" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Content-Type: application/json" \
  -d '{"fields":{"entity_name":"Northwind Trading Ltd","registration_number":"7712043",
       "jurisdiction":"US","incorporation_date":"2019-04-02"}}'
```

```python Python theme={null}
filled = client.put("/documents/doc_1a7Kd2/content", {"fields": {
    "entity_name": "Northwind Trading Ltd", "registration_number": "7712043",
    "jurisdiction": "US", "incorporation_date": "2019-04-02",
}})
```

```javascript Node theme={null}
const filled = await client.put("/documents/doc_1a7Kd2/content", { fields: {
  entity_name: "Northwind Trading Ltd", registration_number: "7712043",
  jurisdiction: "US", incorporation_date: "2019-04-02",
} });
```

```go Go theme={null}
filled, err := client.Put("/documents/doc_1a7Kd2/content", hevn.Body{"fields": hevn.Body{
	"entity_name": "Northwind Trading Ltd", "registration_number": "7712043",
	"jurisdiction": "US", "incorporation_date": "2019-04-02",
}})
```

Key format and refusals are on [Fill the typed fields read off a document](https://docs.gethevn.com/api-reference/documents/fill-the-typed-fields-read-off-a-document.md).

> [card] [Next: Virtual accounts](https://docs.gethevn.com/developers/virtual-accounts.md)


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