title: Wallets description: "Create, claim, configure, freeze, rename, and rotate keys."
Base URL: https://api.cardzero.ai/v1
Create wallet
POST /walletsNo authentication. Rate-limited at 50/hour per IP.
Body:
{
"name": "My Agent",
"version": "v3"
}
| Field | Type | Required | Default |
| --- | --- | --- | --- |
| name | string | no | auto-generated |
| version | "v2" | "v3" | no | "v2" |
Response 201:
{
"id": "wallet_7370ee785775",
"chainAddress": "0xa1f2…70D0",
"claimKey": "czk_a1b2c3d4e5f6g7h8_…",
"name": "My Agent",
"status": "pending",
"version": "v3"
}
The wallet is lazy-deployed: address is real (CREATE2-deterministic), but no contract on-chain yet. Owner claims to deploy.
Claim wallet
POST /auth/claimNo authentication (claim key serves as proof). Rate-limited at 10/hour per IP.
New user (creates account):
{
"claimKey": "czk_a1b2c3d4_…",
"username": "myname",
"password": "MyStrongPass123!"
}
Existing user (attaches wallet to logged-in account):
{
"claimKey": "czk_a1b2c3d4_…"
}
(Send with Authorization: Bearer <jwt> header.)
Response 201:
{
"token": "eyJ…",
"userId": "user_…",
"agentConfig": {
"apiKey": "czapi_a1b2c3d4_…",
"walletId": "wallet_7370ee785775",
"summary": "== CardZero Wallet Summary ==\n…"
}
}
The summary is a multi-line block the Owner can paste back to the agent.
List wallets
GET /walletsJWT required.
Response 200:
{
"wallets": [
{
"id": "wallet_…",
"chain_address": "0x…",
"name": "My Agent",
"status": "active",
"frozen": 0,
"tx_limit": "1.0",
"daily_limit": "5.0",
"expires_at": null,
"wallet_version": "v3",
"created_at": 1715000000
}
]
}
Get wallet
GET /wallets/:idJWT + ownership.
Returns the same shape as the list endpoint.
Get balance
GET /wallets/:id/balanceJWT or API Key.
Response 200:
{
"walletId": "wallet_…",
"balance": "10.50",
"currency": "USDC"
}
Update spending rules
PATCH /wallets/:id/configJWT + ownership.
Body (all fields optional):
{
"txLimit": "1.0",
"dailyLimit": "5.0",
"whitelist": ["0x1234…", "0x5678…"],
"expiresAt": 1735689600
}
Pass "0" to remove a limit. Pass [] to clear the whitelist.
Rename
PATCH /wallets/:id/nameJWT + ownership.
{ "name": "New name" }
Limits: 1–50 chars.
Freeze / unfreeze
POST /wallets/:id/freezePOST /wallets/:id/unfreezeJWT + ownership. Both block until on-chain confirmed (~1–2s).
When frozen, all payments + Job operations revert with WALLET_FROZEN.
Get / rotate API key
GET /wallets/:id/api-keyPOST /wallets/:id/api-key/rotateJWT + ownership. GET returns the current plaintext key (decrypts from DB).
POST /rotate invalidates the current key and returns a new one.
{
"apiKey": "czapi_…",
"walletId": "wallet_…"
}
Get / rotate webhook secret
GET /wallets/:id/webhook-secretPOST /wallets/:id/webhook-secret/rotateJWT + ownership. The webhook secret is per-wallet; used as HMAC-SHA256 key for outgoing webhook signatures.
{
"webhookSecret": "whsec_…",
"walletId": "wallet_…"
}
See Verify webhooks for how to use it.
Errors
| Code | HTTP | When |
| --- | --- | --- |
| INVALID_VERSION | 400 | version not "v2" or "v3" |
| V3_NOT_CONFIGURED | 503 | V3 factory address missing in server env |
| WALLET_NOT_FOUND | 404 | bad wallet ID |
| FORBIDDEN | 403 | wallet ID doesn't belong to JWT user |
| WALLET_NOT_ACTIVE | 400 | wallet is in "pending" status (not claimed yet) |
| INVALID_NAME | 400 | name length out of [1,50] |
| RATE_LIMITED | 429 | exceeded walletCreateLimiter |