Set up
Wallets and agent keys
Each agent gets a wallet and an ed25519 keypair. The wallet holds the public key and the agent holds the private key. How to create both, and how to look after them.
One wallet per agent
A wallet holds money and belongs to your workspace. An agent wallet also holds the public key that its payment proofs are checked against. Give each agent its own wallet:
- a compromised or misbehaving agent can only spend what its own wallet holds;
- purchase history and receipts stay separate per agent;
- the MCP packages can find the wallet from your key automatically, but only when the workspace has exactly one agent wallet. With several, you name the one to use.
Create one in the console
Buyer → Agents → Create agent. Name the agent, pick how you'll fund it (card or USDC), and optionally set an approval threshold and require approval for unverified sellers — those two rules are added to your workspace policy, so they apply to every agent. The console generates the keypair, provisions the agent's wallet, and shows the private key once, to copy or download. Neither Zanora nor the console keeps a copy, so store it where the agent will run.
Create one with the API
Generate a keypair
openssl genpkey -algorithm ed25519 -out agent-key.pem
chmod 600 agent-key.pem
openssl pkey -in agent-key.pem -pubout # the public half, for the next stepimport { generateEd25519KeyPair } from "@zanora/sdk";
const { publicKeyPem, privateKeyPem } = generateEd25519KeyPair();
// Store privateKeyPem somewhere only the agent can read.node -e 'const {generateKeyPairSync}=require("node:crypto");
const {publicKey,privateKey}=generateKeyPairSync("ed25519");
require("node:fs").writeFileSync("agent-key.pem",privateKey.export({type:"pkcs8",format:"pem"}),{mode:0o600});
console.log(publicKey.export({type:"spki",format:"pem"}))'Register the wallet
curl -s -X POST https://api.zanora.dev/v1/wallets -H "x-api-key: $WKEY" \
-H 'content-type: application/json' \
-d '{"ownerType":"agent","ownerId":"invoice-bot","publicKeyPem":"-----BEGIN PUBLIC KEY-----\n…\n-----END PUBLIC KEY-----\n"}'ownerId is your label for the agent. It appears in payment proofs, so use something plain like invoice-bot. The response is the wallet, including its id (wal_…).
Looking after the private key
It's the spending authority, and it can't be recovered
Whoever has agent-key.pem can spend that wallet's balance, within your policies. Zanora never had the private key, so a lost key can't be reissued, only replaced (below). Treat the key like a wallet seed phrase.
- Keep it in a file with mode
0600, or in your secrets manager. In MCP configs, point to it withZANORA_AGENT_KEY_FILErather than pasting it in, because config files get shared and appear in screen-shares. - Only fund a wallet with what you'd accept losing to that agent.
- If a key leaks: freeze the wallet (
POST /v1/wallets/:id/freeze), then replace its key.
Replacing a lost or leaked key
In the console, open the agent (Buyer → Agents, then click its name) and click Replace key. With the API, POST /v1/wallets/:id/key with a new public key. The wallet keeps its balance, history, policies and agent name.
- The old key stops working immediately.
- The new key can't pay for 24 hours. Purchases in that window are refused with
WALLET_KEY_COOLING_DOWN, and nothing is charged. - The platform's operators are alerted.
The delay and the alert exist because replacing the key is exactly what someone with a stolen workspace API key would do to take over the wallet. The window is your chance to notice a replacement you didn't make: revoke the leaked API key, then replace the key again.
openssl genpkey -algorithm ed25519 -out agent-key.pem && chmod 600 agent-key.pem
openssl pkey -in agent-key.pem -pubout -out agent-key.pub
curl -s -X POST https://api.zanora.dev/v1/wallets/$WAL/key -H "x-api-key: $WKEY" \
-H 'content-type: application/json' \
-d "$(jq -n --rawfile pem agent-key.pub '{publicKeyPem: $pem}')"
# → { "id": "wal_…", "keyReplacedAt": "…", "keyUsableAt": "…", … }Send only the public key. A private key in the body is refused, but by then it has left your machine, so treat it as leaked and generate another.
Reading a wallet
In the console, Buyer → Agents lists each agent. Click one to open its page: balance and available balance, spend today, calls in the last 24 hours, approvals waiting, its agent and wallet ids, a fingerprint of its registered public key, whether it can pay right now, and its recent purchases.
| Call | Returns |
|---|---|
GET /v1/wallets | every wallet in your workspace |
GET /v1/wallets/:id/balance | { balanceMicros, availableMicros, currency }. Available excludes money on hold |
GET /v1/wallets/:id/ledger | every entry, oldest first: funding, charges, refunds |
POST /v1/wallets/:id/freeze / unfreeze | stop or allow outgoing payments |
POST /v1/wallets/:id/key | replace the agent's signing key (see above) |
Next: fund the wallet.