> ## Documentation Index
> Fetch the complete documentation index at: https://hevninc.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> HEVN has two products and they authorize money differently. 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 backend holds a developer key, signs on behalf of client accounts it created, and HEVN co-signs. Never mix the two models in an answer.
> 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 an account the integrator created; 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. The CLI uses an app API key in X-Api-Key. They are different credentials and neither works on the other surface.

# Quickstart

> In the sandbox: log in with a developer key, create a client company, fund its wallet, and move its money on chain.

In about twenty minutes you will have a client company that holds 6 testnet USDC and has sent 5 of
them out of its own wallet, signed by your key. Nine steps, all of them in the sandbox.

## Before you start

* **Create a sandbox account in a browser.** Sign up at
  [`https://sandbox.gethevn.com`](https://sandbox.gethevn.com) with your work email. Sandbox and
  production are separate accounts; nothing you do here touches real money.
* **Create a developer key, in the same browser.** Open **Settings → Partner program → Developer keys** and create one.
  You set two things that can never be changed afterwards: the **IP allowlist**, which for this
  quickstart is the egress address of the machine you will run it from, and the **scopes**, which for
  this quickstart are `payout:sign` and `recipient:write`. Every later call is re-checked against the
  allowlist — a request from an address you did not list is a `403` however correct the key and the
  signature are — and a route whose scope the key lacks is a `403` with `details.requiredScope`.
* **Save the private key into your environment.** The browser shows it once. Write it to
  `$HEVN_KEY_PEM` with mode `0600`; HEVN never holds it and cannot show it again. Walk-through:
  [Developer key](/whitelabel/developer-key).
* **Set the environment and install two dependencies.** Everything after that is code.

| Environment | API base URL                               | App, in a browser             | 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         |

Every example on these pages reads the same 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, never in your repo
export HEVN_CLIENT_ID="cl_7YQ2Kf3mN8"                # the client a call acts for
```

Three more come out of the first two: `HEVN_ACCESS_TOKEN`, the one-hour token
[logging in](/whitelabel/sessions) returns; `HEVN_USER_ID`, your own account's `cl_…` id, which the
same response returns as `userId` and which the `/client*` routes need in `X-Hevn-Account` when you
read your own account; and `HEVN_PUBLIC_KEY`, the base64 public half of the PEM, which
`POST /dapi/v1/payouts` takes as `publicKey`.

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

Nothing crosses between the two environments: developer keys, clients, tokens and rail ids are
per-environment, and a sandbox token is refused in production.

<CodeGroup>
  ```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.
  ```
</CodeGroup>

<Note>
  The sandbox is the same API against one emulated partner on Base Sepolia. Its money comes from a shared faucet account: at most **100 tokens per credit**, at least `0.10`, and **60 money calls per account per hour**. `GET /dapi/v1/sandbox/treasury` reports the live ceiling. Full table in [Limits](/whitelabel/reference/limits#sandbox).
</Note>

## Nine steps

<Steps>
  <Step title="Log in">
    Two calls: `POST /dapi/v1/auth/challenge` returns a payload built by HEVN, and
    `POST /dapi/v1/auth/token` exchanges your signature over it for a session. The helpers this uses —
    `load_key`, `public_key_b64`, `sign_bytes`, `sign_payload` — are on
    [Developer key](/whitelabel/developer-key) and [Signing](/whitelabel/signing).

    <CodeGroup>
      ```python Python theme={null}
      import os, secrets, time, httpx


      def login(key) -> dict:
          api, email = os.environ["HEVN_API"], os.environ["HEVN_EMAIL"]
          nonce = secrets.randbits(48)
          expiry = int(time.time() * 1000) + 120_000
          proof = f"hevn-developer-key-login:{email}:{nonce}:{expiry}".encode()

          started = httpx.post(f"{api}/auth/challenge", json={
              "email": email,
              "publicKey": public_key_b64(key),
              "nonce": nonce,
              "requestExpiry": expiry,
              "signature": sign_bytes(key, proof),
          })
          started.raise_for_status()
          challenge = started.json()

          minted = httpx.post(f"{api}/auth/token", json={
              "challengeId": challenge["challengeId"],
              "signature": sign_payload(key, challenge["payload"]),
          })
          minted.raise_for_status()
          return minted.json()  # accessToken, refreshToken, expiresIn, userId
      ```

      ```javascript Node theme={null}
      export async function login(key) {
        const api = process.env.HEVN_API;
        const email = process.env.HEVN_EMAIL;
        const nonce = Number(process.hrtime.bigint() % 281474976710656n);
        const expiry = Date.now() + 120_000;
        const proof = Buffer.from(`hevn-developer-key-login:${email}:${nonce}:${expiry}`);

        const post = async (path, body) => {
          const response = await fetch(`${api}${path}`, {
            method: "POST",
            headers: { "content-type": "application/json" },
            body: JSON.stringify(body),
          });
          if (!response.ok) throw new Error(`${path} ${response.status} ${await response.text()}`);
          return response.json();
        };

        const challenge = await post("/auth/challenge", {
          email,
          publicKey: publicKeyB64(key),
          nonce,
          requestExpiry: expiry,
          signature: signBytes(key, proof),
        });

        return post("/auth/token", {
          challengeId: challenge.challengeId,
          signature: signPayload(key, challenge.payload),
        });
      }
      ```

      ```go Go theme={null}
      func Login(key *ecdsa.PrivateKey) (Tokens, error) {
      	api, email := os.Getenv("HEVN_API"), os.Getenv("HEVN_EMAIL")
      	nonce := rand.Int63n(1 << 48)
      	expiry := time.Now().Add(2 * time.Minute).UnixMilli()

      	proof := fmt.Sprintf("hevn-developer-key-login:%s:%d:%d", email, nonce, expiry)
      	proofSignature, err := SignBytes(key, []byte(proof))
      	if err != nil {
      		return Tokens{}, err
      	}
      	publicKey, err := PublicKeyB64(key)
      	if err != nil {
      		return Tokens{}, err
      	}

      	var challenge struct{ ChallengeID, Payload string }
      	if err := PostJSON(api+"/auth/challenge", map[string]any{
      		"email": email, "publicKey": publicKey, "nonce": nonce,
      		"requestExpiry": expiry, "signature": proofSignature,
      	}, &challenge); err != nil {
      		return Tokens{}, err
      	}

      	payloadSignature, err := SignPayload(key, challenge.Payload)
      	if err != nil {
      		return Tokens{}, err
      	}
      	var tokens Tokens
      	err = PostJSON(api+"/auth/token", map[string]any{
      		"challengeId": challenge.ChallengeID, "signature": payloadSignature,
      	}, &tokens)
      	return tokens, err
      }
      ```
    </CodeGroup>

    ```json Response theme={null}
    {
      "accessToken": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9…",
      "refreshToken": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9…",
      "expiresIn": 3600,
      "userId": "cl_9f2c1ab84d7e4f1fa3c65b0e7d9a2c41"
    }
    ```

    Keep both: `export HEVN_ACCESS_TOKEN=…` and `export HEVN_USER_ID=cl_9f2c…`.
  </Step>

  <Step title="Check that your account may create clients">
    Being an integrator is not something the API grants — HEVN switches it on for your account, in
    the sandbox as in production. The cheapest confirmation is a list that answers instead of
    refusing. `GET /clients` is the collection you own, so it is the one call on this page that must
    **not** carry `X-Hevn-Account`; sending it is `400 account_scope_conflict`.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -s "$HEVN_API/clients" \
        -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" | jq
      ```

      ```python Python theme={null}
      mine = hevn.get("/clients")["items"]
      ```

      ```javascript Node theme={null}
      const { items: mine } = await hevn.get("/clients");
      ```

      ```go Go theme={null}
      page, err := api.Get("/clients")
      mine := page.List("items")
      ```
    </CodeGroup>

    ```json Response theme={null}
    { "items": [] }
    ```

    The `hevn` / `api` client, `key` and `poll_until` in every tab below are
    [the reference client](/whitelabel/reference/client) — copy it once and the rest of this page
    is three lines a step.

    An empty page means you are an integrator with no clients yet — carry on. If the next step
    answers `403 integrator_inactive` or `403 client_creation_not_enabled`, the flag is not on
    your account yet; ask HEVN before you write any more code. See
    [Going live](/whitelabel/going-live#what-hevn-must-enable).
  </Step>

  <Step title="Create a client and wait for it to be ready">
    `phone` is optional here and required later by every bank rail, so send it now.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST "$HEVN_API/clients" \
        -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "Content-Type: application/json" \
        -d '{"name":"Northwind Trading Ltd","email":"ops@northwind.example","phone":"+13125550142"}'
      ```

      ```python Python theme={null}
      client = hevn.post("/clients", {
          "name": "Northwind Trading Ltd", "email": "ops@northwind.example",
          "phone": "+13125550142",
      })
      ```

      ```javascript Node theme={null}
      const client = await hevn.post("/clients", {
        name: "Northwind Trading Ltd", email: "ops@northwind.example",
        phone: "+13125550142",
      });
      ```

      ```go Go theme={null}
      client, err := api.Post("/clients", hevn.Body{
      	"name": "Northwind Trading Ltd", "email": "ops@northwind.example",
      	"phone": "+13125550142",
      })
      ```
    </CodeGroup>

    The `201` answers `status: "provisioning"`, an id you can already read, and a `pollUrl` of
    `/dapi/v1/client` — a path that names no account by itself. The id goes in `X-Hevn-Account`
    instead, which is how every read of one client works. Poll it every two seconds; it becomes
    `ready` in about thirty seconds, when the client's wallet exists.

    <CodeGroup>
      ```bash cURL theme={null}
      export HEVN_CLIENT_ID=cl_7YQ2Kf3mN8
      curl -s "$HEVN_API/client" \
        -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" \
        -H "X-Hevn-Account: $HEVN_CLIENT_ID" | jq '{status, baseSmartWallet}'
      ```

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

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

      ```go Go theme={null}
      clientID := client.Str("id")
      northwind := api.ActingAs(clientID)
      ready, err := hevn.PollUntil(
      	func() (hevn.Payload, error) { return northwind.Get("/client") },
      	func(seen hevn.Payload) bool { return seen.Str("status") != "provisioning" },
      	2*time.Minute,
      )
      ```
    </CodeGroup>

    ```json Response theme={null}
    { "status": "ready", "baseSmartWallet": "0x2f1c9d0b8a7e6532d41ac9f0b3e27d5581a4c6f9" }
    ```
  </Step>

  <Step title="Fund the client's wallet">
    The sandbox mints emulated money. Without `bankId` this is a token transfer straight to the
    client's wallet; the `X-Hevn-Account` header that `northwind` carries is what makes the call act
    for the client.

    <CodeGroup>
      ```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":"6.00","token":"USDC"}'
      ```

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

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

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

    A `200` means the tokens landed; a `202` means they are on their way and the same request,
    with the same key, is safe to repeat.
  </Step>

  <Step title="Read the balance you just created">
    `/client/balance` names no client in its path either: the header picks the account, and leaving
    it out is a `422` for a missing header rather than a read of your own wallet.

    <CodeGroup>
      ```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 = northwind.get("/client/balance")
      ```

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

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

    ```json Response theme={null}
    {
      "clientId": "cl_7YQ2Kf3mN8",
      "baseSmartWallet": "0x2f1c9d0b8a7e6532d41ac9f0b3e27d5581a4c6f9",
      "accounts": [{ "account": "USDC", "balance": "6.000000", "usdValue": "6.00" }],
      "totalUsd": "6.00"
    }
    ```

    That balance is read from Base Sepolia, not from a HEVN table.
  </Step>

  <Step title="Save a contact">
    Pay the money to an address you control: your own account's wallet. Read it with the same
    balance route, acting as yourself — the `userId` from step 1 goes in `X-Hevn-Account`, because
    that header is the only thing that selects an account on `/client*`.

    <CodeGroup>
      ```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)

      curl -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\"}}"
      ```

      ```python Python theme={null}
      own_wallet = hevn.acting_as(user_id).get("/client/balance")["baseSmartWallet"]
      contact = northwind.post("/contacts", {
          "name": "Treasury",
          "crypto": {"chain": "base", "token": "USDC", "address": own_wallet},
      })
      ```

      ```javascript Node theme={null}
      const { baseSmartWallet: ownWallet } = await hevn.actingAs(userId).get("/client/balance");
      const contact = await northwind.post("/contacts", {
        name: "Treasury",
        crypto: { chain: "base", token: "USDC", address: ownWallet },
      });
      ```

      ```go Go theme={null}
      own, err := api.ActingAs(userID).Get("/client/balance")
      ownWallet := own.Str("baseSmartWallet")
      contact, err := northwind.Post("/contacts", hevn.Body{
      	"name":   "Treasury",
      	"crypto": hevn.Body{"chain": "base", "token": "USDC", "address": ownWallet},
      })
      ```
    </CodeGroup>

    Keep the `ct_…` id from the response: `export HEVN_CONTACT_ID=ct_3K9…`. `user_id` is the
    `userId` login returned in step 1.
  </Step>

  <Step title="Prepare the payout">
    `publicKey` says which of your keys will sign this payment.

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

      curl -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\":\"5.00\",\"publicKey\":\"$HEVN_PUBLIC_KEY\"}"
      ```

      ```python Python theme={null}
      payout = northwind.post("/payouts", {
          "contactId": contact["id"], "amount": "5.00", "publicKey": key.public_key,
      }, idempotency_key="quickstart-payout-1")
      ```

      ```javascript Node theme={null}
      const payout = await northwind.post("/payouts", {
        contactId: contact.id, amount: "5.00", publicKey: key.publicKey,
      }, { idempotencyKey: "quickstart-payout-1" });
      ```

      ```go Go theme={null}
      payout, err := northwind.Post("/payouts", hevn.Body{
      	"contactId": contact.Str("id"), "amount": "5.00", "publicKey": key.PublicKey,
      }, hevn.IdempotencyKey("quickstart-payout-1"))
      ```
    </CodeGroup>

    ```json Response theme={null}
    {
      "id": "po_6d21…",
      "kind": "onchain",
      "status": "awaitingSignature",
      "approval": { "id": "…", "payload": "eyJib2R5Ijp…", "expiresAt": "2026-09-17T10:06:10Z" },
      "debit": { "address": "0x8f3bd1c7a0e94f25b6d1c83a5e07f4b219ad6e80", "amount": "5.00",
                 "amountAtomic": "5000000", "token": "USDC", "chainId": 84532 },
      "contact": { "contactId": "ct_3K9…", "name": "Treasury",
                   "address": "0x8f3bd1c7a0e94f25b6d1c83a5e07f4b219ad6e80",
                   "chainId": "base", "token": "USDC" },
      "idempotencyKey": "quickstart-payout-1"
    }
    ```

    A contact that is a wallet address makes this an on-chain payout — `kind` says `onchain`, there is
    no conversion, and `quote` carries no rate and no fee. `contact` is the snapshot of the saved
    destination, fixed when the payment was booked. `debit` is what will actually leave the wallet,
    decoded from the operation you are about to sign, and for a wallet contact its `address` is that
    destination. `approval.payload` is the bytes to sign, live for 120 seconds.

    `quickstart-payout-1` names this payment. Re-run this step with the same key and you replay
    the payout you already have, at the same price; change the key and you send twice.
  </Step>

  <Step title="Check the debit, sign it, confirm">
    Check the terms against what you meant to send, then sign the decoded payload and confirm.
    Nothing here re-prices anything: `payout` is the record step 7 returned.

    <CodeGroup>
      ```python Python theme={null}
      import base64
      from cryptography.hazmat.primitives import hashes
      from cryptography.hazmat.primitives.asymmetric import ec


      def sign_bytes(key, message: bytes) -> str:
          """base64(DER ECDSA P-256 / SHA-256) over exactly these bytes."""
          return base64.b64encode(key.sign(message, ec.ECDSA(hashes.SHA256()))).decode()


      def sign_payload(key, payload_b64: str) -> str:
          return sign_bytes(key, base64.b64decode(payload_b64))
      ```

      ```javascript Node theme={null}
      import { sign } from "node:crypto";

      export function signBytes(key, message) {
        return sign("sha256", message, { key, dsaEncoding: "der" }).toString("base64");
      }

      export function signPayload(key, payloadB64) {
        return signBytes(key, Buffer.from(payloadB64, "base64"));
      }
      ```

      ```go Go theme={null}
      func SignBytes(key *ecdsa.PrivateKey, message []byte) (string, error) {
      	digest := sha256.Sum256(message)
      	der, err := ecdsa.SignASN1(rand.Reader, key, digest[:])
      	if err != nil {
      		return "", err
      	}
      	return base64.StdEncoding.EncodeToString(der), nil
      }

      func SignPayload(key *ecdsa.PrivateKey, payloadB64 string) (string, error) {
      	message, err := base64.StdEncoding.DecodeString(payloadB64)
      	if err != nil {
      		return "", err
      	}
      	return SignBytes(key, message)
      }
      ```
    </CodeGroup>

    <CodeGroup>
      ```python Python theme={null}
      debit, expected = payout["debit"], own_wallet
      if (debit["amount"], debit["token"], debit["address"].lower()) != ("5.00", "USDC", expected.lower()):
          raise RuntimeError(f"refusing to sign {debit}")

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

      ```javascript Node theme={null}
      const { amount, token, address } = payout.debit;
      const expected = ownWallet.toLowerCase();
      if (amount !== "5.00" || token !== "USDC" || address.toLowerCase() !== expected) {
        throw new Error(`refusing to sign ${amount} ${token} to ${address}`);
      }

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

      ```go Go theme={null}
      expected := strings.ToLower(ownWallet)
      if payout.Str("debit.amount") != "5.00" || payout.Str("debit.token") != "USDC" ||
      	strings.ToLower(payout.Str("debit.address")) != expected {
      	return fmt.Errorf("refusing to sign %v", payout["debit"])
      }

      signature, err := key.SignPayload(payout.Str("approval.payload"))
      receipt, err := northwind.Post("/payouts/"+payout.Str("id")+"/confirm",
      	hevn.Body{"signature": signature})
      ```
    </CodeGroup>

    A `200` carries `status: "settled"` and the `transactionHash`. A `202` carries
    `status: "submitted"`: the operation is on its way and has no receipt yet, so confirm again with
    the same body until you get a `200`. Confirm is always safe to repeat and never pays twice.
  </Step>

  <Step title="Read the ledger">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -s "$HEVN_API/transactions?limit=5" \
        -H "Authorization: Bearer $HEVN_ACCESS_TOKEN" -H "X-Hevn-Account: $HEVN_CLIENT_ID" \
        | jq '.items[] | {id, type, status, isIncome, from, to, txHash}'
      ```

      ```python Python theme={null}
      rows = northwind.get("/transactions", {"limit": 5})["items"]
      ```

      ```javascript Node theme={null}
      const { items: rows } = await northwind.get("/transactions", { limit: 5 });
      ```

      ```go Go theme={null}
      page, err := northwind.Get("/transactions", hevn.Query{"limit": "5"})
      rows := page.List("items")
      ```
    </CodeGroup>

    A row carries its amounts as two sides — `from` is what left the source, `to` is what arrived —
    rather than a single `amount`. On this payout both are `5.000000 USDC`.

    The 5 USDC that left in step 8 is there with its `txHash`, and the same 5 USDC are now in your
    own account's balance — read it with the balance call from step 5, with `$HEVN_USER_ID` in
    `X-Hevn-Account`.
  </Step>
</Steps>

## What just happened

* **Your key, not a password.** Login was a signature over a payload HEVN built, and the payout was
  a second signature over an operation HEVN built. Neither exposes a secret you could leak twice.
  See [Signing](/whitelabel/signing).
* **The client is a real account.** It has its own wallet, its own ledger and its own legal identity —
  you act for it with one header. See [Accounts and control](/whitelabel/accounts-and-control).
* **The money is real testnet money.** It moved on Base Sepolia; `transactionHash` is on chain.
* **Every constant came out of an earlier response.** Client id, wallet address, contact id, approval
  payload — nothing was invented.
* **One key was yours to choose:** the `Idempotency-Key`. It names the payment, so a retry is the
  same payment. See [Conventions](/whitelabel/conventions#idempotency-key).

## What this quickstart did not do

It never touched fiat. Getting the client its own account details, and paying a bank account with a currency
conversion, needs a client the partner has approved: the KYB document filled and its rail open. That is the next
piece of work — [Onboard a client](/whitelabel/onboarding), then
[Virtual accounts](/whitelabel/virtual-accounts) and [Payouts](/whitelabel/payouts).

<Card title="Next: Accounts and control" icon="shield" href="/whitelabel/accounts-and-control">
  Who can move a client's money, and what happens if your key leaks.
</Card>
