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

# Conventions

> Money format, ids, casing, idempotency, pagination, status codes, errors, rate limits and lifetimes on all /dapi/v1 routes.

All `/dapi/v1` routes share one format for money, ids, casing, idempotency and pagination, and one set of limits. Send amounts as decimal strings, and send an `Idempotency-Key` with `POST /dapi/v1/payouts` and other writes that book a payment.

## Money

An amount is a decimal string in the major unit of the asset: `"500.00"`, `"0.9247"`, `"1204.500000"`. Do not send a fractional JSON number, an atomic-unit integer or exponent notation. Rates use the same format. `feeBps` is an integer number of basis points.

Precision depends on the route:

| Route | Too many digits |
| - | - |
| Escrow, sandbox credits and payouts | More decimals than the token allows return `422 validation_failed` with `details.fields[0].code = "amount_precision"`. A payout checks `amount` against the source token. |
| Payins, swaps and payouts | An amount with more than 15 significant digits can return `422 amount_precision_unsupported` with `details.field`. |

A payin, swap or payout takes `amount`, the side sent, or `amountTo`, the side received, and not both. HEVN prices the other side.

In JavaScript and Go a decimal arrives as a string, so a `typeof x === "number"` check skips it. Parse with a decimal type.

## Ids

An id is a prefixed, opaque string. Store it as text. Do not parse it, derive it or assume a length.

| Prefix | Resource |
| - | - |
| `cl_` | Client, and your integrator account |
| `po_` | Payout, over a bank rail or on chain |
| `pi_` | Payin |
| `swp_` | Swap |
| `txn_` | Transaction |
| `ct_` | Contact |
| `bnk_` | Virtual account |
| `acc_` | External account or sub account ([Accounts](https://docs.gethevn.com/developers/accounts.md)) |
| `doc_` | Document |
| `kyb_` | KYB application |
| `esc_` | Escrow deal |
| `dk_` | [Developer key](https://docs.gethevn.com/developers/authentication.md#developer-key). Not sent to this API. |

An id of the wrong kind in a body or query field returns `400 invalid_id`, with the expected prefix in `details`. In a path it returns the `404` of that resource.

## Casing

| Item | Rule |
| - | - |
| Request bodies, query parameters, response fields, keys in `error.details` | camelCase |
| `error.code` | Lowercase `snake_case` |
| Enum values | Send them as the spec prints them, without case-folding. Most are `snake_case`, and some are upper case or ranges. |
| Unknown keys | `422 validation_failed` with `code: "extra_forbidden"` |
| Null fields | Omitted from responses. A missing key is unset. |
| Timestamps | RFC 3339 in UTC with a `Z` suffix: `2026-09-17T10:04:11Z` |

A contact's `address`, and the `fields` and `holder` of a `bank` or `payer` requisite, ignore unknown keys. A misspelt `streetAddress` counts as missing: on a bank contact that needs an address, `POST /dapi/v1/contacts` returns `422 validation_failed`, and the message names the missing keys in `snake_case`.

The `fields` keys on `PUT /dapi/v1/documents/{documentId}/content` are document field names such as `registration_number` and `address.street_address`. A refusal that requires one of these fields returns its name. See [Fill a document's fields by hand](https://docs.gethevn.com/developers/clients.md#fill-a-documents-fields-by-hand).

## Idempotency-Key

Every write accepts `Idempotency-Key`, and no route requires it. The value matches `^[A-Za-z0-9._:-]{1,128}$`.

Derive the key from an id in your system.

```python Python theme={null}
key = f"payout-{invoice.id}"  # "payout-INV-2026-114"
client.post("/payouts", payout, idempotency_key=key)
```

```javascript Node theme={null}
const key = `payout-${invoice.id}`;
await client.post("/payouts", payout, { idempotencyKey: key });
```

```go Go theme={null}
key := "payout-" + invoice.ID
client.Post("/payouts", payout, hevn.IdempotencyKey(key))
```

> [!WARNING]
>
> Do not generate the key in the helper that sends the request, from a clock or at random. Do not change it on a retry. A new key books a second payment.

| Case | Result |
| - | - |
| Same key, same request | `200` with `Idempotency-Replayed: true` and the first resource |
| Same key, different body | `409 idempotency_key_reused`. `details` names the key's resource. |
| Malformed key | `400 idempotency_key_invalid` |
| No key | HEVN derives one from the request and returns it as `idempotencyKey`. An identical repeat is a replay. Later retries can send the returned value as `Idempotency-Key`. |
| Replay window | 7 days for a key you send, 2 hours for a derived key |
| Scope | Your account and the operation. The same key on `POST /payouts` and `POST /contacts` is not a replay. |

To book two identical payouts within 2 hours, send a different key with each.

| Route under `/dapi/v1` | What the key identifies |
| - | - |
| `POST /payouts` | The payment. For a bank contact, the booking and its price. For a wallet contact, the key is part of the `po_` id. |
| `POST /swaps` | The swap or move |
| `POST /escrow` | The deal, its resolved windows and its `approve` action. The raw key is returned as `action.idempotencyKey`. |
| `POST /escrow/{escrowId}/actions` | The action. The raw key is the `{idempotencyKey}` segment of the confirm URL. |
| `POST /sandbox/deposits`, `/sandbox/payins/{payinId}/complete`, `/sandbox/payouts/{payoutId}/status` | The emulated credit or status change |
| `POST /contacts`, `PATCH` and `DELETE /contacts/{contactId}` | None. Account details deduplicate a contact. |
| `POST /clients` | The creation request. The same key returns the same client. |
| `POST /client/accounts` | None. The address and label identify an external account ([External accounts](https://docs.gethevn.com/external-wallets/external-accounts.md)). The label and `asset` identify a sub account ([Sub accounts](https://docs.gethevn.com/sub-accounts/overview.md)). |
| `POST /clients/link`, `POST /banks`, `PUT /client/kyb`, `POST /client/kyb/complete`, `POST /payins`, `POST /payins/{payinId}/instructions` | None. The linked account, product, client or payin identifies the request. A key sent to these routes is accepted and has no effect. |

## Why a retry cannot pay twice

Each payout has one slot in the paying wallet, the main wallet or a sub account: an ERC-4337 nonce derived from the payout. The network accepts one operation per slot.

When the slot is used or an operation is in flight, the call returns `409 already_funded`, `409 payment_slot_consumed` or `409 funding_in_progress`. After `payment_slot_consumed` an operation has landed, possibly reverted, and signing again has no effect. [Retrying a confirm](https://docs.gethevn.com/developers/signing.md#retrying-a-confirm) lists the next step for each.

Escrow actions and payments sent from the HEVN app use the same slots.

## Pagination

A list returns a cursor page.

```json theme={null}
{ "items": [ … ], "nextCursor": "eyJ2IjoiMjAyNi0wOS0xN1QxMDowNDoxMVoiLCJpIjoi…" }
```

Send `cursor`, the unchanged `nextCursor` of the previous page, until the response omits `nextCursor`.

A cursor sent to a different query returns `400 invalid_cursor`. Lists have no `offset` and no total count. `GET /dapi/v1/transactions/summary` takes the `GET /dapi/v1/transactions` filters and returns totals.

## Status codes

| Code | When | Read |
| - | - | - |
| `200` | A read, an update, or a replay of a create | `Idempotency-Replayed: true` on a replay |
| `201` | A resource was created | `Location` |
| `202` | Accepted, not final | `status` and `pollUrl` in the body |
| `4xx`, `5xx` | Refused | The [error envelope](#errors) |

No route returns `204`. `Location` and `pollUrl` are paths from the host root, including `/dapi/v1`.

The API sends no webhooks. After a `202`, read `pollUrl` with backoff until `status` is final. See [HTTP client](https://docs.gethevn.com/developers/reference/client.md) for the helper and [Rate limits](#rate-limits) for read limits.

A client's `pollUrl` is `/dapi/v1/client`. Poll it with the new `cl_…` in `X-Hevn-Account`.

A payout with `authorization: "wallet"` has no confirm call. Poll `GET /dapi/v1/payouts/{payoutId}` after the customer's wallet sends the transfer.

## Errors

A refusal returns `{ "error": { "code", "message", "details" } }`. Branch on `code`. The fields, the `details` keys and the full list of codes are in [Errors](https://docs.gethevn.com/developers/reference/errors.md).

> [!WARNING]
>
> Funding refusals report `details.available` and `details.required` in atomic units: `"3000000"` is 3 USDC. Do not mix them with major-unit amounts.

## Rate limits

A request over a rate limit returns `429 rate_limited` with a `Retry-After` header. Wait that many seconds, then retry.

| Window | Limit | Keyed on | Applies to |
| - | - | - | - |
| Writes | 120 per minute | credential | `POST`, `PUT`, `PATCH` and `DELETE` under `/dapi/v1` |
| Reads | 600 per minute | credential | `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` |

Requests with different `X-Hevn-Account` values share the write and read counters of one credential.

`POST /dapi/v1/auth/refresh` has no login limit. It counts as a write.

Booking and confirming a signed payout counts as two writes. Poll with backoff ([Status codes](#status-codes)), and read `GET /dapi/v1/transactions` once for the account where you can, not once per client.

A sandbox host may run with the credential and account limits off. Test the `Retry-After` path before you go live. Sandbox amount caps and faucet rules are listed in [Sandbox](https://docs.gethevn.com/developers/sandbox.md#limits).

## Lifetimes

| Item | Lifetime | After it ends |
| - | - | - |
| Login challenge | Up to 5 minutes, set with `requestExpiry` | `410 challenge_expired` |
| Access token | 1 hour | `401 token_expired`. Call `POST /dapi/v1/auth/refresh`. |
| Payout quote | Until `quote.expiresAt` | `410 quote_expired` |
| Payout approval, on your main wallet or a sub account | 120 seconds | `409 funding_attempt_expired`, or `410 approval_expired` with `authorization: intent`. Book again with the same `Idempotency-Key` for a new approval. |
| Swap or move approval | 120 seconds | `409 swap_not_confirmable` |
| Transfer that funds a payout from an external account | Until `transfer.expiresAt` | A transfer mined later does not fund the payout ([External wallets](https://docs.gethevn.com/external-wallets/money-movement.md)). |
| Link code from the sign-in 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 `Idempotency-Key`. |

Other lifetimes are on the REST API pages: the refresh token on [Exchange a signed login challenge](https://docs.gethevn.com/api-reference/auth/exchange-a-signed-login-challenge-for-a-session.md), the sign-in window session on [Create a sign-in window session](https://docs.gethevn.com/api-reference/accounts/create-a-sign-in-window-session.md), the payin quote on [Create an incoming transfer quote](https://docs.gethevn.com/api-reference/transfers/create-an-incoming-transfer-quote.md), the escrow windows on [Open an escrow](https://docs.gethevn.com/api-reference/escrow/open-an-escrow-and-return-the-payers-approval.md) and the escrow chain read on [Reconcile one escrow](https://docs.gethevn.com/api-reference/escrow/reconcile-one-escrow-with-the-chain.md). After a sign-in window session expires, create a new one. The replay windows of an `Idempotency-Key` are in [Idempotency-Key](#idempotency-key).

## Setting limits

Field sizes on API requests are on the REST API page of each operation. These limits apply to settings in the HEVN app:

| Setting | Limit |
| - | - |
| Allowed sites in the sign-in window | 20 origins |
| `publicKey` on a developer key | 4,096 characters, DER-encoded |
| IP allowlist on a developer key | 1 to 64 addresses or CIDR networks, fixed when the key is created |

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


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