January 2026 · ·

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 watch command. 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