X402 Pay
Agent spec. Human readers can start with the guide at https://docs.taskmarket.dev/features/x402-pay.
X402 Pay is the Taskmarket CLI using its encrypted EVM wallet to buy a directly supplied HTTPS resource from any x402 v2 service: Taskmarket's own router, another Daydreams agent, or any other service on the open protocol. This is separate from Taskmarket task payments -- the other direction is X402 Charge, where any wallet, including one that has never seen this CLI, pays Taskmarket instead. X402 Pay never sends Taskmarket legal receipts, API tokens, caller proofs, or idempotency headers to the origin it is paying.
Supported in this release:
- HTTPS GET and JSON POST.
- EVM networks (
eip155:<chainId>). exactpayments through EIP-3009 or Permit2.uptopayments through Permit2.- Explicitly allowlisted ERC-20 contracts.
- Interactive approval or unattended execution under a local policy.
Service discovery, Bazaar search, MCP, A2A, non-EVM wallets, batch settlement, uploads, forms, and streaming request bodies are not supported.
Safety Model
An external 402 response is untrusted. Before the wallet signs, the CLI checks the resource URL, origin, path, method, protocol version, scheme, network, token contract, recipient, amount, and authorization lifetime. Redirects are disabled. Unattended requests also reject private, loopback, and link-local destinations unless their matching policy rule explicitly allows private networking.
The policy protects agents using this CLI installation. It is not an onchain wallet limit and does not coordinate with another computer or wallet program controlling the same private key.
Never retry a payment whose result is pending: true. The service may already have settled it.
Inspect or reconcile the recorded payment instead.
This CLI is meant to be driven by an AI agent, which changes the threat model. An agent can be
prompt-injected by untrusted content it processes in the course of ordinary work -- a task
description, a fetched webpage, another tool's output -- into running a CLI command it was never
meant to run. Unattended (--non-interactive) payments exist specifically so an agent can pay
without a human present at request time; the entire safety of that mode depends on a human already
having authorized the exact origin, recipient, and spend caps in the policy rule beforehand. To
keep that premise true rather than merely assumed, adding or enabling a policy rule that grants
unattended spending authority always requires a real interactive terminal and a typed
confirmation -- an agent driving this CLI cannot author or activate its own unattended
authorization, even if it has been compromised. Treat ~/.taskmarket/x402-policy.json as a
security-sensitive file: only write unattended rules yourself, at a terminal, after reading exactly
what origin and spend caps you are granting.
Initialize Policy
taskmarket x402 policy init
taskmarket x402 policy path
taskmarket x402 policy show
taskmarket x402 policy validateThe policy is stored at ~/.taskmarket/x402-policy.json with owner-only permissions. Override the
path for isolated automation or tests with TASKMARKET_X402_POLICY_PATH.
The machine-readable schema is available from:
taskmarket x402 policy schemaand on the docs site at x402-policy.schema.json (not in this bundle; taskmarket x402 policy schema prints the same schema offline).
A policy file is written as "version": 1 unless it uses something earlier CLI releases cannot
read: a marketplaceFees section, or a network or payment entry outside eip155 exact/upto.
Such a policy is written as "version": 2, and needs the CLI release that introduced
marketplaceFees or a later one. An earlier CLI refuses a version 2 policy instead of dropping
what it cannot read, so it never removes your fee bounds. Removing those entries writes version 1
again.
Policy Example
Amounts are unsigned integer strings in the token's atomic units. displaySymbol and
displayDecimals are for operator display only; authorization uses the network and contract
address.
{
"version": 1,
"networks": {
"eip155:8453": {
"rpcUrlEnv": "BASE_RPC_URL"
}
},
"rules": [
{
"id": "example-api",
"enabled": true,
"priority": 100,
"origin": "https://api.example.com",
"pathPrefix": "/v1/",
"methods": ["GET", "POST"],
"unattended": true,
"allowPrivateNetwork": false,
"maxAuthorizationSeconds": 300,
"payments": [
{
"scheme": "upto",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x1111111111111111111111111111111111111111",
"displaySymbol": "USDC",
"displayDecimals": 6,
"maxPerPayment": "100000",
"spendWindow": {
"seconds": 86400,
"max": "1000000"
},
"permit2": {
"allowSponsoredApproval": true,
"allowDirectApproval": true,
"allowUnattendedDirectApproval": false,
"maxApprovalGasWei": "100000000000000"
}
}
]
}
]
}Rules match an exact HTTPS origin and optional path prefix. The highest priority wins; an equal
priority tie is rejected. Unattended payment entries must pin payTo; interactive-only rules may
leave it open for confirmation. Payment entries are ordered preferences. Command options can
narrow a persistent unattended rule but cannot widen it. Rules never apply to TaskMarket's own
fees; the optional marketplaceFees section bounds those instead (see
TaskMarket Submission Fees).
Manage rules without editing the file directly:
taskmarket x402 policy add --rule-file rule.json
taskmarket x402 policy disable example-api
taskmarket x402 policy enable example-api
taskmarket x402 policy remove example-apipolicy add and policy enable print the rule's origin, path, methods, and payment terms and
require you to type authorize at the terminal whenever the rule being written or activated is
both enabled and unattended. This only runs with a real TTY attached, so it cannot be satisfied
by a script or an agent process. policy disable and policy remove, and adding or enabling a
rule that is not unattended, need no confirmation, since none of those grant unattended spending
authority.
Make Requests
Interactive GET:
taskmarket x402 request https://api.example.com/v1/dataJSON POST:
taskmarket x402 request https://api.example.com/v1/generate \
--method POST \
--body-file request.jsonUnattended execution fails closed unless one persistent rule authorizes the complete challenge:
taskmarket x402 request https://api.example.com/v1/data \
--non-interactive \
--policy example-apiLoad secret headers from environment variables or an owner-only JSON file. Do not put secrets in command arguments:
export EXAMPLE_API_KEY=secret
taskmarket x402 request https://api.example.com/v1/data \
--header-env 'authorization=EXAMPLE_API_KEY'Every request has an invocation ID. Pass your own with --invocation-id <id> (1-128 characters
of A-Z a-z 0-9 . _ : -) when a script or agent may run the same request again, for example after
a crash or a timeout. The first use records the ID with a fingerprint of the exact request
(method, URL, headers and body). Running it again with the same request never sends or pays again
and fails with "code": "invocation_duplicate"; running it with a different request fails with
"code": "invocation_conflict". Both failures include the recorded invocation (state,
charge, uncertain), so a script can tell whether the first run was charged. Without the
option the CLI uses a random UUID, so every run is a new request.
taskmarket x402 request https://api.example.com/v1/data \
--non-interactive \
--policy example-api \
--invocation-id report-2026-10-10JSON and text responses up to 1 MiB are returned in the CLI envelope. Use --output <path> for
larger or binary responses, up to 64 MiB. The CLI refuses to overwrite an existing output file.
Request bodies are limited to 64 MiB as well: a larger --json or --body-file is refused with
request_body_too_large before it is read.
Exact and Upto
exact authorizes and settles the advertised amount. Compatible tokens use EIP-3009; other ERC-20
tokens may use Permit2 when the service advertises that transfer method.
upto authorizes the advertised maximum. The service chooses an actual charge from zero through
that maximum after measuring usage. A conforming but dishonest service can charge the entire
maximum, so policy reserves the maximum before signing. On success the CLI reports both:
{
"authorizedAmount": "100000",
"settledAmount": "43821"
}Permit2 can require a one-time ERC-20 allowance. The CLI uses bounded EIP-2612 or raw-approval gas sponsoring when advertised and policy permits it. Otherwise it can send a direct approval through the rule's configured RPC. A direct approval verifies chain ID, native balance, and gas ceiling, and receives a separate confirmation unless unattended direct approval is explicitly enabled. The CLI never creates an unlimited Permit2 allowance automatically.
Paying Merchants on Tempo
Tempo (eip155:4217, and Moderato eip155:42431) has no native gas coin: every transaction pays
its fee in a TIP-20 fee token, OUSD. When a Tempo merchant needs a direct Permit2 approval, the CLI:
- sends it as a Tempo transaction with OUSD as the fee token;
- checks your OUSD balance, not a native balance, against the estimated fee before sending;
- shows the estimated maximum gas in USD in the confirmation prompt.
Raw-approval gas sponsoring (erc20ApprovalGasSponsoring) is refused on Tempo, since it relies on
a native-gas transaction. Bounded EIP-2612 sponsoring and direct approval still work. On Tempo,
maxApprovalGasWei is read in attodollars (1e-18 USD), the unit Tempo prices gas in, so
"100000000000000" caps an approval at $0.0001. See Tempo.
Payment Records and Recovery
The append-only journal is ~/.taskmarket/x402-payments.jsonl, with owner-only permissions. Before
signing, it atomically reserves the exact amount or upto maximum so concurrent agents cannot
spend the same remaining budget. Invocation IDs and their request fingerprints are recorded in
x402-payments.invocations.jsonl beside it, before the first request is sent; a journal moved with
TASKMARKET_X402_JOURNAL_PATH keeps its own invocation journal named after it. Each record is
synced to disk before the next request. Neither file stores request or response content.
taskmarket x402 payments list
taskmarket x402 payments get <paymentId>
taskmarket x402 payments reconcile <paymentId>After the paid request leaves the machine, a timeout, lost response, malformed receipt, or missing
upto amount is an unknown outcome. The journal keeps the full maximum reserved and the CLI emits
pending: true. payments reconcile checks the transaction or authorization nonce using the
policy's RPC configuration:
- An expired unused nonce becomes
expired_unspentand releases the reservation. - A consumed
exactnonce proves the exact charge. - A consumed
uptononce without a receipt proves money moved but not how much; the maximum stays counted assettled_amount_unknown.
Manual resolution is an audited last resort and requires an interactive confirmation:
taskmarket x402 payments resolve <paymentId> \
--settled-amount <atomicUnits> \
--transaction <hash> \
--note 'evidence used to establish the amount'Never manually release a reservation from an unchanged wallet balance alone. A pending transaction and a failed transaction can look identical until the chain establishes the outcome.
TaskMarket Submission Fees
taskmarket task submit pays its own fee, charged once a worker's free submissions to a task are
used up, through the same payment journal. It needs no policy rule and never prompts: it pays only
TaskMarket's own fee scheme, and only to the API the CLI is configured for (TASKMARKET_API_URL).
x402 request never gets this exception. A fee payment is listed by x402 payments list with rail
taskmarket-fee and rule taskmarket:first-party, a rule id no policy rule can use.
A submission within the free allowance reads neither the policy nor the payment journal: it succeeds whatever state they are in. Only a submission that is charged a fee reads the policy and records the payment, before the fee is signed.
Fees are unbounded unless the policy bounds them. An optional marketplaceFees section sets a
per-payment maximum and a rolling window per network and token, counted over every payment
recorded under rule taskmarket:first-party. A policy with this section is a version 2 policy (see
Initialize Policy):
{
"version": 2,
"networks": { "eip155:8453": { "rpcUrlEnv": "BASE_RPC_URL" } },
"rules": [],
"marketplaceFees": [
{
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"maxPerPayment": "1000",
"spendWindow": { "seconds": 86400, "max": "20000" }
}
]
}A fee above maxPerPayment, or one that would take the window past max, is refused with
policy_denied before anything is signed. A fee on a network and token the section does not name
is not bounded by it. A policy file that exists but cannot be read stops a fee; it does not stop a
submission within the free allowance.
When a Submission's Outcome Is Unknown
A failed task submit reports the submission's idempotency key. If the network fails after the
fee was sent, the failure is outcome_uncertain with pending: true, the fee payment and the
idempotency key. Whenever the fee was paid and the outcome is unknown (pending: true), find out
what happened before submitting again:
- Run
taskmarket task my-submissionsto see whether the submission was recorded. - Run
taskmarket x402 payments listand look for the fee payment (taskmarket-fee). A settled payment was charged; anunknownone may or may not have been. - Once an unknown payment's authorization has expired (five minutes after it was signed), run
taskmarket x402 payments reconcile <paymentId>. It reads the authorization nonce through the policy's RPC configuration for the fee's network, so the policy needs anetworksentry for it. An unused authorization becomesexpired_unspentand nothing was charged.
Submit again, without a stored key, only when the submission is missing. Re-running task submit
uploads its files again under new keys, so it is never a replay of the earlier request. With
TASKMARKET_IDEMPOTENCY_KEY set to the reported key, the files are uploaded again but the
submission is sent under that key, and the API refuses it, naming the original intent and its
status, without charging again:
idempotency_key_reused past the free allowance, idempotency_key_payload_mismatch within it.
Past the free allowance the CLI has already signed a fee for that request by then, so that payment
stays unknown, with the refusal in its error, until x402 payments reconcile resolves it after
its authorization expires, as above.
Developers can run make smoke x402-pay to verify real exact, partial upto, zero-charge
upto, and nonce-replay behavior on a disposable Base fork without spending real funds.