# AgentKV — capability reference

Encrypted, wallet-authenticated key-value storage for AI agents, paid per request over the x402 protocol. This is the reference for AgentKV's executable operations: parameters, authentication, side effects, and the confirmation rules an agent must follow before calling them.

Companion manifests: [OpenAPI 3.1](https://agentx402.ai/openapi.json) · [MCP descriptor](https://agentx402.ai/.well-known/mcp.json). Site index: [llms.txt](https://agentx402.ai/llms.txt).

> Status: the API base `https://api.agentx402.ai` is **live on Base mainnet** — paid operations settle in real USDC. SDK: `npm i @agentkv/client`; CLI: `npx @agentkv/cli`; MCP: `npx @agentkv/cli mcp` — or call the HTTP API directly.

## Before you call

- **Base URL:** `https://api.agentx402.ai` (configured as `AGENTKV_ENDPOINT` by the client, CLI, and MCP server).
- **Versioning:** the canonical path prefix is **`/v1`** (e.g. `POST /v1/kv/{key}`) — new integrations should target it. The pre-`/v1` paths shown historically (e.g. `POST /kv/{key}`) remain supported as aliases for backward compatibility; each capability below lists both.
- **Network & asset:** payments settle in **USDC on Base**.
- **Identity:** wallet-native by default — an agent is identified by its **wallet** (its namespace), no signup. An opt-in **account-key mode** authenticates with an `Authorization: Bearer ak_…` token instead, for managed wallets that can't sign (see [Account-key mode](#account-key-mode)).
- **Keys:** the key is the last URL path segment and must match `^[A-Za-z0-9._:-]{1,200}$`.
- **Values:** encrypted client-side with **AES-256-GCM** before they are sent; the server stores ciphertext it cannot read. Stored (ciphertext) value ≤ **256 KB**.
- **TTL:** `ttl_days` defaults to 90. `strict_ttl: false` (default) is sliding — a read extends expiry; `strict_ttl: true` is absolute from write time.
- **Discovery:** `set` and `get` (`/v1/kv/{key}`) are listed in the x402 Bazaar directory, so agents that discover services via the Bazaar (rather than reading this doc) can find AgentKV directly.
- **Hints:** a paid `set`/`get` response may include a top-level `hints` array (≤ 3 strings) — one-time onboarding hints on a namespace's first paid use, and once more when cumulative pay-as-you-go spend crosses the prepay-savings threshold. Absent otherwise; never present on credit-funded ops.
<!-- gen:start rate-limit -->
- **Rate limit:** two independent ceilings, both returning HTTP `429` with `Retry-After`.
  - **Per wallet:** roughly 100 burst / 20 per second once the account is funded; an unfunded principal gets 25 burst / 5 per second.
  - **Per IP:** 500 requests per 10 seconds, applied at the edge before routing, so it covers every endpoint. Retry after 2s.
<!-- gen:end rate-limit -->

### Authentication

<!-- gen:start auth-table -->
| Scheme | Used by | How |
|---|---|---|
| **x402** (paid) | `set`, `get`, `deposit`, `accountDeposit` | 402 + `PAYMENT-REQUIRED`; resend with `PAYMENT-SIGNATURE` (v2). |
| **EIP-712 identity** | `delete`, `balance`, `list-keys` (free); `set`, `get` (from prepaid credits) | `X-AgentKV-Signature` / `-Nonce` / `-Timestamp`. |
| **Account-key bearer** (opt-in) | `set`, `get`, `delete`, `balance`, `list-keys`, `accountDeposit` | `Authorization: Bearer ak_…`; debits prepaid credits. On `set`/`get`, may additionally carry an x402 `PAYMENT-SIGNATURE` to settle that op's own price inline (pay-per-op). |
<!-- gen:end auth-table -->

### Pricing (published rates; the 402 is authoritative)

<!-- gen:start pricing-table -->
| Operation | Pay-as-you-go | Prepaid credits (a tenth) |
|---|---|---|
| `set` (write) | $0.005 | $0.0005 (5 credits) |
| `get` (read) | $0.003 | $0.0003 (3 credits) |
| `delete` | Free | — |
| `deposit` | the deposited amount (≥ $1) | — |
| `balance` | Free | — |
| `list-keys` | Free | — |
<!-- gen:end pricing-table -->

1 credit = $0.0001; $1 buys 10,000 credits. Prepaid credits cost a tenth of the pay-per-op price. Full detail: [pricing](https://agentx402.ai/pricing.md).

### Confirmation rule (load-bearing)

<!-- gen:start confirmation-rule -->
**Before executing any STATE-CHANGING operation (`set`, `delete`, `deposit`, `accountDeposit`), an agent MUST obtain explicit user confirmation.**
<!-- gen:end confirmation-rule -->
Read-only operations (`get`, `balance`) do not change stored data, but `get` **spends USDC** — run paid reads only within a user-authorized budget (`maxSpendUsd` / `AGENTKV_MAX_SPEND_USD`), which makes the client refuse any call that would exceed the cap.

---

## set

Encrypt and store a value at a key. **STATE-CHANGING. Paid (x402).** Requires explicit user confirmation.

`POST /v1/kv/{key}` (canonical). Legacy alias: `POST /kv/{key}` (still supported).

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `key` | path | string | yes | Key, `^[A-Za-z0-9._:-]{1,200}$`. |
| `value` | body | string | yes | The value, **already AES-256-GCM encrypted client-side**. Stored ciphertext ≤ 256 KB. |
| `ttl_days` | body | integer | no | Days until expiry (default `90`, must be > 0). |
| `strict_ttl` | body | boolean | no | `true` = absolute expiry; `false` (default) = sliding (reads extend it). |
| `key_name` | body | string | no | Optional client-encrypted opaque key name (≤ 4096 bytes); surfaced by `list-keys` as `(key, key_name)` pairs. |
| `Idempotency-Key` | header | string | no | Reuse across a retry for exactly-once (payment settles before the write). |

- **Auth:** any of — x402 (`PAYMENT-SIGNATURE`) to pay per write; EIP-712 identity headers to spend a credit-funded wallet's **prepaid credits** (no x402); or `Authorization: Bearer ak_…` in account-key mode.
- **Response `200`:** `{ "ok": true, "expires_at": "<ISO-8601>", "usage": { "service": "kv", "op": "write", "price_usd": <number>, "list_price_usd": <number>, "credits_charged": <int>, "cache_hit?": <boolean> }, "hints?": ["<string>", "..."] }`. `usage.price_usd` is the amount actually charged (credit-discounted if paid by credits); `usage.list_price_usd` is the un-discounted pay-per-op rate. `hints` is a one-time onboarding array (see [Before you call](#before-you-call)) — absent otherwise; never present on credit-funded ops. Errors: `400` (`value_too_large`, `invalid_request`), `402` (`payment_required`), `429` (`rate_limited`), `503` (`facilitator_unavailable` — transient facilitator failure, safe to retry the same signed authorization; distinct from the terminal `402`).

## get

Read and decrypt a value. **READ-ONLY. Paid (x402)** — spends USDC per call; run under a spend cap.

`GET /v1/kv/{key}` (canonical). Legacy alias: `GET /kv/{key}` (still supported).

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `key` | path | string | yes | Key to read. |
| `Idempotency-Key` | header | string | no | Reuse across a retry of the same paid read. |

- **Auth:** any of — x402 (`PAYMENT-SIGNATURE`) to pay per read; EIP-712 identity headers to spend a credit-funded wallet's **prepaid credits** (no x402); or `Authorization: Bearer ak_…` in account-key mode.
- **Response `200`:** `{ "value": "<ciphertext>", "expires_at", "strict_ttl", "ttl_days", "ttl_remaining_seconds", "usage": { "service": "kv", "op": "read", "price_usd": <number>, "list_price_usd": <number>, "credits_charged": <int>, "cache_hit?": <boolean> }, "hints?": ["<string>", "..."] }`. `usage.price_usd` is the amount actually charged (credit-discounted if paid by credits); `usage.list_price_usd` is the un-discounted pay-per-op rate. `hints` is a one-time onboarding array (see [Before you call](#before-you-call)) — absent otherwise; never present on credit-funded ops. The `@agentkv/client` decrypts `value` locally. `404` if the key is missing or expired (billed only on a hit). Errors: `402` (`payment_required`), `429` (`rate_limited`), `503` (`facilitator_unavailable` — transient facilitator failure, safe to retry the same signed authorization; distinct from the terminal `402`).

## delete

Remove a key. **STATE-CHANGING. Free.** Requires explicit user confirmation.

`DELETE /v1/kv/{key}` (canonical). Legacy alias: `DELETE /kv/{key}` (still supported).

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `key` | path | string | yes | Key to remove. |

- **Auth:** EIP-712 identity headers (`X-AgentKV-Signature`, `X-AgentKV-Nonce`, `X-AgentKV-Timestamp`).
- **Response `200`:** `{ "ok": true }`.

## deposit

Pre-pay credits (spend at a tenth the pay-per-op price). **STATE-CHANGING. Paid (x402).** Requires explicit user confirmation.

`POST /v1/credits/deposit` (canonical). Legacy alias: `POST /credits/deposit` (still supported).

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `amount` | x402 payment | USD | yes | The amount to deposit, **set by the `PAYMENT-SIGNATURE` authorization** (≥ $1, a whole number of credits). Via the client/MCP this is the `amount_usd` argument. |

- **Auth:** x402 (`PAYMENT-SIGNATURE`); the deposited amount is the payment amount.
- **Response `200`:** `{ "credits_added": <int>, "balance": <int> }`. $1 mints 10,000 credits.

## balance

Read the wallet's credit balance. **READ-ONLY. Free.**

`GET /v1/credits/balance` (canonical; also aliased at the platform-root `GET /v1/balance`). Legacy alias: `GET /credits/balance` (still supported).

- **Parameters:** none.
- **Auth:** EIP-712 identity headers.
- **Response `200`:** `{ "balance": <int> }`. (The current balance is also returned on every response via the `X-AgentKV-Credits-Remaining` header.)

## list-keys

List your keys as `(key, key_name)` pairs. **READ-ONLY. Free.**

`GET /v1/kv` (canonical — **not** `/v1/list-keys`). Legacy alias: `GET /list-keys` (still supported).

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `cursor` | query | string | no | Opaque pagination cursor from the previous page; omit for the first page. |
| `limit` | query | integer | no | Max keys per page (default `100`, max `1000`). |

- **Auth:** EIP-712 identity headers (or `Authorization: Bearer ak_…` in account-key mode).
- **Response `200`:** `{ "items": [{ "key": "<hashed digest>", "key_name": "<ciphertext>|null" }], "cursor": "<next>|null" }`. `key` is an opaque per-wallet digest; `key_name` is the optional client-encrypted name — the `@agentkv/client` decrypts names locally and returns `{ keys, cursor }`. **Values are never returned.**

## Account-key mode

An opt-in identity for a **managed wallet that can't sign** (custodial / MPC), decoupled from the paying wallet — the **payer funds, the bearer owns**.

- The account is an opaque **`ak_<64 lowercase hex>`** bearer token, generated client-side. The server stores only its **SHA-256 hash** (which names the namespace); a lost token is unrecoverable.
- Present it as **`Authorization: Bearer ak_…`** on `set`, `get`, `delete`, `balance`, and `list-keys`. Reads and writes **debit the account's prepaid credits** — no x402 payment, no EIP-712 signature.
- Values need a **local** encryption key in this mode (there is no wallet key to derive one from).
- **Provisioning.** A brand-new `ak_…` has no server-side account until its first payment. On the **paid** routes (`set`/`get`), a bearer op against an unprovisioned namespace now returns a payable **`402 account_not_provisioned`** — distinct from the ordinary out-of-credit `402 insufficient_credits` — carrying an x402 `PAYMENT-REQUIRED` challenge, so a decoupled payer (e.g. awal) can settle it and provision the account in the same call. On the **free** routes (`delete`, `balance`, `list-keys`), an unprovisioned namespace is unchanged: **`401 account_not_found`**. Either way, one explicit deposit (see [accountDeposit](#accountdeposit)) also provisions the account and prepays credits.

### Paying for account-key `set`/`get`

Three ways to cover an account-key read or write — pick one:

1. **Prepaid credits (default).** Fund the namespace with [`accountDeposit`](#accountdeposit); `set`/`get` then debit the balance. When it runs short, the op returns a `402`.
2. **Inline pay-per-op** — CLI `AGENTKV_INLINE=awal` (SDK `opInlinePayer`). A `set`/`get` carries **both** `Authorization: Bearer ak_…` **and** an x402 `PAYMENT-SIGNATURE`: the server serves prepaid credits first and, only if short, settles **that op's own price** on-chain inline — no `accountDeposit`, no maintained balance. Scope is `set`/`get` only (`delete` is free, so it has no inline path). Mutually exclusive per op with auto top-off — if both are configured, **top-off wins**.
3. **Auto top-off** — `AGENTKV_TOPOFF=awal` refills the credit balance from awal via `accountDeposit` (see [accountDeposit](#accountdeposit)).

An insufficient-credits `402` (`insufficient_credits`) on `/kv` (`set`/`get`) **carries the op-price `PAYMENT-REQUIRED` challenge**, so a decoupled payer (e.g. awal) can settle that single op on-chain inline on the bearer's behalf — this is what makes inline pay-per-op and top-off-on-`402` work. Other bearer routes' `402`s still carry **no** challenge (a bearer client has no wallet to pay with). A never-funded namespace's first `set`/`get` gets the distinct `402 account_not_provisioned` challenge instead — also payable, and also carrying the op-price challenge — but the official SDK/CLI **do not** pay it by default: auto-funding an unprovisioned key is indistinguishable from funding a typo'd or rotated one. Opt in with `bootstrap: true` (SDK) / `AGENTKV_BOOTSTRAP=1` (CLI) to let inline pay-per-op or auto top-off cover that first call too — automatic for a key minted by `agentkv account new` and read back from its own `account.json`, explicit for an `AGENTKV_ACCOUNT_KEY` supplied via the environment. A bare x402 client with no such gating (e.g. `awal x402 pay` directly) just pays the challenge and provisions the account.

### accountDeposit

Fund — create-or-top-up — the account-key namespace named by the bearer token. **STATE-CHANGING. Paid (x402).** Requires explicit user confirmation.

`POST /v1/account/deposit` (canonical). Legacy alias: `POST /account/deposit` (still supported).

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| `ak_…` bearer | header | string | yes | `Authorization: Bearer ak_…` — the account/namespace to fund. |
| `amount` | x402 payment | USD | yes | Deposit amount, **set by the `PAYMENT-SIGNATURE` authorization** (≥ $1; $1 = 10,000 credits). |

- **Auth:** **both** an `Authorization: Bearer ak_…` (the namespace) **and** an x402 `PAYMENT-SIGNATURE` (the funding) — any signing wallet can fund an account it holds the token for.
- **Response `200`:** `{ "credits_added": <int>, "balance": <int> }`, with a `PAYMENT-RESPONSE` header. The raw token is never echoed. `401 invalid_account_key` if the bearer is missing or malformed.

**Automating this deposit:** the official SDK/CLI can call this same route on the account's behalf — set `AGENTKV_TOPOFF=awal` and it pays a top-off (default $1, this route's minimum) whenever tracked credits fall below a watermark (default $0.50) or an op hits an `insufficient_credits` `402`, then retries. By default this **maintains an already-funded account only** — it does not fire on the distinct `account_not_provisioned` `402` a fresh namespace's first op returns, so make one explicit `accountDeposit` first, or opt in to `AGENTKV_BOOTSTRAP=1` (see [Account-key mode](#account-key-mode)) to let it cover that first call too. This is client behavior only; the route, its parameters, and its auth are unchanged.

## Local-only: `wallet_address`

The MCP server exposes one tool beyond the operations above: **`wallet_address`** — a read-only, free, *local* convenience that returns the agent's wallet address, derived from the configured key. It makes no API call and no payment, so it has no HTTP operation here; it appears only in the [MCP descriptor](https://agentx402.ai/.well-known/mcp.json).
