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 presentpayment.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 presentpayment.collect(amount=..., order_id=..., contract_id=..., atoa_customer_id=...) charges a COLLECT contract the customer authorized earlier. See off-session charges.
  • Paying outpayment.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.

Related packages

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

atoa_agent_pay-0.0.2.tar.gz (53.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

atoa_agent_pay-0.0.2-py3-none-any.whl (57.4 kB view details)

Uploaded Python 3

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

Hashes for atoa_agent_pay-0.0.2.tar.gz
Algorithm Hash digest
SHA256 27f6e7f9b6a537aa8c18c892bad74ea4989b698a92fe1160f9a9ceee46132ccd
MD5 ab9e26bbfc96f4aceba6190a58b182ea
BLAKE2b-256 e6ef318c7b5cb6b1b38704362f110d659b912cae843fb58174ec7ae9511fc04e

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for atoa_agent_pay-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 fc2552aaa1d2e4fc5535e1254df50c525d569e9407273385cad924338247380c
MD5 5a91e3ea4304df8548cd08cfd7d4c842
BLAKE2b-256 b87b3b1ba65acee335055be007a1825f639b4cd5513d1fa7a300441c221f9c20

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page