— API · AUTHENTICATION

API Keys

Optional Bearer credentials for agents. Issue once with a SIWE (EIP-4361) personal_sign message (action issue_api_key); use t402_live_ prefix on subsequent calls.

SCOPES
5
read · balance:read · endpoints:write · pay · payout:request
KEY FORMAT
t402_live_
shown once at issue
AUTH
SIWE
EIP-4361 personal_sign · Base 8453

Overview

Wallet actions use SIWE (EIP-4361): POST /v1/auth/challenge returns a plain-text message and nonceId; sign with personal_sign on Base (chainId 8453); POST the action route with {wallet, signature, message, nonceId}. Key params live in the signed message Resources URNs — never in a side channel. List and revoke also work via GET/DELETE /v1/keys with Bearer scopes or legacy stats signatures.

POST/v1/auth/key

Issue API key

SIWE action issue_api_key — two-step challenge flow; no Bearer

Error codes
400 invalid_input401 invalid_signature410 nonce_consumed429 rate_limited

Flow

1 — Challengeapplication/json
POST /v1/auth/challenge
Content-Type: application/json

{
  "action": "issue_api_key",
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "params": {
    "keyName": "agent-prod",
    "scopes": "read,pay",
    "validityDays": 30,
    "dailyCapUsdcMicro": 1000000
  }
}
Challenge responseapplication/json
{
  "nonceId": "0xa1b2c3…",
  "message": "tools402.dev wants you to sign…",
  "issuedAt": "2026-06-08T08:30:00.000Z"
}
2 — Sign + submitapplication/json
POST /v1/auth/key
Content-Type: application/json

{
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "signature": "0x…",
  "message": "tools402.dev wants you to sign…",
  "nonceId": "0x…"
}
Responseapplication/json
HTTP/1.1 201 Created
Content-Type: application/json

{
  "apiKey": "t402_live_aB3xK9mN2pQ7rS4tU8vW1yZ5cD6fG0hJ",
  "keyId": 42,
  "walletAddress": "0xd6e8af2f65b4c9acc7bf14a3096056e89e312878",
  "name": "agent-prod",
  "scopes": [
    "read",
    "pay"
  ],
  "createdAtMs": 1717842600000,
  "expiresAtMs": 1720434600000,
  "dailyCapUsdcMicro": 1000000
}

SIWE message format (issue_api_key)

Plain-text EIP-4361 message returned by POST /v1/auth/challenge — sign with personal_sign (not signTypedData). Params are encoded in Resources URNs.

tools402.dev wants you to sign in with your Ethereum account:
0xd6e8af2f65b4c9acc7bf14a3096056e89e312878

Authorize tools402 to issue a new API key on your behalf.
This signature is off-chain and moves no funds.

URI: https://tools402.dev
Version: 1
Chain ID: 8453
Nonce: a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef1234
Issued At: 2026-06-08T08:30:00.000Z
Resources:
- urn:tools402:keyName:agent-prod
- urn:tools402:scopes:read,pay
- urn:tools402:validityDays:30
- urn:tools402:dailyCapUsdcMicro:1000000
IMPLEMENTATION
curl -X POST "https://api.tools402.dev/v1/auth/challenge" \
  -H "Content-Type: application/json" \
  -d '{
  "action": "issue_api_key",
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "params": {
    "keyName": "agent-prod",
    "scopes": "read,pay",
    "validityDays": 30,
    "dailyCapUsdcMicro": 1000000
  }
}'

# personal_sign the returned message (EIP-4361 plain text), then:
curl -X POST "https://api.tools402.dev/v1/auth/key" \
  -H "Content-Type: application/json" \
  -d '{
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "signature": "0xSIG",
  "message": "SIWE_MESSAGE_FROM_CHALLENGE",
  "nonceId": "0xNONCE_FROM_CHALLENGE"
}'
POST/v1/auth/keys

List API keys (SIWE)

SIWE action list_api_keys — alternative to GET /v1/keys Bearer

Error codes
401 invalid_signature410 nonce_consumed

Flow

1 — Challengeapplication/json
POST /v1/auth/challenge
Content-Type: application/json

{
  "action": "list_api_keys",
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "params": {}
}
Challenge responseapplication/json
{
  "nonceId": "0xa1b2c3…",
  "message": "tools402.dev wants you to sign…",
  "issuedAt": "2026-06-08T08:30:00.000Z"
}
2 — Sign + submitapplication/json
POST /v1/auth/keys
Content-Type: application/json

{
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "signature": "0x…",
  "message": "tools402.dev wants you to sign…",
  "nonceId": "0x…"
}
Responseapplication/json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "keys": [
    {
      "id": 42,
      "prefix": "t402_live_aB3x",
      "name": "agent-prod",
      "scopes": [
        "read",
        "pay"
      ],
      "revokedAtMs": null
    }
  ]
}
IMPLEMENTATION
curl -X POST "https://api.tools402.dev/v1/auth/challenge" \
  -H "Content-Type: application/json" \
  -d '{
  "action": "list_api_keys",
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "params": {}
}'

# personal_sign the returned message (EIP-4361 plain text), then:
curl -X POST "https://api.tools402.dev/v1/auth/keys" \
  -H "Content-Type: application/json" \
  -d '{
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "signature": "0xSIG",
  "message": "SIWE_MESSAGE_FROM_CHALLENGE",
  "nonceId": "0xNONCE_FROM_CHALLENGE"
}'
POST/v1/auth/keys/:id/revoke

Revoke API key (SIWE)

SIWE action revoke_api_key — alternative to DELETE /v1/keys/:id Bearer

Error codes
401 invalid_signature404 key_not_found410 nonce_consumed

Flow

1 — Challengeapplication/json
POST /v1/auth/challenge
Content-Type: application/json

{
  "action": "revoke_api_key",
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "params": {
    "keyId": 42
  }
}
Challenge responseapplication/json
{
  "nonceId": "0xa1b2c3…",
  "message": "tools402.dev wants you to sign…",
  "issuedAt": "2026-06-08T08:30:00.000Z"
}
2 — Sign + submitapplication/json
POST /v1/auth/keys/:id/revoke
Content-Type: application/json

{
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "signature": "0x…",
  "message": "tools402.dev wants you to sign…",
  "nonceId": "0x…"
}
Responseapplication/json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "keyId": 42
}
IMPLEMENTATION
curl -X POST "https://api.tools402.dev/v1/auth/challenge" \
  -H "Content-Type: application/json" \
  -d '{
  "action": "revoke_api_key",
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "params": {
    "keyId": 42
  }
}'

# personal_sign the returned message (EIP-4361 plain text), then:
curl -X POST "https://api.tools402.dev/v1/auth/keys/42/revoke" \
  -H "Content-Type: application/json" \
  -d '{
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "signature": "0xSIG",
  "message": "SIWE_MESSAGE_FROM_CHALLENGE",
  "nonceId": "0xNONCE_FROM_CHALLENGE"
}'
POST/v1/auth/keys/:id/configure

Configure key

SIWE action configure_api_key — partial update of name, daily cap, and/or chain preference. At least one field required. No Bearer.

Error codes
400 invalid_input401 invalid_signature404 key_not_found409 key_revoked

Flow

1 — Challengeapplication/json
POST /v1/auth/challenge
Content-Type: application/json

{
  "action": "configure_api_key",
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "params": {
    "keyId": 42,
    "name": "agent-prod"
  }
}
Challenge responseapplication/json
{
  "nonceId": "0xa1b2c3…",
  "message": "tools402.dev wants you to sign…",
  "issuedAt": "2026-06-08T08:30:00.000Z"
}
2 — Sign + submitapplication/json
POST /v1/auth/keys/:id/configure
Content-Type: application/json

{
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "signature": "0x…",
  "message": "tools402.dev wants you to sign…",
  "nonceId": "0x…",
  "name": "agent-prod"
}
Responseapplication/json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "keyId": 42,
  "name": "agent-prod",
  "dailyCapUsdcMicro": 1000000,
  "chainPreference": "base"
}

Optional body fields: name (string), dailyCapUsdcMicro (integer, 0 = unlimited), chainPreference (base · ethereum · any). Send only fields you change; body must match the signed SIWE resources.

IMPLEMENTATION
curl -X POST "https://api.tools402.dev/v1/auth/challenge" \
  -H "Content-Type: application/json" \
  -d '{
  "action": "configure_api_key",
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "params": {
    "keyId": 42,
    "name": "agent-prod"
  }
}'

# personal_sign the returned message (EIP-4361 plain text), then:
curl -X POST "https://api.tools402.dev/v1/auth/keys/42/configure" \
  -H "Content-Type: application/json" \
  -d '{
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "signature": "0xSIG",
  "message": "SIWE_MESSAGE_FROM_CHALLENGE",
  "nonceId": "0xNONCE_FROM_CHALLENGE",
  "name": "agent-prod"
}'
POST/v1/auth/usage

Query usage (SIWE)

SIWE action query_usage — wallet-signed usage report

Error codes
401 invalid_signature410 nonce_consumed

Flow

1 — Challengeapplication/json
POST /v1/auth/challenge
Content-Type: application/json

{
  "action": "query_usage",
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "params": {
    "fromDay": "2026-06-01",
    "toDay": "2026-06-08",
    "keyId": 42
  }
}
Challenge responseapplication/json
{
  "nonceId": "0xa1b2c3…",
  "message": "tools402.dev wants you to sign…",
  "issuedAt": "2026-06-08T08:30:00.000Z"
}
2 — Sign + submitapplication/json
POST /v1/auth/usage
Content-Type: application/json

{
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "signature": "0x…",
  "message": "tools402.dev wants you to sign…",
  "nonceId": "0x…"
}
Responseapplication/json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "wallet": "0xd6e8af2f65b4c9acc7bf14a3096056e89e312878",
  "fromDay": "2026-06-01",
  "toDay": "2026-06-08",
  "totalUsdcMicro": 250000,
  "rows": []
}
IMPLEMENTATION
curl -X POST "https://api.tools402.dev/v1/auth/challenge" \
  -H "Content-Type: application/json" \
  -d '{
  "action": "query_usage",
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "params": {
    "fromDay": "2026-06-01",
    "toDay": "2026-06-08",
    "keyId": 42
  }
}'

# personal_sign the returned message (EIP-4361 plain text), then:
curl -X POST "https://api.tools402.dev/v1/auth/usage" \
  -H "Content-Type: application/json" \
  -d '{
  "wallet": "0xD6E8aF2F65B4C9ACC7BF14A3096056e89E312878",
  "signature": "0xSIG",
  "message": "SIWE_MESSAGE_FROM_CHALLENGE",
  "nonceId": "0xNONCE_FROM_CHALLENGE"
}'
GET/v1/keys

List API key prefixes (Bearer)

Bearer scope read, or ?wallet=&sig=&ts= stats signature

Error codes
400 invalid_wallet401 invalid_api_key401 invalid_signature401 invalid_timestamp401 unauthorized403 insufficient_scope

Request → Response

Requestapplication/json
GET /v1/keys
Authorization: Bearer t402_live_…

# or wallet signature:
GET /v1/keys?wallet=0xD6E8…2878&sig=0x…&ts=1717000000
Responseapplication/json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true
}
IMPLEMENTATION
curl -X GET "https://api.tools402.dev/v1/keys" \
  -H "Authorization: Bearer t402_live_…"
# or wallet signature: ?wallet=0x…&sig=0x…&ts=1717000000
DELETE/v1/keys/:id

Revoke own API key (Bearer)

Bearer with all 5 scopes, or wallet signature headers/query

Error codes
400 invalid_id400 invalid_wallet401 invalid_api_key401 invalid_signature401 invalid_timestamp401 unauthorized403 insufficient_scope404 key_not_found

Request → Response

Requestapplication/json
DELETE /v1/keys/:id
Authorization: Bearer t402_live_…

# or X-Wallet / X-Signature / X-Timestamp headers
Responseapplication/json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true
}
IMPLEMENTATION
curl -X DELETE "https://api.tools402.dev/v1/keys/42" \
  -H "Authorization: Bearer t402_live_…"

API key scopes

Five scopes from openapi.json ApiKeyScope enum.

ScopeDescription
readList endpoints, stats, and API key prefixes for the wallet.
balance:readRead unified account balance via GET /v1/account.
endpoints:writePublish, update, or soft-delete seller endpoints.
payBuyer identity scope — x402 X-Payment / X-Session-Token proof still required per paid call.
payout:requestTrigger payout toward the registered payout address only (never a caller-supplied destination).