Agent Pay gives your AI agents a guarded way to move money — within limits a human approved once. Collect takes money in; send pays money out. Contracts are AP2-aligned mandates; sends and off-session charges pause for Strong Customer Authentication. Atoa enforces every limit server-side.

Your agent calls Atoa, which enforces consent and caps, then collects money in from a customer or sends money out to a payee.

Install

npm install @atoapayments/agent-pay        # Node 22+

Setup

Get a sandbox API key from the Atoa dashboard and set it in your environment. Sandbox and production keys are separate.

export ATOA_API_KEY="your_sandbox_api_key"

Create a client and register your agent. Registration is idempotent. The ES256 key signs every request; here it’s generated in memory — in production, load it from your secrets manager or use a KMS signer. Both ways to supply credentials — env var vs. explicit — are covered in Authentication.

import atoa_agent_pay

private_key_pem, _ = atoa_agent_pay.generate_es256_keypair()
# api_key is read from ATOA_API_KEY; the public key is derived from private_key_pem.
atoa = atoa_agent_pay.init(environment="sandbox", private_key_pem=private_key_pem)

atoa.agent.register(name="Bookings assistant")   # registers the derived public key

Receiving or sending?

Decision tree: which way is the money going? Receiving splits on whether the customer is here now — yes is a pay-link (payment.collect, no contract), no is an off-session charge (payment.collect under a COLLECT contract). Sending is payment.send under a SEND contract the owner authorises once.
  • Customer presentpayment.collect returns a pay-link/QR. No contract. → Collect
  • Customer not presentpayment.collect with a contractId, under a COLLECT contract the customer approved once. → Collect
  • Paying outpayment.send under a SEND contract the account owner approved once. → Send

Collect your first payment

Create a pay-link and wait for the result. In sandbox, open the link, pick the Atoa Test Bank, and choose the outcome yourself.

req = atoa.payment.collect(
    amount={"amount": 45.00},    # decimal major units; GBP by default
    order_id="booking-8812",     # your own reference
)

print(req.payment_url)           # open this and choose an outcome

settled = atoa.payment.await_settled(req.payment_request_id)
print(settled.status)            # COMPLETED

Send your first payout

Create a SEND contract, approve it once at the authorizationUrl, then send. Every payout pauses for the business owner’s approval (SCA) before money moves. In sandbox the recipient account decides the outcome — discover the accounts with atoa.sandboxTestAccounts().

contract = atoa.contract.create(
    type="SEND",
    name="Supplier payouts",
    limits={
        "max_per_payment": 500.00,
        "period_limits": [{"amount": 2000.00, "period": "MONTH"}],
        "valid_to": "2026-12-31T23:59:59Z",
    },
)

print(contract.authorization_url)               # the account owner approves here
atoa.contract.await_active(contract.contract_id)

result = atoa.payment.send(
    contract_id=contract.contract_id,
    payments=[{
        "amount": {"amount": 250.00},
        "beneficiary": {"name": "ACME LTD", "sortCode": "040004", "accountNumber": "12345678"},  # sandbox: settles COMPLETED
        "orderId": "payout-3001",
    }],
)

print(result.next_action.approval_url)          # the owner approves the payout here — in sandbox, open it yourself
atoa.payment.await_decision(result.next_action.approval_id)   # APPROVED → the payment executes

print(result[0].status)                         # COMPLETED

Money moved in sandbox. The next_action approval step applies to every send and every off-session charge — see Collect and Send.

Next

Download the notebook · run it locally

The full flow as a runnable Jupyter notebook — pip install atoa-agent-pay, add a sandbox ATOA_API_KEY, run.