Sandbox
Test mode on the Anvil fork — APIs, wallet, RPC, SDK, CLI, MCP
Sandbox (seller Test mode) lets you create payable APIs and settle x402 payments without real funds. Settlements run on an Anvil fork of Base mainnet (eip155:845320402), not public Base or Base Sepolia.
In the seller dashboard, switch the environment toggle to Sandbox. The same mode is available from SDKs, CLI, MCP, and the control plane API.
What you get
| Piece | Detail |
|---|---|
| Environment | Control plane development (UI label: Sandbox) |
| Network | CAIP-2 eip155:845320402 (Anvil --chain-id 845320402) |
| Payment token | Base mainnet USDC at 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (forked state) |
| Facilitator | Dev facilitator (facilitator-dev) — not the public testnet facilitator |
| Billing | Development traffic is typically fee-exempt |
| Persistence | Fork state is ephemeral — restarts wipe balances |
Sandbox is for iterating APIs, endpoints, and payment flows. Use Production for live mainnet settlement.
Chain and RPC (chain-dev)
Ax402 runs chain-dev: Anvil fork + chain-manager faucet API + facilitator-dev.
Public endpoints (hosted)
| Service | URL |
|---|---|
| Anvil RPC | https://rpc-dev.ax402.io |
| Chain manager | https://chain-manager-dev.ax402.io:8099 |
| Dev facilitator | https://facilitator-dev.ax402.io |
Add the network in MetaMask (or another wallet):
- Network name: Ax402 Sandbox Fork
- RPC URL:
https://rpc-dev.ax402.io(or therpc_urlfromGET /config/local-devon your control plane) - Chain ID:
845320402 - Currency symbol: ETH
- USDC:
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Control plane config for clients:
GET /config/local-devReturns enabled, chain_id, network, rpc_url, faucet_enabled, and the sandbox payment token. SDKs: client.config.localDev() (TypeScript), LocalDevConfig (Go), config.local_dev() (Python).
Local full stack
With the ax402 compose stack:
| Service | Host URL |
|---|---|
| Anvil RPC | http://127.0.0.1:18545 |
| Chain manager | http://127.0.0.1:8099 |
| Dev facilitator | http://127.0.0.1:8089 |
cd ax402 && make up
make restart-chain # fresh Anvil forkAuth and environment selection
| Method | How Sandbox is selected |
|---|---|
| Dashboard | Environment toggle → Sandbox (X-Ax402-Environment: development) |
| API key | ax402_sandbox_... keys (created while in Sandbox) — environment is baked into the key; use AX402_BASE_URL=https://api.staging.ax402.io |
| JWT | Header X-Ax402-Environment: development (or CLI --sandbox) |
| Env vars | AX402_ENVIRONMENT=development or AX402_SANDBOX=1 (JWT / CLI only — not for API keys) |
ax402_live_... keys are production-only (https://api.ax402.io). Combining them with --sandbox / development fails fast. API key prefix and base URL must match — wrong pairing returns 401 invalid token.
Sandbox wallet routes require a JWT (same as keys commands), not an API key.
Sandbox wallet (private key)
Each seller account can have a platform-managed EVM keypair for Sandbox payments. GET /sandbox-wallet get-or-creates the wallet, returns the private key, and kicks an async faucet (ETH + USDC on the fork).
CLI (recommended)
export AX402_BASE_URL=https://api.ax402.io # JWT + --sandbox works on prod or staging
export AX402_JWT=$(ax402 auth login --email you@example.com --password '...')
# Full private key in JSON; --wait polls until balances look funded
export AX402_EVM_PRIVATE_KEY=$(
ax402 --sandbox sandbox-wallet get --json --wait | jq -r .private_key
)
ax402 pay url --url "https://your-api.dev….ax402.io/v1/hello"ax402 --sandbox sandbox-wallet get [--wait|--no-wait] [--json]
ax402 --sandbox sandbox-wallet reset [--wait|--no-wait] [--json]Human output redacts the key; --json includes private_key. --wait is on by default for human mode and off for --json unless you pass --wait.
TypeScript SDK
import { Ax402Client } from "@ax402/sdk";
const client = new Ax402Client({
baseUrl: process.env.AX402_BASE_URL!,
token: process.env.AX402_JWT!,
environment: "development",
});
const wallet = await client.sandboxWallet.get();
// wallet.address, wallet.private_key, wallet.private_key_redacted
const balances = await client.config.devBalances(wallet.address);
// balances.eth_wei, balances.usdc_base (hex)
await client.config.devFaucet(wallet.address, "all"); // optional manual refillGo
cl := ax402.NewClient(baseURL, ax402.Credentials{Token: jwt},
ax402.WithEnvironment("development"))
wallet, err := cl.GetSandboxWallet(ctx)
balances, err := cl.DevBalances(ctx, wallet.Address)Python
from ax402_sdk import Ax402Client
client = Ax402Client(base_url, token=jwt, environment="development")
wallet = client.sandbox_wallet.get()
balances = client.config.dev_balances(wallet["address"])Plain HTTP
GET /sandbox-wallet
Authorization: Bearer <JWT>
X-Ax402-Environment: developmentPOST /sandbox-wallet/reset
Authorization: Bearer <JWT>
X-Ax402-Environment: developmentPOST /dev/balances
Authorization: Bearer <JWT>
X-Ax402-Environment: development
Content-Type: application/json
{"address":"0x..."}POST /dev/faucet
Authorization: Bearer <JWT>
X-Ax402-Environment: development
Content-Type: application/json
{"address":"0x...","asset":"all"}Response shape for wallet routes: address, private_key, private_key_redacted.
Create APIs and endpoints in Sandbox
Development APIs must use accepts on eip155:845320402 only. Default SDK/CLI helpers use Base USDC on that network.
CLI
export AX402_BASE_URL=https://api.staging.ax402.io # required for ax402_sandbox_... keys
export AX402_API_KEY=ax402_sandbox_... # or AX402_JWT + --sandbox on either host
ax402 --sandbox apis create \
--name "Demo" --slug demo \
--upstream https://api.example.com \
--pay-to 0xYourWallet --json
ax402 --sandbox endpoints create \
--api-id API_ID --path "/v1/*" --price-usdc 0.10 --json--sandbox (or AX402_ENVIRONMENT / AX402_SANDBOX) sets the development environment and defaults endpoint pricing to the fork. Explicit --network / --accepts still win.
TypeScript
import {
Ax402Client,
buildSandboxUsdcAccept,
usdcToAtomic,
} from "@ax402/sdk";
const client = new Ax402Client({
baseUrl: process.env.AX402_BASE_URL!,
apiKey: process.env.AX402_API_KEY!, // ax402_sandbox_...
environment: "development",
});
const api = await client.apis.create({
name: "Demo",
slug: "demo",
upstream_base_url: "https://api.example.com",
pay_to_mode: "user_wallet",
pay_to_address: "0x...",
});
await client.endpoints.create(api.id, {
method: "GET",
path_pattern: "/v1/*",
accepts: [buildSandboxUsdcAccept("exact", usdcToAtomic("0.10"))],
});Go
cl := ax402.NewClient(baseURL, ax402.Credentials{APIKey: testKey},
ax402.WithEnvironment("development"))
api, _ := cl.CreateAPI(ctx, ax402.CreateAPIInput{ /* ... */ })
accept, _ := ax402.BuildSandboxUsdcAccept("exact", "100000")
cl.CreateEndpoint(ctx, api.ID, ax402.CreateEndpointInput{
Method: "GET", PathPattern: "/v1/*", Accepts: []ax402.AcceptOption{accept},
})Python
from ax402_sdk import Ax402Client, build_sandbox_usdc_accept
client = Ax402Client.from_env() # AX402_ENVIRONMENT=development or ax402_sandbox_ key
api = client.apis.create({...})
client.endpoints.create(api.id, {
"method": "GET",
"path_pattern": "/v1/*",
"accepts": [build_sandbox_usdc_accept("100000")],
})MCP
Set AX402_API_KEY to an ax402_sandbox_... key and AX402_BASE_URL=https://api.staging.ax402.io (API key environment is fixed by prefix — AX402_ENVIRONMENT does not apply). Default ax402_add_endpoint / OpenAPI import pricing uses sandbox-fork USDC accepts.
MCP is API-key only — it cannot call /sandbox-wallet. Use the CLI + JWT for the funded private key, then set AX402_EVM_PRIVATE_KEY for ax402_pay_url.
Plain API
POST /apis
X-API-Key: ax402_sandbox_...
# or Authorization: Bearer <JWT>
# X-Ax402-Environment: development
Content-Type: application/json
{"name":"Demo","slug":"demo","upstream_base_url":"https://api.example.com","pay_to_mode":"user_wallet","pay_to_address":"0x..."}POST /apis/{id}/endpoints
Content-Type: application/json
{
"method": "GET",
"path_pattern": "/v1/*",
"accepts": [{
"scheme": "exact",
"network": "eip155:845320402",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "100000",
"max_timeout_seconds": 300,
"extra": {"name": "USDC", "version": "2", "assetTransferMethod": "eip3009"}
}]
}Pay a Sandbox gateway URL
- Create API + endpoint in Sandbox (above).
- Load the sandbox wallet private key into
AX402_EVM_PRIVATE_KEY. - Ensure the wallet has fork ETH + USDC (
sandbox-wallet get --waitor faucet). - Pay the gateway hostname (not the control plane):
ax402 pay url --url "https://demo.dev.<platform-domain>/v1/hello" --jsonOr TypeScript @ax402/sdk/buyer, Go/Python buyer modules, or MCP ax402_pay_url.
Shared demo gateway (when provisioned): check the seller dashboard Sandbox onboarding flow — useful for smoke tests; create your own API for real work.
Environment variables
| Variable | Purpose |
|---|---|
AX402_BASE_URL | https://api.staging.ax402.io with ax402_sandbox_... keys; https://api.ax402.io for ax402_live_... or JWT + --sandbox |
AX402_API_KEY | ax402_sandbox_... for Sandbox automation (pair with staging URL) |
AX402_JWT | Required for sandbox-wallet and keys |
AX402_ENVIRONMENT | development selects Sandbox |
AX402_SANDBOX | 1 / true — alias for development |
AX402_EVM_PRIVATE_KEY | Buyer signer (export from sandbox-wallet get) |
Limitations
- Only the sandbox fork network — no Hedera / Base Sepolia / mainnet accepts on development APIs
- Fork balances reset when chain-dev restarts
- Shared staging Anvil is multi-tenant — do not rely on sticky balances
- Batch-settlement is not supported on the sandbox fork helpers (
exactonly) - Sandbox wallet is JWT-only (not available via MCP API keys)