Skip to content

Python API reference

The subetha package on PyPI is the payer side only: decode a 402 offer, decide under an operator-configured spending policy, sign, pay, report. The provider, facilitator, and settlement layers are TypeScript and live in the product repository; paying touches no zk code and no BUSL code — the package depends only on httpx, eth-account, and eth-utils. It is a reference implementation written against the wire protocol alone. Apache-2.0; Python >= 3.11.

applies to subetha 0.2.0 (PyPI, verified 2026-07-27)
Terminal window
pip install subetha

Status: experimental (0.x). The package’s own README states the API may change in breaking ways between 0.x minor releases, and stability will be declared at 1.0.

Known source divergence (documented, not resolved here). The package README at the pinned commit still recommends the pin subetha>=0.1,<0.2, which excludes the 0.2.0 release that is current on PyPI (registry-verified 2026-07-27). Until that is reconciled upstream, treat 0.2.0 as the installable version and the README’s pin line as stale (python/README.md).

from subetha import … exposes exactly the following (python/src/subetha/__init__.py):

export kind description
SubethaClient class the payer client (below)
SpendingPolicy class operator-configured spending policy (below)
SCHEME constant "subetha-zerc20"
SubethaError exception all client failures normalize to this; messages are redacted before they reach the caller
Offer dataclass quoted, token, network, gasless, list_price?, fee?, bps?, expires_at?, approval_required?
Quote dataclass status, offer?, error?
Payment dataclass amount, token, network, fee?, burn_tx_hash?, settle?
PayResult dataclass status, resource, truncated, payment?
ApprovalRequest dataclass what the human is asked to approve (and the identity the approval is bound to): url, quoted, token, network, pay_to, challenge_nonce, fee?
Requirements dataclass validated view of a subetha-zerc20 offer; raw keeps the exact dict received in the 402 for the verbatim echo

(python/src/subetha/client.py)

SubethaClient(
*,
private_key=None, # the paying account (permit mode: only signs, no gas)
rpc_url="http://127.0.0.1:8545",
mode="permit", # "permit" (gasless, default) or "self-transfer"
policy=None, # SpendingPolicy; a default local-only policy when unset
approve_above=None, # base units; quoted amounts above this need a human yes
approve_payment=None, # callable(ApprovalRequest) -> bool
approval_timeout_s=120.0,
timeout_s=30.0,
on_pre_submit=None, # callable(Requirements) hook before the paying request
signer=None, # external signing callable (mutually exclusive with private_key)
payer_address=None, # required with signer; invalid without it
)

Public methods:

method description
SubethaClient.from_env(env=None) classmethod: build from the SUBETHA_* environment (same variable names as the TypeScript tools — see the configuration reference)
quote(url, method="GET", body=None) -> Quote fetch the offer without paying; never needs the private key. Sets Offer.approval_required when the quoted amount is above approve_above
pay(url, method="GET", body=None, headers=None) -> PayResult full payment flow: policy gate → 402 → offer selection → optional approval → sign/broadcast → paid request. One payment at a time (internal lock); reserved PAYMENT-*/X-PAYMENT headers are rejected
report() -> dict totals, payments, and attempts (including approval events) — the policy’s audit trail

Failure semantics the client guarantees (from the module and README at the pin): redirects are refused so a payment cannot be steered off the host allowlist; a definitive settle rejection on the permit path releases the budget reservation (no funds moved); a paid request that then fails with an HTTP error keeps the spend in the budget on the safe side; every error message is redacted before it reaches the caller.

(python/src/subetha/policy.py)

SpendingPolicy(
*,
allowed_hosts=("localhost", "127.0.0.1"), # or "*" to allow all
allowed_network="eip155:31337",
allowed_token=None,
max_per_payment=None, # base units
max_total=None, # base units, per network|token pair
)

The policy enforces at two points: check_url(raw_url) before any request leaves (hostname allowlist), and filter_offers(offers) at payment selection (network/token pins, per-payment and total caps). Spend accounting happens at the irreversibility point, and report() exposes the audit trail. The remaining methods (record_spend, release, record_payment, record_attempt) are the accounting hooks the client drives.

From the package README at the pinned commit — a local, non-production setup (loopback URLs, dev placeholder key):

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)