New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

settled-x402

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

settled-x402

Don't let your agent pay an x402 endpoint that doesn't deliver. Checks each quote against Settled's independent evidence (paid test purchases, payTo, payee, buyer reports) before it is signed, and screens every paid answer for instructions aimed at the ag

latest
npmnpm
Version
0.8.0
Version published
Weekly downloads
0
Maintainers
1
Weekly downloads
 
Created
Source

settled-x402

Check an x402 quote against independent evidence before your agent signs it.

settled-x402 asks Settled about every x402 payment before your agent signs it. Settled is an independent x402 index. It probes endpoints itself, buys from them with real USDC, and checks what comes back against each listing. For the payment in hand, the guard checks:

  • The payTo. Is it the one Settled's own probe gets from the same URL at about the same time? A quote that pays someone else is how a spoofed endpoint, or a man in the middle, gets paid.
  • Delivery. Did Settled's scout get real content back when it paid this endpoint, and did that content match the listing? The settlement transaction comes with the answer.
  • The seller. Has the payTo ever been used on Base? Does the seller run a clone farm? What did paying agents report?
  • The price. Is it higher than the one Settled was quoted?

Payments with a verdict you block, avoid by default, are stopped before anything is signed. The same guard holds every payment to the spending limits you set: a cap per call, a daily budget that stops the agent, and an "ask me above $X" dial. Under withSettled and guardGlobalFetch, every paid answer is also screened for instructions aimed at the agent before the agent reads it. Every result also carries the price's tier: common (free), rare (up to $1 a call) or legendary (over $1). The free check covers 25 calls a day; with renew: true the guard keeps a pass of its own, bought with the agent's wallet for $1 a month, and never runs out.

npm install settled-x402

Node 20 or later, no dependencies, ES modules (require() works on Node 20.19+ and 22.12+).

Set it up with @x402/fetch or @x402/axios

Wrap your x402 client in an x402HTTPClient and guard that. The guard then knows both the URL you requested and the exact option your client is about to pay:

import { x402Client, x402HTTPClient, wrapFetchWithPayment } from '@x402/fetch';
import { registerExactEvmScheme } from '@x402/evm/exact/client';
import { privateKeyToAccount } from 'viem/accounts';
import { settledGuard } from 'settled-x402';

const client = new x402Client();
registerExactEvmScheme(client, { signer: privateKeyToAccount(process.env.PRIVATE_KEY) });

const httpClient = settledGuard(new x402HTTPClient(client));
const fetchWithPayment = wrapFetchWithPayment(fetch, httpClient);

wrapAxiosWithPayment(axios.create(), httpClient) works the same way.

Give the agent a wallet of its own, funded with a small float of USDC, never your main wallet. The guard and its limits decide what the agent pays, but anyone who gets the key can move everything in the wallet.

When the guard blocks a payment, nothing is signed. The call rejects with an error whose message contains Payment creation aborted: Settled: avoid for <url>: <reasons>.

You can also guard the plain x402Client with settledGuard(client). The guard then sees only the URL the 402 names (resource.url), which the seller writes. It still checks that URL's payTo against Settled. But a seller can leave the URL out and get unknown, which isn't blocked unless you block unknown. Prefer the x402HTTPClient form.

With x402-fetch (v1) or any payment wrapper that takes a fetch

import { wrapFetchWithPayment } from 'x402-fetch';
import { withSettled } from 'settled-x402';

const fetchWithPayment = wrapFetchWithPayment(withSettled(fetch, { network: 'base' }), walletClient);

withSettled sits under the payment wrapper. When an endpoint answers 402 with an x402 quote, the quote is checked before the wrapper sees it. A blocked verdict throws SettledBlockedError, whose error.result holds the full result, so the wrapper never signs.

withSettled can't see which option the wrapper will pick, so it judges every option in the quote, from the header and from the body, and reports the riskiest. Set network to the networks your wallet pays on so that options on other chains don't count.

The paid answer, the retry that carries the payment, comes back through withSettled too, and that is where the response screen runs: the answer is handed back with x-settled-screen: clean | suspicious | not-scanned in its headers.

With Coinbase AgentKit

AgentKit's x402 actions build their own x402 client and pay through the global fetch, so there is no client to hand to settledGuard. Guard the global fetch instead, and give the agent Settled's actions:

npm install settled-x402 @coinbase/agentkit
import { AgentKit, x402ActionProvider } from '@coinbase/agentkit';
import { guardGlobalFetch } from 'settled-x402';
import { settledActionProvider } from 'settled-x402/agentkit';

const limits = { maxUsdPerCall: 1, dailyBudgetUsd: 5 }; // optional; see Spending limits
guardGlobalFetch({ network: 'base', ...limits }); // the network your wallet pays on

const agentkit = await AgentKit.from({
  walletProvider,
  actionProviders: [x402ActionProvider(), settledActionProvider(limits)],
});

guardGlobalFetch(options) puts withSettled under every fetch in the process. When make_http_request_with_x402 or retry_http_request_with_x402 gets a quote with a blocked verdict, the action fails with Settled: avoid for <url>: <reasons> before anything is signed. make_http_request fails the same way, so the agent finds out before it asks to pay. Anything that isn't a 402 with an x402 quote passes through untouched. Calling it again changes nothing, and the function it returns puts the original fetch back.

settledActionProvider(options) adds two actions. Neither pays anything.

ActionArgumentsReturns
SettledActionProvider_check_x402_endpointurl, method (GET or POST)The endpoint's quote, read without paying, with Settled's verdict and reasons, judged on the wallet's network
SettledActionProvider_find_x402_endpointsquery, maxPriceUsd, limitLive endpoints on the wallet's network, with price, quality score and whether Settled's test purchase delivered

It takes pass (or reads SETTLED_PASS from the environment, or uses the pass a renewing guard keeps in ~/.settled-x402/pass.json), network, timeoutMs, skipHosts, includeQuery, baseUrl and the spending limits (maxUsdPerCall, dailyBudgetUsd, askAbove, budgetFile, ledger). Give it the same limits as guardGlobalFetch, and the check action tells the agent whether a payment would_be_stopped or whether the operator is asked, before it tries; every result and every found endpoint carries its rarity. Without a pass, both actions count against the free calls (25 a day per address, 300 with a payer record).

Discovery

AgentKit's discover_x402_services reads a facilitator's /discovery/resources list. Settled serves that format at https://settled.tools/discovery/resources, limited to endpoints it has checked: live, priced in USDC, probed in the last 3 days, and not failing Settled's last test purchase. Endpoints Settled has bought from successfully come first, and each item's metadata.settled holds the evidence.

  • AgentKit 0.10 takes any URL as the facilitator. Tell the agent to use https://settled.tools.
  • Unreleased AgentKit (its main branch) only takes facilitators you register: x402ActionProvider({ registeredFacilitators: { settled: 'https://settled.tools' } }). The agent then asks for settled.

From n8n and other tools that shouldn't hold a key: the payment proxy

A wallet key pasted into a workflow tool can be read by more than the workflow that uses it. In n8n, a key in an environment variable can be read by any workflow on the instance, a key typed into a Code node by anyone who can open that workflow, and a community node that signs payments runs with full access to the instance. settled-x402 proxy keeps the key in one small process on your machine and does the paying. A tool sends the request it wants made to POST /pay with a bearer token. The proxy makes it with the standard x402 client under settledGuard, so Settled's check, the loadout and your limits all apply before anything is signed, and it screens the paid answer on the way back. The tool only ever holds the token. Whoever gets the token can spend up to your limits on the hosts you allow; whoever gets the key can take the whole wallet.

Make a wallet for the proxy, then start it:

mkdir -p ~/.settled-x402 && (umask 077; openssl rand -hex 32 > ~/.settled-x402/proxy-key)
npx -y -p settled-x402 -p @x402/fetch@2 -p @x402/evm@2 -p viem@2 settled-x402 proxy --max-usd-per-call 0.25 --daily-budget-usd 5

The first line makes a new private key that only your user can read. On start, the proxy prints the wallet's address: send it a small float of USDC on Base, an amount you'd be fine losing. The wallet needs no ETH, because an x402 payment is a signed authorization that the seller's facilitator submits. The proxy also prints its token the first time; it is kept in ~/.settled-x402/proxy-token.

curl -s http://127.0.0.1:4021/pay \
  -H "authorization: Bearer $(cat ~/.settled-x402/proxy-token)" \
  -H 'content-type: application/json' \
  -d '{"url": "https://api.example.com/paid", "method": "POST", "body": {"q": "hello"}}'

POST /pay takes url, method (default GET), headers and body (an object is sent as JSON). It answers:

StatusWhenWhat comes back
200The endpoint answered, paid or freeok and status (the endpoint's own), paid, payment (tx, network, payer), settled (verdict, reasons, price, payTo, spend), screen (verdict, findings), and the answer as json, body or body_base64
403Refusedblocked_by says what refused it. verdict, limit and client_cap stop the payment before anything is signed; loadout (a host outside --only-hosts) stops it before anything is requested. screen, only with --screen throw, refuses an answer that was already paid for, and paid and payment say so
400A bad requesterror: bad_url, bad_method, bad_request, or private_host (the proxy pays public endpoints only)
401No token, or the wrong one
502The endpoint couldn't be reached, or the payment failedmessage

GET /status (with the token) shows the wallet, the limits and today's spend; GET /health needs no token.

FlagDefault
--key-fileSETTLED_PROXY_KEY, else ~/.settled-x402/proxy-keyThe wallet key, hex. A file others can read gets a warning
--token-fileSETTLED_PROXY_TOKEN, else ~/.settled-x402/proxy-token (made the first time)The bearer token callers send
--port, --host4021, 127.0.0.1Any other host prints a warning: anyone who can reach the port and has the token can spend up to the limits
--max-usd-per-callthe x402 client's own cap, $1 a paymentStop any call priced over this
--daily-budget-usd5Stop once today's calls would add up to more than this; none lifts it, leaving the wallet balance as the only limit
--only-hostsany hostThe loadout, comma-separated: api.exa.ai,*.agent402.tools
--blockavoidVerdicts that stop a payment, comma-separated
--pass, --payer-tokenSETTLED_PASS, SETTLED_PAYER_TOKENAs for the guard (Free, or with a pass)
--screenflagthrow refuses a suspicious answer, off turns the screen off
--budget-fileSETTLED_BUDGET_FILE, else ~/.settled-x402/spend.jsonWhere the daily total is kept

The proxy pays with @x402/fetch, @x402/evm and viem, which settled-x402 doesn't depend on. The npx -p line fetches them; or install them next to it (npm install settled-x402 @x402/fetch@2 @x402/evm@2 viem@2) and run npx settled-x402 proxy. In code: import { createProxy } from 'settled-x402/proxy'.

In n8n, import Pay for an x402 API through the Settled proxy (Workflows → Import from URL): an HTTP Request node that posts to the proxy with a Header Auth credential (name Authorization, value Bearer <token>), and an IF that sends answers one way and refusals the other. settled.tools/loadout builds the proxy command from your limits and loot. n8n on the same machine reaches the proxy at http://localhost:4021, and n8n in Docker Desktop at http://host.docker.internal:4021. With Docker on Linux, or docker compose, run the proxy as a service on n8n's network with --host 0.0.0.0 and no published port (the loadout's Docker kit has the compose file). n8n Cloud can't reach a proxy on your machine.

Check one endpoint

import { check } from 'settled-x402';

const result = await check('https://api.example.com/paid', { quote: paymentRequired, network: 'base' });
result.verdict; // 'pay' | 'caution' | 'avoid' | 'unknown' | 'skipped'
result.reasons; // why, in plain words
result.options; // each option in the quote and how its payTo compares with Settled's record
result.rarity; // 'common' | 'rare' | 'legendary' | null: what a call costs, as loot
result.spend; // how the call stands against your spending limits (null when none is set)
result.blockedBy; // 'verdict' | 'limit' | 'operator' | null
result.details; // link to Settled's full answer

Without a quote, the guard can only report Settled's record for the URL. To have the quote read for you, use inspect. It requests the endpoint without paying (GET, or POST with { method: 'POST' }) and checks the 402 it gets back:

import { inspect } from 'settled-x402';

const result = await inspect('https://api.example.com/paid', { network: 'base' });
result.endpoint; // { url, httpStatus, error }: what the unpaid request got

From the command line

$ npx settled-x402 check https://api.bitrefill.com/x402/gift-cards/search
PAY · https://api.bitrefill.com/x402/gift-cards/search · rare loot (up to $1 a call)
  quote     $0.002 on base to 0x480C…846A (4 options)
  options   base: $0.002 to 0x480C…846A · payTo matches Settled
            (arbitrum: $0.002 to 0x480C…846A · same address as on Settled's network)
            (polygon: $0.002 to 0x480C…846A · same address as on Settled's network)
            (solana: $0.002 to 5yASLj…NgRn · not on record at Settled)
            the verdict covers the options on base; pick another with --network
  Settled   live, delivered · $0.002 on base to 0x480C…846A · scout's last purchase 2026-10-01 (tx 0x62cd…5a29)
  why       payTo matches what Settled sees for this endpoint (0x480C…846A)
            a real payment by Settled's scout came back with content, tx 0x62cd…5a29
  details   https://settled.tools/v1/free/check?url=https%3A%2F%2Fapi.bitrefill.com%2Fx402%2Fgift-cards%2Fsearch

The command reads the endpoint's 402 quote without paying, then asks Settled about it.

  • Network: by default the verdict covers the options on the network Settled has on record. Use --network solana to judge another.
  • Exit codes: 0 pay, 2 caution, 3 avoid, 4 unknown, 1 usage or internal error.
  • Other flags: --json prints the full result; --post reads a POST endpoint's quote.

npx settled-x402 spend prints today's tally from the ledger a guard with dailyBudgetUsd keeps (--budget-file <path> for one elsewhere): what was let through, call by call, with each call's rarity. npx settled-x402 pass shows the pass a guard with renew keeps: its term, when it runs out, when it was bought. check uses that pass when no --pass or SETTLED_PASS is given.

npx settled-x402 screen <file> (or on stdin) runs the response screen on any text: the findings with a sample of each, exit code 3 when suspicious, --json for the full result.

Settled's MCP server over stdio

For Claude Desktop, Cursor and other clients that start MCP servers as a command:

{
  "mcpServers": {
    "settled": { "command": "npx", "args": ["-y", "settled-x402", "mcp"] }
  }
}

With a pass, add "env": { "SETTLED_PASS": "<pass>" }; the pass a renewing guard keeps on the same machine is used without that. Clients that support remote MCP servers can connect to https://settled.tools/mcp directly. In Claude Code that's claude mcp add --transport http settled https://settled.tools/mcp.

Give your agent the skill

The package ships the settled-x402 Agent Skill: the same rules as the guard, written for an agent that reads instructions rather than calling code. It tells the agent to run Settled's free check before any x402 payment it hasn't made successfully before, walk a short decision table (sanctioned payee, seller on hold, dead status, payTo mismatch with the quote in hand, price drift, verified delivery, quality), preflight when the money matters, report afterwards, and say why. Three files: SKILL.md, references/api.md and scripts/settled_check.py (standard-library Python, prints a one-line decision).

npx settled-x402 skill install            # copies it into ./.claude/skills/settled-x402
npx settled-x402 skill install --global   # into ~/.claude/skills, for every project
npx settled-x402 skill install --to <dir> # into any skills folder
npx settled-x402 skill                    # prints where the files are: node_modules/settled-x402/skills/settled-x402
npx settled-x402 skill show               # prints SKILL.md

Claude Code and the Claude Agent SDK load skills from .claude/skills/; Claude Managed Agents, OpenClaw and other harnesses take the folder or the files. The skill expects the Settled MCP server or plain HTTP (GET https://settled.tools/v1/free/check?url=...), so it works without this package's code too. Claude Code users can instead install the plugin, which adds the skill and the MCP server together: /plugin marketplace add justinedwardvolmer/settled-skill, then /plugin install settled@settled.

Verdicts

VerdictWhat it meansBy default
payThe payTo matches what Settled sees, and nothing on record is against itpaid
cautionWorth a look, not damning. Examples: a price above what Settled was quoted, a different address on another EVM chain, no on-chain history for the payTo, a paid call that didn't match its listing, failing probespaid
avoidStrong evidence against paying: a payTo on Settled's network and asset that isn't the one Settled sees, a scout payment that came back empty, a seller on hold (it took the scout's payment twice in 30 days and delivered nothing), a clone farm, or most paying agents reporting failure. With a pass it also covers honeypot listings and circular paymentsblocked
unknownNothing to judge by. Settled has no quote for this network or URL, can't be reached, or your free checks are used uppaid
skippedNot sent to Settled: payments to settled.tools itself, skipHosts, and local or private addressespaid, never blocked

To block more, pass block, for example settledGuard(httpClient, { block: ['avoid', 'caution'] }). Add 'unknown' to fail closed when Settled can't vouch for a payment.

Spending limits

The verdict is about the seller. The limits are about your money, and the same guard enforces both, where the payment is about to be signed:

const pay = wrapFetchWithPayment(fetch, settledGuard(httpClient, {
  maxUsdPerCall: 1, // never sign a call priced over $1
  dailyBudgetUsd: 5, // stop once today's calls would add up to more than $5
  askAbove: 0.25, // ask before paying more than 25¢ for one call
  ask: async (q) => operatorSaysYes(`Pay $${q.priceUsd} (${q.rarity}) to ${q.url}? $${q.remainingUsd} of today's budget left.`),
}));
OptionWhat it does
maxUsdPerCallA call priced over this is stopped. 0 pays nothing: free endpoints only
dailyBudgetUsdA call that would take today's total (UTC) over this is stopped. The total counts what the guard let through, which is the most that can have been paid. It resets at 00:00 UTC
askAboveThe softer dial. A call priced over this is put to the operator first; 0 asks about every paid call. With ask, your function gets the call and answers true or false. Without it, the guard asks on the terminal when the process has one (askTimeoutMs, default 2 minutes; silence is a no), and otherwise stops the call, so a headless agent with askAbove and no ask pays only below the line
budgetFileWhere the daily total lives, so a restart doesn't reset it: default SETTLED_BUDGET_FILE, else ~/.settled-x402/spend.json. false keeps it in memory
ledgerYour own store for the total (load() and save()), for agents that run as several processes
onlyHostsThe loadout: the only hosts the agent may pay, such as ['api.exa.ai', '*.agent402.tools']. A quote from anywhere else is stopped whatever Settled says (blockedBy: 'loadout'); settled.tools itself stays payable, so a pass still goes through

Build one at settled.tools/loadout: pick the runtime, set the limits, choose the services as loot, copy the kit.

A stopped call fails the same way a blocked verdict does: settledGuard aborts the payment, withSettled and guardGlobalFetch throw SettledBlockedError, and the message leads with what stopped it (Settled: over your daily budget for <url>: $2.50 would take today's spend to $6.00, over your dailyBudgetUsd ($5.00; $3.50 spent so far); the budget resets at 00:00 UTC). The error's blockedBy is 'limit', 'operator' or 'loadout', and spend says which limit, what the call cost and what is left today. check and inspect apply the limits too, without counting or asking: an agent that checks first learns the payment would be stopped (spend.decision), or that the operator is asked ('ask').

Three things to know. A quote whose price the guard can't read in USD (an asset it doesn't know) can't be held to a limit, so with any limit set it is stopped. The daily total is kept per ledger: guards with the same budgetFile share it (within one process their reads and writes are queued; across processes a call let through at the same instant by another process can be missed, so give agents that run as several processes a ledger of their own), and guards with memory ledgers each keep their own. And a quote with several options is held to its most expensive one when the guard can't tell which will be paid (withSettled, guardGlobalFetch); settledGuard knows the selected option and holds the payment to exactly that price.

@x402/core has a cap of its own, spendControls.maxAmountPerPayment, $1 per payment by default. It is a cap per payment on the client; it has no daily total, no asking and no verdict, and the other clients don't have it. Both can be on at once.

Price tiers

Every result says what a call costs as a tier, so an agent choosing between endpoints, a log line or a dashboard can use one word for the cost class instead of a number:

TierPrice per callColor
commonfreegrey
rareup to $1blue
legendaryover $1gold

The line at $1 is where the x402 client's own default cap sits (maxAmountPerPayment: "$1"), so a legendary call is one the client would refuse out of the box. It is also a caution on its own ("it costs $2.50 per call"), and askAbove: 1 is the dial that makes the operator confirm every one. The tier is result.rarity (from the quote's price, else Settled's recorded price; an endpoint Settled gets for free is common), rarityOf(priceUsd) for any price, and RARITIES for labels and colors. The names come from the loadout builder, which shows an agent's services as loot; the API's /v1/loot?tier= uses the same three.

What the payTo check can and can't catch

The guard compares the option you're paying with what Settled's prober gets from the same URL at about the same time. Settled reuses a probe that is under 5 minutes old; otherwise it probes while you wait.

It catches:

  • A quote that pays someone other than the one Settled sees on the same network, in any asset, including a different address hidden next to the right one.
  • A quote whose header and body disagree.
  • A 402 that claims to be a settled.tools route but pays someone else.

It can't catch:

  • The endpoint itself switching its payTo for everyone. Settled sees the switch too. The scout's receipts were for the old address.
  • Options on networks Settled has no quote for. Settled records the cheapest option of each endpoint. On other EVM chains the guard compares addresses: a different address is caution, because honest sellers do this. In a sample of 385 live x402 quotes, 13 of the 93 sellers offering several EVM chains used a different address on one of them. On other chains, such as Solana when Settled recorded Base, the payTo can't be compared, so the verdict is unknown.

A seller that honestly uses different addresses for different assets on one network will get avoid for the asset Settled didn't record.

After a seller legitimately changes its payTo, payments can be blocked for up to about 10 minutes, until both caches refresh.

Selling an API? Ask agents for proof with x401

settled-x402/x401 lets a server ask callers for a Settled payer record before serving them: a signed credential that shows facts from the agent's real on-chain payment history (for example, active on 3 or more of the last 30 days, and not on the sanctions list) and keeps the rest hidden. Use it to give free calls only to agents that pay their way, without logins or API keys.

import { requirePayerRecord } from 'settled-x402/x401';

const gate = requirePayerRecord({
  origin: 'https://api.example.com',      // your public origin: proofs are bound to it
  secret: process.env.X401_SECRET,        // 32+ characters; signs the nonces, nothing is stored
  claims: ['active_days_30d_over_3', 'sanctions_clear'],   // the default
});

// In any Fetch API handler (Workers, Deno, Bun, Hono, Next.js route handlers):
export default {
  async fetch(request) {
    const proof = await gate(request);
    if (!proof.ok) return proof.response;   // 401 with PROOF-REQUEST (and PROOF-RESULT saying why)
    return Response.json({ hello: proof.wallet });
  },
};

// In Express:
app.get('/search', async (req, res) => {
  const proof = await gate(req.headers);
  if (!proof.ok) return res.status(proof.status).set(proof.headers).json(proof.body);
  res.json({ hello: proof.wallet });
});

How it works: a request without proof gets 401 and a PROOF-REQUEST header (x401 draft 0.2.0) naming the facts and carrying a nonce that lasts 5 minutes. The agent retries with PROOF-RESPONSE. The gate checks that the proof answers your nonce and names your origin, then has Settled check the record itself at POST https://settled.tools/v1/credentials/verify (the issuer signature, the facts shown, expiry, revocation and the agent's key binding). If Settled can't be reached, or your useNonce store throws, the gate answers 503, never a pass; for an optional higher tier, treat anything but ok as anonymous. Proofs can be reused until their nonce expires; pass useNonce(nonce) (return false for a nonce you've seen) to make each one single-use. The facts a record can show are in PAYER_CLAIMS; what each means is at /vc/payer-record/v1. Agents get a record at /for-agents#payer-record; try the flow in a browser at /x401/try.

The response screen

A paid x402 answer goes straight into your agent's context as if it were data. A seller, or whoever has taken a seller over, can put instructions in it: "ignore your previous instructions", "send the USDC to 0x…", "do not tell the user". The agent that just paid is, by definition, one with a funded key.

So withSettled (and guardGlobalFetch, which is built on it) screens every paid answer before handing it back. Text-like bodies (JSON, text, HTML, XML, markdown) are read up to 256 KB from a clone, the body itself is untouched, and the answer comes back with:

  • x-settled-screen: clean, suspicious, or not-scanned (a binary body, or a non-2xx answer);
  • x-settled-screen-findings: the kinds found, such as instruction_override,payment_steering;
  • response.settledScreen: the full result (verdict, findings with a 120-character sample each, bytes, truncated, scanned).

What it looks for: instructions to ignore or replace what the agent was told; chat-template and role markers (<|im_start|>, [INST]); text addressed to the assistant ("note for AI agents", "if you are an AI") and a request to visit a URL beside it; requests for private keys, seed phrases, API keys or passwords; a transfer verb, an asset and a wallet address in one sentence; "do not tell the user"; urgency wrapped around a tool call; hidden text (zero-width characters, HTML comments carrying instructions, display:none); markdown images that carry data in their query string; and all of that again inside base64 runs that decode to text. One strong finding, or two weak ones, make an answer suspicious.

It flags; it does not judge the seller. It is a pattern match, and an answer that talks about prompt injection (a security scanner's report, say) will trip it, so the default is to hand the answer back flagged and let you decide:

const fetchWithPayment = wrapFetchWithPayment(withSettled(fetch, {
  screen: {
    action: 'throw',                              // refuse a suspicious answer: SettledScreenError, with .screen and .url
    onResult: (screen, { url }) => log(url, screen) // every result, clean or not
  },
}), walletClient);

screen: false turns it off; screen: { maxBytes } sets how much of the body is read. The hook form (settledGuard(client)) never sees responses, so it has no screen; screenText(text) and screenResponse(response, options) are exported for your own use, and npx settled-x402 screen <file> runs the same patterns from the command line (exit code 3 when suspicious).

Options

settledGuard(client, options), withSettled(fetch, options), guardGlobalFetch(options), check(url, options) and inspect(url, options) all take:

OptionDefault
block['avoid']Verdicts that stop the payment
passnoneA Settled pass (a day, 30 days or a year): switches to /v1/preflight and removes the daily cap
renewoffKeep a pass going without a human: true (a 30-day pass, at most $1), a term ('day', 'month', 'year') or { pass, upToUsd, passFile, store, onRenew, onError, pay } (details). Guards only; check and inspect use pass
payerTokennoneThe agent's payer record as a token (x4t_… from POST /x401/token, 24 hours): the free check counts the client by wallet, 300 a day instead of 25 per address. A refused token falls back to an anonymous check with a note; a pass takes precedence
networkallNetworks your wallet pays on, such as 'base' or ['base', 'eip155:137'], or 'auto' for the one Settled has on record. Used only when the selected option isn't known (withSettled, check). Without it, an endpoint that also offers a chain Settled has no quote for, like Bitrefill's Solana option, comes out unknown
onVerdictnoneCalled with every result, blocked or not. It isn't awaited
timeoutMs8000How long to wait for Settled. A timeout gives unknown
cacheTtlMs300000How long an answer is reused for the same URL
skipHosts[]Hosts never sent to Settled, such as 'api.mycompany.com' or '*.mycompany.com'
includeQueryfalseAlso send the URL's query string to Settled
allowPrivateHostsfalseAlso send localhost and private-network URLs. Only useful with your own baseUrl
fetchglobalThis.fetchThe fetch used to reach Settled. Pass one that uses your proxy if your agent needs one
baseUrlhttps://settled.tools
maxUsdPerCallnoneStop any call priced over this many dollars (Spending limits)
dailyBudgetUsdnoneStop once today's calls would add up to more than this
askAbovenoneAsk the operator before paying more than this for one call
askterminal promptWho to ask: (question) => boolean, with url, priceUsd, rarity, remainingUsd and the full result
askTimeoutMs120000How long the terminal prompt waits; silence is a no
budgetFileSETTLED_BUDGET_FILE, else ~/.settled-x402/spend.jsonWhere the daily total is kept; false for memory only
ledgerthe fileYour own store for the total: { load(), save(state) }
onlyHostsany hostThe loadout: the only hosts the agent may pay; everything else is stopped (settled.tools stays payable)
screentrueThe response screen on every paid answer (withSettled and guardGlobalFetch only): true flags, { action: 'throw', maxBytes, onResult } refuses or sizes it, false turns it off

inspect also takes method ('GET' or 'POST'), body (a string; default '{}' for POST) and quoteTimeoutMs (default 15000).

Free, or with a pass

Without a pass, the guard uses Settled's free check:

  • 25 checks a day for an anonymous client, counted by IP address and reset at 00:00 UTC; 300 a day for an agent that shows a Settled payer record (free, counted by wallet; https://settled.tools/for-agents#payer-record).
  • To show the record from the guard, pass payerToken: a token from POST https://settled.tools/x401/token with the record (it lasts 24 hours and is bound to the wallet). The guard sends it as Authorization: Bearer on each free check; result.payerRecord says whether Settled accepted it, and a refused token falls back to an anonymous check rather than losing the verdict. In AgentKit, SETTLED_PAYER_TOKEN in the environment does the same.
  • Those are shared with Settled's free MCP tools and /v1/free/endpoints. Every free answer says how many are left (free.left_today) and, near the cap, how to continue.
  • At most 30 checks a minute.

When the free checks run out, verdicts are unknown and payments go through unless you block unknown.

A pass removes the daily cap, allows 120 requests a minute and switches the guard to Settled's preflight, which adds seller settlement-integrity and honeypot checks. It is paid in USDC over x402, and there are three terms (terms of sale):

TermRoutePrice
a dayPOST /v1/pass$0.05
30 daysPOST /v1/pass/month$1
a yearPOST /v1/pass/year$10

A pass that renews itself

The simplest way to hold one is to let the guard buy it:

const httpClient = settledGuard(new x402HTTPClient(client), { renew: true });

Before the first check, and again an hour before each pass runs out, the guard buys a 30-day pass with the client it guards: it POSTs to /v1/pass/month, reads the price in Settled's 402, and signs only if that price is within renew.upToUsd, which is the term's list price unless you set it (renew: { upToUsd: 1.5 } says: keep renewing through a price rise up to $1.50; a 402 above it is not paid, the guard carries on with the free check and onError says why). The pass is kept in SETTLED_PASS_FILE, else ~/.settled-x402/pass.json, so a restart does not buy another one, and npx settled-x402 pass shows it. A pass Settled refuses (expired, invalid) is forgotten and the next check buys a new one. A pass you give by hand alongside renew is used first, until Settled refuses it. Several checks arriving at once share one purchase; a failed purchase is retried after 10 minutes.

That purchase is the guard's own. It is held to renew.upToUsd and to nothing else: not your onlyHosts, not maxUsdPerCall, not the daily budget, which are for the agent's spending. result.renew on every result says which term is kept, when the pass in hand expires and the last renewal error, if any; onRenew is called once per purchase with the pass, its expiry and the price.

settledGuard(httpClient, {
  renew: {
    pass: 'year',                 // 'day' | 'month' (default) | 'year'
    upToUsd: 12,                  // the most to pay for one term; default: the term's list price
    passFile: '/var/lib/agent/settled-pass.json', // or false for memory only; or `store: { load(), save(state) }`
    onRenew: (r) => log.info(`Settled pass renewed until ${r.expiresAt} for $${r.priceUsd}`),
    onError: (e) => log.warn(e.message),
  },
});

Under withSettled and guardGlobalFetch there is no client to pay with, so give renew.pay, the fetch that pays (your fetchWithPayment); the guard sees that purchase's 402 like any other and lets it through within the cap. A year pass costs $10 in one payment, over the x402 client's default cap of $1 per payment, so for pass: 'year' with @x402/core raise spendControls.maxAmountPerPayment (or set spendControls: false); otherwise the client refuses the purchase and onError carries its message.

storedPass() returns the pass a renewing guard keeps on the machine, while it is valid, for code that cannot renew but should use it; the CLI, the MCP bridge and the AgentKit provider do that on their own. To buy a pass yourself instead, with the client you already have:

const res = await fetchWithPayment('https://settled.tools/v1/pass/month', { method: 'POST' });
const { pass, expires_at } = await res.json();
const guarded = settledGuard(new x402HTTPClient(client), { pass });

What is sent to Settled

Sent: only the origin and path of the endpoint you're about to pay, with no query string, fragment or credentials, plus your IP address as with any request. Your keys, the payment and your request body are never sent. Local and private-network addresses are never sent.

The AgentKit search action also sends its keyword, network and price limit. inspect and the AgentKit check action read the quote from the endpoint directly, not through Settled.

Added to Settled's public index: an endpoint Settled hasn't seen before. Settled probes it itself, then adds it. Put the hosts of endpoints you'd rather keep out in skipHosts.

Signed: Settled's answers are signed (EIP-191). result.answer holds the answer, which you can verify at https://settled.tools/v1/verify or with any EVM library.

Limits of the evidence

  • Delivery: Settled's scout buys from each seller in turn, usually that seller's cheapest endpoint: sellers listed up to $0.25 about once a week, sellers whose endpoints all cost $0.25 to $1.00 about once every 60 days, and nothing over $1.00. So delivery evidence covers at least one endpoint per seller, not every endpoint, can be weeks old for dearer sellers, and does not exist for endpoints over $1.00.
  • Not advice: the data is observational and best-effort, and it is not financial advice. If you think a verdict is wrong, tell @settledfyi.

License

MIT

Keywords

x402

FAQs

Package last updated on 26 Oct 2026

Related posts