Metadata-Version: 2.4
Name: atoa-agent-pay
Version: 0.0.2
Summary: The ergonomic, standalone Python SDK for agentic payments: embed payments in your product (or give your AI agent a payment tool) under a signed, capped, payee-scoped contract over HTTP. Three nouns — agent, contract, payment.
Project-URL: Homepage, https://docs.paywithatoa.co.uk/agent-pay/overview
Project-URL: Documentation, https://docs.paywithatoa.co.uk/agent-pay/overview
Author: Atoa Payments Limited
License-Expression: MIT
License-File: LICENSE
Keywords: agentic-payments,ai-agents,fintech,jws,mcp,open-banking,payments,sdk
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: cryptography>=42
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.7
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# 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](https://img.shields.io/pypi/v/atoa-agent-pay.svg)](https://pypi.org/project/atoa-agent-pay/)
[![Python versions](https://img.shields.io/pypi/pyversions/atoa-agent-pay.svg)](https://pypi.org/project/atoa-agent-pay/)
[![license](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework/blob/main/packages/agent-pay/python/LICENSE)

`atoa-agent-pay` is a small client for [Atoa](https://paywithatoa.co.uk) 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`](https://www.npmjs.com/package/@atoapayments/agent-pay) (proven by a shared conformance suite), so a Python integration and a TypeScript one hit the same backend interchangeably.

## Install

```bash
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](https://dashboard.paywithatoa.co.uk/) (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`.

```python
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](https://docs.paywithatoa.co.uk/agent-pay/collect#off-session) · [SCA on a charge](https://docs.paywithatoa.co.uk/agent-pay/collect#sca-on-a-charge)
- **Send (money out)** — https://docs.paywithatoa.co.uk/agent-pay/send · [SCA on a payout](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](https://docs.paywithatoa.co.uk/agent-pay/reference#authentication) · [KMS / custom signer](https://docs.paywithatoa.co.uk/agent-pay/reference#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](https://docs.paywithatoa.co.uk/agent-pay/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](https://docs.paywithatoa.co.uk/agent-pay/collect#off-session).
- **Paying out** — `payment.send(contract_id=..., payments=[...])` moves money under a **SEND** contract the account holder authorized once at their bank. See [Send](https://docs.paywithatoa.co.uk/agent-pay/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](https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework/blob/main/packages/agent-pay/README.md) and [CONCEPTS](https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework/blob/main/packages/agent-pay/CONCEPTS.md).

## 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](https://docs.paywithatoa.co.uk/agent-pay/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](https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework/blob/main/packages/agent-pay/ERRORS.md).

### 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](https://docs.paywithatoa.co.uk/agent-pay/send#sca-on-a-payout), [SCA on a charge](https://docs.paywithatoa.co.uk/agent-pay/collect#sca-on-a-charge), and the [Approvals overview](https://docs.paywithatoa.co.uk/agent-pay/approvals).

## 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:

```python
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](https://docs.paywithatoa.co.uk/agent-pay/ai-agents) and the [MCP server docs](https://docs.paywithatoa.co.uk/mcp-server).

## 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](https://docs.paywithatoa.co.uk/agent-pay/reference) and [ERRORS](https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework/blob/main/packages/agent-pay/ERRORS.md).

## Authentication, keys & sandbox

Two credentials ride on every request: your **API key** (from the [dashboard](https://dashboard.paywithatoa.co.uk/); pins the environment) and a per-request **ES256 signature** from your agent's key.

```python
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`](https://www.npmjs.com/package/@atoapayments/agent-pay).
- **Browser approvals (TypeScript-only)** — embed the hosted SCA approval page in a web UI: [`@atoapayments/agentic-payment-approvals-js`](https://www.npmjs.com/package/@atoapayments/agentic-payment-approvals-js). Python integrations deliver the `approval_url` from `next_action` instead.
- **Monorepo & guides** — https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework/blob/main/packages/agent-pay/README.md

## License

**License.** MIT — see [LICENSE](./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](./SECURITY.md).
