await_settled → awaitSettled), with identical parameters and byte-identical signed requests.
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 verifies against the public key you registered once. The private key is never sent. Requests can’t be tampered with or replayed.
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):
KMS / custom signer
Pass asigner instead of a PEM. The SDK hands it the canonical subject string per request and expects a
detached compact JWS 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.
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 decimal major units —{ 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[].
object
list: page (zero-based, default 0) and size (default 20).
agent
agent.register(opts)
Bootstrap your agent’s identity — the challenge → sign → register handshake in one call. Idempotent: the same
key/env/business returns the same agent; a changed name/description is a metadata update, not a conflict.
Parameters
string
required
Human-readable agent name shown in the Atoa dashboard.
string
Free-text description of what this agent does.
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).
RegisteredAgent
string
The id this client signs as from now on.
string
The business this agent belongs to.
'sandbox' | 'production'
string
string
Free-text description, if you set one.
string
Echoed here only —
me/list do not return it.agent.me()
Read this agent’s own identity as the service sees it. Takes no parameters.
Request
AgentIdentity
string
string
'sandbox' | 'production'
string
string
Free-text description, if you set one.
string
e.g.
ACTIVE.agent.list()
List every agent registered under your business + environment. Authenticated by the SDK API key alone (no JWS).
Takes no parameters.
Request
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
string
required
Your label for this spending authority (e.g. “Supplier payouts”).
'SEND' | 'COLLECT'
default:"SEND"
SEND (money out) or COLLECT (money in, off-session).string
Required for
COLLECT — the customer this contract charges (id from customer.create).
Ignored for SEND.string
Free-text description.
ContractLimitsInput
required
The per-payment cap, ≥1 period cap, and validity window.
Contract
string
string
The agent that owns this contract.
'sandbox' | 'production'
'SEND' | 'COLLECT'
Emitted on every row so you can branch without inference.
string (ISO datetime)
ContractStatus
Fresh from
create: PENDING_AUTHORIZATION.string
The page the human authorizes at (create/update only — absent on
get/list).ContractLimits
The caps echoed back with defaults resolved.
ContractUsageWindow[]
Live per-period headroom (populated on
get — see below).string
COLLECT only — the customer this contract charges.
{ paymentMethodId, lastFourDigits?, brand?, expiryDate? }
COLLECT only — the masked card the customer linked. Absent until they link one on the contract page.
number
COLLECT only — authorized terms version (1 on first approval; increments per approved update).
{ limits, requestedAt } | null
A staged limits change; the
limits above stay enforced until it’s authorized. null when nothing is staged.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
string
required
number
How long to wait before throwing
AUTHORIZATION_TIMEOUT.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
string
required
Contract
The Contract shape (see contract.create), plus live per-period usage:
ContractUsageWindow[]
One window per period cap —
remaining = cap − usedThisPeriod − reserved.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
ListContractsOptions
All filters optional.
listAll takes the same shape minus page.Page<Contract> (listAll → Contract[])
Page<Contract>
Contract[]
Every page flattened.
contract.update(id, input)
Change a contract’s limits — a re-consent. Returns it PENDING_AUTHORIZATION with a new authorizationUrl;
the old consent stays live and payable until re-authorized. Poll awaitActive again.
Parameters
string
required
{ limits: ContractLimitsInput }
required
The new caps (same shape as
create).Contract
The Contract, back at PENDING_AUTHORIZATION with a fresh authorizationUrl.
contract.revoke(id)
Terminate the contract. Payments against it are then rejected.
Parameters
string
required
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, currency? }
required
The amount to collect.
string
required
Your order reference — echoed on the payment, webhooks, and dashboard. Not an idempotency key.
string
Present → off-session charge on this
COLLECT contract’s linked method. Absent → a pay-link/QR the customer pays
now.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.string
YOUR id for the payer, echoed back everywhere. Omit for guest checkout (one is synthesized).
{ fullName?, email?, phoneCountryCode?, phoneNumber? }
Prefill the checkout with the customer’s details.
string
Where the customer lands after paying.
number (ms)
default:"180000"
Pay-link lifetime (default 3 minutes).
('PAY_BY_BANK' | 'CARD')[]
Restrict how the customer may pay. Both show by default.
boolean
Save the card during checkout. Needs
atoaCustomerId + paymentMethod: ['CARD']; incompatible with splitBill.string
Charge under a specific store (see
store.list); defaults to the primary store.'EXTERNAL_DISPLAY' | 'EXTERNAL_DISPLAY_PNG' | 'RECEIPT' | 'RECEIPT_PNG'
Till/receipt QR template — sets
templateUrl.boolean
default:"true"
Allow several attempts while the link is live.
boolean
object
Echoed back to your
redirectUrl after payment.string
Free-text note captured on the payment.
PaymentRequest
string
Poll
awaitSettled with this — the same id Atoa’s checkout widgets + direct API use.string
Your reference, echoed back.
string
Your payer reference (or the synthesized one for guest checkout).
Amount
The requested amount + currency.
string
Pay-link mode — the link/QR the customer pays at. Also
expiresAt.PaymentStatus / string
Off-session charge mode — immediate charge state + the contract it ran under.
NextAction
Off-session charge mode, only when the customer must approve — see approval gate. Absent for a pay-link.
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
string
required
The
ACTIVE SEND contract every instruction is paid against.SendPaymentInstruction[]
required
1–20 instructions; each
orderId must be unique in the batch.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). See payment.get for the
full Payment shape.
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
string
required
A
paymentRequestId or a paymentIdempotencyId.Payment
'CREDIT' | 'DEBIT'
CREDIT = money in (COLLECT) · DEBIT = money out (SEND). Discriminates which fields below are present.PaymentStatus
AWAITING_AUTHORIZATION · PENDING · AUTHORIZED · COMPLETED · FAILED · CANCELLED · EXPIRED · PARTIALLY_REFUNDED · REFUNDED.number / string
What settled — flat, not nested.
string
Your reference (not a de-dup key).
string | null
Settlement-attempt id;
null until an attempt exists.string
SEND (DEBIT) only — the contract this draw ran under.
{ name, sortCode?, accountNumber? }
SEND (DEBIT) only — who you paid, masked.
string
SEND (DEBIT) only — present while the draw is gated on an approval.
string
COLLECT (CREDIT) only — the payment-request (parent) id.
string
COLLECT (CREDIT) only — your own payer reference, and the Atoa customer id (once known).
string
COLLECT (CREDIT) only — who paid, once an attempt exists: payer name, bank, and masked account.
string
Either direction — present on a terminal
FAILED/CANCELLED.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
string
required
A
paymentRequestId or a paymentIdempotencyId.number
How long to wait before throwing
SETTLEMENT_TIMEOUT.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
ListPaymentsOptions
All filters optional.
listAll takes the same shape minus page.Page<Payment> (listAll → Payment[])
Page<Payment>
Payment[]
Every page flattened.
payment.refund(id, input)
Refund a COMPLETED collected payment — full or partial.
Parameters
string
required
The collected payment to refund.
CreateRefundInput
required
{ amount, reason? } — the amount must not exceed the paid amount. Sandbox: reason: "FAILURE TEST" forces a
FAILED refund.Refund
string
The refund id — pass to
cancelRefund while still INITIATED.string
The collected payment this refund belongs to.
string (ISO datetime)
RefundStatus
INITIATED · COMPLETED · FAILED · CANCELLED.Amount
The refunded amount (
refund_amount, a float, in Python).Amount
The original paid amount.
payment.listRefunds(id) · payment.cancelRefund(refundId)
List the refunds of one collected payment, or cancel a still-INITIATED refund.
Parameters
string
required
string
required
Only a not-yet-processed refund can be cancelled.
Refund[]
The refunds of the payment.
CancelRefundResult
{ refundId, message? }.payment.cancel(id)
Cancel an unpaid collect (link) or a charge resting at AUTHORIZED.
Parameters
string
required
Payment
The CANCELLED payment.
payment.awaitDecision(approvalId)
Poll an approval (from a send/collect nextAction) to its terminal decision. See the
approval gate for the flow.
Parameters
string
required
From a
nextAction.number
How long to wait before throwing
SETTLEMENT_TIMEOUT.Approval
string
'SEND' | 'COLLECT'
Which side the approval gates.
string
The contract this approval is bound to.
ApprovalStatus
PENDING · APPROVED · DECLINED · CANCELLED · EXPIRED · SUPERSEDED.string (ISO datetime)
When the approval lapses.
string (ISO datetime)
string
Who decided and when, once resolved.
payment.cancelApproval(contractId, approvalId)
Agent-initiated cancel of a pending approval — voids the underlying draws.
Parameters
string
required
string
required
{ 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
string
required
2–30 characters.
string
Valid email. Either
email or a phone number is required.string
Digits only (e.g.
44). Pair with phoneNumber.string
Digits only, without country code.
'INDIVIDUAL' | 'BUSINESS'
default:"INDIVIDUAL"
string
Business customers only.
string
Optional postal details.
Customer
string
The
atoaCustomerId used across collect + saved cards.string
string
'INDIVIDUAL' | 'BUSINESS'
string (ISO datetime)
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
string
required
Partial<CreateCustomerInput>
required
Any subset of the create fields.
Customer
The
Customer shape (above).DeleteResult
{ success, message }.customer.list(opts?)
List the customers this agent created.
Parameters
{ page?, size? }
Page<Customer>
object
store · client-level
store.list(opts?)
List this business’s stores — discover a storeId to tag a collect by location.
Parameters
{ page?, size? }
Page<Store>
object
checkAvailability()
Unauthenticated health probe. Takes no parameters and never throws — check the returned flag.
Request
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
SandboxTestAccounts
object
{ sandbox, note, accounts } — the recipient accounts and their forced outcomes.Errors
Business outcomes are returned — branch on thePayment’s status and failureReason. Operational faults are
thrown AgentPayError subclasses — catch and switch on code.
Returned failureReason values:
Thrown
AgentPayError subclasses:
Retry guidance:
- Retry:
RATE_LIMIT(back off),SERVER_ERROR,NETWORK_ERRORand timeouts — but re-read withget/awaitSettledfirst; the operation may still be in flight, andorderIdis 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
FAILEDis a decision, not a glitch.
Sandbox
Create the client withenvironment: "sandbox" and a sandbox API key. No real money moves; you choose every
outcome.
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. Asend 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 party 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 WebAuthn passkey.
Your app never collects the credential.
string
Always
"APPROVAL" today; switch on it so new action kinds don’t break you.string
Pass to
awaitDecision / cancelApproval.string
Bearer secret for the hosted page / the approvals browser SDK.
string
The hosted approval page to hand to the approver.
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.
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.
Go-live checklist
- Production API key as
ATOA_API_KEY, client withenvironment: "production". - Signing key from a secrets manager, or a KMS signer — never a generated throwaway.
- Branch on all three shapes: returned
COMPLETED, returnedFAILED/CANCELLEDwith afailureReason, thrownAgentPayError. - Deliver the
approvalUrlto the approver (or drive the browser SDK) on every send and off-session charge; handleAPPROVAL_DECLINED/APPROVAL_EXPIRED; observe withawaitDecision. - Register webhooks for status changes; keep
get/awaitSettledpolling as a fallback. - Run one small real payment end to end — including a declined and a cancelled path — before scaling up.
Changelog
- 0.0.1 — initial release.
paymentRequestId, customer ids, statuses, field names) carry straight over to Atoa’s direct API if
you outgrow the SDK.