@agent-score/pay is the universal agent-payment CLI for shell-tool LLM agents. It manages encrypted keystores, parses 402 challenges from any merchant, signs the right credential per rail, and submits the request; all from one shell command.
AgentScore Pay works with any 402/MPP merchant in the ecosystem; AgentScore-gated or not. It contacts AgentScore APIs only when the merchant’s 402 challenge requires AgentScore identity. For everything else, pay walks the open 402 protocol like any other compliant client.
| Built by | AgentScore |
|---|---|
| Native rails | x402 EVM (Base mainnet/sepolia), MPP (Tempo mainnet/Moderato testnet, Solana mainnet/devnet via solana/charge) |
| Discovers via | x402 Bazaar (Coinbase) + mpp.dev/services |
| Identity | merchant-driven: pass X-Operator-Token, X-Wallet-Address, both, or neither |
| Hints for unsupported rails | yes: points to the right tool (e.g. link-cli for Stripe SPT) |
Installation
First-run setup
Create encrypted keystores for all three native chains in one shot:--no-mnemonic for separate raw keys per chain. The keystore is encrypted with AES-256-GCM (scrypt KDF); set AGENTSCORE_PAY_PASSPHRASE to skip the prompt in scripts.
To get testnet funds:
Paying any 402 endpoint
agentscore-pay pay is a drop-in replacement for curl. It hits the URL, parses the 402 challenge, signs the credential on the appropriate rail, submits via the right header, and returns the merchant’s 200 response. Works with any merchant that speaks 402 (x402 or MPP) on a rail pay can sign.
| Flag | Purpose |
|---|---|
--chain {base,solana,tempo} | Which keystore to sign with. Determines rail (x402 EVM on base, MPP solana/charge on solana, MPP tempo/charge on tempo). |
--network {mainnet,testnet} | Which network to target. Defaults to mainnet. |
--max-spend <usd> | Hard cap on the payment amount; aborts before signing if exceeded. |
--name <name> | Use a named keystore instead of default (multi-wallet setups). |
-H / -d | curl-compatible header / body flags. |
--format {toon,json,yaml,md,jsonl} | Output format. --json is shorthand for --format json. TOON is the default in agent contexts (token-efficient). |
--dry-run | Show what would be signed and sent without submitting. Useful for agent dev/debug: preview rail selection, signer wallet, expected request shape before any real payment. |
Idempotency
Everyagentscore-pay pay invocation generates a stable X-Idempotency-Key header; a SHA-256 hash of (url + method + body + signer); so retries within a single invocation reuse the same key. Merchants that honor Stripe-pattern dedup (which includes most production payment systems) won’t double-charge if a payment settles but the network response is lost mid-flight. Pass your own -H 'X-Idempotency-Key: <value>' to override the auto-generated key (useful for cross-process idempotency: same logical purchase across multiple agentscore-pay pay invocations).
Test-mode addresses
AgentScore reserves seven EVM addresses (0x0000…0001 through 0x0000…0007) as deterministic test fixtures; KYC verified, sanctions clear, age gates passing; so dev/test merchants don’t burn real KYC credits. agentscore-pay recognizes these via isAgentScoreTestAddress(addr) (exposed from @agent-score/pay/test-mode); same helper ships in @agent-score/sdk for backend code paths.
Decimals handling
Spec-compliant 402 responses carrydecimals in the rail requirements. When a merchant omits it, pay applies a strict policy: canonical USDC (matched against the per-chain Circle USDC registry) silently uses 6 decimals; any unrecognized asset triggers merchant_spec_violation and aborts before signing. Refusing to guess avoids orders-of-magnitude mis-billing on tokens with non-6-decimal precision.
Discovery and probing
Find merchants and probe challenges before paying:check and discover surface all rails the merchant accepts; including ones pay can’t fund directly. For unsupported rails pay knows about (Polygon, other x402 networks, Stripe SPT, …) it prints a structured hint pointing to the right tool, so the agent can complete the payment with a sibling CLI without trial-and-error.
Example (check output against a multi-rail merchant):
Identity model
AgentScore Pay is identity-agnostic; it only sends what the merchant or the agent asks for. TheX-Operator-Token and X-Wallet-Address headers are AgentScore’s identity scheme; merchants that don’t use AgentScore can ignore them entirely (or use their own identity headers; pay forwards anything the agent passes via -H).
For AgentScore-gated merchants, two paths:
- Operator token: pass
-H 'X-Operator-Token: opc_...'OR runagentscore-pay passport loginonce and pay auto-attaches it on every settle leg. Reusable across every AgentScore merchant until expiry. Required for rails that have no wallet signature (Stripe SPT, card). - Wallet auth: pass
-H 'X-Wallet-Address: 0x...'(or Solana base58). Works on rails that carry a wallet signature (Tempo MPP, x402 EIP-3009 on Base, x402 SPL Token on Solana). The wallet must resolve to the same operator as the payment signer; same-operator linked wallets are fungible.
AgentScore Passport (one-command identity)
passport login, every subsequent agentscore-pay <url> call auto-attaches X-Operator-Token from the stored Passport. Suppress with --skip-passport for explicit-anonymous traffic; caller-supplied -H "X-Operator-Token: ..." always wins.
Token lifecycle: the access operator_token is 24h and the rotating refresh_token is 90d. pay refreshes the access token silently on the next call after it expires; no agent action needed. The user is only prompted to re-verify in browser (Open this URL to renew:) when both have expired (~90 days of inactivity), or when the Passport was minted via cold-start bootstrap rather than passport login (merchant-initiated sessions don’t issue a refresh_token). pay’s “expires in N days” warning fires only when re-verify is genuinely needed, not on every short-lived access rotation.
Header precedence
Order of preference for the identity header pay attaches on the settle leg:- Caller-supplied
-H 'X-Operator-Token: ...'or-H 'X-Wallet-Address: ...'; wins - Stored Passport’s
operator_token; auto-attached asX-Operator-Token(skip with--skip-passport) X-Wallet-Addressfrom the--chainkeystore; last-resort fallback for first-payment flows where the merchant’s gate captures the wallet under the operator
Native rails
| Rail | Networks | Header | Recovery |
|---|---|---|---|
| x402 exact (EVM) | Base mainnet (eip155:8453), Sepolia (eip155:84532) | X-Payment | EIP-3009 USDC TransferWithAuthorization; gasless from agent (facilitator pays gas) |
| Solana MPP | Solana mainnet-beta, devnet | Authorization: Payment | MPP solana/charge directive signed against the merchant’s challenge |
| Tempo MPP | Tempo mainnet (4217), Moderato testnet (42431) | Authorization: Payment | MPP tempo/charge directive signed against the merchant’s challenge |
Other commands
agentscore-pay --help (or agentscore-pay <command> --help) for full flags. For LLM agents, agent-guide prints a structured how-to (golden path, testnet path, funding, auxiliary commands, pitfalls, identity-error recovery patterns, exit codes); also available as JSON via --format json.
Identity-error recovery + quota observability
assess, sessions, credentials, and associate-wallet surface the SDK’s typed errors as structured CliErrors. The JSON envelope’s code discriminates the recovery path: config_error (API-key issue OR token-expired/invalid; extra carries verify_url/session_id/poll_secret for the verify-poll flow), insufficient_balance (endpoint not enabled; surface to user), quota_exceeded (account cap reached; surface to user, agent retry won’t fix), network_error (transient; retry with backoff). agentscore-pay agent-guide lists each pattern with the exact recovery action.
The pay <url> command additionally throws two non-TTY-only codes when a Passport-required state is hit; surfaced as a structured envelope instead of blocking on the inline browser-redirect flow, so unattended agents can route to passport login. passport_login_required fires when the stored Passport’s access token expired AND silent refresh did not succeed (extra carries previous_token_prefix); passport_required_by_merchant fires when a merchant returns 403 with bootstrap fields and the agent has no usable Passport (extra carries verify_url/session_id/poll_secret/poll_url/order_id). Both have next_steps.action: "passport_login". In a human TTY, pay drives the inline browser flow itself instead of throwing.
When the account has a per-period quota, assess (and other identity commands) emits a quota: { limit, used, reset } block on the success envelope, captured from X-Quota-Limit / X-Quota-Used / X-Quota-Reset response headers. Agents can monitor approach-to-cap proactively (warn at 80%, alert at 95%) before hitting quota_exceeded.
Unlocking the keystore
Three precedence-ordered options for the passphrase prompt:| Mechanism | Best for | Persistence |
|---|---|---|
AGENTSCORE_PAY_PASSPHRASE=<pass> env var | Containers, CI, serverless, daemons, any non-interactive agent | Process / shell session |
agentscore-pay unlock --for 1h | Interactive shell where you’ll run multiple commands | Cached in ~/.agentscore/.unlock (mode 0600) until TTL expires (max 8h); unlock --clear wipes early |
| Per-call interactive prompt | First-time setup, one-off commands | None |
unlock is the fallback.
For agent authors: prefer the env var and read it from your secret store at agent startup. Don’t bake it into the agent’s prompt or memory. agentscore-pay agent-guide emits a structured how-to with the precedence baked in.
MCP server
agentscore-pay --mcp runs the CLI as a stdio JSON-RPC MCP server, exposing every command (wallet + payment + identity) as a tool. agentscore-pay mcp add registers the binary with Claude Code, Cursor, Amp, and other MCP-aware agents. agentscore-pay --llms emits a markdown manifest of every command (or --llms --format json for JSON Schema).
Scripting
Output adapts to context; terminal use gets pretty-printed, pipes/agents get a structured envelope (TOON by default; token-efficient, ~40% fewer tokens than JSON). Pass--json (or --format yaml|md|jsonl) to lock the format. AGENTSCORE_PAY_PASSPHRASE skips the passphrase prompt for unattended runs:
agentscore-pay --help; non-zero exits indicate an error category (invalid_input, unsupported_rail, network_error, merchant_error, etc.) so scripts can branch.
Source + releases
- npm: npmjs.com/package/@agent-score/pay
- GitHub: github.com/agentscore/pay
- Homebrew tap: github.com/agentscore/homebrew-tap