Skip to main content

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.

PyPI version Python versions license

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:

Core concepts

Three routes cover everything (Overview):

  • Customer present — payment.collect(amount=..., order_id=...) returns a payment_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 (no contract_id): a payment_url + qr_code_url. With contract_id (+ atoa_customer_id): an off-session charge against a COLLECT contract. Not idempotent — the payment_request_id is the source of truth.
  • send(...) → SendResult. payments is always a list (1-20 per call); the result is a list of Payment in order (with an optional .next_action when the SCA gate is on).
  • contract.revoke(id) → ContractRevokeResult returns a confirmation, not None.
  • Calls accept plain dicts (snake_case or camelCase). For IDE autocomplete, the TypedDicts AmountInput, PeriodLimitInput, ContractLimitsInput, BeneficiaryInput, SendPaymentInput, CollectCustomerInput, CreateCustomerInput live in atoa_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.

License

License. MIT. 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.

Metadata

Release files for atoa-agent-pay 0.0.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for atoa-agent-pay 0.0.5
File Size Uploaded
atoa_agent_pay-0.0.5.tar.gz 68.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for atoa-agent-pay 0.0.5
File Interpreter ABI Platform
atoa_agent_pay-0.0.5-py3-none-any.whl Python 3 none any Details

Total release size: 142.1 kB

Release files / atoa_agent_pay-0.0.5.tar.gz

Download URL atoa_agent_pay-0.0.5.tar.gz
Size 68.9 kB
Tags Source
SHA-256 checksum
How to use checksums
50c3c69eda5921bde35921626ae7f8846552c19c292ad61abc3c26a476405914
BLAKE2b-256 checksum
How to use checksums
9233c5abfe863f51064d28ed68ffa7fa29941c0cd7ced5cf9ba3244603c95c7f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / atoa_agent_pay-0.0.5-py3-none-any.whl

Download URL atoa_agent_pay-0.0.5-py3-none-any.whl
Size 73.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c5c7b588aaf2dede0ccb4014db5f723c9059a56e5463723eca7e236aff0a6990
BLAKE2b-256 checksum
How to use checksums
e70b9deef3c7c29645a59966440d02117d3c82295d50ee244a4edadcfbfbc9ee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.5 This release

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page