atoa-agent-pay
The ergonomic, standalone Python SDK for agentic payments — move money in both directions from your backend or your AI agent, over HTTP, in a few lines. Collect from your customers (a pay-link/QR, or an off-session charge under a contract they authorized) and send to bank accounts under signed, capped contracts that Atoa enforces server-side. Every request is signed for you.
atoa-agent-pay is a small client for Atoa that lets a program — an AI agent, or an ordinary backend job — collect and send real money. It is HTTP-only, fully type-hinted (py.typed), signs every request with a per-request ES256 signature, and ships both sync and async clients. A business outcome (a payment that fails or is rejected) is a returned value you branch on; only operational faults raise. Behaviour and the signed wire format are byte-identical to the TypeScript @atoapayments/agent-pay (proven by a shared conformance suite), so a Python integration and a TypeScript one hit the same backend interchangeably.
Install
pip install atoa-agent-pay
Requires Python 3.10+ (see the badge for the exact range). Depends only on httpx, cryptography, and pydantic.
Quickstart
You need an API key from the Atoa dashboard (the key selects the environment; it defaults to the ATOA_API_KEY env var). api_url is optional — the SDK targets the right host for the environment.
import atoa_agent_pay
private_key_pem, _ = atoa_agent_pay.generate_es256_keypair() # prod: load a PEM from your secrets manager, or 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 (customer present, no contract) --
req = atoa.payment.collect(amount={"amount": 45.00}, order_id="booking-8812")
print(req.payment_url) # give the customer this link (or req.qr_code_url)
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 (1-20 per call); 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)
An async client is a drop-in: atoa = atoa_agent_pay.init_async(...) returns an AsyncAtoaAgentPay whose methods are awaitable (await atoa.payment.collect(...)). Money in is a grouped Amount in DECIMAL major units ({"amount": 12.50}, not 1250; currency defaults to GBP); money you read back on a Payment is flat (paid_amount + currency). No webhooks — you poll.
Documentation
The public docs are the source of truth for naming and behaviour:
- Overview — https://docs.paywithatoa.co.uk/agent-pay/overview
- Collect (money in) — https://docs.paywithatoa.co.uk/agent-pay/collect · off-session charges · SCA on a charge
- Send (money out) — https://docs.paywithatoa.co.uk/agent-pay/send · SCA on a payout
- AI agents & tools — https://docs.paywithatoa.co.uk/agent-pay/ai-agents
- Reference (methods, types, errors, auth) — https://docs.paywithatoa.co.uk/agent-pay/reference · Authentication · KMS / custom signer
- Sandbox guide — https://docs.paywithatoa.co.uk/atoa-sandbox · Go-live checklist — https://docs.paywithatoa.co.uk/go-live
- Runnable notebook — https://atoa-pdf.s3.eu-west-2.amazonaws.com/developer-guide.ipynb
Core concepts
Three routes cover everything (Overview):
- Customer present —
payment.collect(amount=..., order_id=...)returns apayment_url+qr_code_url; the customer picks their bank or card on Atoa's page. No contract. - Customer not present —
payment.collect(amount=..., order_id=..., contract_id=..., atoa_customer_id=...)charges a COLLECT contract the customer authorized earlier. See off-session charges. - Paying out —
payment.send(contract_id=..., payments=[...])moves money under a SEND contract the account holder authorized once at their bank. See Send.
A contract is a limited, revocable authority bounded by a per-payment cap, one or more period caps, and an end date (valid_to); Atoa enforces the caps server-side. Contracts are AP2-aligned mandates. A gated send or off-session collect can pause for Strong Customer Authentication (SCA) — see the SCA gate below. The full mental model is in the monorepo overview and CONCEPTS.
API reference
Create a client with atoa_agent_pay.init(...) (sync) or init_async(...) (async): environment (required), plus optional api_url, api_key, private_key_pem, public_key_pem, signer, clock, http_client, timeout. Client members: atoa.agent_id, atoa.environment, atoa.api_url, atoa.check_availability(), atoa.sandbox_test_accounts().
Five namespaces (snake_case throughout):
| Namespace | Methods |
|---|---|
atoa.agent |
register(name=..., description=?, agent_id=?, public_key_pem=?) (idempotent; name required) · me() |
atoa.contract |
create(name=..., limits=..., description=?, type=?, atoa_customer_id=?) · get(id) · list(...) · list_all(...) · await_active(id, timeout_ms=?) · update(id, limits=...) · revoke(id) |
atoa.payment |
collect(amount=..., order_id=..., contract_id=?, ...) · send(contract_id=..., payments=[...]) · get(id) · list(...) · list_all(...) · await_settled(id, timeout_ms=?) · cancel(id) · refund(id, amount=..., reason=?) · list_refunds(id) · cancel_refund(refund_id) · await_decision(approval_id, timeout_ms=?) · cancel_approval(contract_id, approval_id) |
atoa.customer |
create(...) · get(id) · list(...) · update(id, ...) · delete(id) |
atoa.store |
list(...) |
Notes worth knowing before the full reference:
collect(...) → PaymentRequest. Default (nocontract_id): apayment_url+qr_code_url. Withcontract_id(+atoa_customer_id): an off-session charge against a COLLECT contract. Not idempotent — thepayment_request_idis the source of truth.send(...) → SendResult.paymentsis always a list (1-20 per call); the result is a list ofPaymentin order (with an optional.next_actionwhen the SCA gate is on).contract.revoke(id) → ContractRevokeResultreturns a confirmation, notNone.- Calls accept plain dicts (snake_case or camelCase). For IDE autocomplete, the TypedDicts
AmountInput,PeriodLimitInput,ContractLimitsInput,BeneficiaryInput,SendPaymentInput,CollectCustomerInput,CreateCustomerInputlive inatoa_agent_pay.core.
A 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) or "CREDIT" (collected); the attempt id is .payment_idempotency_id (None until an attempt exists). Full model + reason tables + retry rules: ERRORS.
The SCA gate (next_action)
A gated send or off-session collect returns a next_action of shape {approval_id, client_secret, approval_url}. A human approves on Atoa's hosted page. In Python you deliver the approval_url to the approver (the browser approvals SDK is TypeScript-only), then poll atoa.payment.await_decision(next_action.approval_id) (or atoa.payment.cancel_approval(contract_id, approval_id)). See SCA on a payout, SCA on a charge, and the Approvals overview.
Give the tools to an AI agent
create_agent_pay_tools(atoa) returns the canonical, self-describing tool specs (the 22-tool AGENT_PAY_TOOLS set) plus 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"}
Prefer zero code? Atoa ships a hosted MCP server (a superset of these tools) for Claude Desktop / Cursor — see AI agents & tools and the MCP server docs.
Errors
Every operational fault raises a typed AgentPayError subclass with a stable code — branch on err.code or the class, never the message. Business outcomes (name mismatch, cap exceeded, settlement failed, customer cancelled) come back as a FAILED/CANCELLED Payment with a failure_reason instead.
Common classes: AuthError (401/403), ValidationError (400/422), NotFoundError (404), ConflictError (409), RateLimitError (429), NetworkError, RegistrationError, AuthorizationTimeoutError / AuthorizationFailedError (await_active), SettlementTimeoutError (await_settled / await_decision), KeyNotFoundError. There is also a first-class contract-charge ladder (ContractNotActiveError, CapExceededError, NoPaymentMethodError, …). Full model, tables, and retry rules: Reference and ERRORS.
Authentication, keys & sandbox
Two credentials ride on every request: your API key (from the dashboard; pins the environment) and a per-request ES256 signature from your agent's 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)
Where you keep the key is your choice (env var, file, your KMS / Secrets Manager / Vault) — Atoa never holds it. 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; one key per environment.
- Auth & signing: https://docs.paywithatoa.co.uk/agent-pay/reference#authentication
- KMS / custom signer: https://docs.paywithatoa.co.uk/agent-pay/reference#kms-custom-signer
- Sandbox (test accounts, forcing outcomes): https://docs.paywithatoa.co.uk/atoa-sandbox
Related packages
- TypeScript sibling — same SDK, byte-identical wire artifacts:
@atoapayments/agent-pay. - Browser approvals (TypeScript-only) — embed the hosted SCA approval page in a web UI:
@atoapayments/agentic-payment-approvals-js. Python integrations deliver theapproval_urlfromnext_actioninstead. - Monorepo & guides — https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework/blob/main/packages/agent-pay/README.md
License
License. MIT — see LICENSE. This covers the code in this package.
Service terms. Use of the Atoa API is governed by the Atoa Services Agreement: https://paywithatoa.co.uk/terms/. The MIT license applies to this SDK only and grants no rights to the Atoa service.
Trademarks. "Atoa" and the Atoa logo are trademarks of Atoa Payments Limited. The MIT license grants rights in the code, not in our names or marks — a modified or redistributed copy must not be presented as an Atoa product.
Security. Report vulnerabilities to hello@paywithatoa.co.uk — please do not open a public issue. See SECURITY.md.
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.2.tar.gz.
File metadata
- Download URL: atoa_agent_pay-0.0.2.tar.gz
- Upload date:
- Size: 53.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
27f6e7f9b6a537aa8c18c892bad74ea4989b698a92fe1160f9a9ceee46132ccd
|
|
| MD5 |
ab9e26bbfc96f4aceba6190a58b182ea
|
|
| BLAKE2b-256 |
e6ef318c7b5cb6b1b38704362f110d659b912cae843fb58174ec7ae9511fc04e
|
Provenance
The following attestation bundles were made for atoa_agent_pay-0.0.2.tar.gz:
Publisher:
agent-pay-python-prod-release.yml on ATOAPaymentsLimited/AtoaAgenticFramework
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
atoa_agent_pay-0.0.2.tar.gz -
Subject digest:
27f6e7f9b6a537aa8c18c892bad74ea4989b698a92fe1160f9a9ceee46132ccd - Sigstore transparency entry: 2342082621
- Sigstore integration time:
-
Permalink:
ATOAPaymentsLimited/AtoaAgenticFramework@6d992b974e4c6903cbe5258028f75c5236d9ef47 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ATOAPaymentsLimited
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
agent-pay-python-prod-release.yml@6d992b974e4c6903cbe5258028f75c5236d9ef47 -
Trigger Event:
push
-
Statement type:
File details
Details for the file atoa_agent_pay-0.0.2-py3-none-any.whl.
File metadata
- Download URL: atoa_agent_pay-0.0.2-py3-none-any.whl
- Upload date:
- Size: 57.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc2552aaa1d2e4fc5535e1254df50c525d569e9407273385cad924338247380c
|
|
| MD5 |
5a91e3ea4304df8548cd08cfd7d4c842
|
|
| BLAKE2b-256 |
b87b3b1ba65acee335055be007a1825f639b4cd5513d1fa7a300441c221f9c20
|
Provenance
The following attestation bundles were made for atoa_agent_pay-0.0.2-py3-none-any.whl:
Publisher:
agent-pay-python-prod-release.yml on ATOAPaymentsLimited/AtoaAgenticFramework
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
atoa_agent_pay-0.0.2-py3-none-any.whl -
Subject digest:
fc2552aaa1d2e4fc5535e1254df50c525d569e9407273385cad924338247380c - Sigstore transparency entry: 2342082628
- Sigstore integration time:
-
Permalink:
ATOAPaymentsLimited/AtoaAgenticFramework@6d992b974e4c6903cbe5258028f75c5236d9ef47 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ATOAPaymentsLimited
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
agent-pay-python-prod-release.yml@6d992b974e4c6903cbe5258028f75c5236d9ef47 -
Trigger Event:
push
-
Statement type: