atoa-agent-pay (Python)
Both directions of money for your product or your AI agent: collect payments from your customers (link, QR, or an off-session charge under a COLLECT contract they authorized), and send payments under signed, capped contracts Atoa enforces server-side — over HTTP, in a few lines.
The ergonomic, standalone Python SDK for Atoa payments. Behaviour and the signed wire
format are byte-identical to the TypeScript @atoa/agent-pay (proven by a shared conformance suite), so a Python
integration and a TypeScript one talk to the same backend interchangeably.
New to AI agents, MCP, contracts, or request-signing? Read ../CONCEPTS.md first — every term in a few minutes.
Install
pip install atoa-agent-pay
Requires Python 3.10+. Depends only on httpx, cryptography, and pydantic.
The six nouns
The surface is agent · contract · payment · customer · store, driven through atoa.agent /
atoa.contract / atoa.payment / atoa.customer / atoa.store:
agent— your identity + a signing key.register(name=...)is idempotent (a human-readablenameis required).contract— a limited, revocable authority bounded by a per-payment cap + ≥1 period cap + an end date, in one of two directions:SEND(money out — the account holder authorizes once at their bank) orCOLLECT(money in while the customer is not present — the customer authorizes once on the contract page). A pay-link collect needs no contract.payment—collect(money IN — link/QR, or an off-session charge whencontract_idis passed),send(money OUT — contract-capped, 1–20 instructions per call), andget/list/list_all/await_settled/cancel/capture/refund/list_refunds/cancel_refund.customer— your payers + their saved payment methods (guest checkout needs none).store— read-only reference data (your stores, to find astore_id).
One Payment shape covers both directions — type: "DEBIT" (money out) or "CREDIT" (money in). Money you send
in is a grouped Amount in DECIMAL major units (12.50, not 1250; currency defaults to GBP); money you read
back on a Payment is flat (paid_amount + currency). Both sync (AtoaAgentPay, via init) and async
(AsyncAtoaAgentPay, via init_async) clients are provided. No webhooks — you poll.
Quickstart
api_url is optional (defaults per environment); the API key defaults to the ATOA_API_KEY env var (see
../QUICKSTART.md).
import atoa_agent_pay
private_key_pem, _ = atoa_agent_pay.generate_es256_keypair() # prod: load from your secrets manager / sign in your KMS
atoa = atoa_agent_pay.init(environment="sandbox", private_key_pem=private_key_pem)
atoa.agent.register(name="Bookings assistant") # idempotent; name required
# ── collect — money IN (no contract) ──
req = atoa.payment.collect(amount={"amount": 45.00}, order_id="booking-8812")
print(req.payment_url) # give the customer this link (or the QR)
print(atoa.payment.await_settled(req.payment_request_id).status) # "COMPLETED"
# ── send — money OUT (under a capped contract) ──
contract = atoa.contract.create(
name="Supplier payouts",
limits={
"max_per_payment": 50.00,
"period_limits": [{"amount": 500.00, "period": "MONTH"}],
"valid_to": "2026-12-31T23:59:59Z",
},
)
atoa.contract.await_active(contract.contract_id) # resolves once the account holder authorizes (sandbox: the Atoa Test Bank)
results = atoa.payment.send( # payments is ALWAYS a list; result in order
contract_id=contract.contract_id,
payments=[{
"amount": {"amount": 12.50},
"beneficiary": {"name": "ACME LTD", "sort_code": "040004", "account_number": "12345678"},
"order_id": "order-9281",
}],
)
payment = results[0]
if payment.payment_idempotency_id: # a business FAILED is RETURNED, not raised
print(atoa.payment.await_settled(payment.payment_idempotency_id).status) # "COMPLETED"
else:
print("refused:", payment.failure_reason, "—", payment.failure_reason_description)
A business outcome (a FAILED/CANCELLED payment with a failure_reason) is RETURNED on the Payment (branch on
.status), never an exception; only operational faults raise typed AgentPayErrors. Full walkthrough with every
positive and negative case (real responses stored in the cells): notebooks/lifecycle.ipynb
(and the plain-script examples/walkthrough.py). The COLLECT-contract cookbook (charge a
customer on file, every refusal recovered): examples/collect_contracts.py.
Custom-signer / KMS example:
examples/kms_signer.py.
API reference
Client-level
| Member | Signature | Notes |
|---|---|---|
atoa.agent_id |
str | None |
Set after register. |
atoa.environment |
"sandbox" | "production" |
Bound to the key. |
atoa.api_url |
str |
The resolved service base URL. |
atoa.check_availability() |
→ {"available": bool, ...} |
Unauthenticated probe; never raises. |
atoa.sandbox_test_accounts() |
→ {"sandbox", "note", "accounts"} |
Env-aware send test recipients; empty in production. |
atoa.agent—register(name=..., description=?, agent_id=?, public_key_pem=?) → RegisteredAgent,me() → AgentIdentity.atoa.contract—create(name=..., limits=..., description=?, type=?, atoa_customer_id=?) → Contract(type="COLLECT"needsatoa_customer_id),get(id),list(...) → Page[Contract],list_all(...),await_active(id, timeout_ms=?),update(id, limits=...)(staged until re-approved),revoke(id).atoa.payment—collect(amount=..., order_id=..., contract_id=?, ...) → PaymentRequest,send(contract_id=..., payments=[...]) → list[Payment](1–20 per call),get(id) → Payment(either id kind),list(...) → Page[Payment],list_all(...),await_settled(id, timeout_ms=?),cancel(id),capture(id),refund(id, amount=..., reason=?),list_refunds(id),cancel_refund(refund_id).atoa.customer—create/get/list/update/delete, pluslist_payment_methods/get_payment_method/delete_payment_method.atoa.store.list(...)— read-only reference data.
Payment mirrors the service — branch on .status (AWAITING_AUTHORIZATION / PENDING / AUTHORIZED / COMPLETED /
FAILED / CANCELLED / EXPIRED / REFUNDED / PARTIALLY_REFUNDED / DISPUTE_RAISED / DISPUTE_WON /
DISPUTE_LOST; COMPLETED is settled), then .failure_reason (fall back to
.failure_reason_description). type is "DEBIT" (sent, carries beneficiary + contract_id) or "CREDIT"
(collected, carries payment_request_id + the flat payer fields consumer_name/bank_name/bank_account_no +
status_details) — EXCEPT a collected payment whose status becomes REFUNDED/PARTIALLY_REFUNDED or a dispute
state (DISPUTE_RAISED/DISPUTE_WON/DISPUTE_LOST), which reports type as "DEBIT" (money now moving, or
already moved, back out); payment_request_id stays present regardless, so don't use type to infer the original
direction once a refund or dispute has occurred. The attempt id is .payment_idempotency_id
(None until an attempt exists); lists are offset Page objects (.data, .total_count, .page, .size). Full
model + reason tables + retry rules: ../ERRORS.md.
Typed inputs. Calls accept plain dicts (snake_case or camelCase). For IDE autocomplete, the TypedDicts
AmountInput, PeriodLimitInput, ContractLimitsInput, BeneficiaryInput, SendPaymentInput,
CollectCustomerInput, CreateCustomerInput (in atoa_agent_pay.core) describe the preferred
keys.
Give the tools to an AI agent
create_agent_pay_tools(atoa) returns the canonical, self-describing tool specs + a call() dispatcher:
from atoa_agent_pay import create_agent_pay_tools
tools = create_agent_pay_tools(atoa) # hand tools.specs to your agent runtime
out = tools.call("collect_payment", {"amount": {"amount": 45.0}, "orderId": "booking-8812"}) # {"result","text","isError"}
The tools are the canonical 26-tool AGENT_PAY_TOOLS set — availability/identity, the contract tools, both money
verbs (collect_payment / send_payment), payment reads, cancel/capture, refunds, customers + saved methods, and
reference data — the same set the MCP exposes.
Minimal example: examples/ai_agent.py (optional — most integrations don't need it). Zero-code
in Claude Desktop / Cursor: ../QUICKSTART.md → Zero-code (MCP).
Keys & custody
The SDK needs an elliptic-curve private key handed in — where you keep it is your choice (env var, file, your KMS / Secrets Manager / Vault). Atoa builds no provider integrations and never holds your key.
import os, atoa_agent_pay
private_key_pem = os.environ["AGENT_PRIVATE_KEY_PEM"] # prod: from your secret store
atoa = atoa_agent_pay.init(environment="production", private_key_pem=private_key_pem)
Prefer to sign inside your HSM/KMS? Pass a signer= callback (with public_key_pem=) so the key never enters your
process. Never hardcode, commit, or log the key or the token. One key per environment. The three custody tiers, the
decision table, and a runnable KMS example (examples/kms_signer.py):
../CONCEPTS.md → Keys & signing.
What's in the box
| Module | Purpose |
|---|---|
atoa_agent_pay |
the client surface — init / init_async, AtoaAgentPay, AsyncAtoaAgentPay, tools, errors |
atoa_agent_pay.core |
typed models (Amount, Contract, Payment, Page, …) + the input TypedDicts |
atoa_agent_pay.crypto |
canonical JSON + detached-signature core (advanced; the Signer seam, Es256Signer, verify_es256) |
atoa_agent_pay.signing |
the signed subjects |
atoa_agent_pay.tools |
framework-agnostic tool specs + a call() |
Conformance & parity
The Python crypto is checked byte-for-byte against vectors generated from the TypeScript SDK:
npx tsx conformance/generate-vectors.ts # regenerate the shared golden vectors (from packages/agent-pay)
pytest # prove Python parity (from packages/agent-pay/python)
The full TS↔Python symbol map is the appendix at the bottom of ../QUICKSTART.md.
Learn more
../QUICKSTART.md · ../CONCEPTS.md · ../ERRORS.md · ../SANDBOX.md · ../MIGRATION.md
UNLICENSED — internal Atoa SDK.
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 atoa_agent_pay-0.0.1.tar.gz.
File metadata
- Download URL: atoa_agent_pay-0.0.1.tar.gz
- Upload date:
- Size: 54.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2992fb85cb3461a5c17232aafce2b03ca487f97e57a214978523216a5bc7277c
|
|
| MD5 |
a412f3d4042f3e37ac1403b685d8ab3b
|
|
| BLAKE2b-256 |
fd11df419ca8972c8d85db70ca771439a8a2930a9ee2c799205f3e5cf80d0125
|
File details
Details for the file atoa_agent_pay-0.0.1-py3-none-any.whl.
File metadata
- Download URL: atoa_agent_pay-0.0.1-py3-none-any.whl
- Upload date:
- Size: 54.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8143247b8c9901635622411e6cb6362f417c6529283691acf1cf8ba310c9e550
|
|
| MD5 |
be177910c6c926070c2c485b06d0579a
|
|
| BLAKE2b-256 |
612b9bf7aeb34f53c614ca6a99365dfff12169b2ac53ae2b621a0a41e629e832
|