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

# Limits

> Every number that can refuse your request: rate limits, expiry windows, idempotency scoping, page sizes and sandbox caps.

Every limit on this page is enforced by the API, not by a client library. Each one has a slug in [Errors](https://docs.gethevn.com/whitelabel/reference/errors.md) and, where it is a rate limit, a `Retry-After` header.

## Rate limits

Authenticated windows are keyed per **credential** — the access token's credential, not the account it acts for. Switching `X-Hevn-Account` does not buy a second budget, and a thousand clients driven from one server share one pair of counters.

| Window | Limit | Keyed on | Applies to |
| - | - | - | - |
| Writes | 120 per minute | credential | every `POST`, `PUT`, `PATCH`, `DELETE` under `/dapi/v1` |
| Reads | 600 per minute | credential | every `GET` under `/dapi/v1` |
| Login | 20 per 5 minutes | client IP | `POST /dapi/v1/auth/challenge`, `POST /dapi/v1/auth/token` |
| Login, per email | 10 per 5 minutes | the email in the body | `POST /dapi/v1/auth/challenge` |
| Login, per challenge | 10 per 5 minutes | `challengeId` | `POST /dapi/v1/auth/token` |
| Sandbox money | 60 per hour | account | `POST /dapi/v1/sandbox/deposits`, `/payins/{payinId}/complete`, `/payouts/{payoutId}/status` |

The three login windows cover `POST /auth/challenge` and `POST /auth/token` only. `POST /auth/refresh` carries no
login limiter — it is an ordinary write, and it re-checks the developer key behind the session before it mints
anything.

Exceeding one answers `429 rate_limited` with `Retry-After` in seconds. Sleep for it and retry — the helper in [HTTP client](https://docs.gethevn.com/whitelabel/reference/client.md) does this for you.

Two consequences worth designing for: a payroll run of 500 payouts is 1,000 writes and needs pacing, and a poller that reads every client every second will exhaust the read budget long before the data changes. Poll with backoff, and batch what you can into `GET /dapi/v1/transactions` for the whole account.

## Windows

| What | Lives for | When it expires |
| - | - | - |
| Login challenge | up to 5 minutes, and you choose it with `requestExpiry` | `410 challenge_expired` |
| Access token | 60 minutes | `401 token_expired` — re-mint with `POST /dapi/v1/auth/refresh` |
| Refresh token | 60 days | log in again |
| Payout or payin price | at most 2 minutes | `409 quote_expired` |
| Payout and transfer approval, on your own account | 120 seconds | `409 funding_attempt_expired` — re-open with the same key |
| Sign-in window session | about 10 minutes, until `expiresAt` | the window shows the link as expired; create a new session |
| Link code from the window | 5 minutes | `410 link_code_expired` on `POST /dapi/v1/clients/link` — start a new sign-in |
| Escrow action approval | 60 seconds | `410 approval_expired` on confirm — prepare the action again with the same key |
| Escrow on-chain refresh | one chain read per 15 seconds per deal | extra `POST /dapi/v1/escrow/{escrowId}/sync` calls answer the stored projection |
| Escrow deal windows | `approveWithin` 1h, `holdFor` 14d, `refundableFor` 90d by default, each from the end of the previous one | the action leaves `availableActions` |

An approval is short because it is a signed instruction to move money. Nothing in this API is designed for a human to approve a payload later: prepare, sign and confirm inside one request of your own.

## Idempotency

`Idempotency-Key` matches `^[A-Za-z0-9._:-]{1,128}$`. It is accepted on every write and required on none. Derive it from your own business id — `order-A-1187`, `payroll-2026-09-17-contractor-41` — and never from a clock or a random value.

The key is scoped to `(your account, the operation)`. The same string on two operations is not a replay, and two integrators using `invoice-1` never collide.

| Route | What the key anchors |
| - | - |
| `POST /dapi/v1/payouts` | the payment. For a bank contact it anchors the booking and its price; for a wallet contact it is part of the `po_` id, so the same key always names the same payment. A retry replays; different terms answer `409 idempotency_key_reused`. |
| `POST /dapi/v1/escrow` | the deal, its resolved windows and its `approve` action. The **raw** key is echoed as `action.idempotencyKey`. |
| `POST /dapi/v1/escrow/{escrowId}/actions` | the action. The raw key is the address in the confirm URL. |
| `POST /dapi/v1/sandbox/deposits`, `/payins/{payinId}/complete`, `/payouts/{payoutId}/status` | the emulated credit or status move. |
| `POST /dapi/v1/contacts`, `PATCH`, `DELETE /dapi/v1/contacts/{contactId}` | nothing: a contact is deduplicated by its own account details. |
| `POST /dapi/v1/clients/link`, `POST /dapi/v1/banks`, `PUT /dapi/v1/client/kyb`, `POST /dapi/v1/client/kyb/complete`, `POST /dapi/v1/payins`, `POST /dapi/v1/payins/{payinId}/instructions` | nothing: each is identified by its own natural key — the linked account, the rail, the client, the payin. Sending a key is harmless. |

Send no key and the server derives one from the request itself, echoes it as `idempotencyKey`, and you can pin that value from then on. A derived key replays for **2 hours**; an explicit key replays for **7 days**. That is why two genuinely distinct payouts of the same amount, minutes apart, both need explicit keys.

A response that replayed a create answers `200` with `Idempotency-Replayed: true` instead of `201`.

## Pages and sizes

| Field | Limit |
| - | - |
| `limit` on any list | 1 to 100, default 50 |
| `cursor` | opaque, ≤ 512 characters, bound to the query that produced it |
| `Idempotency-Key` | 1 to 128 characters of `A-Za-z0-9._:-` |
| `paymentReference` | 140 characters |
| `purpose` | 64 characters |
| `documentIds` on a payout | 10 |
| `publicKey` on a payout | 512 characters |
| `redirectUri` on a sign-in window session | 2,048 characters |
| Allowed sites in the sign-in window | 20 origins |
| `signature` | 2,048 characters |
| Escrow duration | `^\d{1,4}[smhd]$` — up to 9999 seconds, minutes, hours or days |
| `publicKey` on a developer key | 4,096 characters |
| The IP allowlist on a developer key | 1 to 64 addresses or CIDR networks |

There is no `offset` anywhere. Walk a list by passing `nextCursor` back as `cursor` until it is absent.

The two developer-key caps are the only ones you cannot raise by calling again: the allowlist is frozen when the key
is created, so a network the allowlist does not cover needs a second key, not an edit. The `publicKey` cap
there is the DER-encoded public half, which is a different field from the 512-character `publicKey` a payout carries.

## Sandbox

The sandbox runs on shared testnet infrastructure — one faucet account, one gas budget — so it caps what a single integrator can move. None of these limits exist in production. See [Sandbox](https://docs.gethevn.com/whitelabel/sandbox.md#limits).

| Limit | Value | Refusal |
| - | - | - |
| One payout | at most `1` of the debited account | `422 sandbox_amount_limit_exceeded`, `details.operation = "payout"` |
| One escrow | `maxAmount` at most `1` | `422 sandbox_amount_limit_exceeded`, `details.operation = "escrow"` |
| Smallest emulated credit | `0.10` of the token | `422 amount_below_minimum` |
| One payin credit (bank deposit, completed payin) | `1`, live as `maxCredit` on `GET /dapi/v1/sandbox/treasury` | `422 sandbox_amount_limit_exceeded`, `details.operation = "payin"` |
| One faucet credit (deposit without `bankId`) | `5` | `422 sandbox_amount_limit_exceeded`, `details.operation = "faucet"` |
| Faucet eligibility | the wallet holds less than `1` of the token | `409 sandbox_faucet_balance_too_high`, with `details.balance` and `details.threshold` |
| Money calls | 60 per account per hour | `429 rate_limited` |
| Faucet account balance | must cover the credit, and gas must be non-zero | `503 provider_unavailable` |

Keep two test payouts more than `0.20` apart in amount: returned money is attributed to a refunded payout by amount, within a `0.20` tolerance, so two near-identical test payouts can end up labelled with each other's refund.

> [!NOTE]
>
> Rate limiting is a deployment setting, and a sandbox host may run with the per-credential and per-account counters switched off. A run that never sees a `429` there is not evidence that your pacing fits the production budget — build the `Retry-After` path before you go live.

> [card] [Next: HTTP client](https://docs.gethevn.com/whitelabel/reference/client.md)
>
> The whole client — key loading, login, acting as a client, signing, retries — in Python, Node and Go.


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