> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paywithatoa.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent Pay SDK Reference: Python & TypeScript

> Full Atoa Agent Pay SDK reference: authentication, KMS and custom signers, and every method for collecting and sending money in Python and TypeScript.

Examples default to **Python** (snake\_case) — most AI integrations are Python. TypeScript is the same surface in
camelCase (`await_settled` → `awaitSettled`), with identical parameters and byte-identical signed requests.

| Package | Registry | Version | Runtime |
| - | - | - | - |
| `@atoapayments/agent-pay` | npm | 0.0.3 | Node 22+ |
| `atoa-agent-pay` | PyPI | 0.0.3 | Python 3.10+ |
| `@atoapayments/agentic-payment-approvals-js` | npm | 0.0.3 | Browser (TypeScript) |
| `@atoapayments/pay-embed` | npm | 0.0.1 | Browser — [embedded checkout](https://docs.paywithatoa.com/agent-pay/embed.md) |

## Authentication

Two credentials on every request:

* **API key (tier 1)** — read from `ATOA_API_KEY`; identifies your business and pins the environment. Sandbox and
  production keys are separate and not interchangeable.
* **ES256 signing key (tier 2)** — the SDK signs every request; Atoa checks the signature against the public key
  you registered once. The private key is never sent. Requests can't be tampered with or replayed.

You never sign anything yourself — you only decide where the private key lives: pass `privateKeyPem` loaded from
your secrets manager, or a custom signer (below) so the key never enters your process. Re-registering the same
agent with a different key is rejected. Keep both credentials out of source control, logs, and client-side code.

Three ways to supply the two credentials — pick one (Python shown; TypeScript mirrors it in camelCase):

```python Env key + PEM (default) theme={null}
# ATOA_API_KEY is read from the environment; the public key is derived from the PEM.
atoa = atoa_agent_pay.init(environment="sandbox", private_key_pem=private_key_pem)
atoa.agent.register(name="Bookings assistant")
```

```python Explicit API key theme={null}
# Pass the key in (e.g. pulled from a secrets manager) instead of the env var.
atoa = atoa_agent_pay.init(api_key=api_key, environment="sandbox", private_key_pem=private_key_pem)
atoa.agent.register(name="Bookings assistant")
```

```python KMS / custom signer theme={null}
# The signing key never enters the SDK, so you register its public key yourself.
atoa = atoa_agent_pay.init(environment="sandbox", signer=signer)
atoa.agent.register(name="Payouts worker", public_key_pem=public_key_pem)
```

## KMS / custom signer

Keep the private key in a KMS (key management service) so it never enters your process: pass a `signer` instead of
a PEM. Per request, the SDK hands your signer the canonical subject string and expects a **detached compact JWS**
(JSON Web Signature) back: `b64url(header)..b64url(rawSig)`, where `rawSig` is the 64-byte IEEE-P1363 `r‖s`
form (a KMS returns DER — convert it). Register the **public** key.

```python Python theme={null}
import atoa_agent_pay
from atoa_agent_pay import JwsSignature
from atoa_agent_pay.crypto import b64url_encode

class KmsSigner:                                  # the signer only signs — it never holds the key bytes
    def sign(self, subject: str, kid: str | None = None) -> JwsSignature:
        header = b64url_encode(
            (f'{{"alg":"ES256","kid":"{kid}"}}' if kid else '{"alg":"ES256"}').encode()
        )
        signing_input = f"{header}.{b64url_encode(subject.encode())}"
        raw_sig = der_to_raw_es256(my_kms.sign_sha256_es256(signing_input))   # DER → 64-byte r‖s
        return {"alg": "ES256", "jws": f"{header}..{b64url_encode(raw_sig)}"}  # detached

atoa = atoa_agent_pay.init(
    environment="sandbox", signer=KmsSigner(), public_key_pem=my_public_key_pem
)
atoa.agent.register(name="Payouts worker", public_key_pem=my_public_key_pem)
```

```typescript TypeScript theme={null}
import { createAgentPayClient, type Signer } from "@atoapayments/agent-pay";

const b64url = (b: Buffer | string) => Buffer.from(b).toString("base64url");

const kmsSigner: Signer = {                     // the signer only signs — it never holds the key bytes
  sign(subject, kid) {
    const header = b64url(JSON.stringify(kid ? { alg: "ES256", kid } : { alg: "ES256" }));
    const input = `${header}.${b64url(subject)}`;
    const rawSig = derToRawEs256(myKms.signSha256Es256(input));   // DER → 64-byte r‖s
    return { jws: `${header}..${b64url(rawSig)}` };               // detached: empty middle segment
  },
};

const atoa = createAgentPayClient({ environment: "sandbox", signer: kmsSigner });
await atoa.agent.register({ name: "Payouts worker", publicKeyPem: myPublicKeyPem });   // register the public key once
```

## Methods

Every operation is laid out the same way — **Parameters** (marked required or optional), a **Request** example
(Python + TypeScript), then the **Response** type and an example. Python is snake\_case; TypeScript mirrors it in
camelCase; every call is async. Amounts are in pounds, not pence — `{ amount: 12.50, currency?: "GBP" }`, so £12.50
is `12.50`, never `1250`; `currency` defaults to GBP.

**Pagination.** Every `list(opts?)` returns a `Page<T>` envelope; the paired `listAll(opts?)` walks every page and
returns a flat `T[]`.

**Page\<T>** `object`

**fields**

**data** `T[]`

The page of items (`Contract` / `Payment` / `Customer` / `Store`).

**totalCount** `number`

Total matching rows across all pages.

**page** `number`

Zero-based index of this page.

**size** `number`

Page size (default 20).

Common paging inputs on every `list`: `page` (zero-based, default `0`) and `size` (default `20`).

## agent

### `agent.register(opts)`

Set up your agent's identity — the challenge → sign → register handshake in one call. **Idempotent** (safe to call
again): the same key/env/business returns the same agent; a changed `name`/`description` is a metadata update, not
a conflict.

**Parameters**

**name** `string` *required*

Human-readable agent name shown in the Atoa dashboard.

**description** `string`

Free-text description of what this agent does.

**publicKeyPem** `string`

Your ES256 public key (PEM, SPKI). Pass it **only** with a KMS/custom signer; omit when the SDK holds the private
key (it derives the public key itself).

**Request**

```python Python theme={null}
agent = atoa.agent.register(name="Bookings assistant")
```

```typescript TypeScript theme={null}
const agent = await atoa.agent.register({ name: "Bookings assistant" });
```

**Response** → `RegisteredAgent`

**agentId** `string`

The id this client signs as from now on.

**businessId** `string`

The business this agent belongs to.

**environment** `'sandbox' | 'production'`

**name** `string`

**description** `string`

Free-text description, if you set one.

**publicKeyPem** `string`

Echoed here **only** — `me`/`list` do not return it.

```json Example theme={null}
{
  "agentId": "agt_9f2c…",
  "businessId": "biz_41a0…",
  "environment": "sandbox",
  "name": "Bookings assistant",
  "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMFk…\n-----END PUBLIC KEY-----"
}
```

### `agent.me()`

Read this agent's own identity as the service sees it. Takes no parameters.

**Request**

```python Python theme={null}
self = atoa.agent.me()
```

```typescript TypeScript theme={null}
const self = await atoa.agent.me();
```

**Response** → `AgentIdentity`

**agentId** `string`

**businessId** `string`

**environment** `'sandbox' | 'production'`

**name** `string`

**description** `string`

Free-text description, if you set one.

**status** `string`

e.g. `ACTIVE`.

```json Example theme={null}
{ "agentId": "agt_9f2c…", "businessId": "biz_41a0…", "environment": "sandbox", "name": "Bookings assistant", "status": "ACTIVE" }
```

### `agent.list()`

List every agent registered under your business + environment. Authenticated by the SDK API key alone (no JWS).
Takes no parameters.

**Request**

```python Python theme={null}
all = atoa.agent.list()
```

```typescript TypeScript theme={null}
const all = await atoa.agent.list();
```

**Response** → `AgentIdentity[]`

An array of `AgentIdentity` (shape as `agent.me`).

## contract

### `contract.create(input)`

Create a spending contract. Returns it `PENDING_AUTHORIZATION` with an `authorizationUrl` the account holder (SEND)
or customer (COLLECT) opens to authorize; poll `awaitActive` until `ACTIVE`.

**Parameters**

**name** `string` *required*

Your label for this spending authority (e.g. "Supplier payouts").

**type** `'SEND' | 'COLLECT'` (default: SEND)

`SEND` (money out) or `COLLECT` (money in, off-session).

**atoaCustomerId** `string`

**Required for `COLLECT`** — the customer this contract charges (`id` from `customer.create`).
Ignored for SEND.

**description** `string`

Free-text description.

**limits** `ContractLimitsInput` *required*

The per-payment cap, ≥1 period cap, and validity window.

**ContractLimitsInput**

**maxPerPayment** `number` *required*

Max for any single payment, in the contract currency.

**periodLimits** `\{ amount, period, alignment? \}[]` *required*

One or more period caps (≥1). `period` ∈ `DAY` · `WEEK` · `FORTNIGHT` · `MONTH` · `HALF_YEAR` · `YEAR`;
`alignment` is `CALENDAR` (default) or `ANCHORED`. A longer period's cap must be ≥ a shorter one's.

**validTo** `string (ISO datetime)` *required*

When the consent expires — must be bounded.

**validFrom** `string (ISO datetime)` (default: now)

When the consent starts.

**currency** `string` (default: GBP)

The single currency for every cap in this contract.

**initialCharge** `ContractInitialChargeInput`

**COLLECT only** — take a first charge inside the customer's approval: their single approval activates the contract
**and** pays this amount, so there is no separate approval (or `payment.collect` call) for charge #1. See
[Charge on approval](https://docs.paywithatoa.com/agent-pay/collect.md#charge-on-approval).

**ContractInitialChargeInput**

**amount** `number` *required*

In pounds, not pence, in the contract currency — ≥ £1 and ≤ `maxPerPayment`.

**orderId** `string` *required*

Your order reference for the charge (≤ 50 chars).

**notes** `string`

Optional short note for your own tracking (≤ 30 chars).

**storeId** `string`

Attribute the charge to a store (from `store.list`); defaults to your primary store. The store's name/logo shows on the customer's contract page.

**Request**

```python Python theme={null}
contract = atoa.contract.create(
    name="Supplier payouts",
    limits={
        "maxPerPayment": 50.0,
        "periodLimits": [{"amount": 500.0, "period": "MONTH"}],
        "validTo": "2026-12-31T23:59:59Z",
    },
)
```

```typescript TypeScript theme={null}
const contract = await atoa.contract.create({
  name: "Supplier payouts",
  limits: {
    maxPerPayment: 50.0,
    periodLimits: [{ amount: 500.0, period: "MONTH" }],
    validTo: "2026-12-31T23:59:59Z",
  },
});
// → open contract.authorizationUrl, then contract.awaitActive(contract.contractId)
```

**Response** → `Contract`

**contractId** `string`

**agentId** `string`

The agent that owns this contract.

**environment** `'sandbox' | 'production'`

**type** `'SEND' | 'COLLECT'`

Emitted on every row so you can branch without inference.

**createdAt** `string (ISO datetime)`

**status** `ContractStatus`

Fresh from `create`: `PENDING_AUTHORIZATION`.

**authorizationUrl** `string`

The page the human authorizes at (create/update only — **absent** on `get`/`list`).

**limits** `ContractLimits`

The caps echoed back with defaults resolved.

**usage** `ContractUsageWindow[]`

Live per-period headroom (populated on `get` — see below).

**atoaCustomerId** `string`

**COLLECT only** — the customer this contract charges.

**linkedMethod** `\{ paymentMethodId, lastFourDigits?, brand?, expiryDate? \}`

**COLLECT only** — the masked card the customer linked. Absent until they link one on the contract page.

**termsVersion** `number`

**COLLECT only** — authorized terms version (1 on first approval; increments per approved update).

**initialCharge** `ContractInitialCharge`

**COLLECT only, when one was set at create** — the bundled first charge:
`{ amount, currency, orderId, storeId?, status, paymentRequestId?, failureReason? }`.
`status` runs `AWAITING_APPROVAL` → `PENDING` → `COMPLETED` / `FAILED` (`INITIATION_FAILED` if the charge could not
start — the contract stays `ACTIVE` regardless). `paymentRequestId` is minted when the customer approves; once
present, poll `payment.get` with it. Before that, `orderId` is your correlation handle.

**pendingUpdate** `\{ limits, requestedAt \} | null`

A staged limits change; the `limits` above stay enforced until it's authorized. `null` when nothing is staged.

```json Example theme={null}
{
  "contractId": "ctr_7b31…",
  "type": "SEND",
  "status": "PENDING_AUTHORIZATION",
  "authorizationUrl": "https://pay.atoa.me/consent/ctr_7b31…",
  "limits": {
    "maxPerPayment": 50.0,
    "periodLimits": [{ "amount": 500.0, "period": "MONTH", "alignment": "CALENDAR" }],
    "currency": "GBP",
    "validFrom": "2026-07-23T10:00:00Z",
    "validTo": "2026-12-31T23:59:59Z"
  }
}
```

### `contract.awaitActive(id, opts?)`

Poll `get` until the contract is `ACTIVE`, with internal backoff. Throws `AUTHORIZATION_TIMEOUT` on timeout or
`AUTHORIZATION_FAILED` if the human declined / the link expired.

**Parameters**

**contractId** `string` *required*

**opts.timeoutMs** `number`

How long to wait before throwing `AUTHORIZATION_TIMEOUT`.

**Request**

```python Python theme={null}
active = atoa.contract.await_active(contract.contract_id)
```

```typescript TypeScript theme={null}
const active = await atoa.contract.awaitActive(contract.contractId);
```

**Response** → `Contract`

The `Contract` (shape above) once `status` is `ACTIVE`.

### `contract.get(id)`

Read a contract's current state, including live `usage`. No webhooks — poll this.

**Parameters**

**contractId** `string` *required*

**Request**

```python Python theme={null}
contract = atoa.contract.get(id)
```

```typescript TypeScript theme={null}
const contract = await atoa.contract.get(id);
```

**Response** → `Contract`

The `Contract` shape (see `contract.create`), plus live per-period usage:

**usage** `ContractUsageWindow[]`

One window per period cap — `remaining = cap − usedThisPeriod − reserved`.

**ContractUsageWindow**

**period** `PeriodUnit`

`DAY` · `WEEK` · `FORTNIGHT` · `MONTH` · `HALF_YEAR` · `YEAR`.

**cap** `number`

The limit for this window.

**usedThisPeriod + reserved + remaining** `number`

Settled spend, in-flight (initiated, unsettled) spend, and headroom left.

**periodEnd** `string (ISO datetime)`

When this window and its headroom reset.

### `contract.list(opts?)` · `contract.listAll(opts?)`

List this business's contracts. `list` returns one page; `listAll` walks every page and returns a flat array. (Two
methods, one underlying list — grouped for that reason.)

**Parameters**

**opts** `ListContractsOptions`

All filters optional. `listAll` takes the same shape minus `page`.

**ListContractsOptions**

**type** `'SEND' | 'COLLECT'`

Narrow to one direction.

**status** `ContractStatus`

Narrow to one status.

**atoaCustomerId** `string`

**COLLECT only** — narrow to one customer's contracts.

**environment** `'sandbox' | 'production'`

**page** `number` (default: 0)

Zero-based page index.

**size** `number` (default: 20)

Page size.

**Request**

```python Python theme={null}
page = atoa.contract.list(type="SEND", status="ACTIVE")
all = atoa.contract.list_all(type="COLLECT")
```

```typescript TypeScript theme={null}
const page = await atoa.contract.list({ type: "SEND", status: "ACTIVE" });
const all = await atoa.contract.listAll({ type: "COLLECT" });
```

**Response** → `Page<Contract>` (`listAll` → `Contract[]`)

**list** `Page\<Contract\>`

The [pagination envelope](#methods); `data` is `Contract[]`.

**listAll** `Contract[]`

Every page flattened.

### `contract.update(id, input)`

Change a contract's `limits` — the human must approve the new caps. Returns it `PENDING_AUTHORIZATION` with a
**new** `authorizationUrl`; the old consent stays live and payable until the new one is approved. Poll
`awaitActive` again.

**Parameters**

**contractId** `string` *required*

**input** `\{ limits: ContractLimitsInput \}` *required*

The new caps (same shape as `create`).

**Request**

```python Python theme={null}
reconsent = atoa.contract.update(id, limits={
    "maxPerPayment": 75.0, "periodLimits": [{"amount": 750.0, "period": "MONTH"}], "validTo": "2027-01-31T23:59:59Z",
})
```

```typescript TypeScript theme={null}
const reconsent = await atoa.contract.update(id, {
  limits: { maxPerPayment: 75.0, periodLimits: [{ amount: 750.0, period: "MONTH" }], validTo: "2027-01-31T23:59:59Z" },
});
```

**Response** → `Contract`

The `Contract`, back at `PENDING_AUTHORIZATION` with a fresh `authorizationUrl`.

### `contract.revoke(id)`

Terminate the contract. Payments against it are then rejected.

**Parameters**

**contractId** `string` *required*

**Request**

```python Python theme={null}
result = atoa.contract.revoke(id)
```

```typescript TypeScript theme={null}
const result = await atoa.contract.revoke(id);
```

**Response** → `ContractRevokeResult`

**ContractRevokeResult** `object`

`{ contractId, type, status }` — the lean terminal confirmation.

## payment

### `payment.collect(input)`

Money **in**, one verb. `contractId` **absent** → a pay-link/QR the customer pays now. `contractId` **present** →
an off-session charge on the linked method of that `COLLECT` contract (no card details, ever). Either way, follow
with `awaitSettled(paymentRequestId)`. One payment per call; not idempotent.

**Parameters**

**amount** `\{ amount, currency? \}` *required*

The amount to collect.

**orderId** `string` *required*

Your order reference — echoed on the payment and the dashboard (and on webhooks, when they arrive in a later
phase). Not an idempotency key.

**contractId** `string`

Present → off-session charge on this `COLLECT` contract's linked method. Absent → a pay-link/QR the customer pays
now.

**atoaCustomerId** `string`

The managed Atoa customer (`id` from `customer.create`). **Required for an off-session charge** — pass it with
`contractId`, and it **must match the contract's own `atoaCustomerId`** (a mismatch is rejected with
`CONTRACT_CUSTOMER_MISMATCH`). Optional for a pay-link, where it links the payment to a managed customer (and
enables `savePaymentMethod`). Not the same as `customerId`, which is your own reference.

**customerId** `string`

YOUR id for the payer, echoed back everywhere. Omit for guest checkout (one is synthesized).

**customer** `\{ fullName?, email?, phoneCountryCode?, phoneNumber? \}`

Prefill the checkout with the customer's details.

**redirectUrl** `string`

Where the customer lands after paying.

**expiresIn** `number (ms)` (default: 180000)

Pay-link lifetime (default 3 minutes).

The rest are **optional power knobs** — the simple path never needs them:

**paymentMethod** `('PAY_BY_BANK' | 'CARD')[]`

Restrict how the customer may pay. Both show by default.

**savePaymentMethod** `boolean`

Save the card during checkout. Needs `atoaCustomerId` + `paymentMethod: ['CARD']`; incompatible with `splitBill`.

**storeId** `string`

Charge under a specific store (see `store.list`); defaults to the primary store.

**template** `'EXTERNAL_DISPLAY' | 'EXTERNAL_DISPLAY_PNG' | 'RECEIPT' | 'RECEIPT_PNG'`

Till/receipt QR template — sets `templateUrl`.

**allowRetry** `boolean` (default: true)

Allow several attempts while the link is live.

**enableTips / strictExpiry / splitBill** `boolean`

**callbackParams** `object`

Echoed back to your `redirectUrl` after payment.

**notes** `string`

Free-text note captured on the payment.

**Request**

```python Pay-link theme={null}
req = atoa.payment.collect(amount={"amount": 45.0}, order_id="booking-8812")
```

```typescript Pay-link theme={null}
const req = await atoa.payment.collect({ amount: { amount: 45.0 }, orderId: "booking-8812" });
// req.paymentUrl → give the customer the link or QR
```

```typescript Off-session theme={null}
const req = await atoa.payment.collect({
  amount: { amount: 9.99 },
  orderId: "sub-2026-07",
  contractId: "ctr_col_5a…",
  atoaCustomerId: "cus_1f…",
});
```

**Response** → `PaymentRequest`

**paymentRequestId** `string`

Poll `awaitSettled` with this — the same id Atoa's checkout widgets + direct API use.

**orderId** `string`

Your reference, echoed back.

**customerId** `string`

Your payer reference (or the synthesized one for guest checkout).

**amount** `Amount`

The requested amount + currency.

**paymentUrl + qrCodeUrl** `string`

Pay-link mode — the link/QR the customer pays at. Also `expiresAt`.

**status + contractId** `PaymentStatus / string`

Off-session charge mode — immediate charge state + the contract it ran under.

**nextAction** `NextAction`

Off-session charge mode, only when the customer must approve — see [approval gate](#approval-gate). Absent for a pay-link.

```json Pay-link theme={null}
{
  "paymentRequestId": "prq_a81f…",
  "paymentUrl": "https://pay.atoa.me/prq_a81f…",
  "qrCodeUrl": "https://pay.atoa.me/prq_a81f….svg",
  "orderId": "booking-8812",
  "customerId": "guest_3c…",
  "amount": { "amount": 45.0, "currency": "GBP" },
  "expiresAt": "2026-07-23T10:03:00Z"
}
```

### `payment.send(input)`

Money **out** against an `ACTIVE` SEND contract. `payments` is always an array (1–20; a single payment = one
element); the result is a `Payment[]` in the same order. A terminal `FAILED` with a `failureReason` is a returned
per-item `Payment`, **not** a thrown error — only operational faults throw. Confirm each with
`awaitSettled`.

**Parameters**

**contractId** `string` *required*

The `ACTIVE` SEND contract every instruction is paid against.

**payments** `SendPaymentInstruction[]` *required*

1–20 instructions; each `orderId` must be unique in the batch.

**SendPaymentInstruction**

**amount** `\{ amount, currency? \}` *required*

**beneficiary** `\{ name, sortCode, accountNumber \}` *required*

`name` is checked by Confirmation of Payee; `sortCode` 6-digit, `accountNumber` 8-digit.

**orderId** `string` *required*

Your reference, passed through. **Not** an idempotency key.

**reference** `string`

Shown to the beneficiary (1–35 chars).

**Request**

```python Python theme={null}
payments = atoa.payment.send(
    contract_id=contract.contract_id,
    payments=[{
        "amount": {"amount": 12.5},
        "beneficiary": {"name": "ACME LTD", "sortCode": "040004", "accountNumber": "12345678"},
        "orderId": "order-9281",
    }],
)
```

```typescript TypeScript theme={null}
const [payment] = await atoa.payment.send({
  contractId: contract.contractId,
  payments: [
    {
      amount: { amount: 12.5 },
      beneficiary: { name: "ACME LTD", sortCode: "040004", accountNumber: "12345678" },
      orderId: "order-9281",
    },
  ],
});
const settled = await atoa.payment.awaitSettled(payment.paymentIdempotencyId);
```

**Response** → `SendResult` (a `Payment[]` + optional `nextAction`)

A `Payment[]` in input order (iteration / `result[0]` work as before), with an optional `nextAction` *(present when
an owner approval gates the batch — see [approval gate](#approval-gate))*. See `payment.get` for the
full `Payment` shape.

```json Example theme={null}
[
  {
    "type": "DEBIT",
    "status": "PENDING",
    "paidAmount": 12.5,
    "currency": "GBP",
    "orderId": "order-9281",
    "contractId": "ctr_7b31…",
    "beneficiary": { "name": "ACME LTD", "sortCode": "04****", "accountNumber": "****5678" },
    "paymentIdempotencyId": "ATOA1692…"
  }
]
```

### `payment.get(id)`

Read one payment. One read model for both directions; accepts a `paymentRequestId` (CREDIT parent — current/best
state) **or** a `paymentIdempotencyId` (one attempt).

**Parameters**

**id** `string` *required*

A `paymentRequestId` or a `paymentIdempotencyId`.

**Request**

```python Python theme={null}
payment = atoa.payment.get(payment_request_id)
```

```typescript TypeScript theme={null}
const payment = await atoa.payment.get(paymentRequestId);
```

**Response** → `Payment`

**type** `'CREDIT' | 'DEBIT'`

`CREDIT` = money in (COLLECT) · `DEBIT` = money out (SEND). Discriminates which fields below are present.

**status** `PaymentStatus`

`AWAITING_AUTHORIZATION` · `PENDING` · `AUTHORIZED` · `COMPLETED` · `FAILED` · `CANCELLED` · `EXPIRED` · `PARTIALLY_REFUNDED` · `REFUNDED`.

**paidAmount + currency** `number / string`

What settled — flat, not nested.

**orderId** `string`

Your reference (not a de-dup key).

**paymentIdempotencyId** `string | null`

Settlement-attempt id; `null` until an attempt exists.

**contractId** `string`

**SEND (DEBIT) only** — the contract this draw ran under.

**beneficiary** `\{ name, sortCode?, accountNumber? \}`

**SEND (DEBIT) only** — who you paid, masked.

**approvalId + approvalExpiresAt** `string`

**SEND (DEBIT) only** — present while the draw is gated on an approval.

**paymentRequestId** `string`

**COLLECT (CREDIT) only** — the payment-request (parent) id.

**customerId + atoaCustomerId** `string`

**COLLECT (CREDIT) only** — your own payer reference, and the Atoa customer id (once known).

**consumerName + bankName + bankAccountNo** `string`

**COLLECT (CREDIT) only** — who paid, once an attempt exists: payer name, bank, and masked account.

**failureReason + failureReasonDescription** `string`

Either direction — present on a terminal `FAILED`/`CANCELLED`.

```json Example theme={null}
{
  "type": "CREDIT",
  "status": "COMPLETED",
  "paidAmount": 45.0,
  "currency": "GBP",
  "orderId": "booking-8812",
  "paymentIdempotencyId": "ATOA1780…",
  "paymentRequestId": "prq_a81f…"
}
```

### `payment.awaitSettled(id, opts?)`

Poll a payment until it stops moving (`COMPLETED`/`FAILED`/`CANCELLED`, `EXPIRED` on CREDIT, or `AUTHORIZED`). Throws
`SETTLEMENT_TIMEOUT` on timeout. Accepts a `paymentRequestId` or a `paymentIdempotencyId`.

**Parameters**

**id** `string` *required*

A `paymentRequestId` or a `paymentIdempotencyId`.

**opts.timeoutMs** `number`

How long to wait before throwing `SETTLEMENT_TIMEOUT`.

**Request**

```python Python theme={null}
settled = atoa.payment.await_settled(payment_request_id)
```

```typescript TypeScript theme={null}
const settled = await atoa.payment.awaitSettled(paymentRequestId);
```

**Response** → `Payment`

The `Payment` (shape above) at a resting state.

### `payment.list(opts?)` · `payment.listAll(opts?)`

List this agent's payments across both directions. `list` returns one page; `listAll` walks every page. (Two
methods, one underlying list.)

**Parameters**

**opts** `ListPaymentsOptions`

All filters optional. `listAll` takes the same shape minus `page`.

**ListPaymentsOptions**

**type** `'SEND' | 'COLLECT'`

Direction — `SEND` (money out) or `COLLECT` (money in). Rows come back with `type``DEBIT`/`CREDIT` respectively.

**status** `PaymentStatus`

Narrow to one status.

**contractId** `string`

**SEND only** — narrow to one contract's draws.

**paymentRequestId** `string`

**COLLECT only** — the attempts of one payment request (parent → children).

**atoaCustomerIds** `string[]`

**COLLECT only** — narrow to specific payers by their Atoa customer id(s).

**page** `number` (default: 0)

Zero-based page index.

**size** `number` (default: 20)

Page size.

**Request**

```python Python theme={null}
page = atoa.payment.list(type="COLLECT", status="COMPLETED")
all = atoa.payment.list_all(type="SEND")
```

```typescript TypeScript theme={null}
const page = await atoa.payment.list({ type: "COLLECT", status: "COMPLETED" });
const all = await atoa.payment.listAll({ type: "SEND" });
```

**Response** → `Page<Payment>` (`listAll` → `Payment[]`)

**list** `Page\<Payment\>`

The [pagination envelope](#methods); `data` is `Payment[]` (shape above).

**listAll** `Payment[]`

Every page flattened.

### `payment.refund(id, input)`

Refund a `COMPLETED` collected payment — full or partial.

**Parameters**

**paymentRequestId** `string` *required*

The collected payment to refund.

**input** `CreateRefundInput` *required*

`{ amount, reason? }` — the amount must not exceed the paid amount. Sandbox: `reason: "FAILURE TEST"` forces a
`FAILED` refund.

**Request**

```python Python theme={null}
refund = atoa.payment.refund(payment_request_id, amount={"amount": 45.0}, reason="Order returned")
```

```typescript TypeScript theme={null}
const refund = await atoa.payment.refund(paymentRequestId, { amount: { amount: 45.0 }, reason: "Order returned" });
```

**Response** → `Refund`

**refundId** `string`

The refund id — pass to `cancelRefund` while still `INITIATED`.

**paymentRequestId** `string`

The collected payment this refund belongs to.

**createdAt** `string (ISO datetime)`

**status** `RefundStatus`

`INITIATED` · `COMPLETED` · `FAILED` · `CANCELLED`.

**refundAmount** `Amount`

The refunded amount (`refund_amount`, a float, in Python).

**paidAmount** `Amount`

The original paid amount.

```json Example theme={null}
{ "refundId": "ref_2b8c…", "status": "INITIATED", "refundAmount": { "amount": 45.0, "currency": "GBP" }, "paidAmount": { "amount": 45.0, "currency": "GBP" } }
```

### `payment.listRefunds(id)` · `payment.cancelRefund(refundId)`

List the refunds of one collected payment, or cancel a still-`INITIATED` refund.

**Parameters**

**listRefunds: paymentRequestId** `string` *required*

**cancelRefund: refundId** `string` *required*

Only a not-yet-processed refund can be cancelled.

**Request**

```python Python theme={null}
refunds = atoa.payment.list_refunds(payment_request_id)
atoa.payment.cancel_refund(refunds[0].refund_id)
```

```typescript TypeScript theme={null}
const refunds = await atoa.payment.listRefunds(paymentRequestId);
await atoa.payment.cancelRefund(refunds[0].refundId);
```

**Response**

**listRefunds** `Refund[]`

The refunds of the payment.

**cancelRefund** `CancelRefundResult`

`{ refundId, message? }`.

### `payment.cancel(id)`

Cancel an unpaid collect (link) or a charge resting at `AUTHORIZED`.

**Parameters**

**paymentRequestId** `string` *required*

**Request**

```python Python theme={null}
cancelled = atoa.payment.cancel(payment_request_id)
```

```typescript TypeScript theme={null}
const cancelled = await atoa.payment.cancel(paymentRequestId);
```

**Response** → `Payment`

The `CANCELLED` payment.

### `payment.awaitDecision(approvalId)`

Poll an approval (from a `send`/`collect` `nextAction`) to its terminal decision. See the
[approval gate](#approval-gate) for the flow.

**Parameters**

**approvalId** `string` *required*

From a `nextAction`.

**opts.timeoutMs** `number`

How long to wait before throwing `SETTLEMENT_TIMEOUT`.

**Request**

```python Python theme={null}
decision = atoa.payment.await_decision(next_action.approval_id)
if decision.status != "APPROVED":
    ...  # handle
```

```typescript TypeScript theme={null}
const decision = await atoa.payment.awaitDecision(nextAction.approvalId);
if (decision.status !== "APPROVED") { /* handle */ }
```

**Response** → `Approval`

**approvalId** `string`

**type** `'SEND' | 'COLLECT'`

Which side the approval gates.

**contractId** `string`

The contract this approval is bound to.

**status** `ApprovalStatus`

`PENDING` · `APPROVED` · `DECLINED` · `CANCELLED` · `EXPIRED` · `SUPERSEDED`.

**expiresAt** `string (ISO datetime)`

When the approval lapses.

**createdAt** `string (ISO datetime)`

**decidedBy + decidedAt** `string`

Who decided and when, once resolved.

### `payment.cancelApproval(contractId, approvalId)`

Agent-initiated cancel of a pending approval — voids the underlying draws.

**Parameters**

**contractId** `string` *required*

**approvalId** `string` *required*

**Request**

```python Python theme={null}
atoa.payment.cancel_approval(contract_id, approval_id)
```

```typescript TypeScript theme={null}
await atoa.payment.cancelApproval(contractId, approvalId);
```

**Response**

**result** `\{ approvalId, status \}`

The cancelled approval id + its new status.

## customer

### `customer.create(input)`

Create a managed customer — needed for off-session COLLECT contracts (the customer links a card to the contract on
its authorization page). Guest checkout does not require one.

**Parameters**

**fullName** `string` *required*

2–30 characters.

**email** `string`

Valid email. **Either `email` or a phone number is required.**

**phoneCountryCode** `string`

Digits only (e.g. `44`). Pair with `phoneNumber`.

**phoneNumber** `string`

Digits only, without country code.

**type** `'INDIVIDUAL' | 'BUSINESS'` (default: INDIVIDUAL)

**vatNumber** `string`

Business customers only.

**address, city, postcode** `string`

Optional postal details.

**Request**

```python Python theme={null}
customer = atoa.customer.create(full_name="Jane Doe", email="jane@example.com")
```

```typescript TypeScript theme={null}
const customer = await atoa.customer.create({ fullName: "Jane Doe", email: "jane@example.com" });
```

**Response** → `Customer`

**id** `string`

The `atoaCustomerId` used across collect + saved cards.

**fullName** `string`

**email / phoneNumber** `string`

**type** `'INDIVIDUAL' | 'BUSINESS'`

**createdAt** `string (ISO datetime)`

```json Example theme={null}
{ "id": "cus_1f…", "fullName": "Jane Doe", "email": "jane@example.com", "type": "INDIVIDUAL", "createdAt": "2026-07-23T10:00:00Z" }
```

### `customer.get(id)` · `customer.update(id, input)` · `customer.delete(id)`

Read, edit, or remove one managed customer. (Three verbs on one resource id — grouped for that reason.)

**Parameters**

**customerId** `string` *required*

**update: input** `Partial\<CreateCustomerInput\>` *required*

Any subset of the create fields.

**Request**

```python Python theme={null}
customer = atoa.customer.get(id)
atoa.customer.update(id, email="new@example.com")
atoa.customer.delete(id)
```

```typescript TypeScript theme={null}
const customer = await atoa.customer.get(id);
await atoa.customer.update(id, { email: "new@example.com" });
await atoa.customer.delete(id);
```

**Response**

**get / update** `Customer`

The `Customer` shape (above).

**delete** `DeleteResult`

`{ success, message }`.

### `customer.list(opts?)`

List the customers this agent created.

**Parameters**

**opts** `\{ page?, size? \}`

**fields**

**page** `number` (default: 0)

Zero-based page index.

**size** `number` (default: 20)

Page size.

**Request**

```python Python theme={null}
page = atoa.customer.list(page=0, size=20)
```

```typescript TypeScript theme={null}
const page = await atoa.customer.list({ page: 0, size: 20 });
```

**Response** → `Page<Customer>`

**Page\<Customer>** `object`

The [pagination envelope](#methods); `data` is `Customer[]`.

## store · client-level

### `store.list(opts?)`

List this business's stores — discover a `storeId` to tag a collect by location.

**Parameters**

**opts** `\{ page?, size? \}`

**fields**

**page** `number` (default: 0)

Zero-based page index.

**size** `number` (default: 20)

Page size.

**Request**

```python Python theme={null}
stores = atoa.store.list()
```

```typescript TypeScript theme={null}
const stores = await atoa.store.list();
```

**Response** → `Page<Store>`

**Page\<Store>** `object`

The [pagination envelope](#methods); `data` is `Store[]`. Pass a store's `id` as `storeId` on a collect.

### `checkAvailability()`

Unauthenticated health probe. Takes no parameters and **never throws** — check the returned flag.

**Request**

```python Python theme={null}
health = atoa.check_availability()
```

```typescript TypeScript theme={null}
const health = await atoa.checkAvailability();
```

**Response** → `AvailabilityStatus`

**AvailabilityStatus** `object`

Whether the backend is reachable — inspect the flag rather than catching an error.

### `sandboxTestAccounts()`

The sandbox SEND recipients and the outcome each forces. Takes no parameters; production has none.

**Request**

```python Python theme={null}
test = atoa.sandbox_test_accounts()
```

```typescript TypeScript theme={null}
const test = atoa.sandboxTestAccounts();
```

**Response** → `SandboxTestAccounts`

**SandboxTestAccounts** `object`

`{ sandbox, note, accounts }` — the recipient accounts and their forced outcomes.

## Errors

Business outcomes are **returned** — branch on the `Payment`'s `status` and `failureReason`. Operational faults are
**thrown** `AgentPayError` subclasses — catch and switch on `code`.

**Returned `failureReason` values:**

| Direction | Reasons |
| - | - |
| Send (`DEBIT`) | `NAME_MISMATCH` · `LIMIT_EXCEEDED` · `CONTRACT_INACTIVE` · `SERVICE_UNAVAILABLE` · `SETTLEMENT_FAILED` |
| Collect (`CREDIT`) | `PAYMENT_REJECTED` · `AUTHORIZATION_FAILED` · `CUSTOMER_CANCELLED` · `MERCHANT_CANCELLED` |
| Approvals (either) | `APPROVAL_DECLINED` · `APPROVAL_EXPIRED` |
| Any | `UNKNOWN` — read `failureReasonDescription` |

**Thrown `AgentPayError` subclasses:**

| `code` | Thrown when |
| - | - |
| `AUTH_ERROR` | 401/403 — key invalid, not entitled, or a revoked agent. |
| `VALIDATION_ERROR` | 400/422 — malformed request. |
| `NOT_FOUND` | 404 — unknown or not-owned contract / payment / customer. |
| `CONFLICT` | 409 — re-registering an agent with a different key, env, or business. |
| `REGISTRATION_ERROR` | The register handshake failed. |
| `RATE_LIMIT` | 429 — back off exponentially and retry. |
| `KEY_NOT_FOUND` | No signing key available for the requested id. |
| `CONTRACT_CHARGE_ERROR` | Base of the collect-charge ladder below — catch this to handle any charge refusal in one branch. |
| `CONTRACT_NOT_FOUND` / `CONTRACT_NOT_ACTIVE` / `CONTRACT_TYPE_MISMATCH` / `CONTRACT_CUSTOMER_MISMATCH` | The contract is unknown, not chargeable (`.reason` says why), the wrong type, or another customer's. |
| `CAP_EXCEEDED` | Over a cap — `.remaining` is what's left this window. |
| `NO_PAYMENT_METHOD` | No usable method linked to the contract. |
| `ATOA_CUSTOMER_REQUIRED` | A contract charge without `atoaCustomerId`. |
| `PARAMS_CONFLICT` | Mutually exclusive options in one call. |
| `AUTHORIZATION_TIMEOUT` / `AUTHORIZATION_FAILED` | `awaitActive` timed out / the approver declined or the link expired. |
| `SETTLEMENT_TIMEOUT` | `awaitSettled` timed out. |
| `SERVER_ERROR` | 5xx — the service received the request but failed on its side. Retryable. |
| `API_ERROR` | An HTTP status the SDK doesn't map to a specific class (402, 405, 408, …). `status` is set. |
| `NETWORK_ERROR` | Connection failed (DNS/TLS/timeout) — the server never answered. Retryable. |

Retry guidance:

* **Retry:** `RATE_LIMIT` (back off), `SERVER_ERROR`, `NETWORK_ERROR` and timeouts — but re-read with `get` / `awaitSettled` first;
  the operation may still be in flight, and `orderId` is not a de-duplication key.
* **Fix first:** `VALIDATION_ERROR`, `AUTH_ERROR`, `PARAMS_CONFLICT`, `CAP_EXCEEDED` (charge ≤ `.remaining`),
  `CONTRACT_NOT_ACTIVE` (re-approve).
* **Don't retry:** a returned `FAILED` is a decision, not a glitch.

## Sandbox

Create the client with `environment: "sandbox"` and a sandbox API key. No real money moves; you choose every
outcome.

| Flow | How the outcome is decided |
| - | - |
| Send | Recipient account: `040004` / `12345678` → `COMPLETED`; `10000002`, `10000003` → `FAILED`. Fetch with `sandboxTestAccounts()` — don't hardcode. |
| Collect (pay-link) | Open `paymentUrl`, choose the **Atoa Test Bank**, pick `COMPLETED` / `FAILED` / `PENDING`. Cancel there → `CUSTOMER_CANCELLED`. |
| Expiry / cancel | Leave a link unpaid past `expiresIn` → `EXPIRED`; call `payment.cancel` → `MERCHANT_CANCELLED`. |
| Approvals | Open the `approvalUrl` and approve, decline, or let it lapse. |
| Failed refund | `payment.refund(...)` with `reason: "FAILURE TEST"`. |

Production is the same code with a production key and `environment: "production"` — real banks, real approvers,
real money.

## Approval gate

Every send and every off-session charge pauses for approval — this is SCA (Strong Customer Authentication). A
`send` result (still a `Payment[]`) and an off-session `collect` result carry the action under **`nextAction`**
(TS) / **`next_action`** (Python); a pay-link collect has none. The approver is the person bound to the contract —
the **business owner** for SEND, the **customer** for an off-session COLLECT — deciding on Atoa's hosted page with
a one-time code or a passkey. Your app never collects the credential.

**type** `string`

`"APPROVAL"` for this gate. The full action vocabulary — every type, `for`, `mustEscalate`, and the fallback rule — is [the nextAction contract](https://docs.paywithatoa.com/agent-pay/next-action.md); switch on `type` so new kinds don't break you.

**approvalId** `string`

Pass to `awaitDecision` / `cancelApproval`.

**clientSecret** `string`

Bearer secret for the hosted page / the approvals browser SDK.

**approvalUrl** `string`

The hosted approval page to hand to the approver.

**expiresAt** `string (ISO datetime)`

When the approval lapses.

## Approvals browser SDK

`@atoapayments/agentic-payment-approvals-js` embeds Atoa's hosted approval page as an iframe **inside a container you
provide** and resolves to the decision. Browser/TypeScript only; zero dependencies. Python integrators share the
`approvalUrl` or drive this from their web layer. Full guide: [**Approvals SDK**](https://docs.paywithatoa.com/agent-pay/approvals.md).

```typescript theme={null}
import { confirmApproval } from "@atoapayments/agentic-payment-approvals-js";

const approval = confirmApproval({
  container: "#approval",                // a selector or HTMLElement you render + size
  clientSecret: result.nextAction.clientSecret,
  colorScheme: "light",                  // "light" | "dark"
  onEvent: (e) => console.log(e.type),   // lifecycle stream (opened, loaded, approved, …)
});
const { status } = await approval.result; // APPROVED | DECLINED | EXPIRED | SUPERSEDED | CANCELLED
approval.destroy();                        // from your own close affordance; no-op after a decision
```

`confirmApproval` returns an `ApprovalHandle` (`result` · `on(…)` · `destroy()`) — it is not awaited directly. Other
options: `theme` (bounded, contrast-clamped tokens), `labels.approve`
(`APPROVE` | `PAY` | `CONFIRM` | `AUTHORIZE`), `onResult`, `apiUrl` (local-stack override). Events: `opened` ·
`loaded` · `approved` · `declined` · `expired` · `superseded` · `error` · `closed`.

> [!WARNING]
>
> The credential — code or passkey — is entered only on Atoa's page. Never build your own form that collects it.

## FAQ

> **Do I have to register my domain to use the embedded checkout?**
>
> No. There's no origin allowlist and nothing to register — any page can host the checkout, `localhost`
> included.
>
> What protects the payment is the API key your server used to create it, and the customer's own one-time code
> or passkey, entered inside the frame on Atoa's origin.

> **Can I use the embed without a backend?**
>
> No. The API key creates payments and must stay on your server, and there is no method to create a payment
> from the browser. `AtoaUI.mount()` shows a payment that already exists.
>
> With no backend, send the customer a pay-link from `payment.collect` instead.

> **My container stays blank.**
>
> Check the browser console. `AtoaUI.mount: the embed frame at <origin> never connected within <n>ms` means the
> iframe loaded but never answered — usually a `frame-src` content-security policy on your own page, or a
> network path that can't reach Atoa.
>
> If nothing is logged at all, confirm `container` resolved to a real element and that you passed
> `environment`.

> **Can I apply my brand colours and font to the checkout?**
>
> Not today. `theme` takes `mode` — `'auto'`, `'light'` or `'dark'` — and nothing else.
>
> A checkout a host can restyle is a checkout a host can make look like something it is not, and the trust
> the frame carries is what you are embedding. Brand treatment, when it ships, will be issued to your
> business from Atoa's side rather than accepted from the page.

> **Do webhooks fire for agentic contract payments?**
>
> Not in this phase. Merchant webhooks do not fire for contract payments — poll from your server with
> `payment.get` or `awaitSettled` for settlement truth. Webhooks arrive in a later phase.

## Go-live checklist

1. Production API key as `ATOA_API_KEY`, client with `environment: "production"`.
2. Signing key from a secrets manager, or a [KMS signer](#kms-custom-signer) — never a generated throwaway.
3. Branch on all three shapes: returned `COMPLETED`, returned `FAILED`/`CANCELLED` with a `failureReason`, thrown
   `AgentPayError`.
4. Deliver the `approvalUrl` to the approver (or drive the browser SDK) on every send and off-session charge;
   handle `APPROVAL_DECLINED` / `APPROVAL_EXPIRED`; observe with `awaitDecision`.
5. Poll from your server — `get` / `awaitSettled` — for settlement truth. Merchant webhooks do not fire for
   agentic contract payments in this phase; webhooks arrive in a later phase.
6. Run one small real payment end to end — including a declined and a cancelled path — before scaling up.

## Changelog

* **0.0.3** — `initialCharge` on COLLECT contracts ([charge on approval](https://docs.paywithatoa.com/agent-pay/collect.md#charge-on-approval)), the
  `get_agent` tool, and the [`nextAction`](https://docs.paywithatoa.com/agent-pay/next-action) envelope on verb results.
* **0.0.2** — first MIT-licensed release.
* **0.0.1** — initial release.

Identifiers (`paymentRequestId`, customer ids, statuses, field names) carry straight over to Atoa's direct API if
you outgrow the SDK.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.