Skip to main content

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-readable name is 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) or COLLECT (money in while the customer is not present — the customer authorizes once on the contract page). A pay-link collect needs no contract.
  • paymentcollect (money IN — link/QR, or an off-session charge when contract_id is passed), send (money OUT — contract-capped, 1–20 instructions per call), and get / 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 a store_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.agentregister(name=..., description=?, agent_id=?, public_key_pem=?) → RegisteredAgent, me() → AgentIdentity.
  • atoa.contractcreate(name=..., limits=..., description=?, type=?, atoa_customer_id=?) → Contract (type="COLLECT" needs atoa_customer_id), get(id), list(...) → Page[Contract], list_all(...), await_active(id, timeout_ms=?), update(id, limits=...) (staged until re-approved), revoke(id).
  • atoa.paymentcollect(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.customercreate / get / list / update / delete, plus list_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

atoa_agent_pay-0.0.1.tar.gz (54.9 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.1-py3-none-any.whl (54.3 kB view details)

Uploaded Python 3

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

Hashes for atoa_agent_pay-0.0.1.tar.gz
Algorithm Hash digest
SHA256 2992fb85cb3461a5c17232aafce2b03ca487f97e57a214978523216a5bc7277c
MD5 a412f3d4042f3e37ac1403b685d8ab3b
BLAKE2b-256 fd11df419ca8972c8d85db70ca771439a8a2930a9ee2c799205f3e5cf80d0125

See more details on using hashes here.

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

Hashes for atoa_agent_pay-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8143247b8c9901635622411e6cb6362f417c6529283691acf1cf8ba310c9e550
MD5 be177910c6c926070c2c485b06d0579a
BLAKE2b-256 612b9bf7aeb34f53c614ca6a99365dfff12169b2ac53ae2b621a0a41e629e832

See more details on using hashes here.

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