x402-cli: A CLI for Testing Payment-Gated APIs
I built a command-line tool to test x402-enabled endpoints. What started as a personal debugging utility for port402.com turned into a proper developer tool—one that validates payment-gated APIs, signs gasless USDC transfers, and handles the entire x402 handshake without leaving the terminal.
If you're building or integrating with x402 endpoints, this tool lets you verify everything works before writing a single line of client code. Think of it as curl for the paid web.
The Problem
When I started building x402 endpoints for port402, I ran into a recurring frustration. Every time I deployed a new endpoint, I had to manually verify it was working correctly. This meant opening a terminal, crafting curl requests with the right headers, parsing JSON responses to find payment requirements, and then—if I wanted to actually test a payment—manually signing an EIP-3009 authorization and constructing the X-PAYMENT header.
The process looked something like this:
# Step 1: Check if endpoint returns 402
curl -s -w "%{http_code}" https://api.example.com/endpoint
# Step 2: Get the payment requirements
curl -s https://api.example.com/endpoint | jq '.accepts[0]'
# Step 3: Manually sign an EIP-3009 authorization... somehow
# Step 4: Construct the X-PAYMENT header...
# Step 5: Retry the request with the payment header...
This was tedious for one endpoint, and completely impractical when I had a dozen endpoints to verify after a deployment. I needed something like curl but aware of the x402 protocol—something that could check payment requirements, validate the response format, and optionally sign and submit a test payment.
What is x402?
Before diving into the CLI, it helps to understand what x402 actually is. The protocol uses HTTP 402 (Payment Required)—that status code that's been "reserved for future use" since HTTP/1.1 was standardized in 1999. Twenty-five years later, we finally have a use for it.
The x402 protocol, developed by Coinbase, standardizes how APIs can require payment before serving a response. Instead of API keys, rate limits, and monthly subscriptions, you simply pay per request. The magic is in EIP-3009—a standard that lets you authorize USDC transfers without spending ETH on gas. You sign a message locally, the server collects payment through a facilitator, and the whole thing settles on-chain without you ever touching a blockchain directly.
Here's how it works. When you make a request to an x402-enabled endpoint without paying, the server returns a 402 response with payment requirements in the body:
$ curl -s -w "\nHTTP Status: %{http_code}\n" https://health.port402.com/check
{
"x402Version": 1,
"accepts": [
{
"scheme": "exact",
"network": "base-sepolia",
"maxAmountRequired": "100000",
"resource": "https://health.port402.com/check",
"payTo": "0x1234567890abcdef1234567890abcdef12345678",
"validUntil": "2026-01-10T12:00:00Z",
"extra": {
"name": "API Health Check",
"description": "Check if an API endpoint is healthy"
}
}
],
"error": "Payment required"
}
HTTP Status: 402
The response tells you everything you need to pay: the network (Base Sepolia), the amount (100000 units, which is $0.10 USDC with 6 decimals), the recipient address, and how long this offer is valid.
To access the resource, the client signs an EIP-3009 "transferWithAuthorization"—a gasless USDC transfer that authorizes the server to pull funds from your wallet. The signed authorization goes in the X-PAYMENT header:
$ curl -s https://health.port402.com/check \
-H "X-PAYMENT: eyJwYXlsb2FkIjp7InNpZ25hdHVyZSI6IjB4Li4uIn19..."
{
"health": {
"url": "https://api.example.com/status",
"status": 200,
"ok": true,
"latencyMs": 245
},
"context": {
"runId": "550e8400-e29b-41d4-a716-446655440000"
}
}
The server verifies the signature, settles the payment on-chain (or through a facilitator), and returns the actual response. No API keys, no subscriptions, no account creation—just pay-per-request.
How the CLI Works
The CLI implements a state machine that mirrors the x402 protocol flow. Understanding this helps you debug issues and know exactly what's happening at each step. Here's the complete flow for both the health and test commands:
x402-cli State Machine
┌─────────┐
│ START │
└────┬────┘
│
▼
┌──────────────┐
│Parse Command │───▶ EXIT 2 (invalid input)
└──────┬───────┘
│
▼
┌──────────────┐
│ Send Request │───▶ EXIT 3 (network error)
└──────┬───────┘
│
▼
┌──────────────┐
│ Check Status │───▶ EXIT 4 (not 402)
└──────┬───────┘
│ 402
▼
┌──────────────┐
│ Parse x402 │───▶ EXIT 4 (invalid format)
│ Requirements │
└──────┬───────┘
│
▼
┌──────────────┐
│ Validate │───▶ EXIT 4 (unsupported)
│Network/Token │
└──────┬───────┘
│
├── health ──▶ EXIT 0 ✓
│
│ test
▼
┌──────────────┐
│ Load Wallet │───▶ EXIT 2 (bad keystore)
└──────┬───────┘
│
▼
┌──────────────┐
│ Check Amount │───▶ EXIT 2 (exceeds limit)
│vs --max-amt │
└──────┬───────┘
│
▼
┌──────────────┐
│ Confirm? │───▶ EXIT 0 (declined)
└──────┬───────┘
│
├── --dry-run ▶ EXIT 0 ✓
│
▼
┌──────────────┐
│Sign EIP-3009 │───▶ EXIT 1 (sign failed)
└──────┬───────┘
│
▼
┌──────────────┐
│ Send + Pay │───▶ EXIT 3 (network error)
└──────┬───────┘
│
▼
┌──────────────┐
│Check Response│───▶ EXIT 5 (payment rejected)
└──────┬───────┘
│ 200
▼
EXIT 0 ✓
Success!
The diagram shows how the CLI validates each step before proceeding. For the health command, it exits early after validation—no wallet needed. The test command continues through the full payment flow, with safety checks like --max-amount and confirmation prompts along the way.
Each exit code maps to a specific failure type, making it easy to handle errors in scripts. For example, exit code 3 (network error) might warrant a retry, while exit code 4 (protocol error) indicates the endpoint isn't properly configured.
Installation
The CLI is written in Go, so it compiles to a single binary with no dependencies. There are a few ways to install it:
Homebrew (macOS/Linux)
brew tap port402/tap
brew install x402-cli
Go Install
go install github.com/port402/x402-cli/cmd/x402@latest
Binary Download
Pre-built binaries for Linux, macOS, and Windows are available on the releases page.
After installation, verify it works:
$ x402 version
x402 version 0.2.2
Commit: 4310178
Built: 2026-01-14T10:15:00Z
Go version: go1.24.11
OS/Arch: darwin/arm64
Commands
The CLI has five main commands: health, agent, test, batch-health, and version. Each one is designed for a specific part of the x402 testing workflow.
x402 health
The health command checks if an endpoint is x402-enabled without making a payment. It's the first thing you run when verifying a new endpoint.
$ x402 health https://health.port402.com/check
✓ Endpoint is reachable
✓ Returns 402 Payment Required
✓ Has valid x402 payment requirements
✓ Has EVM payment options
✓ Uses known token (USDC)
Payment Requirements:
Network: base-sepolia (Chain ID: 84532)
Amount: 0.10 USDC
Pay To: 0x1234...5678
Valid: until 2026-01-10T12:00:00Z
The command validates several things: the endpoint is reachable, it returns HTTP 402, the response body contains valid x402 payment requirements, there's at least one EVM payment option, and the token is one we recognize (currently USDC on supported networks).
For more detail, add the --verbose flag:
$ x402 health https://health.port402.com/check --verbose
Request:
URL: https://health.port402.com/check
Method: GET
Timeout: 30s
Response:
Status: 402 Payment Required
Time: 245ms
Payment Requirements (x402 v1):
Scheme: exact
Network: base-sepolia
Chain ID: 84532
Token: USDC (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)
Amount: 100000 (0.10 USDC)
Pay To: 0x1234567890abcdef1234567890abcdef12345678
Resource: https://health.port402.com/check
Valid Until: 2026-01-10T12:00:00Z
Extra:
name: API Health Check
description: Check if an API endpoint is healthy
Validation:
✓ Valid x402 response
✓ Supported network
✓ Known token
✓ Valid payment address
✓ Not expired
For POST-only endpoints (like some agent APIs), use the --method or -X flag:
$ x402 health https://api.example.com/agent -X POST
If you're checking an endpoint that might be part of an A2A (Agent-to-Agent) ecosystem, add the --agent flag to discover agent capabilities alongside the health check:
$ x402 health https://api.example.com/endpoint --agent
✓ https://api.example.com/endpoint
Status: 402 Payment Required
Protocol: v2 (current)
Latency: 120ms
Payment: 0.01 USDC on Base
Checks:
✓ Endpoint reachable
✓ Returns 402
✓ Valid payment header
✓ Has payment options
✓ Has EVM option
✓ Known token
Agent: Recipe Agent v1.0.0
Agent that helps with recipes
Provider: Example Inc (https://example.com)
Skills:
• recipe-search
Find recipes by ingredients or cuisine type
• meal-plan
Generate weekly meal plans based on preferences
Capabilities: streaming
This is useful when you want to verify both payment requirements and agent capabilities in a single command. The agent card is discovered from the endpoint's /.well-known/agent.json path (or variants).
And for scripting or CI integration, there's --json:
$ x402 health https://health.port402.com/check --json | jq '.valid'
true
x402 agent
The agent command discovers A2A (Agent-to-Agent) protocol agent cards. This is useful when you want to understand what an AI agent can do before interacting with it.
$ x402 agent https://api.example.com
✓ Agent card found (/.well-known/agent.json)
Agent:
Name: Recipe Agent
Version: 1.0.0
Description: Agent that helps with recipes
Skills:
• recipe-search — Find recipes by ingredients
• meal-plan — Generate weekly meal plans
Docs: https://docs.example.com/agent
The command tries several well-known paths to find the agent card:
/.well-known/agent.json(A2A v0.1)/.well-known/agent-card.json(A2A v0.2+)/.well-known/agents.json(Wildcard spec)
If an agent card isn't found at any of these paths, you'll see a helpful message:
$ x402 agent https://example.com
⚠ No agent card found
Tried:
• /.well-known/agent.json (404)
• /.well-known/agent-card.json (404)
• /.well-known/agents.json (404)
Hint: Use --card-url to specify a custom location
For agents that host their card at a non-standard path, use the --card-url flag:
$ x402 agent https://api.example.com --card-url /custom/agent.json
The difference between x402 agent and x402 health --agent is subtle but important. Use x402 agent when you want to explore an agent's capabilities independently. Use x402 health --agent when you're primarily checking payment requirements and want to include agent info as context.
x402 test
The test command makes an actual payment to an x402 endpoint. This is where you need a wallet.
$ x402 test https://health.port402.com/check \
--keystore ~/.foundry/keystores/test-wallet
Enter keystore password: ****
Payment Details:
Endpoint: https://health.port402.com/check
Network: base-sepolia
Amount: 0.10 USDC
Pay To: 0x1234...5678
Proceed with payment? [y/N]: y
✓ Signed EIP-3009 authorization
✓ Submitted request with X-PAYMENT header
✓ Received 200 OK
Response:
{
"health": {
"url": "https://api.example.com/status",
"status": 200,
"ok": true,
"latencyMs": 189
}
}
The CLI prompts for confirmation before signing anything. For automated testing, you can skip the prompt:
$ x402 test https://health.port402.com/check \
--keystore ~/.foundry/keystores/test-wallet \
--skip-payment-confirmation
And for truly paranoid testing, there's --dry-run which shows exactly what would happen without signing or sending anything:
$ x402 test https://health.port402.com/check \
--keystore ~/.foundry/keystores/test-wallet \
--dry-run
[DRY RUN] Would sign and submit payment:
From: 0xYourWalletAddress
To: 0x1234...5678
Amount: 0.10 USDC
Network: base-sepolia
No transaction was signed or submitted.
You can also set a safety cap to prevent accidental overpayment:
$ x402 test https://expensive-api.example.com/endpoint \
--keystore ~/.foundry/keystores/test-wallet \
--max-amount 0.05
Error: Payment amount (1.00 USDC) exceeds maximum (0.05 USDC)
Use --max-amount to increase the limit if intended.
For endpoints that require specific HTTP methods or request bodies:
$ x402 test https://api.example.com/agent \
--keystore ~/.foundry/keystores/test-wallet \
--method POST \
--header "Content-Type: application/json" \
--data '{"query": "What is the weather?"}'
x402 batch-health
When you have multiple endpoints to check, batch-health reads URLs from a JSON file and checks them all:
$ cat urls.json
[
"https://health.port402.com/check",
"https://api.port402.com/agent",
{"url": "https://api.port402.com/webhook", "method": "POST"}
]
$ x402 batch-health urls.json
Checking 3 endpoints...
[1/3] https://health.port402.com/check
✓ Valid x402 endpoint (0.10 USDC)
[2/3] https://api.port402.com/agent
✓ Valid x402 endpoint (0.25 USDC)
[3/3] https://api.port402.com/webhook (POST)
✗ Connection refused
Summary: 2/3 healthy
For faster checks, run them in parallel:
$ x402 batch-health urls.json --parallel 5
And for CI pipelines where you want to fail fast:
$ x402 batch-health urls.json --fail-fast
# Exits with code 1 on first failure
Setting Up a Wallet
The test command requires a wallet to sign payments. I recommend using a Foundry keystore—it's encrypted, works well with other Ethereum tooling, and keeps your private key off the command line.
# Install Foundry if you haven't
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Create a new keystore (will prompt for password)
cast wallet new ~/.foundry/keystores/
# Or import an existing private key
cast wallet import test-wallet --interactive
The CLI also supports raw private keys via the --wallet flag or PRIVATE_KEY environment variable, but I'd avoid those for anything other than throwaway test wallets.
For testing on Base Sepolia, you'll need test USDC. Get some from Circle's Faucet—select Base Sepolia and USDC.
Supported Networks and Tokens
The CLI currently supports these networks:
- Ethereum Mainnet (eip155:1)
- Base Mainnet (eip155:8453)
- Base Sepolia (eip155:84532) — for testing
- Ethereum Sepolia (eip155:11155111) — for testing
- Polygon (eip155:137)
- Arbitrum One (eip155:42161)
- Optimism (eip155:10)
USDC is the only supported token right now—it's the most widely used stablecoin for x402 payments and has native support for gasless transfers via EIP-3009.
Exit Codes
For scripting and CI integration, the CLI uses specific exit codes:
- 0 — Success
- 1 — General failure
- 2 — Input validation error (bad URL, missing flag)
- 3 — Network error (connection refused, timeout)
- 4 — Protocol error (invalid 402 response, unsupported network)
- 5 — Payment rejected (insufficient funds, invalid signature)
This makes it easy to handle different failure modes in scripts:
#!/bin/bash
x402 health "$URL"
case $? in
0) echo "Endpoint healthy" ;;
3) echo "Network issue - retrying..." ;;
4) echo "Not a valid x402 endpoint" ;;
*) echo "Unknown error" ;;
esac
Why a CLI?
I considered building a web UI, a VS Code extension, even a Postman plugin. But I kept coming back to the command line. Here's why.
Composability. CLIs integrate naturally into existing workflows. I can pipe output to jq, use it in shell scripts, run it in CI pipelines, and combine it with other Unix tools. The batch health check command came directly from this philosophy—I needed to verify 20 endpoints after a deployment, and x402 batch-health urls.json --parallel 5 solved that in seconds. A web UI would have been prettier, but far less useful for automation.
Wallet security. The CLI keeps private keys local—they never leave your machine. With a keystore, they're encrypted at rest too. A web-based tool would need to either ask you to paste private keys (terrible) or integrate with browser wallets (added complexity for what should be a simple testing tool). By supporting Foundry keystores, the CLI works seamlessly with the same wallet you're already using for smart contract development.
Debugging visibility. When an x402 endpoint isn't working, x402 health --verbose shows you exactly what's happening: the raw request, the response, each validation step, and where it failed. This level of detail is hard to achieve in a GUI without cluttering the interface. The verbose output has saved me hours of debugging—I can immediately see if the issue is network connectivity, an invalid 402 response format, an unsupported network, or an expired payment window.
Portability. Go compiles to a single static binary. No runtime dependencies, no package managers, no version conflicts. Drop it on any Linux server, any Mac, any Windows machine, and it just works. That's invaluable when you're SSH'd into a server and need to quickly verify an endpoint.
What's Next
The CLI has grown significantly since launch. The recent addition of A2A agent discovery lets you explore what AI agents can do before paying to interact with them—a natural complement to payment-gated APIs. But there's more on the roadmap:
- Solana support. The x402 protocol isn't EVM-only—it works with any blockchain that supports gasless transfers. Solana has its own token transfer mechanisms, and adding support would make the CLI useful for a much broader set of endpoints.
- Multi-token support. Right now it's USDC-only, but some endpoints might accept other stablecoins or even native tokens. The architecture supports this; it's mostly a matter of adding token configurations.
- A
watchcommand. For continuous monitoring—hit an endpoint every N seconds, alert if it stops returning valid 402 responses. Useful for uptime monitoring of payment-gated services. - Receipt verification. After a payment, verify that the on-chain transaction actually settled. Right now we trust the server's 200 response, but we could verify the payment hit the blockchain.
For now, it does what I need—quickly verify x402 endpoints and make test payments without leaving the terminal. If you're building with x402, give it a try. And if you run into issues or have feature requests, open an issue on GitHub.
Resources
- GitHub: github.com/port402/x402-cli
- x402 Protocol: x402.org
- Coinbase x402 Docs: docs.cdp.coinbase.com/x402
- A2A Protocol: a2a-protocol.org
- EIP-3009: Transfer With Authorization
- port402: port402.com