Pay for x402 (HTTP 402) APIs privately over SubEtha's zERC20 rail — Python payer client
Project description
subetha (Python) — private x402 payments for Python agents
Pay for x402 (HTTP 402) paywalled APIs over SubEtha's zERC20 rail so that no on-chain trail links the payer to the payee: the burn itself is public (amounts stay transparent), but nothing on-chain connects it to the provider's treasury. This is the Python counterpart of the TypeScript agent tools (docs/AGENT-TOOLS.md): the same wire protocol (docs/PROTOCOL.md — this package is a reference implementation written against that spec), the same spending-policy semantics.
Status
Experimental (0.x). The API may change in breaking ways between 0.x minor
releases while integrations (agent frameworks, plugins) shape it. If you depend on
this package, pin a minor: subetha>=0.1,<0.2. Stability will be declared at
1.0. Changes are recorded in
CHANGELOG.md.
Scope
This package is the payer side only: decode a 402 offer, decide under an operator-configured spending policy, sign, pay, report. That is a deliberate, stable boundary — it is also what the API-stability promise will cover at 1.0.
The provider, facilitator, and settlement layers are TypeScript and live in the
same repository; there is no plan to
port them. Paying touches no zk code and no BUSL code: this package depends only on
httpx and eth-account.
Install
pip install subetha
For development (from a repository checkout):
uv pip install -e python/ # from the repository root
# or: pip install -e python/
Dependencies: httpx, eth-account (no web3.py). Python ≥ 3.11.
Use
from subetha import SubethaClient, SpendingPolicy
client = SubethaClient(
private_key="0x…", # the paying account (permit: only signs, no gas)
rpc_url="http://127.0.0.1:8545",
mode="permit", # gasless (default); or "self-transfer"
policy=SpendingPolicy(
allowed_hosts=["127.0.0.1"], # first line of defense — keep it tight
max_per_payment=100_000, # token base units
max_total=5_000_000,
),
approve_above=10_000, # optional human-in-the-loop
approve_payment=lambda info: ask_human(info), # True = approve
)
q = client.quote("http://127.0.0.1:4031/api/complete", method="POST", body='{"prompt":"hi"}')
print(q.offer.quoted, q.offer.fee, q.offer.approval_required)
r = client.pay("http://127.0.0.1:4031/api/complete", method="POST", body='{"prompt":"hi"}')
print(r.status, r.payment.amount, r.resource)
print(client.report()) # totals / payments / attempts (incl. approvals)
SubethaClient.from_env() reads the same SUBETHA_* variables as the TypeScript tools
(SUBETHA_PAYER_PK, SUBETHA_RPC_URL, SUBETHA_MODE, SUBETHA_ALLOWED_HOSTS,
SUBETHA_ALLOWED_NETWORK, SUBETHA_ALLOWED_TOKEN, SUBETHA_MAX_PER_PAYMENT,
SUBETHA_MAX_TOTAL, SUBETHA_APPROVE_ABOVE, SUBETHA_APPROVAL_TIMEOUT_MS,
SUBETHA_TIMEOUT_MS).
Semantics you can rely on
- Policy before money: host allowlist before any request; per-payment / total caps
and network/token pins at offer selection. Refusals raise
SubethaErrorwith the reason; nothing is paid. - Approval: payments quoted above
approve_abovecallapprove_payment(bounded byapproval_timeout_s, default 120 s). Decline / timeout / a raising callback / no callback ⇒ refused. The approval is bound to the exact offer. - Accounting at the point of no return: a self-transfer burn is counted the moment it lands; a permit reservation is released only when settle is definitively rejected.
- One payment at a time; errors are redacted (the key never appears in messages).
mode="permit"fail-fasts when the offer lacks the gasless path — it never silently degrades to a gas-paying transfer.
Key management & safety
This client signs with a raw private key. Treat that as the threat model:
- At runtime the key exists decrypted in process memory. Encrypted config or secret stores protect the key at rest — anything that can read your agent process (or its crash dumps) can read the key. Plan accordingly.
- Use a dedicated hot wallet. Fund the paying account with small amounts, top it
up as needed, and keep it separate from any treasury or personal wallet. If it
leaks, the loss is bounded by the balance and your
max_total. - The spending policy is the operator's, not the agent's. Allowlist, caps, and
approve_aboveare configuration the human sets; agents cannot raise their own limits. Keepallowed_hostsas tight as your deployment allows. - Redaction is scoped. This package keeps the key out of its own errors and reports; whatever you log around it (request dumps, env printouts) is your responsibility.
- Report vulnerabilities privately via GitHub Security Advisories (see SECURITY.md).
LangChain (Python) example
No extra dependency in this package — wrap the client yourself:
from langchain_core.tools import tool
from subetha import SubethaClient, SubethaError
client = SubethaClient.from_env()
@tool
def subetha_x402_pay(url: str, method: str = "GET", body: str | None = None) -> str:
"""Pay for an x402 resource privately under the configured spending policy."""
try:
r = client.pay(url, method=method, body=body)
return f"HTTP {r.status}; paid {r.payment.amount if r.payment else 0}; {r.resource[:2000]}"
except SubethaError as e:
return f"ERROR: {e}" # let the agent read the refusal instead of crashing
Tests
uv run --project python pytest python/tests -q # unit (no chain needed)
# cross-language live e2e (real stack + the TypeScript provider/facilitator):
PERMIT_BURNER_ADDRESS=0x… pnpm --filter @subetha/demo-official exec tsx ../../python/scripts/live-e2e.ts
License
Apache-2.0 (this package). Note that operating a SubEtha facilitator commercially / in production requires a separate use grant from the zERC20 team — see the License section of the repository README.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file subetha-0.2.0.tar.gz.
File metadata
- Download URL: subetha-0.2.0.tar.gz
- Upload date:
- Size: 19.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd6368683e0f9329e7f43996128974dfa17267aa3afe4e82dcb3261ad4e33e49
|
|
| MD5 |
1e7b4c3a51bc00845cba1aa0e8a8d5c5
|
|
| BLAKE2b-256 |
4848a7c2f2160e2c50d6326d5c06fcfe86fa118690a65fab37ac9cb2acc7ba0f
|
Provenance
The following attestation bundles were made for subetha-0.2.0.tar.gz:
Publisher:
python-release.yml on peaceandwhisky/SubEtha
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
subetha-0.2.0.tar.gz -
Subject digest:
dd6368683e0f9329e7f43996128974dfa17267aa3afe4e82dcb3261ad4e33e49 - Sigstore transparency entry: 2148282716
- Sigstore integration time:
-
Permalink:
peaceandwhisky/SubEtha@95ef6e295a19b73823c4926c93d15bd459335341 -
Branch / Tag:
refs/tags/python-v0.2.0 - Owner: https://github.com/peaceandwhisky
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-release.yml@95ef6e295a19b73823c4926c93d15bd459335341 -
Trigger Event:
push
-
Statement type:
File details
Details for the file subetha-0.2.0-py3-none-any.whl.
File metadata
- Download URL: subetha-0.2.0-py3-none-any.whl
- Upload date:
- Size: 23.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9df5f92e4f503d9e76dd0a4250cee5945266f5f4339872edb1086c736c8fa2c4
|
|
| MD5 |
8ce1f04d647041b8614b7188d592c60a
|
|
| BLAKE2b-256 |
111df4cb053c8c662cf5f4bef8e3d3387a8d6ea7712315de7f0915289f276519
|
Provenance
The following attestation bundles were made for subetha-0.2.0-py3-none-any.whl:
Publisher:
python-release.yml on peaceandwhisky/SubEtha
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
subetha-0.2.0-py3-none-any.whl -
Subject digest:
9df5f92e4f503d9e76dd0a4250cee5945266f5f4339872edb1086c736c8fa2c4 - Sigstore transparency entry: 2148282723
- Sigstore integration time:
-
Permalink:
peaceandwhisky/SubEtha@95ef6e295a19b73823c4926c93d15bd459335341 -
Branch / Tag:
refs/tags/python-v0.2.0 - Owner: https://github.com/peaceandwhisky
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-release.yml@95ef6e295a19b73823c4926c93d15bd459335341 -
Trigger Event:
push
-
Statement type: