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

# Money movement

> Virtual accounts, payins, payouts and the ledger of an external account.

Money reaches an external account as USDC on its network and leaves it by a transfer the customer's wallet sends. Name the account by its `acc_…` id: `accountId` on `POST /dapi/v1/banks`, `destinationAccount` on `POST /dapi/v1/payins`, `sourceAccount` on `POST /dapi/v1/payouts`.

The routes take the client's id in `X-Hevn-Account`. The common flows are in [Virtual accounts](https://docs.gethevn.com/developers/virtual-accounts.md), [Payins](https://docs.gethevn.com/developers/payins.md) and [Payouts](https://docs.gethevn.com/developers/payouts.md).

## Virtual accounts

Call `POST /dapi/v1/banks` with a product and the `accountId`. Each incoming payment is delivered as USDC to the account's address.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/banks" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Content-Type: application/json" \
  -d "{\"product\":\"us\",\"accountId\":\"$HEVN_ACCOUNT_ID\"}"
```

```python Python theme={null}
bank = client.post("/banks", {"product": "us", "accountId": account["accountId"]})
bound = client.get("/banks/us", {"accountId": account["accountId"]})
```

```javascript Node theme={null}
const bank = await client.post("/banks", { product: "us", accountId: account.accountId });
const bound = await client.get("/banks/us", { accountId: account.accountId });
```

```go Go theme={null}
bank, err := client.Post("/banks", hevn.Body{"product": "us", "accountId": account.Str("accountId")})
bound, err := client.Get("/banks/us", hevn.Query{"accountId": account.Str("accountId")})
```

```json Response 201, Location: /dapi/v1/banks/us?accountId=acc_9f2c… theme={null}
{ "product": "us", "status": "preparing", "id": "bnk_5Hq2…", "accountId": "acc_9f2c…",
  "blockers": [ { "code": "kyb_required", … } ], … }
```

Statuses, `blockers[]` and the account details are in [Virtual accounts](https://docs.gethevn.com/developers/virtual-accounts.md#statuses). Fields: [Request a receiving account](https://docs.gethevn.com/api-reference/virtual-accounts/request-a-receiving-account-for-a-product.md).

| Rule | Detail |
| - | - |
| Key | One virtual account per client, product and `accountId`. A repeat returns `200` with `Idempotency-Replayed: true`. |
| Products | A product is accepted when its rail delivers USDC on chain to the address ([Deliver to an account](https://docs.gethevn.com/developers/virtual-accounts.md#deliver-to-an-account)). Another product returns `409 account_destination_unsupported`. |
| Read | `GET /dapi/v1/banks/{product}?accountId=acc_…`. A product that cannot deliver to the account returns `403 rail_not_available`. `GET /dapi/v1/banks` lists the record with `accountId` set. |
| Delivery | The address does not change. To deliver elsewhere, register another address and request a virtual account on it. |
| Close | [Archiving](https://docs.gethevn.com/external-wallets/external-accounts.md#archive-an-account) the account closes its virtual accounts. |

## On-chain payins

Call `POST /dapi/v1/payins` with `originChainId`, the account as `destinationAccount`, and `refundAddress`. The payin is delivered as USDC on the account's network.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/payins" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Content-Type: application/json" \
  -d "{\"amount\":\"2000.00\",\"currency\":\"USDC\",\"originChainId\":\"arb\",
       \"destinationAccount\":\"$HEVN_ACCOUNT_ID\",
       \"refundAddress\":\"0x51c7a2e9b0d34f6a8e1c5b7d9f02a4c6e8b1d3f5\"}"
```

```python Python theme={null}
payin = client.post("/payins", {
    "amount": "2000.00", "currency": "USDC", "originChainId": "arb",
    "destinationAccount": account["accountId"],
    "refundAddress": "0x51c7a2e9b0d34f6a8e1c5b7d9f02a4c6e8b1d3f5",
})
instructions = client.post(f"/payins/{payin['id']}/instructions", {})
```

```javascript Node theme={null}
const payin = await client.post("/payins", {
  amount: "2000.00", currency: "USDC", originChainId: "arb",
  destinationAccount: account.accountId,
  refundAddress: "0x51c7a2e9b0d34f6a8e1c5b7d9f02a4c6e8b1d3f5",
});
const instructions = await client.post(`/payins/${payin.id}/instructions`, {});
```

```go Go theme={null}
payin, err := client.Post("/payins", hevn.Body{
	"amount": "2000.00", "currency": "USDC", "originChainId": "arb",
	"destinationAccount": account.Str("accountId"),
	"refundAddress":      "0x51c7a2e9b0d34f6a8e1c5b7d9f02a4c6e8b1d3f5",
})
instructions, err := client.Post("/payins/"+payin.Str("id")+"/instructions", hevn.Body{})
```

Give the payer `address`, `chainId`, `amount` and `memo` from the instructions call, then poll `GET /dapi/v1/payins/{payinId}`. Statuses and payer mistakes are in [Payins](https://docs.gethevn.com/developers/payins.md#statuses). Fields: [Create an incoming transfer quote](https://docs.gethevn.com/api-reference/transfers/create-an-incoming-transfer-quote.md).

| Rule | Detail |
| - | - |
| `refundAddress` | Required: an address on `originChainId` that receives the money back when the route cannot complete. Without it the call returns `422 refund_address_required`. |
| `refundAddress` on another destination | `422 validation_failed` when `destinationAccount` is an account key. |
| `rail` | `409 account_destination_unsupported`. Bank money reaches the account through its virtual account. |
| Response | `accountId` names the account. `destinationChainId` is the account's network. |

> [!WARNING]
>
> A refund goes to `refundAddress` on the origin chain. Use an address the payer controls on that chain.

## Payouts

Save a [contact](https://docs.gethevn.com/developers/payouts.md#save-a-contact), then call `POST /dapi/v1/payouts` with the account as `sourceAccount`. `publicKey` is not used.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/payouts" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Idempotency-Key: order-1188" \
  -H "Content-Type: application/json" \
  -d "{\"contactId\":\"ct_3K9…\",\"amount\":\"250.00\",\"sourceAccount\":\"$HEVN_ACCOUNT_ID\"}"
```

```python Python theme={null}
payout = client.post("/payouts", {
    "contactId": contact["id"],
    "amount": "250.00",
    "sourceAccount": account["accountId"],
}, idempotency_key="order-1188")
transfer = payout.get("transfer")
```

```javascript Node theme={null}
const payout = await client.post("/payouts", {
  contactId: contact.id,
  amount: "250.00",
  sourceAccount: account.accountId,
}, { idempotencyKey: "order-1188" });
const transfer = payout.transfer;
```

```go Go theme={null}
payout, err := client.Post("/payouts", hevn.Body{
	"contactId":     contact.Str("id"),
	"amount":        "250.00",
	"sourceAccount": account.Str("accountId"),
}, hevn.IdempotencyKey("order-1188"))
```

```json Response 201, Location: /dapi/v1/payouts/po_4c8a… theme={null}
{
  "id": "po_4c8a…",
  "kind": "fiat",
  "status": "awaitingSignature",
  "idempotencyKey": "order-1188",
  "quote": { … },
  "authorization": "wallet",
  "transfer": {
    "chain": "base",
    "chainId": 8453,
    "token": "USDC",
    "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "fromAddress": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
    "toAddress": "0x3e5b…",
    "amount": "250.00",
    "amountAtomic": "250000000",
    "expiresAt": "2026-11-12T10:19:11Z"
  },
  "contact": { "contactId": "ct_3K9…", "name": "Ardenne Fabrication SARL" }
}
```

`approval`, `debit` and `intent` are absent. Fields: [Create an outgoing transfer](https://docs.gethevn.com/api-reference/transfers/create-an-outgoing-transfer.md).

### Send the transfer

Have the customer's wallet call the ERC-20 `transfer(toAddress, amountAtomic)` on `tokenAddress`, from `fromAddress`, on `chainId`. Read `authorization` before `status`. With `wallet`, `awaitingSignature` means the transfer is outstanding.

| Transfer | Result |
| - | - |
| `amountAtomic` from `fromAddress` to `toAddress`, mined before `expiresAt` | Funds the payout. The status leaves `awaitingSignature`. |
| From another address, of another amount, or mined after `expiresAt` | Does not fund the payout. |
| A second transfer of the same amount | Does not fund a payout. |
| None before `expiresAt` | The payout ends as `failed`, with `failure.code` `payout_canceled` or `quote_expired`. Book a new one. |

> [!WARNING]
>
> A transfer that does not match `transfer` leaves the wallet and does not fund the payout. Send one transfer per payout, with the amount, sender and deadline of `transfer`.

`POST /dapi/v1/payouts/{payoutId}/confirm` returns `400 invalid_request` with `details.reason` `funded_by_wallet_transfer`.

### Check the instruction

Check `transfer` on your server before your product asks the wallet to sign.

| Field | Check |
| - | - |
| `chainId` | `8453` in production, `84532` in the sandbox. |
| `tokenAddress` | The USDC contract you hold in your own configuration for that chain. |
| `fromAddress` | The address of the account you named as `sourceAccount`. |
| `toAddress` | Differs from `fromAddress` and from `tokenAddress`. |
| `amount`, `amountAtomic` | Equal to each other at 6 decimals, and to the amount you booked. |
| `expiresAt` | In the future, with time for the wallet to sign and the transfer to be mined. |

`toAddress` is the address the payout is collected at. Your server cannot derive it, so pass it to the wallet as returned.

### Track the payout

Poll `GET /dapi/v1/payouts/{payoutId}`. The statuses are in [Track it to completion](https://docs.gethevn.com/developers/payouts.md#track-it-to-completion).

| Case | Behaviour |
| - | - |
| Replay with the same `Idempotency-Key` before funding | The same payout and the same `transfer`. |
| Replay after funding | The payout as it stands, with no `transfer`. |
| Same key, another `sourceAccount` | `409 idempotency_key_reused` |

### Refusals

| Error | Cause |
| - | - |
| `409 source_account_unsupported` | The account is not on `base`, or the payout is not a USDC payout funded from the account's balance. |
| `404 account_not_found` | Unknown or archived `acc_…` id. |
| `403 transfer_policy_denied` | The client's transfer policy refuses the payout. `transferPolicy` is on `GET /dapi/v1/client`. |
| `403 account_spend_forbidden` | Your seat on the client cannot pay from its external accounts. |
| `422 account_funding_invalid` | The booking can no longer be funded from the account. Book a new payout. |

Other payout codes are in [Errors](https://docs.gethevn.com/developers/reference/errors.md#payouts).

## Balance and ledger

| Read | Behaviour |
| - | - |
| Balance | `balance` on `GET /dapi/v1/client/accounts` ([Balance](https://docs.gethevn.com/external-wallets/external-accounts.md#balance)). `GET /dapi/v1/client/balance` has no row for an external account. |
| Ledger rows | HEVN writes a row for each transfer it arranged: an incoming payment of a virtual account, a delivered payin, a payout. Each has `accountId`. Other transfers of the address have no row. |
| Filter | `GET /dapi/v1/transactions`, `/transactions/summary` and `/transactions/export` take `accountId`. An archived account's rows stay readable. An id of another client returns `404 account_not_found`. |
| Summary | Income of an external account is left out of `/transactions/summary` unless `accountId` names the account. |

Paging, filters and exports are in [Balances and transactions](https://docs.gethevn.com/developers/balances-and-transactions.md).

## Sandbox

In the sandbox, fund the address through a virtual account or a public testnet faucet, and send the payout `transfer` to the faucet account. The products, limits and refusals are in [Accounts](https://docs.gethevn.com/developers/sandbox.md#accounts).

> [card] [Next: Sandbox and going live](https://docs.gethevn.com/developers/sandbox.md)


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