> ## Documentation Index
> Fetch the complete documentation index at: https://docs.key0.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Standalone Docker

> Run Key0 as a standalone Docker container alongside your existing API. Two options: a visual Setup UI for zero-config start, or environment variables for automation.

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

<Note>
  New to Key0? Read [Core Concepts](/introduction/core-concepts) first to understand terms like Plan, Challenge, AccessGrant, and EIP-3009. See [Two Modes](/introduction/two-modes) to understand how Standalone differs from Embedded.
</Note>

<Frame>
  <iframe width="560" height="315" src="https://www.youtube.com/embed/pEio0ora9GI?si=sM7hzPh58iqAOQwj" title="Key0 Docker Tutorial" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen />
</Frame>

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](/guides/agent-cli) guide for step-by-step instructions.

<Tabs>
  <Tab title="Setup UI">
    ## Option A: Setup UI (zero-config start)

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

    <Steps>
      <Step title="Start the container">
        The `full` profile starts Key0 with managed Redis and Postgres -- batteries included.

        Download the Docker Compose file:

        ```bash theme={null}
        curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/key0ai/key0/refs/heads/main/docker/docker-compose.yml
        ```

        Then start the services:

        ```bash theme={null}
        docker compose --profile full up
        ```
      </Step>

      <Step title="Open the Setup UI">
        Navigate to [http://localhost:3000](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**
      </Step>

      <Step title="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.
      </Step>
    </Steps>

    ### Docker Compose profiles

    Choose a profile based on what managed infrastructure you need:

    | Profile              | What starts                                                  |
    | -------------------- | ------------------------------------------------------------ |
    | *(none)*             | Key0 only -- bring your own Redis + Postgres via env vars    |
    | `--profile redis`    | Key0 + managed Redis                                         |
    | `--profile postgres` | Key0 + managed Postgres (still needs Redis externally)       |
    | `--profile full`     | Key0 + managed Redis + managed Postgres (batteries included) |

    Managed infrastructure is auto-detected at startup. No connection strings to configure when using profiles.
  </Tab>

  <Tab title="Environment Variables">
    ## Option B: Environment variables

    Pass configuration directly for scripted or CI/CD deployments.

    <Steps>
      <Step title="Set the two required variables">
        Key0 needs two things to start:

        * `KEY0_WALLET_ADDRESS` -- your USDC-receiving wallet address (0x...)
        * `ISSUE_TOKEN_API` -- the URL that Key0 POSTs to after payment is verified

        ```bash theme={null}
        docker run \
          -e KEY0_WALLET_ADDRESS=0xYourWallet \
          -e ISSUE_TOKEN_API=https://api.example.com/issue-token \
          -p 3000:3000 \
          key0ai/key0:latest
        ```
      </Step>

      <Step title="Or use Docker Compose">
        For production, Docker Compose manages Redis alongside Key0:

        ```bash theme={null}
        cp docker/.env.example docker/.env
        ```

        Edit `docker/.env` and set `KEY0_WALLET_ADDRESS` and `ISSUE_TOKEN_API`, then start:

        ```bash theme={null}
        docker compose -f docker/docker-compose.yml --profile redis up
        ```
      </Step>
    </Steps>

    <Info>
      Even with environment variables pre-configured, the Setup UI is always available at `/setup` for reconfiguration.
    </Info>
  </Tab>
</Tabs>

## ISSUE\_TOKEN\_API contract

After Key0 verifies an on-chain USDC payment, it POSTs to your `ISSUE_TOKEN_API` endpoint with the payment details:

```json Request body theme={null}
{
  "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.

<CodeGroup>
  ```json JWT credential theme={null}
  {
    "token": "eyJ...",
    "expiresAt": "2025-01-01T00:00:00Z",
    "tokenType": "Bearer"
  }
  ```

  ```json API key credential theme={null}
  {
    "apiKey": "sk-123",
    "apiSecret": "secret",
    "expiresAt": "2025-06-01T00:00:00Z"
  }
  ```
</CodeGroup>

<Note>
  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](/architecture/refunds) for details.
</Note>

## 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`:

```bash theme={null}
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:

| Header           | Value                                 |
| ---------------- | ------------------------------------- |
| `x-key0-tx-hash` | On-chain transaction hash             |
| `x-key0-plan-id` | Plan that was charged                 |
| `x-key0-amount`  | Dollar amount (e.g. `"$0.01"`)        |
| `x-key0-payer`   | Payer wallet address (when available) |

<Note>
  `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.
</Note>

***

## Test it

Once the container is running, verify the setup:

```bash Check the agent card theme={null}
curl http://localhost:3000/.well-known/agent.json
```

```json Expected output theme={null}
{
  "name": "My Service",
  "description": "Payment-gated API",
  "url": "http://localhost:3000",
  "skills": [{ "id": "basic", "name": "Basic plan" }]
}
```

```bash Browse available plans theme={null}
curl http://localhost:3000/discover
```

```json Expected output theme={null}
{
  "agentName": "My Service",
  "description": "Payment-gated API",
  "plans": [
    { "planId": "basic", "unitAmount": "$0.10", "description": "Basic plan" }
  ],
  "routes": []
}
```

```bash Request access to a plan (returns 402 challenge) theme={null}
curl -X POST http://localhost:3000/x402/access \
  -H "Content-Type: application/json" \
  -d '{"planId": "basic"}'
```

```json Expected output (HTTP 402) theme={null}
{
  "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](/guides/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:

```bash Request a paid route (returns 402 challenge) theme={null}
curl http://localhost:3000/api/weather/london
```

```json Expected output (HTTP 402 — same as subscription) theme={null}
{
  "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](/guides/paying-for-access#per-request-flow) for the full client walkthrough.

## Docker image tags

| Tag                   | When to use                             |
| --------------------- | --------------------------------------- |
| `latest`              | Latest stable release                   |
| `1.2.3` / `1.2` / `1` | Specific version pinning for production |
| `canary`              | Latest main branch build (for testing)  |

## Next steps

<CardGroup cols={2}>
  <Card title="Environment Variables" icon="gear" href="/deployment/environment-variables">
    Full reference of all configuration options including PROXY\_TO\_BASE\_URL.
  </Card>

  <Card title="Pay-Per-Request Standalone Example" icon="coins" href="/examples/ppr-standalone">
    Runnable example: Key0 as a payment gateway proxying to a backend API.
  </Card>

  <Card title="Storage Configuration" icon="database" href="/architecture/storage">
    Redis, Postgres, and custom storage backends.
  </Card>

  <Card title="Automatic Refunds" icon="rotate-left" href="/architecture/refunds">
    How Key0 handles failed deliveries with on-chain refunds.
  </Card>
</CardGroup>
