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

# Configuration

> Environment variables, KV stores, and plugin hooks for @agentcash/router.

## Two ways to initialize

**`createRouterFromEnv` (recommended)** reads `process.env`, validates every value up front, and throws a single `RouterConfigError` listing every problem at once. Protocols toggle on by presence: x402 when the CDP key pair is set, MPP when `MPP_SECRET_KEY` is set (at least one is required).

```typescript lib/router.ts theme={null}
import { createRouterFromEnv } from '@agentcash/router';

export const router = createRouterFromEnv({
  title: 'My API',
  description: 'Pay-per-call search.',
  guidance: 'POST /search with { q: string }. Returns top 10 results.',
});
```

**`createRouter(config)`** takes an explicit `RouterConfig`: use it for custom networks, multiple payees, non-standard assets, or anything the env entry point doesn't expose. It runs the same validation.

## Core

| Var                 | Required           | Purpose                                                                                                                                                                                                                            |
| ------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `EVM_PAYEE_ADDRESS` | yes                | `0x…` address that receives x402 and MPP payments. The zero address is rejected.                                                                                                                                                   |
| `BASE_URL`          | yes outside Vercel | Origin URL (`https://api.example.com`). Used as the 402 realm, OpenAPI server URL, and MPP memo prefix, so it must match your public domain. On Vercel it is auto-derived from `VERCEL_PROJECT_PRODUCTION_URL`, then `VERCEL_URL`. |

## x402

Auto-enabled when both CDP vars are set.

| Var                                    | Required             | Purpose                                                                                                                                                |
| -------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `CDP_API_KEY_ID`, `CDP_API_KEY_SECRET` | when x402 is enabled | [Coinbase Developer Platform](https://portal.cdp.coinbase.com/projects/api-keys) credentials for the default EVM facilitator. The free tier is enough. |
| `X402_BUILDER_CODE`                    | no                   | Base Builder Code for ERC-8021 on-chain attribution. Credits each settled payment to your app on the [Base dashboard](https://dashboard.base.org).     |
| `SOLANA_PAYEE_ADDRESS`                 | no                   | Adds a Solana accept. Solana clients can only pay static-priced `.paid()` routes (`.upTo()` is Base-only and `.session()` is MPP-only).                |

<Note>
  Creating the router kicks off a background fetch of the facilitator's supported payment kinds, including during `next build`. With missing or placeholder CDP keys you'll see `[x402] facilitator /supported failed, using hardcoded baseline` in build logs. That's a graceful fallback, not a fatal error.
</Note>

## MPP

Auto-enabled when `MPP_SECRET_KEY` is set.

| Var                 | Required            | Purpose                                                                                                                   |
| ------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `MPP_SECRET_KEY`    | when MPP is enabled | Server-side MPP secret (`openssl rand -hex 32`).                                                                          |
| `MPP_CURRENCY`      | when MPP is enabled | Tempo currency address. Use the exported `TEMPO_USDC_ADDRESS` constant for Tempo USDC.                                    |
| `MPP_OPERATOR_KEY`  | for `.session()`    | Signs server-side close/settle. Setting it auto-enables session mode. Must be the private key **of** `EVM_PAYEE_ADDRESS`. |
| `MPP_FEE_PAYER_KEY` | no                  | Sponsors client gas for channel open/topUp. Must resolve to a different address than the operator.                        |
| `TEMPO_RPC_URL`     | no                  | Tempo JSON-RPC endpoint. Defaults to the public `https://rpc.tempo.xyz`.                                                  |

For local development, mint a throwaway keypair where payee and operator line up:

```bash theme={null}
node -e "const {generatePrivateKey, privateKeyToAccount} = require('viem/accounts');
  const pk = generatePrivateKey();
  console.log('EVM_PAYEE_ADDRESS=' + privateKeyToAccount(pk).address);
  console.log('MPP_OPERATOR_KEY=' + pk);"
```

## KV store

| Var                                    | Required                   | Purpose                                                                               |
| -------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------- |
| `KV_REST_API_URL`, `KV_REST_API_TOKEN` | recommended for production | Upstash / Vercel KV. Backs SIWX nonces, SIWX entitlements, and MPP replay protection. |

<Warning>
  Without a KV store, this state lives in per-process memory. On serverless or multi-instance deployments, a wallet that paid on instance A is treated as unpaid on instance B, so pay-once-then-replay routes charge users twice. Attach a KV store before production traffic.
</Warning>

## Plugin hooks

Pass a `RouterPlugin` to observe the request lifecycle. All hooks are optional and fire-and-forget: they never delay the response.

```typescript theme={null}
import { createRouterFromEnv, type RouterPlugin } from '@agentcash/router';

const myPlugin: RouterPlugin = {
  onRequest(meta) {},
  onPaymentVerified(ctx, payment) {},
  onPaymentSettled(ctx, settlement) {},
  onResponse(ctx, response) {},
  onError(ctx, error) {},
  onAlert(ctx, alert) {},
};

export const router = createRouterFromEnv({
  title: 'My API',
  plugin: myPlugin,
});
```

### Debugging with `onAlert`

The router reports internal warnings (failed payment verification, simulation failures, misconfiguration) through `onAlert`. With no plugin registered these are silently dropped, so a logging plugin is the fastest way to see why a request failed:

```typescript theme={null}
const loggingPlugin: RouterPlugin = {
  onAlert(_ctx, alert) {
    (alert.level === 'error' ? console.error : console.warn)(
      `[router:${alert.route}] ${alert.message}`,
      alert.meta ?? '',
    );
  },
  onError(_ctx, error) {
    console.error(`[router] ${error.status} ${error.message} (settled=${error.settled})`);
  },
};
```
