Keys and accounts

# Child keys and moving funds

A root key (or a passkey account) mints child keys: ordinary `gk_` keys with their own balance. Money moves between root and children by transfer, so the total never changes.

**On this page**

- [Rules](https://inference.dagon.so/docs/child-keys#rules)
- [Endpoints](https://inference.dagon.so/docs/child-keys#endpoints)
- [Example](https://inference.dagon.so/docs/child-keys#example)

## Rules

- Every `gk_` key without a parent is a root. A child cannot create children.
- A root holds at most 100 live children and may create 30 per hour.
- Funding moves money from the root to the child; reclaiming moves the child's *available* balance (funded minus spent minus held by requests in flight) back. Money held by a request in flight never moves.
- The root is the account: a child stops working when its root is deleted, disabled or suspended.
- Children of one root are linkable to each other. Use separate roots when keys must stay unlinkable.

## Endpoints

All need `Authorization: Bearer <root key>` or a passkey session; a child key gets `403 forbidden`. Amounts are `amount_usd` in whole cents (`400 invalid_amount` otherwise); more than is available is `402 insufficient_funds`; an unknown child id is `404 key_not_found`.

| Request | Result |
| --- | --- |
| `POST /v1/keys/children` `{name?, amount_usd?, expires_at?}` | `201 {key, data, root}`; `key` is shown once. `expires_at` is ISO 8601 or epoch milliseconds. |
| `GET /v1/keys/children` | The live children with balances, never secrets. |
| `POST /v1/keys/children/:id/fund` `{amount_usd}` | `{data, root, transferred_micros}`; `409 key_expired` for an expired child. |
| `POST /v1/keys/children/:id/reclaim` `{amount_usd}` or `{all: true}` | Same shape. |
| `DELETE /v1/keys/children/:id` | `{deleted: true, reclaimed_micros, root}`; repeating it reclaims 0. |

Transfers are limited to 600 per hour per root.

## Example

**curl**

```curl
# Mint a child with $5 that expires in a day
curl https://inference.dagon.so/v1/keys/children \
  -H "Authorization: Bearer $DAGON_ROOT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "ci runner", "amount_usd": 5, "expires_at": "2030-01-01T00:00:00Z"}'

# Take back whatever it has not spent
curl https://inference.dagon.so/v1/keys/children/$CHILD_ID/reclaim \
  -H "Authorization: Bearer $DAGON_ROOT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"all": true}'
```

**Python**

```python
import os, requests

H = {"Authorization": f"Bearer {os.environ['DAGON_ROOT_KEY']}"}
child = requests.post("https://inference.dagon.so/v1/keys/children", headers=H,
                      json={"name": "ci runner", "amount_usd": 5}).json()
print(child["key"])  # shown once
requests.post(f"https://inference.dagon.so/v1/keys/children/{child['data']['id']}/reclaim", headers=H, json={"all": True})
```

**TypeScript**

```ts
const H = { authorization: `Bearer ${process.env.DAGON_ROOT_KEY}`, "content-type": "application/json" };
const child = await (await fetch("https://inference.dagon.so/v1/keys/children", {
  method: "POST", headers: H, body: JSON.stringify({ name: "ci runner", amount_usd: 5 }),
})).json();
console.log(child.key); // shown once
await fetch(`https://inference.dagon.so/v1/keys/children/${child.data.id}/reclaim`, { method: "POST", headers: H, body: JSON.stringify({ all: true }) });
```

Last updated 2026-11-07

[Previous **Authentication**](https://inference.dagon.so/docs/authentication) [Next **Passkey accounts**](https://inference.dagon.so/docs/passkeys)
