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

# Remote transfers

> Let a program send USDC from your HEVN account with a key capped by an onchain spend permission.

A remote transfer key lets a program send USDC from your HEVN account (a profile plus a Base smart wallet) with no approval step. Create the key in the app, then call `GET /get_balance` to confirm the key and its allowance.

> [!WARNING]
>
> A leaked key can spend up to its allowance. Grant the smallest allowance that covers the job. For everyday payments use the app or the HEVN assistant, which files transfer requests that you approve.

## Model

| Piece | Meaning |
| - | - |
| Remote transfer key | A secret that starts with `rsl_`, shown once at creation. HEVN stores its SHA-256 hash. Keys that start with `mcp_` still work. |
| Spender wallet | A Coinbase Smart Wallet, one per key, returned as `walletAddress`. Its owners are the `SpendPermissionManager` contract and a signer that HEVN derives from the key. HEVN predicts the address at creation. The wallet deploys with the key's first transfer. |
| Spend permission | An onchain allowance per period that your smart wallet signs for the spender wallet. See [Spend permissions](https://docs.gethevn.com/general/security-model.md#shared-access). |
| Transfer | One sponsored user operation from the spender wallet. `SpendPermissionManager.spend` pulls the amount from your wallet, then a USDC `transfer` sends it to the receiver. You pay no gas. |

Without an active spend permission, each transfer returns `403`. The key does not use your wallet's signing key, which stays in [Privy](https://docs.gethevn.com/general/security-model.md#keys-and-privy).

A key has no scopes. It can call every remote transfer route.

## Create a key

1. Open **Settings → Remote Transfers** in [the app](https://app.gethevn.com) and click **Create remote transfer key**.
2. Enter a name and a spending limit (an amount and a period).
3. Confirm. HEVN issues the key, and your wallet signs a spend permission for the key's spender wallet with the limit you set.
4. Copy the key. HEVN cannot show it again.

Keep the key in a secret store on the machine that uses it, out of repositories and logs. HEVN notifies you when a key is created.

## Change the limit

Open the key in the app and update its spend permission. Your wallet signs a new permission that replaces the old one.

## Revoke a key

Delete the key in the app. The app revokes the spend permission onchain, then deletes the key. The deleted key stops authenticating at once.

Keys do not expire. A deleted key cannot be restored, so create a new one.

If a key leaks, delete it in the app and create a new key with a new allowance. Until the key is deleted, spending is bounded by the allowance left in the current period and by `maxSpend` if set.

## Call the routes

Remote transfer routes take a remote transfer key in `X-Api-Key` and return camelCase JSON. Amounts are decimal USDC on Base. Paths are relative to `HEVN_RT`.

```bash theme={null}
export HEVN_RT="https://api.hevn.finance/api/v1/remote_spend_limit"
export HEVN_RT_KEY="rsl_…"   # from Settings → Remote Transfers, shown once
```

The prefix `/api/v1/mcp` serves the same routes and is deprecated.

## Get balance

`GET /get_balance` returns your wallet's USDC balance and the key's allowance left in the current period, both read from the chain.

```bash theme={null}
curl "$HEVN_RT/get_balance" -H "X-Api-Key: $HEVN_RT_KEY"
```

```json theme={null}
{
  "email": "ops@example.com",
  "address": "0x4E1d…a07B",
  "balance": 1250.5,
  "remaining": 400.0
}
```

`email` is the key's account and `address` is your Base smart wallet. `remaining` is `0` when the key has no active spend permission. An account with no smart wallet returns `404 Smart wallet not found`.

## Resolve address

`GET /resolve_address?email=` returns the Base smart wallet address for an email address.

```bash theme={null}
curl "$HEVN_RT/resolve_address?email=vendor@example.com" -H "X-Api-Key: $HEVN_RT_KEY"
```

```json theme={null}
{ "email": "vendor@example.com", "address": "0x9b2C…44De" }
```

If the email has no HEVN account, HEVN creates one and records its wallet address, which the email's owner controls. See [deterministic addresses](https://docs.gethevn.com/general/what-is-hevn.md#deterministic-addresses).

## Transfer

`POST /transfer` pays a contact, an invoice or a payout quote. Send one of `contactId`, `invoiceId` or `quoteId`.

| Field | Type | Meaning |
| - | - | - |
| `contactId` | UUID | Your contact. Pays `amount` to its saved wallet address, or to the wallet of its HEVN account |
| `amount` | number | USDC to send with `contactId` |
| `invoiceId` | UUID | An invoice where you are the client. Pays the invoice amount to the contractor's HEVN wallet |
| `quoteId` | string | A bank or cross-chain payout quote created in the app. Pays the quote amount to its one-time address |
| `memo` | string | Optional note recorded on the transaction |

A request with none of the three ids returns `422`. If several are sent, `invoiceId` takes precedence over `quoteId`, and `quoteId` over `contactId`. `Idempotency-Key` is optional. Send it on each contact transfer.

```bash Contact theme={null}
curl -X POST "$HEVN_RT/transfer" \
  -H "X-Api-Key: $HEVN_RT_KEY" \
  -H "Idempotency-Key: payroll-2026-10-alice" \
  -H "Content-Type: application/json" \
  -d '{"contactId": "0c8e5f0a-3a6b-4d1e-9b7c-5e2f1a9d8c34", "amount": 250, "memo": "October retainer"}'
```

```bash Invoice theme={null}
curl -X POST "$HEVN_RT/transfer" \
  -H "X-Api-Key: $HEVN_RT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"invoiceId": "b7a1d3e2-6c4f-4a8b-8e1d-0f9c2b5a7d63"}'
```

```bash Quote theme={null}
curl -X POST "$HEVN_RT/transfer" \
  -H "X-Api-Key: $HEVN_RT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"quoteId": "q_8f2a…"}'
```

```json theme={null}
{ "txHash": "0x5d9e…c1a2", "userCreated": false, "emailSent": false }
```

`txHash` is the onchain transaction. `userCreated` and `emailSent` are `false` on this route.

| Status | `detail` | Cause |
| - | - | - |
| 400 | `amount is required for contact transfers` | `contactId` without `amount` |
| 400 | `Contact has no wallet address` | The contact has no wallet address and no HEVN account |
| 400 | `Invoice has no contractor email` | The invoice has no contractor to pay |
| 400 | `Quote has no deposit address` | The quote is not ready to fund |
| 400 | `Quote is funded with <token>, not USDC` | The quote expects another token |
| 403 | `Invoice does not belong to this account` | You are not the invoice's client |
| 404 | `Contact not found`, `Invoice not found`, `Quote not found` | The id does not exist or is not yours |
| 409 | `This payout can no longer be funded. Request a new quote.` | The quote expired or changed state |

## Transfer to email

`POST /transfer_to_user` pays an email address. If the email has no HEVN account, HEVN creates one, pays its wallet and emails an invitation to claim it. An existing account gets a payment email.

`amount` (number, USDC), `email` and the `Idempotency-Key` header are required. Without the header the call returns `422`. `memo` is accepted and not recorded.

```bash theme={null}
curl -X POST "$HEVN_RT/transfer_to_user" \
  -H "X-Api-Key: $HEVN_RT_KEY" \
  -H "Idempotency-Key: bounty-1042" \
  -H "Content-Type: application/json" \
  -d '{"amount": 75, "email": "contributor@example.com"}'
```

```json theme={null}
{ "txHash": "0x3a7f…e90b", "userCreated": true, "emailSent": true }
```

`userCreated` is `true` when the transfer created the receiving account. `emailSent` is `true` when the invitation or payment email went out. Each new transfer saves the receiver as a contact.

## A transfer runs at most once

Each transfer uses a nonce derived from a payment id, and the spender wallet accepts a nonce once. The payment id is `<keyId>_<reference>`. The reference is the first of these that the request has:

1. `invoiceId`
2. `quoteId`
3. the `Idempotency-Key` header

| Route | Repeat with the same payment id |
| - | - |
| `POST /transfer` | `409 This transfer was already sent: …`. Do not retry |
| `POST /transfer_to_user` | The recorded `txHash`, with `userCreated` and `emailSent` `false`. No money moves |

The reference is scoped to the key, so paying the same invoice through two keys sends two transfers.

> [!WARNING]
>
> A contact transfer without `Idempotency-Key` gets a random reference and is not deduplicated. A retry pays the contact again. Send the header on each contact transfer.

## List transfers

`GET /transfers` lists the account's `POST /transfer_to_user` transfers, newest first. `POST /transfer` payments appear in the app's transaction history.

| Query | Meaning |
| - | - |
| `limit` | 1 to 200, default `50` |
| `offset` | Rows to skip, default `0` |
| `idempotency_key` | Return one transfer by its stored id, `<keyId>_<Idempotency-Key>` |

```bash theme={null}
curl "$HEVN_RT/transfers?limit=20" -H "X-Api-Key: $HEVN_RT_KEY"
```

```json theme={null}
[
  {
    "txHash": "0x3a7f…e90b",
    "amount": 75.0,
    "receiverEmail": "contributor@example.com",
    "receiverAddress": "0x9b2C…44De",
    "transactionLink": "https://basescan.org/tx/0x3a7f…e90b",
    "time": "2026-10-12T10:02:11Z"
  }
]
```

`receiverEmail` contains the contact's saved name. The name is the receiver's full name if the account has one, and the email address otherwise.

## Key routes

The key routes take your app session token in `Authorization: Bearer` in place of a remote transfer key. A key created with the API has no spend permission. Grant it in the app, as in [Change the limit](#change-the-limit).

| Route | Result |
| - | - |
| `POST /api_key` | Creates a key. The only call that returns `key`. Returns `403` in a session where an integrator acts for your account |
| `GET /api_key` | Your keys, newest first, without `key` and with `keyPreview` |
| `DELETE /api_key/{key_id}` | Deletes the key and returns `{"ok": true}`, or `404 API key not found` |

`DELETE /api_key/{key_id}` leaves the spend permission onchain. The deleted key stops authenticating at once. Revoke the permission in the app as well, as in [Revoke a key](#revoke-a-key).

```bash theme={null}
curl -X POST "https://api.hevn.finance/api/v1/remote_spend_limit/api_key" \
  -H "Authorization: Bearer $HEVN_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Payroll bot",
    "ipRestriction": "203.0.113.10,203.0.113.11",
    "maxSpend": 5000
  }'
```

| Field | Type | Meaning |
| - | - | - |
| `name` | string, required | Label shown in the app |
| `ipRestriction` | string | Comma-separated exact IP addresses, no CIDR ranges. Omit to allow any address. Not offered in the app |
| `maxSpend` | number | Lifetime USDC cap. Omit for no cap. Not offered in the app |

```json theme={null}
{
  "id": "6f1c2a7e-5b0d-4e8a-9a51-2f7d0c3b9e14",
  "name": "Payroll bot",
  "key": "rsl_Xk2…",
  "walletAddress": "0x7A3b…91cE",
  "ipRestriction": "203.0.113.10,203.0.113.11",
  "maxSpend": 5000.0,
  "totalSpend": 0.0,
  "remaining": 0,
  "lastUse": null,
  "createdAt": "2026-10-12T09:30:00Z"
}
```

| Field | Meaning |
| - | - |
| `totalSpend` | USDC the key has sent, counted against `maxSpend` |
| `remaining` | Allowance left after the key's last transfer, in USDC base units (6 decimals). `0` until the first transfer. [`GET /get_balance`](#get-balance) returns the current figure |
| `lastUse` | Time of the key's last transfer |
| `keyPreview` | List only. The first eight and last four characters of the key, such as `rsl_Xk2a...9fQe` |

```bash theme={null}
curl "https://api.hevn.finance/api/v1/remote_spend_limit/api_key" \
  -H "Authorization: Bearer $HEVN_SESSION_TOKEN"

curl -X DELETE "https://api.hevn.finance/api/v1/remote_spend_limit/api_key/$KEY_ID" \
  -H "Authorization: Bearer $HEVN_SESSION_TOKEN"
```

## Limits

| Limit | Enforced by | Refusal | Next |
| - | - | - | - |
| Spend permission allowance | `SpendPermissionManager`, onchain | `400 Insufficient spend allowance: …` | Wait for the period to reset, or raise the limit in the app |
| `maxSpend`, optional | HEVN, as a lifetime total per key | `403 Spend limit exceeded for this API key` | Create a new key |
| IP allowlist, optional | HEVN, on each request | `403 IP not allowed` | Call from an address on the key's IP allowlist |
| Rate limit | HEVN, 500 requests per hour per key across `/transfer`, `/transfer_to_user` and `/resolve_address` | `429 Too many requests. Try again later.` | Wait `Retry-After` seconds |

Set `maxSpend` and the IP allowlist (`ipRestriction`) with [`POST /api_key`](#key-routes).

## Errors

An error response has one field, `detail`. Branch on the status code and log `detail`.

```json theme={null}
{ "detail": "Insufficient spend allowance: remaining 12.500000 USDC, need 50 USDC" }
```

Route-specific errors are listed under [Transfer](#transfer), and limit refusals under [Limits](#limits).

| Status | `detail` | Cause and next step |
| - | - | - |
| 400 | `Smart wallet not found` | The account has no smart wallet |
| 400 | `API key has no spender wallet. …`, `API key spender wallet does not match the key. …` | The key predates spender wallets. Create a new key |
| 400 | `Transfer failed: …` | The user operation was rejected. Retry with the same reference (`invoiceId`, `quoteId` or `Idempotency-Key`) |
| 401 | `Invalid API key` | The key is wrong or was deleted |
| 403 | `No active spend permission for this API key. …` | Set a spending limit for the key in the app |
| 409 | `This transfer was already sent: …` | The payment id was used. Do not retry |
| 422 | A list of validation errors | A required field, header or `X-Api-Key` is missing, or a value has the wrong type |


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