Skip to main content
By the end of this guide you will have a running Key0 Docker container that:
  • Accepts USDC payments from AI agents
  • Either calls your ISSUE_TOKEN_API to fetch credentials (subscription plans), or proxies to your backend and returns data directly (pay-per-call routes)
  • Automatically refunds agents if your token endpoint fails or the backend returns an error
  • Exposes GET /discover, POST /x402/access, and the generated buyer-onboarding bundle
New to Key0? Read Core Concepts first to understand terms like Plan, Challenge, AccessGrant, and EIP-3009. See Two Modes to understand how Standalone differs from Embedded.
Run Key0 as a standalone service in Docker. It handles agent discovery, payment verification, and credential issuance — your existing API only needs to expose a single token-issuing endpoint. By default, standalone also generates buyer-facing onboarding endpoints from your config:
  • GET /.well-known/agent.json when A2A is enabled
  • GET /.well-known/mcp.json and POST /mcp when MCP is enabled
  • GET /llms.txt
  • GET /skills.md
To distribute a CLI binary to your users: install the SDK, call buildCli(), and upload the output to your preferred host. See the Agent CLI guide for step-by-step instructions.

Option A: Setup UI (zero-config start)

Start the container with no configuration. The Setup UI walks you through everything visually.
1

Start the container

The full profile starts Key0 with managed Redis and Postgres — batteries included.Download the Docker Compose file:
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/key0ai/key0/refs/heads/main/docker/docker-compose.yml
Then start the services:
docker compose --profile full up
2

Open the Setup UI

Navigate to http://localhost:3000. On first launch, you are redirected to /setup.The Setup UI lets you configure:
  • Wallet address — the USDC-receiving address on Base
  • Network — mainnet or Base Sepolia testnet
  • Pricing plans — plan IDs, amounts, and descriptions
  • Token issuance API — the URL Key0 calls after payment to fetch credentials
  • Settlement and refund settings
3

Submit and go

When you submit the form, the server writes the configuration and restarts automatically. Configuration is persisted in a Docker volume (key0-config), so it survives container restarts.

Docker Compose profiles

Choose a profile based on what managed infrastructure you need:
ProfileWhat starts
(none)Key0 only — bring your own Redis + Postgres via env vars
--profile redisKey0 + managed Redis
--profile postgresKey0 + managed Postgres (still needs Redis externally)
--profile fullKey0 + managed Redis + managed Postgres (batteries included)
Managed infrastructure is auto-detected at startup. No connection strings to configure when using profiles.

ISSUE_TOKEN_API contract

After Key0 verifies an on-chain USDC payment, it POSTs to your ISSUE_TOKEN_API endpoint with the payment details:
Request body
{
  "requestId": "uuid",
  "challengeId": "uuid",
  "resourceId": "photo-42",
  "planId": "basic",
  "txHash": "0x...",
  "unitAmount": "$0.10"
}
Your endpoint returns any credential shape you need. Key0 forwards the response directly to the agent.
{
  "token": "eyJ...",
  "expiresAt": "2025-01-01T00:00:00Z",
  "tokenType": "Bearer"
}
If your ISSUE_TOKEN_API endpoint returns an error or is unreachable, Key0 automatically initiates an on-chain refund to the paying agent. See Automatic Refunds for details.

Pay-Per-Call Routes (PROXY_TO_BASE_URL)

For top-level routes, Key0 settles the payment and then proxies the request to your backend — no ISSUE_TOKEN_API call is made for these route purchases. The agent receives a ResourceResponse containing the backend’s actual response data.
┌──────────────┐     ┌──────────────────────┐     ┌──────────────────┐
│ Client Agent │     │    key0 (Docker)      │     │  Your Backend    │
│              │────▶│  GET /api/...         │     │                  │
│              │◀────│  402 + requirements   │     │                  │
│              │     │  402 + requirements   │     │                  │
│              │     │                       │     │                  │
│              │     │  verify on-chain      │     │                  │
│              │────▶│  GET /api/... +       │     │                  │
│              │     │  PAYMENT-SIGNATURE    │     │                  │
│              │     │  proxy request ───────│────▶│  GET /api/...    │
│              │◀────│  ResourceResponse     │◀────│  200 {data}      │
└──────────────┘     └──────────────────────┘     └──────────────────┘
Set PROXY_TO_BASE_URL to your backend’s base URL and add pay-per-call routes to ROUTES:
docker run \
  -e KEY0_WALLET_ADDRESS=0xYourWallet \
  -e PROXY_TO_BASE_URL=https://api.yourdomain.com \
  -e ROUTES='[{"routeId":"weather-query","method":"GET","path":"/api/weather/:city","unitAmount":"$0.01","description":"Current weather — $0.01 per call"}]' \
  -p 3000:3000 \
  key0ai/key0:latest
When PROXY_TO_BASE_URL is set:
  • Pay-per-call routes are handled by the proxy — agents call the route directly, Key0 settles and forwards to PROXY_TO_BASE_URL + path.
  • Subscription plans still call ISSUE_TOKEN_API as normal.
  • Routes and plans can coexist in the same config.
Key0 injects payment metadata headers on every proxied request so your backend can log or verify the payment without a round-trip:
HeaderValue
x-key0-tx-hashOn-chain transaction hash
x-key0-plan-idPlan that was charged
x-key0-amountDollar amount (e.g. "$0.01")
x-key0-payerPayer wallet address (when available)
ISSUE_TOKEN_API is not called for pay-per-call routes when PROXY_TO_BASE_URL is set. You can omit it entirely if all you are selling is route access.

Test it

Once the container is running, verify the setup:
Check the agent card
curl http://localhost:3000/.well-known/agent.json
Expected output
{
  "name": "My Service",
  "description": "Payment-gated API",
  "url": "http://localhost:3000",
  "skills": [{ "id": "basic", "name": "Basic plan" }]
}
Browse available plans
curl http://localhost:3000/discover
Expected output
{
  "agentName": "My Service",
  "description": "Payment-gated API",
  "plans": [
    { "planId": "basic", "unitAmount": "$0.10", "description": "Basic plan" }
  ],
  "routes": []
}
Request access to a plan (returns 402 challenge)
curl -X POST http://localhost:3000/x402/access \
  -H "Content-Type: application/json" \
  -d '{"planId": "basic"}'
Expected output (HTTP 402)
{
  "x402Version": 2,
  "accepts": [{ "amount": "100000", "payTo": "0xYourWallet...", "network": "eip155:84532" }],
  "challengeId": "http-a1b2c3d4-...",
  "error": "Payment required"
}
The discovery call returns all available plans — start here to find a valid planId. The access request returns an x402 challenge with the payment amount and destination wallet. An agent completes the flow by signing an EIP-3009 authorization and submitting proof. See Paying for Access for the full client walkthrough. If you have pay-per-call routes configured (with PROXY_TO_BASE_URL), test the route flow by calling the route directly:
Request a paid route (returns 402 challenge)
curl http://localhost:3000/api/weather/london
Expected output (HTTP 402 — same as subscription)
{
  "x402Version": 2,
  "accepts": [{ "amount": "10000", "payTo": "0xYourWallet...", "network": "eip155:84532" }],
  "challengeId": "http-a1b2c3d4-...",
  "error": "Payment required"
}
After payment, the server returns a ResourceResponse with your backend’s data instead of an AccessGrant. See Paying for Access — Per-Request Flow for the full client walkthrough.

Docker image tags

TagWhen to use
latestLatest stable release
1.2.3 / 1.2 / 1Specific version pinning for production
canaryLatest main branch build (for testing)

Next steps

Environment Variables

Full reference of all configuration options including PROXY_TO_BASE_URL.

Pay-Per-Request Standalone Example

Runnable example: Key0 as a payment gateway proxying to a backend API.

Storage Configuration

Redis, Postgres, and custom storage backends.

Automatic Refunds

How Key0 handles failed deliveries with on-chain refunds.