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

> Link a client in the sandbox, fund its wallet and settle a payout the holder approves.

Link one client, fund its wallet and settle one holder-approved payout in the sandbox. 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).
2. Open **Settings → Partner program → Developer** and create a developer key. Set its [IP allowlist](https://docs.gethevn.com/developers/authentication.md#the-ip-allowlist) to your machine's egress address.
3. Save the private key to `$HEVN_KEY_PEM` with mode `0600`.
4. In the Sign-in window card, add `http://localhost:8080` under **Allowed sites** and save.
5. Have a second Google account with no HEVN sandbox account. Linking an existing account fails with `already_registered`.
6. 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).

## 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`. The Python, Node and Go tabs use the [reference client](https://docs.gethevn.com/developers/reference/client.md).

**2. Serve the two pages**

Save both files in an empty folder and serve it on the allowed origin.

```html index.html theme={null}
<!doctype html>
<meta charset="utf-8">
<title>HEVN quickstart</title>
<input id="url" placeholder="Paste a session url" size="90">
<button id="open">Open HEVN</button>
<pre id="out"></pre>
<script>
  const out = document.getElementById("out");
  const channel = new BroadcastChannel("hevn-connect");
  channel.onmessage = ({ data }) => { out.textContent = JSON.stringify(data.result, null, 2); };
  document.getElementById("open").onclick = () => {
    window.open(document.getElementById("url").value, "hevn-connect", "popup,width=460,height=720");
  };
</script>
```

```html callback.html theme={null}
<!doctype html>
<meta charset="utf-8">
<title>Signing in…</title>
<script>
  const result = Object.fromEntries(new URLSearchParams(location.search));
  if (result.state) {
    const channel = new BroadcastChannel("hevn-connect");
    channel.postMessage({ state: result.state, result });
    channel.close();
    window.close();
  }
</script>
```

```bash theme={null}
python3 -m http.server 8080
```

Open `http://localhost:8080`. A production page needs the checks in [Sign in with HEVN](https://docs.gethevn.com/connect/sign-in-with-hevn.md).

**3. Create a link session**

Call `POST /dapi/v1/connect/sessions` with `action: "link"`. Do not send `X-Hevn-Account`.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/connect/sessions" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"action":"link","redirectUri":"http://localhost:8080/callback.html"}' | jq -r .url
```

```python Python theme={null}
session = hevn.post("/connect/sessions", {
    "action": "link", "redirectUri": "http://localhost:8080/callback.html",
})
print(session["url"])
```

```javascript Node theme={null}
const session = await hevn.post("/connect/sessions", {
  action: "link", redirectUri: "http://localhost:8080/callback.html",
});
console.log(session.url);
```

```go Go theme={null}
session, err := api.Post("/connect/sessions", hevn.Body{
	"action": "link", "redirectUri": "http://localhost:8080/callback.html",
})
fmt.Println(session.Str("url"))
```

```json Response theme={null}
{ "url": "https://sandbox.gethevn.com/connect?session=eyJ…", … }
```

| Error | Cause |
| - | - |
| `403 redirect_uri_not_allowed` | `http://localhost:8080` is not an allowed site. |
| `403 client_creation_not_enabled` | Your account is [not an active integrator](https://docs.gethevn.com/developers/sandbox.md#what-hevn-must-enable). |

**4. Sign in as the customer**

Paste the URL into the page within 10 minutes, click **Open HEVN** and sign in with the second Google account. The page shows the result.

```json theme={null}
{ "code": "eyJhbGciOiJFUzI1NiIs…", "state": "pQ8v2Zr0…" }
```

**5. Exchange the code**

Call `POST /dapi/v1/clients/link` with the code within 5 minutes.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/clients/link" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"code":"eyJhbGciOiJFUzI1NiIs…"}' | jq '{id, status, baseSmartWallet}'
```

```python Python theme={null}
linked = hevn.post("/clients/link", {"code": code})
client = hevn.acting_as(linked["id"])
ready = poll_until(lambda: client.get("/client"),
                   lambda seen: seen["status"] != "provisioning")
```

```javascript Node theme={null}
const linked = await hevn.post("/clients/link", { code });
const client = hevn.actingAs(linked.id);
const ready = await pollUntil(() => client.get("/client"),
  (seen) => seen.status !== "provisioning");
```

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

```json Response theme={null}
{ "id": "cl_7YQ2Kf3mN8", "status": "ready", "baseSmartWallet": "0x2f1c9d0b8a7e6532d41ac9f0b3e27d5581a4c6f9" }
```

Export the id as `HEVN_CLIENT_ID`. While `status` is `provisioning`, poll `GET /dapi/v1/client` with the id in `X-Hevn-Account` every two seconds.

**6. Fund the wallet**

Call [`POST /dapi/v1/sandbox/deposits`](https://docs.gethevn.com/developers/sandbox#mint-money) with the client id in `X-Hevn-Account`.

```bash cURL theme={null}
curl -X POST "$HEVN_API/sandbox/deposits" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Idempotency-Key: quickstart-deposit-1" \
  -H "Content-Type: application/json" -d '{"amount":"5.00","token":"USDC"}'
```

```python Python theme={null}
credit = client.post("/sandbox/deposits", {"amount": "5.00", "token": "USDC"},
                        idempotency_key="quickstart-deposit-1")
```

```javascript Node theme={null}
const credit = await client.post("/sandbox/deposits",
  { amount: "5.00", token: "USDC" },
  { idempotencyKey: "quickstart-deposit-1" });
```

```go Go theme={null}
credit, err := client.Post("/sandbox/deposits", hevn.Body{"amount": "5.00", "token": "USDC"},
	hevn.IdempotencyKey("quickstart-deposit-1"))
```

`200` means the tokens are in the wallet. On `202`, repeat the request with the same `Idempotency-Key`.

**7. Read the balance**

Call `GET /dapi/v1/client/balance`. Without `X-Hevn-Account` the route returns `422`.

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

```python Python theme={null}
balance = client.get("/client/balance")
```

```javascript Node theme={null}
const balance = await client.get("/client/balance");
```

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

```json Response theme={null}
{ "baseSmartWallet": "0x2f1c…", "accounts": [{ "account": "USDC", "balance": "5.000000" }], … }
```

**8. Book a payout**

Read your wallet address from `GET /dapi/v1/client/balance` with `userId` from step 1 in `X-Hevn-Account`. Save it with `POST /dapi/v1/contacts`, then book `1.00` to it with `POST /dapi/v1/payouts`.

```bash cURL theme={null}
export HEVN_OWN_WALLET=$(curl -s "$HEVN_API/client/balance" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
  -H "X-Hevn-Account: $HEVN_USER_ID" | jq -r .baseSmartWallet)

export HEVN_CONTACT_ID=$(curl -s -X POST "$HEVN_API/contacts" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Content-Type: application/json" \
  -d "{\"name\":\"Treasury\",\"crypto\":{\"chain\":\"base\",\"token\":\"USDC\",\"address\":\"$HEVN_OWN_WALLET\"}}" | jq -r .id)

curl -s -X POST "$HEVN_API/payouts" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  -H "Idempotency-Key: quickstart-payout-1" -H "Content-Type: application/json" \
  -d "{\"contactId\":\"$HEVN_CONTACT_ID\",\"amount\":\"1.00\"}"
```

```python Python theme={null}
own_wallet = hevn.acting_as(user_id).get("/client/balance")["baseSmartWallet"]
contact = client.post("/contacts", {
    "name": "Treasury",
    "crypto": {"chain": "base", "token": "USDC", "address": own_wallet},
})
payout = client.post("/payouts", {"contactId": contact["id"], "amount": "1.00"},
                        idempotency_key="quickstart-payout-1")
```

```javascript Node theme={null}
const { baseSmartWallet: ownWallet } = await hevn.actingAs(userId).get("/client/balance");
const contact = await client.post("/contacts", {
  name: "Treasury",
  crypto: { chain: "base", token: "USDC", address: ownWallet },
});
const payout = await client.post("/payouts", { contactId: contact.id, amount: "1.00" },
  { idempotencyKey: "quickstart-payout-1" });
```

```go Go theme={null}
own, err := api.ActingAs(userID).Get("/client/balance")
contact, err := client.Post("/contacts", hevn.Body{
	"name":   "Treasury",
	"crypto": hevn.Body{"chain": "base", "token": "USDC", "address": own.Str("baseSmartWallet")},
})
payout, err := client.Post("/payouts", hevn.Body{"contactId": contact.Str("id"), "amount": "1.00"},
	hevn.IdempotencyKey("quickstart-payout-1"))
```

```json Response theme={null}
{ "id": "po_6d21…", "status": "awaitingSignature", "authorization": "client", … }
```

`authorization: "client"` means the holder approves this payout. Export the id as `HEVN_PAYOUT_ID`.

A new [`Idempotency-Key`](https://docs.gethevn.com/developers/conventions#idempotency-key) books a second payout.

**9. Approve the payout**

Call `POST /dapi/v1/connect/sessions` with `action: "sign"` and the payout id, then open the URL from the page as in step 4.

```bash cURL theme={null}
curl -s -X POST "$HEVN_API/connect/sessions" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d "{\"action\":\"sign\",\"payoutId\":\"$HEVN_PAYOUT_ID\",\"redirectUri\":\"http://localhost:8080/callback.html\"}" \
  | jq -r .url
```

```python Python theme={null}
session = hevn.post("/connect/sessions", {
    "action": "sign", "payoutId": payout["id"],
    "redirectUri": "http://localhost:8080/callback.html",
})
```

```javascript Node theme={null}
const session = await hevn.post("/connect/sessions", {
  action: "sign", payoutId: payout.id,
  redirectUri: "http://localhost:8080/callback.html",
});
```

```go Go theme={null}
session, err := api.Post("/connect/sessions", hevn.Body{
	"action": "sign", "payoutId": payout.Str("id"),
	"redirectUri": "http://localhost:8080/callback.html",
})
```

Sign in with the second Google account and [approve the payout](https://docs.gethevn.com/connect/sign-in-with-hevn.md#approve-a-payout). The page shows:

```json theme={null}
{ "status": "submitted", "payout": "po_6d21…", … }
```

**10. Read the payout**

Do not act on the redirect. Poll `GET /dapi/v1/payouts/{payoutId}` until the status leaves `awaitingSignature` and `submitted`, then read `GET /dapi/v1/transactions`.

```bash cURL theme={null}
curl -s "$HEVN_API/payouts/$HEVN_PAYOUT_ID" \
  -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
  | jq '{status, transactionHash}'
```

```python Python theme={null}
done = poll_until(lambda: client.get(f"/payouts/{payout['id']}"),
                  lambda seen: seen["status"] not in ("awaitingSignature", "submitted"))
rows = client.get("/transactions", {"limit": 5})["items"]
```

```javascript Node theme={null}
const done = await pollUntil(() => client.get(`/payouts/${payout.id}`),
  (seen) => !["awaitingSignature", "submitted"].includes(seen.status));
const { items: rows } = await client.get("/transactions", { limit: 5 });
```

```go Go theme={null}
done, err := hevn.PollUntil(
	func() (hevn.Payload, error) { return client.Get("/payouts/" + payout.Str("id")) },
	func(seen hevn.Payload) bool { s := seen.Str("status"); return s != "awaitingSignature" && s != "submitted" },
	2*time.Minute,
)
page, err := client.Get("/transactions", hevn.Query{"limit": "5"})
```

```json Response theme={null}
{ "status": "settled", "transactionHash": "0x3a77…" }
```

In the ledger row, `from` and `to` are both `1.000000 USDC`. To see the 1 USDC in your account, repeat step 7 with `$HEVN_USER_ID` in `X-Hevn-Account`.

A transfer to `baseSmartWallet` and a payout to a wallet contact need a `ready` client and no verification. Account details and bank payouts need a verified client and an active [virtual account](https://docs.gethevn.com/developers/virtual-accounts.md). A business verifies through a [KYB application](https://docs.gethevn.com/developers/clients.md#write-the-document), and an individual in a [`verify` session](https://docs.gethevn.com/connect/sign-in-with-hevn#verify-an-individual).

> [card] [Next: Sign in with HEVN](https://docs.gethevn.com/connect/sign-in-with-hevn.md)


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