> ## 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 accounts are non-custodial in both products. Self-serve (the General tab): the person who owns the email holds the only key and signs for themselves. Whitelabel (the Whitelabel and REST API tabs): the integrator's customer creates and owns their account through Sign in with HEVN, a HEVN-hosted popup on the integrator's site; the integrator's backend holds a developer key, operates linked accounts and books their payouts, and the account holder approves every payout in that popup. The developer key signs only for the integrator's own wallet; it never signs a client's money.
> Use HEVN's terms exactly. A rail is a bank route a client can request. A virtual account (bnk_) is an opened rail. Account details are what a payer needs in order to wire money, and arrive in a field named requisites. A client is a customer's own account linked to the integrator through Sign in with HEVN (POST /dapi/v1/connect/sessions, then POST /dapi/v1/clients/link); there is no API call that creates one. The business behind it is the integrator's customer. A contact is a saved destination; a beneficiary is the party at the far end of a fiat payout. Every departure of money is a payout, to a bank account or to a wallet alike.
> Never name a banking partner. Say 'a licensed banking partner'.
> There are no published fee rates, spreads or minimums. Prices come from a quote on a specific payment; direct pricing questions to HEVN rather than inventing numbers.
> The whitelabel 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. They are different credentials and neither works on the other surface. The HEVN CLI is discontinued.

# Clients

> Read a client linked through Sign in with HEVN, wait until it is usable, list your clients, and keep the one id that identifies each of them.

A client is your customer's own HEVN account, linked to your integrator account through [Sign in with HEVN](https://docs.gethevn.com/whitelabel/sign-in-with-hevn.md). You do not create it with an API call: the account holder creates it in the sign-in window, and `POST /dapi/v1/clients/link` hands you its id.

```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"
}
```

`email` is the address the holder signed in with. A newly created account has nothing else on it yet, so `name` and `phone` are usually absent: the company's identity goes into its [verification document](https://docs.gethevn.com/whitelabel/onboarding.md), the phone in through `PATCH /dapi/v1/client`. Responses omit fields with no value, so an absent field and a null field are the same thing — see [conventions](https://docs.gethevn.com/whitelabel/conventions.md).

Examples use the client from [HTTP client](https://docs.gethevn.com/whitelabel/reference/client.md). This page straddles the two account scopes: `POST /dapi/v1/clients/link` and `GET /dapi/v1/clients` are about **your** account and **refuse** `X-Hevn-Account` with `400 account_scope_conflict`, while `GET /dapi/v1/client` and `PATCH /dapi/v1/client` act on one client and **require** it. [Sessions](https://docs.gethevn.com/whitelabel/sessions.md#acting-as-a-client) has the whole table.

## Three statuses

```mermaid theme={null}
stateDiagram-v2
    [*] --> provisioning: account created in the sign-in window
    provisioning --> ready: smart wallet deployed
    provisioning --> failed: provisioning refused

    note right of provisioning
        Poll GET /dapi/v1/client every 2 s,
        with the id in X-Hevn-Account.
    end note
    note right of ready
        The account, your seat on it, the KYB subject
        and the Base smart wallet all exist.
    end note
    note right of failed
        Contact support with the client id.
    end note
```

`ready` means usable. It is the only signal you need before you write the client's KYB document, open a rail for it or book its payouts. A client the window just created is usually `ready` by the time you exchange the code; when it is still `provisioning`, wait for it.

## Wait until it is ready

`pollUrl` carries no id. It is literally `/dapi/v1/client`, the singular route that reads whichever account `X-Hevn-Account` names — so a poller sends the `cl_…` in that header rather than building a path from it.

```python Python theme={null}
import random, time


def poll_until(read, done, *, deadline=120.0, first=1.0, cap=8.0):
    """Call read() until done(value) is true. Returns the last value or raises TimeoutError."""
    started, delay = time.monotonic(), first
    while True:
        value = read()
        if done(value):
            return value
        if time.monotonic() - started > deadline:
            raise TimeoutError(f"still {value.get('status')} after {deadline:.0f}s")
        time.sleep(delay + random.uniform(0, delay / 2))
        delay = min(delay * 2, cap)
```

```javascript Node theme={null}
export async function pollUntil(read, done, { deadline = 120_000, first = 1_000, cap = 8_000 } = {}) {
  const until = Date.now() + deadline;
  let delay = first;
  for (;;) {
    const value = await read();
    if (done(value)) return value;
    if (Date.now() > until) throw new Error(`still ${value.status} after ${deadline}ms`);
    await new Promise((wake) => setTimeout(wake, delay + Math.random() * (delay / 2)));
    delay = Math.min(delay * 2, cap);
  }
}
```

```go Go theme={null}
func PollUntil[T any](read func() (T, error), done func(T) bool, deadline time.Duration) (T, error) {
	until, delay := time.Now().Add(deadline), time.Second
	for {
		value, err := read()
		if err != nil {
			return value, err
		}
		if done(value) {
			return value, nil
		}
		if time.Now().After(until) {
			return value, fmt.Errorf("hevn: still pending after %s", deadline)
		}
		time.Sleep(delay + time.Duration(rand.Int63n(int64(delay/2))))
		if delay < 8*time.Second {
			delay *= 2
		}
	}
}
```

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

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

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

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

There is one id to store. `cl_7YQ2Kf3mN8` is what you put in `X-Hevn-Account`: to read this client, to write its KYB document, and to act for it on every money route. Nothing takes it in a path. Keep it in your own records against your own user, server-side — never accept it from a browser, where any page could substitute another customer's id. `baseSmartWallet` is the address that holds the client's balance; keep it for your own reconciliation, but no call takes it as an argument.

`kybStatus` follows the client's verification from here on — `notStarted`, then `pending`, `approved`, `rfi` or `rejected` — and `kybApplicationId` names the latest submission behind it. [Onboarding](https://docs.gethevn.com/whitelabel/onboarding.md) owns that vocabulary; this row is where you read it without a second call.

`verifiedAt` is the one verdict for both kinds of client: when a business passed KYB, or when an individual verified their identity in the [sign-in window](https://docs.gethevn.com/whitelabel/sign-in-with-hevn.md#verify-an-individual). It is absent until then. An individual has no `kybStatus` progress to follow — `verifiedAt` is the field to poll.

A `failed` client answers with a `failure` object whose `code` is `provisioning_failed`, `request_rejected` or `request_canceled`. Contact support with the client id.

## List your clients

```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")
```

`GET /dapi/v1/clients` answers `{ "items": [...], "nextCursor": "..." }`, every account connected to you, newest first. It takes `status` (`provisioning`, `ready`, `failed`), `email` as an exact, lowercased match, and cursor pagination. It is the place to recover an id you lost: a holder who signs in again through the window also gives you a fresh code for the same client.

## Update the phone and the address

`PATCH /dapi/v1/client` writes two things and nothing else — the phone number the rails check, and the postal address. It is a client-scoped route, so the id travels in `X-Hevn-Account`:

```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"})
```

`phone` is E.164, for example `+13125550142`. Every rail a company opens checks for a phone number on the account, so set it before you [open one](https://docs.gethevn.com/whitelabel/virtual-accounts.md). `address` takes `streetAddress`, `addressLine2`, `city`, `state` and `zip`. Only the keys you send are written.

Everything else a company has to tell HEVN is verification data and belongs in its KYB document. The address country is frozen at registration; changing a locked field answers `409 profile_locked` with `details.fields`.

## What you can do with a client

Linking gives your integrator account a seat on the client's account. With it you read and write everything a finance team would set up — the verification document, rails, contacts, payins, the ledger — and you **book** payouts. You never **sign** them: the account holder's own key is the only one that can spend from their wallet, and they approve each payout in the [sign-in window](https://docs.gethevn.com/whitelabel/sign-in-with-hevn.md#approve-a-payout). Your developer key has no power over the client's money at all. That is the whole point of the model — see [accounts and control](https://docs.gethevn.com/whitelabel/accounts-and-control.md).

> [card] [Next: Onboard a client](https://docs.gethevn.com/whitelabel/onboarding.md)
>
> Fill the KYB document, upload its papers, submit, and answer an RFI.


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