Skip to content

Payer quickstart — Python

The subetha package on PyPI is a payer-only reference client: it decodes a 402 offer, decides under an operator-configured spending policy, signs, pays, and keeps an audit trail. It touches no zk code and no BUSL code — its only dependencies are httpx, eth-account, and eth-utils. Everything on this page targets the local, non-production stack: loopback URLs, chain 31337, placeholder keys. The code mirrors the pinned package README; the flow expectations describe what the pinned sources define — the local end-to-end tutorial is where you actually run them.

Terminal window
pip install subetha

Requires Python >= 3.11; Apache-2.0. The installable version at ship time is 0.2.0 (registry-verified 2026-07-27), and the package is experimental (0.x) — the API may change in breaking ways between 0.x minor releases. Note one documented source divergence: the package README at the pinned commit still recommends the pin subetha>=0.1,<0.2, which excludes 0.2.0 — treat 0.2.0 as current and that pin line as stale (details in the Python API reference).

2. Configure the client with a spending policy

Section titled “2. Configure the client with a spending policy”

The SpendingPolicy is the safety layer: it is operator-configured, never agent-configured — hosts, network, token, and budget caps come from you (or the SUBETHA_* environment), not from anything an agent says at runtime.

from subetha import SubethaClient, SpendingPolicy
client = SubethaClient(
private_key="0x…", # placeholder — local anvil dev account only
rpc_url="http://127.0.0.1:8545", # local anvil (chain 31337)
mode="permit", # gasless default; or "self-transfer"
policy=SpendingPolicy(
allowed_hosts=["127.0.0.1"], # first line of defense — keep it tight
allowed_network="eip155:31337",
max_per_payment=100_000, # token base units
max_total=5_000_000, # per network|token pair
),
approve_above=10_000, # optional human-in-the-loop threshold
approve_payment=lambda info: ask_human(info), # True = approve
)

Alternatively, SubethaClient.from_env() builds the client from the same SUBETHA_* variables the TypeScript tools read:

Terminal window
# Placeholders — local dev values only; never commit a real key.
export SUBETHA_PAYER_PK=0x
export SUBETHA_RPC_URL=http://127.0.0.1:8545
export SUBETHA_ALLOWED_HOSTS=127.0.0.1
export SUBETHA_MAX_PER_PAYMENT=100000
export SUBETHA_MAX_TOTAL=5000000

The full variable table (defaults included) is in the configuration reference.

url = "http://127.0.0.1:4031/api/complete" # a locally running provider
# Quote: fetch the offer without paying — never needs the private key.
q = client.quote(url, method="POST", body='{"prompt":"hi"}')
print(q.offer.quoted, q.offer.fee, q.offer.approval_required)
# Pay: policy gate → 402 → offer selection → optional approval → sign → paid request.
r = client.pay(url, method="POST", body='{"prompt":"hi"}')
print(r.status, r.payment.amount, r.resource)
# Report: totals, payments, and attempts (including approval events).
print(client.report())

What a successful pay() means in flow terms: the first request drew a 402 offer, the client paid it, and the re-sent request came back accepted — resource served, settlement acknowledged on the request path. Finalization (the proof-gated mint) happens later, asynchronously, on the facilitator side; no client response ever reports finalized (accepted vs finalized).

Guarantees stated by the pinned client module and README:

  • Host allowlist before any request leaves — and redirects are refused, so a payment cannot be steered onto a host you did not allowlist.
  • Budget accounting at the irreversibility point. A definitive settle rejection on the permit path releases the reservation (no funds moved); a paid request that then fails with an HTTP error keeps the spend counted, on the safe side.
  • One payment at a time (internal lock); reserved PAYMENT-* / X-PAYMENT headers in your own header dict are rejected.
  • Redacted errors — every failure normalizes to SubethaError with secrets scrubbed from the message.