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

# Quickstart

> Open a sub account, request its virtual account, fund it and pay out from it in the sandbox.

Open a sub account in the sandbox, fund it, pay a contact from it and move the rest to your main wallet. Start by logging in with `POST /dapi/v1/auth/challenge` and `POST /dapi/v1/auth/token`.

## Before you start

1. Sign up at [`https://sandbox.gethevn.com`](https://sandbox.gethevn.com) and create a [developer key](https://docs.gethevn.com/developers/authentication.md#developer-key).
2. Save the private key to `$HEVN_KEY_PEM` with mode `0600`.
3. Set the environment and install the dependencies.

| Environment | API base URL | App | Chain |
| - | - | - | - |
| Sandbox | `https://sandbox-api.hevn.finance/dapi/v1` | `https://sandbox.gethevn.com` | Base Sepolia |
| Production | `https://api.hevn.finance/dapi/v1` | `https://app.gethevn.com` | Base |

Developer keys, clients and tokens are per environment.

The examples read these variables:

```bash theme={null}
export HEVN_API="https://sandbox-api.hevn.finance/dapi/v1"
export HEVN_EMAIL="integrator@example.com"
export HEVN_KEY_PEM="$HOME/.hevn/developer-key.pem"  # mode 0600, keep it out of your repo
export HEVN_CLIENT_ID="cl_7YQ2Kf3mN8"                # the client a call acts for
```

| Derived variable | Value |
| - | - |
| `HEVN_ACCESS_TOKEN` | The `accessToken` returned by [login](https://docs.gethevn.com/developers/authentication.md#sessions). |
| `HEVN_USER_ID` | Your `cl_…` id, the `userId` returned by login. |
| `HEVN_PUBLIC_KEY` | The base64 public key of the PEM, sent as `publicKey` to `POST /dapi/v1/payouts`. |

```bash theme={null}
export HEVN_PUBLIC_KEY=$(openssl ec -in "$HEVN_KEY_PEM" -pubout -outform DER 2>/dev/null \
  | base64 | tr -d '\n')
```

```bash Python theme={null}
python -m venv .venv && source .venv/bin/activate
pip install cryptography httpx
```

```bash Node theme={null}
# Node 20 or newer. Signing and HTTP use node:crypto and global fetch.
node --version
npm init --yes
```

```bash Go theme={null}
go mod init example.com/hevn
# Signing and HTTP use the standard library only: crypto/ecdsa, encoding/pem, net/http.
```

> [!NOTE]
>
> The sandbox runs on Base Sepolia with testnet tokens. A payout, escrow or payin is capped at `1`, and a faucet credit at `5` to a wallet holding less than `1`. The sandbox money routes take 60 calls per account per hour. See [Sandbox limits](https://docs.gethevn.com/developers/sandbox.md#limits).

The Python, Node and Go tabs use the [reference client](https://docs.gethevn.com/developers/reference/client.md). `me` is the client bound to your own `cl_…` id, which `/client/accounts` and `/client/balance` require.

## Steps

**1. Log in**

Run the login code in [Sessions](https://docs.gethevn.com/developers/authentication.md#sessions). Export `accessToken` as `HEVN_ACCESS_TOKEN` and `userId` as `HEVN_USER_ID`.

```bash cURL theme={null}
export HEVN_ACCESS_TOKEN="eyJ…"   # accessToken from login
export HEVN_USER_ID="cl_…"        # userId from login
```

```python Python theme={null}
key = Key()
hevn = Hevn(Session(key))
me = hevn.acting_as(os.environ["HEVN_USER_ID"])
```

```javascript Node theme={null}
const key = new Key();
const hevn = new Hevn(new Session(key));
const me = hevn.actingAs(process.env.HEVN_USER_ID);
```

```go Go theme={null}
key, err := hevn.NewKey()
api := &hevn.Client{API: os.Getenv("HEVN_API"), HTTP: http.DefaultClient, Session: &hevn.Session{Key: key}}
me := api.ActingAs(os.Getenv("HEVN_USER_ID"))
```

**2. Open a sub account**

Call `POST /dapi/v1/client/accounts` with `kind: "sub"` and a label.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/client/accounts" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_USER_ID" \
  -H "Content-Type: application/json" \
  -d '{"kind":"sub","label":"customer-1042"}'
```

```python Python theme={null}
sub = me.post("/client/accounts", {"kind": "sub", "label": "customer-1042"})
```

```javascript Node theme={null}
const sub = await me.post("/client/accounts", { kind: "sub", label: "customer-1042" });
```

```go Go theme={null}
sub, err := me.Post("/client/accounts", hevn.Body{"kind": "sub", "label": "customer-1042"})
```

```json Response 201, Location: /dapi/v1/client/accounts/acc_9f2c… theme={null}
{ "accountId": "acc_9f2c…", "kind": "sub", "asset": "USDC", "chain": "base",
  "address": "0x51c7…", "label": "customer-1042", "balance": "0.0", … }
```

A repeat with the same label returns `200` and the same sub account ([Label](https://docs.gethevn.com/sub-accounts/overview.md#label)). Fields: [Add an account](https://docs.gethevn.com/api-reference/client-accounts/add-an-account.md).

**3. Request its virtual account**

Call `POST /dapi/v1/banks` with the product and the `accountId`.

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

```python Python theme={null}
rail = me.post("/banks", {"product": "us", "accountId": sub["accountId"]})
```

```javascript Node theme={null}
const rail = await me.post("/banks", { product: "us", accountId: sub.accountId });
```

```go Go theme={null}
rail, err := me.Post("/banks", hevn.Body{"product": "us", "accountId": sub.Str("accountId")})
```

```json Response 201, Location: /dapi/v1/banks/us?accountId=acc_9f2c… theme={null}
{ "product": "us", "opened": true, "status": "active", "id": "bnk_88Hq3T",
  "accountId": "acc_9f2c…", "requisites": { … }, "blockers": [], … }
```

While `status` is `preparing`, `blockers[]` lists what is pending ([What still blocks it](https://docs.gethevn.com/developers/virtual-accounts.md#what-still-blocks-it)). The sandbox accepts `us` and `swift` for a sub account. Fields: [Request a receiving account for a product](https://docs.gethevn.com/api-reference/virtual-accounts/request-a-receiving-account-for-a-product.md).

**4. Fund the sub account**

Call `POST /dapi/v1/sandbox/deposits` with the `accountId` and no `bankId`. The faucet sends the token the sub account holds to its address.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/sandbox/deposits" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_USER_ID" \
  -H "Idempotency-Key: fund-customer-1042" \
  -H "Content-Type: application/json" \
  -d '{"amount":"2.00","accountId":"acc_9f2c…"}'
```

```python Python theme={null}
credit = me.post("/sandbox/deposits", {"amount": "2.00", "accountId": sub["accountId"]},
                 idempotency_key="fund-customer-1042")
```

```javascript Node theme={null}
const credit = await me.post("/sandbox/deposits",
  { amount: "2.00", accountId: sub.accountId },
  { idempotencyKey: "fund-customer-1042" });
```

```go Go theme={null}
credit, err := me.Post("/sandbox/deposits",
	hevn.Body{"amount": "2.00", "accountId": sub.Str("accountId")},
	hevn.IdempotencyKey("fund-customer-1042"))
```

```json Response 200 theme={null}
{ "depositId": "sbx_cd_1b8e0f7a2c", "status": "settled", "token": "USDC",
  "amount": "2.000000", "credited": "2.000000", "accountId": "acc_9f2c…",
  "txHash": "0x08fa…", "transactionId": "txn_9e4…", … }
```

On `202`, repeat the same request until `status` is `settled`. To emulate a bank transfer to an `active` virtual account, send `{"amount":"1.00","bankId":"bnk_88Hq3T"}` and omit `accountId`. Fields: [Emulate an incoming deposit](https://docs.gethevn.com/api-reference/sandbox/emulate-an-incoming-deposit:-an-account-transfer-with-bankid-a-token-transfer-without.md).

**5. Read the balance**

Call `GET /dapi/v1/client/balance`. The entry with your `accountId` is the sub account.

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

```python Python theme={null}
balance = me.get("/client/balance")
held = next(row for row in balance["accounts"] if row.get("accountId") == sub["accountId"])
```

```javascript Node theme={null}
const balance = await me.get("/client/balance");
const held = balance.accounts.find((row) => row.accountId === sub.accountId);
```

```go Go theme={null}
balance, err := me.Get("/client/balance")
```

```json Response 200 theme={null}
{ "clientId": "cl_9f2c1ab8…", "baseSmartWallet": "0x2f1c…",
  "accounts": [ { "account": "USDC", "balance": "0.000000", "usdValue": "0.00" },
                { "account": "USDC", "accountId": "acc_9f2c…", "balance": "2.000000", "usdValue": "2.00" } ],
  "totalUsd": "2.00" }
```

The balance of a sub account is read from the network when the last read is older than a minute. Fields: [Read a client's balances](https://docs.gethevn.com/api-reference/accounts/read-a-clients-balances.md).

**6. Book a payout from it**

Save a USDC contact on Base with `POST /dapi/v1/contacts`, then call `POST /dapi/v1/payouts` with the sub account as `sourceAccount`. Use a Base Sepolia address you hold.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/contacts" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_USER_ID" \
  -H "Content-Type: application/json" \
  -d '{"name":"Ardenne treasury","crypto":{"chain":"base","token":"USDC","address":"0x7c1d…"}}'

curl -s -X POST "$HEVN_API/payouts" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_USER_ID" \
  -H "Idempotency-Key: redemption-1188" \
  -H "Content-Type: application/json" \
  -d '{"contactId":"ct_9a…","amount":"1.00","sourceAccount":"acc_9f2c…",
       "publicKey":"'"$HEVN_PUBLIC_KEY"'"}'
```

```python Python theme={null}
contact = me.post("/contacts", {
    "name": "Ardenne treasury",
    "crypto": {"chain": "base", "token": "USDC", "address": "0x7c1d…"},
})
payout = me.post("/payouts", {
    "contactId": contact["id"],
    "amount": "1.00",
    "sourceAccount": sub["accountId"],
    "publicKey": key.public_key,
}, idempotency_key="redemption-1188")
```

```javascript Node theme={null}
const contact = await me.post("/contacts", {
  name: "Ardenne treasury",
  crypto: { chain: "base", token: "USDC", address: "0x7c1d…" },
});
const payout = await me.post("/payouts", {
  contactId: contact.id,
  amount: "1.00",
  sourceAccount: sub.accountId,
  publicKey: key.publicKey,
}, { idempotencyKey: "redemption-1188" });
```

```go Go theme={null}
contact, err := me.Post("/contacts", hevn.Body{
	"name":   "Ardenne treasury",
	"crypto": hevn.Body{"chain": "base", "token": "USDC", "address": "0x7c1d…"},
})
payout, err := me.Post("/payouts", hevn.Body{
	"contactId":     contact.Str("id"),
	"amount":        "1.00",
	"sourceAccount": sub.Str("accountId"),
	"publicKey":     key.PublicKey,
}, hevn.IdempotencyKey("redemption-1188"))
```

```json Response 201, Location: /dapi/v1/payouts/po_7e1b… theme={null}
{ "id": "po_7e1b…", "kind": "onchain", "status": "awaitingSignature",
  "authorization": "debit",
  "approval": { "id": "…", "payload": "eyJ…", "expiresAt": "2026-11-13T09:21:10Z" },
  "debit": { "address": "0x7c1d…", "sourceAddress": "0x51c7…",
             "amount": "1.00", "amountAtomic": "1000000", "token": "USDC", "chainId": 84532,
             "operation": { "sender": "0x51c7…", "initCode": "0x3fAB…", "callData": "0x34fc…", … } } }
```

Fields: [Create an outgoing transfer](https://docs.gethevn.com/api-reference/transfers/create-an-outgoing-transfer.md).

**7. Check, sign and confirm**

Compare `debit` with the payout you booked, sign `approval.payload` and call `POST /dapi/v1/payouts/{payoutId}/confirm` within 120 seconds.

```bash cURL theme={null}
# APPROVAL_PAYLOAD is approval.payload from the previous response.
SIGNATURE=$(printf '%s' "$APPROVAL_PAYLOAD" | base64 --decode \
  | openssl dgst -sha256 -sign "$HEVN_KEY_PEM" | base64 | tr -d '\n')

curl -s -X POST "$HEVN_API/payouts/po_7e1b…/confirm" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_USER_ID" \
  -H "Content-Type: application/json" \
  -d '{"signature":"'"$SIGNATURE"'","publicKey":"'"$HEVN_PUBLIC_KEY"'"}'
```

```python Python theme={null}
debit = payout["debit"]
assert payout["authorization"] == "debit"
assert debit["sourceAddress"].lower() == sub["address"].lower()
assert debit["operation"]["sender"] == debit["sourceAddress"]
assert debit["address"].lower() == contact["crypto"]["address"].lower()
assert (debit["amountAtomic"], debit["token"]) == ("1000000", "USDC")

signature = key.sign_payload(payout["approval"]["payload"])
receipt = me.confirm_until_settled(f"/payouts/{payout['id']}/confirm", signature)
```

```javascript Node theme={null}
const { debit } = payout;
const same = (a, b) => a.toLowerCase() === b.toLowerCase();
if (payout.authorization !== "debit"
  || !same(debit.sourceAddress, sub.address)
  || debit.operation.sender !== debit.sourceAddress
  || !same(debit.address, contact.crypto.address)
  || debit.amountAtomic !== "1000000" || debit.token !== "USDC") {
  throw new Error("debit does not match the payout");
}

const signature = key.signPayload(payout.approval.payload);
const receipt = await me.confirmUntilSettled(`/payouts/${payout.id}/confirm`, signature);
```

```go Go theme={null}
if payout.Str("authorization") != "debit" ||
	!strings.EqualFold(payout.Str("debit.sourceAddress"), sub.Str("address")) ||
	payout.Str("debit.operation.sender") != payout.Str("debit.sourceAddress") ||
	!strings.EqualFold(payout.Str("debit.address"), contact.Str("crypto.address")) ||
	payout.Str("debit.amountAtomic") != "1000000" || payout.Str("debit.token") != "USDC" {
	log.Fatal("debit does not match the payout")
}

signature, err := key.SignPayload(payout.Str("approval.payload"))
receipt, err := me.ConfirmUntilSettled("/payouts/"+payout.Str("id")+"/confirm", signature)
```

```json Response 200 theme={null}
{ "id": "po_7e1b…", "kind": "onchain", "status": "settled", "transactionHash": "0x4f1c…" }
```

`debit.sourceAddress` is the address of the sub account, and `debit.operation` is the operation the signature approves ([Payouts](https://docs.gethevn.com/sub-accounts/money-movement.md#payouts)). Confirm outcomes are in [Retrying a confirm](https://docs.gethevn.com/developers/signing.md#retrying-a-confirm).

**8. Move the rest to your main wallet**

Call `POST /dapi/v1/swaps` with the sub account as `sourceAccount` and the account key `USDC` as `destinationAccount`. Sign and confirm with `POST /dapi/v1/swaps/{swapId}/confirm`, then read the swap until it settles.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/swaps" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_USER_ID" \
  -H "Idempotency-Key: sweep-customer-1042" \
  -H "Content-Type: application/json" \
  -d '{"sourceAccount":"acc_9f2c…","destinationAccount":"USDC","amount":"1.00",
       "publicKey":"'"$HEVN_PUBLIC_KEY"'"}'
```

```python Python theme={null}
move = me.post("/swaps", {
    "sourceAccount": sub["accountId"],
    "destinationAccount": "USDC",
    "amount": "1.00",
    "publicKey": key.public_key,
}, idempotency_key="sweep-customer-1042")
assert move["debit"]["sourceAddress"].lower() == sub["address"].lower()
assert move["debit"]["address"].lower() == balance["baseSmartWallet"].lower()

me.post(f"/swaps/{move['id']}/confirm", {"signature": key.sign_payload(move["approval"]["payload"])})
done = poll_until(lambda: me.get(f"/swaps/{move['id']}"),
                  lambda swap: swap["status"] in ("settled", "failed"))
```

```javascript Node theme={null}
const move = await me.post("/swaps", {
  sourceAccount: sub.accountId,
  destinationAccount: "USDC",
  amount: "1.00",
  publicKey: key.publicKey,
}, { idempotencyKey: "sweep-customer-1042" });
if (!same(move.debit.sourceAddress, sub.address) || !same(move.debit.address, balance.baseSmartWallet)) {
  throw new Error("debit does not match the move");
}

await me.post(`/swaps/${move.id}/confirm`, { signature: key.signPayload(move.approval.payload) });
const done = await pollUntil(() => me.get(`/swaps/${move.id}`),
  (swap) => swap.status === "settled" || swap.status === "failed");
```

```go Go theme={null}
move, err := me.Post("/swaps", hevn.Body{
	"sourceAccount":      sub.Str("accountId"),
	"destinationAccount": "USDC",
	"amount":             "1.00",
	"publicKey":          key.PublicKey,
}, hevn.IdempotencyKey("sweep-customer-1042"))
if !strings.EqualFold(move.Str("debit.sourceAddress"), sub.Str("address")) ||
	!strings.EqualFold(move.Str("debit.address"), balance.Str("baseSmartWallet")) {
	log.Fatal("debit does not match the move")
}

signature, err := key.SignPayload(move.Str("approval.payload"))
_, err = me.Post("/swaps/"+move.Str("id")+"/confirm", hevn.Body{"signature": signature})
done, err := hevn.PollUntil(
	func() (hevn.Payload, error) { return me.Get("/swaps/" + move.Str("id")) },
	func(swap hevn.Payload) bool { return swap.Str("status") == "settled" || swap.Str("status") == "failed" },
	2*time.Minute)
```

```json Response 201, Location: /dapi/v1/swaps/swp_9f2c1ab84d theme={null}
{ "id": "swp_9f2c1ab84d", "status": "awaitingSignature",
  "sourceAccount": "acc_9f2c…", "destinationAccount": "USDC",
  "quote": { "fromAmount": "1.00", "fromCurrency": "USDC", "toAmount": "1.00", "toCurrency": "USDC", … },
  "authorization": "debit",
  "approval": { "id": "b1e7…", "payload": "eyJib2R5Ijp7…", "expiresAt": "2026-11-13T09:24:59Z" },
  "debit": { "address": "0x2f1c…", "sourceAddress": "0x51c7…", "amount": "1.00", "token": "USDC", … } }
```

In cURL, sign `approval.payload` as in the previous step. Fields: [Create a transfer between the client's own accounts](https://docs.gethevn.com/api-reference/transfers/create-a-transfer-between-the-clients-own-accounts.md).

> [card] [Next: Money movement](https://docs.gethevn.com/sub-accounts/money-movement.md)


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