settled-x402 (Python)
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. Every result also carries the price's loot rarity: common (free), rare (up to $1 a call) or legendary (over $1).
This is the Python twin of the npm package settled-x402: the same rules, the same verdicts, the same messages, and the same spend ledger file, so an agent written in either language gets the same answer.
pip install settled-x402
Python 3.10 or later. No dependencies of its own; it reaches Settled with the standard library. The guard works with the x402 package 2.x (pip install "x402[clients]").
Set it up with the x402 client
Wrap your x402 client in an x402HTTPClient (or x402HTTPClientSync) and guard that. The guard then knows both the URL you requested and the exact option your client is about to pay:
from x402.client import x402Client
from x402.http.x402_http_client import x402HTTPClient
from x402.http.clients.httpx import x402HttpxClient
from x402.mechanisms.evm.exact import register_exact_evm_client # your scheme, as in x402's docs
from settled_x402 import settled_guard
client = x402Client()
register_exact_evm_client(client, signer)
http = settled_guard(x402HTTPClient(client), max_usd_per_call=1, daily_budget_usd=5)
async with x402HttpxClient(http) as session:
res = await session.get("https://api.example.com/paid")
The sync client works the same way with requests:
from x402.client import x402ClientSync
from x402.http.x402_http_client import x402HTTPClientSync
from x402.http.clients.requests import x402_requests
from settled_x402 import settled_guard, SettledBlockedError
http = settled_guard(x402HTTPClientSync(client), max_usd_per_call=1)
session = x402_requests(http)
try:
res = session.get("https://api.example.com/paid")
except SettledBlockedError as e:
print(e) # Settled: avoid for https://api.example.com/paid: this quote's payTo on base (0x0000…dEaD) is not the payTo Settled sees ...
print(e.result.verdict, e.blocked_by, e.result.reasons)
When the guard blocks a payment, nothing is signed. The call raises SettledBlockedError, which is also x402's PaymentAbortedError and the PaymentError of x402's httpx transport and requests adapter, so handlers you already have catch it. error.result holds the full result.
You can also guard the plain x402Client with settled_guard(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.
When a toolkit builds its own x402 client
Agent frameworks' x402 tools often create the client themselves and never hand it to you. guard_all() guards every x402 client created after it is called, with the same options, and returns a function that undoes it:
from settled_x402 import guard_all
guard_all(max_usd_per_call=0.25, daily_budget_usd=5, only_hosts=["api.exa.ai", "*.agent402.tools"])
# ... build the agent; every x402Client / x402ClientSync it makes is guarded
For an httpx stack with no client object in reach at all, SettledAsyncTransport / SettledTransport sit under x402's transport and read the quote out of the 402 before the payment layer sees it. They can't tell which option will be paid, so they judge every option, report the riskiest, and hold the most expensive option to your limits:
import httpx
from x402.http.clients.httpx import x402_httpx_transport
from settled_x402 import SettledAsyncTransport
inner = SettledAsyncTransport(network="base", max_usd_per_call=1)
session = httpx.AsyncClient(transport=x402_httpx_transport(client, transport=inner))
Check one endpoint
check asks Settled about a URL and compares a quote you already have. Nothing is paid. inspect reads the endpoint's 402 for you first (GET by default), then checks it:
from settled_x402 import check, inspect, acheck, ainspect
r = check("https://api.example.com/paid", quote=payment_required) # payment_required: the 402's body/header, dict or x402 model
r = inspect("https://api.example.com/paid", method="POST")
print(r.verdict, r.blocked, r.rarity, r.reasons)
print(r.quote.pay_to, r.settled.pay_to, r.options) # the comparison, option by option
print(r.to_dict()) # JSON-ready
acheck and ainspect are the same off the event loop. Both apply your limits without counting or asking, so an agent that checks first learns whether a payment would be stopped (r.spend.decision) or the operator asked ("ask").
From the command line
settled-x402 check https://api.example.com/paid # reads the 402 (nothing is paid) and asks Settled
settled-x402 check https://api.example.com/paid --post --json
settled-x402 check https://api.example.com/paid --network base
settled-x402 spend # today's tally from the spend ledger
python -m settled_x402 check <url> # the same without the console script
PAY · https://x402.ottoai.services/whois-lookup · rare loot (up to $1 a call)
quote $0.001 on base to 0x0E84…b808 (2 options)
options base: $0.001 to 0x0E84…b808 · payTo matches Settled
(solana: $0.001 to 6XcSfq…DPN5 · not on record at Settled)
Settled live, delivered · $0.001 on base to 0x0E84…b808 · scout's last purchase 2026-10-02 (tx 0xf17c…9b60)
why payTo matches what Settled sees for this endpoint (0x0E84…b808)
a real payment by Settled's scout came back with content, tx 0xf17c…9b60
details https://settled.tools/v1/free/check?url=https%3A%2F%2Fx402.ottoai.services%2Fwhois-lookup
Exit codes: 0 pay, 2 caution, 3 avoid, 4 unknown, 1 usage or internal error. Environment: SETTLED_PASS, SETTLED_PAYER_TOKEN, SETTLED_BASE_URL, SETTLED_ALLOW_PRIVATE_HOSTS=1, SETTLED_BUDGET_FILE.
Verdicts
| Verdict | What it means | By default |
|---|---|---|
pay |
The payTo matches what Settled sees, and nothing on record is against it | paid |
caution |
Worth 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 probes | paid |
avoid |
Strong 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 payments | blocked |
unknown |
Nothing to judge by. Settled has no quote for this network or URL, can't be reached, or your free checks are used up | paid |
skipped |
Not sent to Settled: payments to settled.tools itself, skip_hosts, and local or private addresses |
paid, never blocked |
To block more, pass block, for example settled_guard(http, 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:
settled_guard(http,
max_usd_per_call=1, # never sign a call priced over $1
daily_budget_usd=5, # stop once today's calls would add up to more than $5
ask_above=0.25, # ask before paying more than 25¢ for one call
ask=lambda q: operator_says_yes(f"Pay ${q.price_usd} ({q.rarity}) to {q.url}? ${q.remaining_usd} of today's budget left."),
)
| Option | What it does |
|---|---|
max_usd_per_call |
A call priced over this is stopped. 0 pays nothing: free endpoints only |
daily_budget_usd |
A 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 |
ask_above |
The 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 (SpendQuestion) and answers True or False; with the async client, ask may be a coroutine function and runs on your event loop. Without it, the guard asks on the terminal when the process has one (ask_timeout_s, default 120; silence is a no), and otherwise stops the call, so a headless agent with ask_above and no ask pays only below the line |
budget_file |
Where the daily total lives, so a restart doesn't reset it: default SETTLED_BUDGET_FILE, else ~/.settled-x402/spend.json, the same file the npm package uses, so a Node guard and a Python guard on one machine share one tally. False keeps it in memory |
ledger |
Your own store for the total (an object with load() and save(state)), for agents that run as several processes |
only_hosts |
The 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 (blocked_by == "loadout"); settled.tools itself stays payable, so a day pass still goes through |
A stopped call raises SettledBlockedError like a blocked verdict, 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 daily_budget_usd ($5.00; $3.50 spent so far); the budget resets at 00:00 UTC). The error's blocked_by is "limit", "operator" or "loadout", and spend says which limit, what the call cost and what is left today. spent_today() reads the tally, and settled-x402 spend prints it.
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 budget_file share it (within one process their reads and writes are serialised; 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 (the transports); settled_guard knows the selected option and holds the payment to exactly that price.
The x402 client has a cap of its own, set_spend_controls({"max_amount_per_payment": "$1"}), $1 per payment by default. It is a cap per payment on the client; it has no daily total, no asking and no verdict. Both can be on at once.
Loot rarity
Every result says what a call costs as a tier, for loadouts, logs and agents choosing between endpoints:
| Rarity | Price per call | Color |
|---|---|---|
common |
free | grey |
rare |
up to $1 | blue |
legendary |
over $1 | gold |
It is result.rarity (from the quote's price, else Settled's recorded price; an endpoint Settled gets for free is common), rarity_of(price_usd) for any price, and RARITIES for labels and colors. Legendary calls are also a caution on their own ("it costs $2.50 per call"), and ask_above=1 is the dial that makes the operator confirm every legendary call.
Options
settled_guard(client, **options), guard_all(**options), SettledAsyncTransport(**options), check(url, quote=None, **options) and inspect(url, **options) all take:
| Option | Default | |
|---|---|---|
block |
["avoid"] |
Verdicts that stop the payment |
pass_token |
none | A Settled day pass: switches to /v1/preflight and removes the daily cap |
payer_token |
none | The 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 |
network |
all | Networks 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 (the transports, check). Without it, an endpoint that also offers a chain Settled has no quote for comes out unknown |
on_verdict |
none | Called with every result, blocked or not; its errors are ignored |
timeout_s |
8 |
How long to wait for Settled. A timeout gives unknown |
cache_ttl_s |
300 |
How long an answer is reused for the same URL |
skip_hosts |
[] |
Hosts never sent to Settled, such as "api.mycompany.com" or "*.mycompany.com" |
include_query |
False |
Also send the URL's query string to Settled |
allow_private_hosts |
False |
Also send localhost and private-network URLs. Only useful with your own base_url |
fetch |
urllib | How to reach Settled: fetch(url, headers, timeout_s) -> (status, headers, text). Pass your own to go through a proxy |
base_url |
https://settled.tools |
|
max_usd_per_call |
none | Stop any call priced over this many dollars (Spending limits) |
daily_budget_usd |
none | Stop once today's calls would add up to more than this |
ask_above |
none | Ask the operator before paying more than this for one call |
ask |
terminal prompt | Who to ask: ask(question) -> bool, with url, price_usd, rarity, remaining_usd and the full result |
ask_timeout_s |
120 |
How long the terminal prompt waits; silence is a no |
budget_file |
SETTLED_BUDGET_FILE, else ~/.settled-x402/spend.json |
Where the daily total is kept; False for memory only |
ledger |
the file | Your own store for the total: load(), save(state) |
only_hosts |
any host | The loadout: the only hosts the agent may pay; everything else is stopped (settled.tools stays payable) |
inspect also takes method ("GET" or "POST"), body (a string; default "{}" for POST) and quote_timeout_s (default 15).
Free, or with a day 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
payer_token: a token fromPOST https://settled.tools/x401/tokenwith the record (it lasts 24 hours and is bound to the wallet). The guard sends it asAuthorization: Beareron each free check;result.payer_recordsays whether Settled accepted it, and a refused token falls back to an anonymous check rather than losing the verdict. - 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 day pass costs $0.05 in USDC over x402. It lasts 24 hours and allows 120 requests a minute with no daily cap. It switches the guard to Settled's preflight, which adds seller settlement-integrity and honeypot checks. Buy one with the client you already have:
res = session.post("https://settled.tools/v1/pass")
http = settled_guard(x402HTTPClientSync(client), pass_token=res.json()["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. inspect reads 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 skip_hosts.
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
Metadata
Release files for settled-x402 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| settled_x402-0.1.0.tar.gz | 51.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| settled_x402-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 93.9 kB
Release files / settled_x402-0.1.0.tar.gz
| Download URL | settled_x402-0.1.0.tar.gz |
|---|---|
| Size | 51.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
066a566fb95d61a77dfd50116b620ab7c275327f6ae538867b65f235990796be
|
|
BLAKE2b-256 checksum How to use checksums |
de7a952cfbccfe108bb2d2d7f7083fd72606d613508364cd65e24b2537222e4a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / settled_x402-0.1.0-py3-none-any.whl
| Download URL | settled_x402-0.1.0-py3-none-any.whl |
|---|---|
| Size | 42.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
23d7353452baeaf2ea6fabbc1a424e30ace0b169e5359036c57cd07b91601890
|
|
BLAKE2b-256 checksum How to use checksums |
acaceae879661b7b1ce417659e1d39e67c198c2e982437af983eb57452d6294b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Security for settled-x402 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.