# Cancel a checkout
Source: https://docs.agentaos.ai/api-reference/checkouts/cancel
POST /gateway/sessions/{sessionId}/cancel
Stops the buyer from paying. If a payment already cleared before the cancel call lands, it still completes: cancelling doesn't reach into the card processor or on-chain state after the fact. Only works while the checkout is `open` (`400` otherwise).
Cancelling only works while the checkout is `open` (`400` otherwise). A `409` means it was modified concurrently.
# Create a checkout
Source: https://docs.agentaos.ai/api-reference/checkouts/create
POST /gateway/sessions
Creates a checkout session, standalone or built from a payment link with `linkId`. The create and list responses do **not** include a plain `amount`, only `amount_override` (`null` unless you passed one). Call "Retrieve a checkout" for a computed, ready-to-display `amount`. Accepts an `Idempotency-Key` header (wins over a body `idempotencyKey` if both are sent); a repeat call with the same key returns the same checkout instead of creating a second one.
The REST path is `/gateway/sessions` (the underlying resource is called a "session"); the TypeScript SDK and the CLI both call it a **checkout** (`agentaos.checkouts`, `agenta pay checkout`).
```mermaid theme={null}
stateDiagram-v2
[*] --> open: POST /gateway/sessions
open --> completed: Payment confirmed
open --> expired: expiresIn elapsed
open --> cancelled: POST .../cancel
completed --> [*]
expired --> [*]
cancelled --> [*]
```
The create and list responses do **not** include a plain `amount`, only `amount_override` (`null` unless you passed one). For a link-based checkout, the effective price is the link's amount. To get a single, ready-to-display price, call [Retrieve a checkout](/api-reference/checkouts/retrieve), which computes `amount` for you.
# List checkouts
Source: https://docs.agentaos.ai/api-reference/checkouts/list
GET /gateway/sessions
Paginated. Same fields as create, per item.
Build a reusable link, then create checkouts against it.
Every completed checkout issues a VAT-correct invoice.
# Retrieve a checkout
Source: https://docs.agentaos.ai/api-reference/checkouts/retrieve
GET /gateway/sessions/{sessionId}
Adds a computed `amount` (the effective price: `amount_override`, else the linked payment link's amount), plus the link's own `link_amount`/`link_currency`/`link_description`, and the checkout's `transactions` and `invoices` arrays.
# List customers
Source: https://docs.agentaos.ai/api-reference/customers/list
GET /gateway/customers
Paginated, newest first, no filters beyond pagination. A customer record is created automatically the first time someone pays you (or you issue them an invoice) with an email attached. There is no `POST` create endpoint: this is a read-only surface. Every field on this resource is camelCase on the wire.
Every field on this resource is camelCase on the wire, unlike Payment Links, Checkouts, Invoices, and Transactions. See [Naming convention](/api-reference/introduction#naming-convention).
A customer is scoped to one organization *and* one environment: a test-mode buyer and a live-mode buyer with the same email are two separate records. `country`/`vatNumber` are remembered from the most recent checkout that supplied them and never blanked by a later checkout that omits them.
Pre-fill `buyerEmail`, `buyerName`, `buyerCountry`, and `buyerVat` to populate this record.
Every invoice carries the same buyer fields.
# Error codes
Source: https://docs.agentaos.ai/api-reference/error-codes
How the API reports errors, the status codes you will see, and how to handle them.
When a request fails, the API returns the matching HTTP status code and a JSON body describing what went wrong. Every response also carries an `x-request-id` header (echoed back as `requestId` in error bodies), include it when you contact support. The [TypeScript SDK](/sdk/pay-errors) turns each response into a typed error for you, so most integrations never parse this body by hand.
## Error response format
```json theme={null}
{
"statusCode": 400,
"error": "Bad Request",
"message": ["amount must be a positive number"],
"errors": [{ "field": "amount", "message": "must be a positive number" }],
"timestamp": "2026-08-06T12:00:00.000Z",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
```
| Field | Type | Description |
| ------------ | ------------------- | ---------------------------------------------------------------------------------- |
| `statusCode` | number | The HTTP status code, repeated in the body. |
| `error` | string | Short error category, such as `Bad Request` or `Not Found`. |
| `message` | string or string\[] | Human-readable detail. Validation failures return an array, one entry per problem. |
| `errors` | array | On `400` validation errors: one `{ field, message }` per invalid field. |
| `timestamp` | string | ISO 8601 time the error was produced. |
| `requestId` | string | Correlates the request. Also returned as the `x-request-id` response header. |
## Status codes
| Status | Meaning | When it occurs |
| ------ | ------------ | --------------------------------------------------------------------------- |
| `400` | Bad request | Missing or invalid parameters. See the `errors` array. |
| `401` | Unauthorized | Missing, invalid, or expired API key. |
| `403` | Forbidden | Valid key, but no access to this resource. |
| `404` | Not found | The resource does not exist, or is not in your organization or environment. |
| `409` | Conflict | A concurrent modification. Retry the read, then the write. |
| `429` | Rate limited | Too many requests. Honor the `Retry-After` header when present. |
| `5xx` | Server error | Something went wrong on our side. Safe to retry with backoff. |
## Common scenarios
### Validation (400)
`message` lists each problem, and `errors` names the offending `field`.
```json theme={null}
{
"statusCode": 400,
"error": "Bad Request",
"message": ["currency must be EUR or USD"],
"errors": [{ "field": "currency", "message": "must be EUR or USD" }],
"timestamp": "2026-08-06T12:00:00.000Z",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
```
### Authentication (401)
The `x-api-key` header is missing or the key is wrong. Check you are using the right key: `sk_test_` for test mode, `sk_live_` for production.
### Not found (404)
The resource ID is wrong, or it belongs to a different environment. Test and live data are separate.
### Rate limited (429)
Back off and retry. When present, the `Retry-After` header tells you how many seconds to wait.
## Handling errors with the SDK
The [TypeScript SDK](/sdk/pay-errors) maps every response to a typed `AgentaOSError` subclass with `status`, `code`, `message`, `requestId`, and (on validation) `errors`, and retries transient failures for you.
```typescript theme={null}
import { AgentaOSError, ValidationError } from '@agentaos/pay';
try {
await agentaos.checkouts.create({ amount: -1 });
} catch (err) {
if (err instanceof ValidationError) {
console.error(err.errors); // [{ field: 'amount', message: '...' }]
} else if (err instanceof AgentaOSError) {
console.error(err.status, err.code, err.requestId);
}
}
```
## Environments
There is one base URL. Your key prefix selects the environment: `sk_test_` uses test mode, `sk_live_` uses production. See [Test mode](/getting-started/test-mode).
## Need help?
Include the `requestId` (or the `x-request-id` header) from the failed response when you contact support. It lets us find the exact request instantly.
# Introduction
Source: https://docs.agentaos.ai/api-reference/introduction
Base URL, authentication, environments, errors, pagination, and rate limits for the AgentaOS REST API.
The AgentaOS REST API lets you create payment links, checkouts, and invoices, read subscriptions, customers, and transactions, and receive webhooks, from any language. It is the same API the [TypeScript SDK](/sdk/pay-overview) and the [`agenta` CLI](/cli/install) call underneath.
Building in TypeScript or Node.js? Use [`@agentaos/pay`](/sdk/pay-overview) instead of raw HTTP. It wraps every endpoint on this page with typed methods, camelCases responses for you, retries on `5xx`, and signs/verifies webhooks. This section documents the wire format the SDK talks to, useful if you're integrating from another language or debugging a raw request.
## Base URL
```
https://api.agentaos.ai/api/v1
```
Every path in this reference is relative to this base. `POST /gateway/payment-links` means `POST https://api.agentaos.ai/api/v1/gateway/payment-links`.
## Authentication
Send your secret key in the `x-api-key` header on every request.
```bash theme={null}
curl https://api.agentaos.ai/api/v1/gateway/payment-links \
-H "x-api-key: sk_live_..."
```
Your secret key, from **app.agentaos.ai → Settings → Developers → API Keys**.
A missing or invalid key returns `401 Unauthorized`. There's no separate bearer-token flow for API keys, only the CLI's browser login session uses `Authorization: Bearer` internally; server-to-server integrations always use `x-api-key`.
## Environments (test vs live)
A key's prefix is both its identity and its environment. There's no separate "mode" parameter to set.
| Prefix | Environment | Data |
| ---------- | ----------- | ------------------------------------------------ |
| `sk_test_` | Test mode | Test data only, free, no verification required |
| `sk_live_` | Live | Real money, requires business verification (KYB) |
Test and live data never mix: a request authenticated with a test key can only see payment links, checkouts, invoices, customers, and transactions created under a test key, and the same is true for live. See [Test mode and live mode](/getting-started/test-mode) for how to get a key of each kind.
## Pagination
Every list endpoint (`GET /gateway/payment-links`, `/sessions`, `/subscriptions`, `/customers`, `/invoices`, `/all-transactions`) takes the same two query parameters and returns the same envelope.
Items per page. Capped server-side at 100 (invoices cap at 5000 for CSV-style bulk export via `limit`).
Number of items to skip, for the next page.
```json Pagination envelope theme={null}
{
"items": [ /* ... */ ],
"total": 137,
"hasMore": true
}
```
The page of results. Shape depends on the resource.
Total matching rows across every page, not just this one.
`true` if `offset + items.length < total`. Computed server-side, don't derive it yourself.
`hasMore` is camelCase on the wire, not `has_more`. It's a computed envelope field, not a database column, see [Naming convention](#naming-convention) below for why that matters.
## Naming convention
Most response fields mirror the underlying data and are `snake_case`. A handful of computed fields ride along already camelCase, and two resources are fully camelCase. In practice, three kinds of fields appear on responses:
Anything stored on the record comes back exactly as named in the database: `created_at`, `amount_override`, `billing_interval`, `buyer_email`, `tax_rate_id`, and so on. This is the majority of fields on Payment Links, Checkouts, and Invoices.
A small number of fields are built by the server at response time rather than read from a column, and those are camelCase as written, with no snake\_case equivalent:
checkoutUrl and x402Url on Checkouts; checkoutUrl on Payment Links
money (an object: currency, grossMinor, feeMinor, vatMinor, netMinor) on Transactions
earnings (an object: currency, grossMinor, agentaosFeeMinor, vatMinor, netMinor) on a single retrieved Invoice, same shape as money except the fee field is named agentaosFeeMinor instead of feeMinor
Two resources are hand-built end to end and never expose a snake\_case column name: **Subscriptions** (`GET /gateway/subscriptions`, `POST /gateway/subscriptions/:id/cancel`) and **Customers** (`GET /gateway/customers`). Every field on these two is camelCase.
If any of this sounds like a footgun for a hand-rolled HTTP client, it is exactly why the [TypeScript SDK](/sdk/pay-overview) exists. It recursively camelCases every response, so `checkoutUrl` and `buyer_email` both come back as `checkoutUrl`/`buyerEmail` on the SDK object regardless of which convention the wire used underneath. If you're not on Node.js, treat the tables on each page in this reference as the literal JSON keys, they're taken directly from what the server sends.
## Money model
A plain `amount` field, on a create body, a Payment Link, a Checkout's `amount_override`, or an Invoice's `amount`/`fiat_amount`/`tax_amount`, is in currency units. `49.99` means €49.99, never cents.
The decimal rule above applies to plainly-named `amount` fields only. Any field whose name ends in `Minor` is an integer count of the smallest currency unit instead: `unitAmountMinor` on a Subscription (`1999` means €19.99), plus every field inside the `money` breakdown on a Transaction and the `earnings` breakdown on a retrieved Invoice (`grossMinor`, `feeMinor`/`agentaosFeeMinor`, `vatMinor`, `netMinor`). The `Minor` suffix is the signal, it's there so these are never confused with a plain decimal `amount`.
The `amount` inside a webhook payload's `data` object is a string, e.g. `"49.99"`, since JSON numbers silently drop trailing zeros and this value has to round-trip exactly for accounting.
## Errors
A non-2xx response is always JSON, and every response carries an `x-request-id` header (echoed as `requestId` in the body):
```json 400 theme={null}
{
"statusCode": 400,
"error": "Bad Request",
"message": ["amount must be a positive number"],
"errors": [{ "field": "amount", "message": "must be a positive number" }],
"timestamp": "2026-08-06T12:00:00.000Z",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
```
Same as the HTTP status code.
Short category, e.g. `Bad Request`.
Human-readable. A validation failure returns an array, one entry per problem.
On a `400` validation error, one `{ field, message }` per invalid field.
ISO 8601, when the error was generated.
Correlation ID, also returned as the `x-request-id` header.
| Status | Meaning |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | Bad request: a required field is missing, out of range, or the wrong type. `ValidationPipe` also rejects any field not in the documented schema. |
| `401` | Missing or invalid `x-api-key`. |
| `403` | The key is valid but the resource belongs to a different organization. |
| `404` | Resource not found (or not visible to your key, we don't distinguish the two). |
| `409` | Conflict: the resource was modified concurrently, or the action doesn't apply to its current state (voiding an already-voided invoice, cancelling an already-cancelled link). |
| `429` | Rate limited. Back off and retry. |
| `500` | Server error. Safe to retry with backoff. |
On a `400`, `errors` gives you per-field detail (`[{ field, message }]`) and `message` carries the same problems as human-readable text. The SDK surfaces these as `ValidationError.errors` and `AgentaOSError.requestId`. See [Error codes](/api-reference/error-codes).
## Rate limits
60 requests per 60 seconds per client IP, by default, across every endpoint in this reference. A small number of unlisted, higher-risk endpoints (outbound sends, public checkout start) have a tighter dedicated limit; none of the endpoints documented in this section do.
A `429` doesn't currently include a guaranteed `Retry-After` header. Back off and retry (the SDK does this automatically, waiting 1 second by default before its single built-in retry on `429`).
## Idempotency
`POST /gateway/sessions` accepts an `Idempotency-Key` header (or an `idempotencyKey` field in the body). Retry the same call with the same key and you get back the exact same checkout, including its already-issued invoice, instead of creating a second one. The SDK generates a random key automatically on every `POST` if you don't supply one.
## Next steps
Create, list, update, and cancel reusable payment links.
Create a one-time checkout session, standalone or from a link.
Events, payload shapes, and signature verification.
Skip the wire format entirely and use typed methods.
# Export invoices as CSV
Source: https://docs.agentaos.ai/api-reference/invoices/export
GET /gateway/invoices/export
Up to 5000 rows for the matching filter, built for accounting software: date, invoice number, description, amount and currency, exchange rate and source, tax name/rate/amount, buyer name/company/country/VAT, and status.
The settled payment behind a paid invoice.
The buyer behind each invoice.
Know the moment a checkout completes and its invoice flips to `paid`.
# List invoices
Source: https://docs.agentaos.ai/api-reference/invoices/list
GET /gateway/invoices
Paginated. Every checkout issues an invoice: `issued` the moment the checkout is created, `paid` once the payment settles, or `voided` if cancelled. The seller of record is our Estonian entity, AgentaOS's Merchant of Record entity, unless the payment settled to your own on-chain wallet (`seller_mode: "crypto"`), in which case you're the seller.
`amount`, `fiat_amount`, and `tax_amount` are decimal currency units, same as everywhere else in this API. `19.99` means EUR 19.99, not cents. See [Money model](/api-reference/introduction#money-model).
# Download the invoice PDF
Source: https://docs.agentaos.ai/api-reference/invoices/pdf
GET /gateway/invoices/{invoiceId}/pdf
Response is `application/pdf` (binary), not JSON. Includes seller and buyer details, the amount (plus fiat equivalent and exchange rate for stablecoin payments), and a tax breakdown.
# Download the receipt PDF
Source: https://docs.agentaos.ai/api-reference/invoices/receipt
GET /gateway/invoices/{invoiceId}/receipt
Same document family as the invoice PDF, but titled as a receipt with its own receipt number, for a **paid** invoice. Falls back to the plain invoice PDF (still valid proof of payment) for invoices issued before receipts existed on your account, rather than `404`ing.
# Retrieve an invoice
Source: https://docs.agentaos.ai/api-reference/invoices/retrieve
GET /gateway/invoices/{invoiceId}
Same fields as the list, plus `earnings`: a fee/VAT/net breakdown, present only for a card/bank Merchant-of-Record sale with a linked, settled transaction. `null` otherwise (crypto sales, or a not-yet-paid `issued` invoice).
# Re-send the receipt email
Source: https://docs.agentaos.ai/api-reference/invoices/send-receipt
POST /gateway/invoices/{invoiceId}/send-receipt
Sends the paid receipt to the buyer email on file. Only works for a `paid` invoice with a `buyer_email`: returns `400` otherwise.
Returns `400` if the invoice isn't `paid` yet, or has no buyer email on file.
# Download a statement
Source: https://docs.agentaos.ai/api-reference/invoices/statement
GET /gateway/invoices/statement
A statement-style PDF covering a date range: opening/closing balance, every transaction in the ledger, and a VAT summary grouped by rate. `from` and `to` are both required, unlike the other date filters in this API.
`from` and `to` are both required, unlike the other date filters in this API.
# Void an invoice
Source: https://docs.agentaos.ai/api-reference/invoices/void
POST /gateway/invoices/{invoiceId}/void
Marks the accounting record void. It never touches the payment itself: if the buyer already paid, nothing is refunded. Use it to correct a bookkeeping mistake, not to reverse a charge. Irreversible: there is no "un-void"; an already-voided invoice returns `400` on a repeat call.
Voiding is irreversible. There's no "un-void." An already-voided invoice returns `400` on a repeat call.
# Cancel a payment link
Source: https://docs.agentaos.ai/api-reference/payment-links/cancel
DELETE /gateway/payment-links/{id}
Deactivates the link. Existing checkouts already in progress are unaffected; no new checkout can be started from it afterward. This is a state change, not a delete: the link and its payment history stay readable.
Cancelling is a state change, not a delete. The link and its payment history stay readable. A `409` means the link changed concurrently (retry the read, then the cancel).
# Create a payment link
Source: https://docs.agentaos.ai/api-reference/payment-links/create
POST /gateway/payment-links
Creates a reusable, shareable payment link.
You do not pass a settlement mode. The server sets it from your account (card and bank by default) and returns it on the response.
# List payment links
Source: https://docs.agentaos.ai/api-reference/payment-links/list
GET /gateway/payment-links
Paginated. See the Money model / Pagination notes on this spec's top-level description.
Start a checkout from a link with `linkId`.
Paying a `type: subscription` link starts a subscription automatically.
Get notified when a link is paid.
# Retrieve a payment link
Source: https://docs.agentaos.ai/api-reference/payment-links/retrieve
GET /gateway/payment-links/{id}
Same fields as create, plus `transactions`: every payment (inbound or outbound) recorded against this link, newest first, same row shape as a Transaction but without the computed `money` breakdown.
# Update a payment link
Source: https://docs.agentaos.ai/api-reference/payment-links/update
PATCH /gateway/payment-links/{id}
Partial update: only send the fields you want to change.
# Cancel a subscription
Source: https://docs.agentaos.ai/api-reference/subscriptions/cancel
POST /gateway/subscriptions/{id}/cancel
Cancels at the end of the current paid period by default: the subscriber keeps what they already paid for, no refund. Pass `atPeriodEnd: false` to cancel immediately instead. Calling cancel again on an already-`canceled` subscription is a no-op. Returns `400` if the subscription never completed a first successful charge (`stripeSubscriptionId` is `null`).
Calling cancel again on an already-`canceled` subscription is a no-op. It returns the same result without hitting the card processor a second time. If the subscription never completed a first successful charge (`stripeSubscriptionId` is `null`), cancel returns `400`.
# List subscriptions
Source: https://docs.agentaos.ai/api-reference/subscriptions/list
GET /gateway/subscriptions
Paginated. A subscription is created automatically when a buyer pays a payment link with `type: "subscription"` at checkout: there is no `POST` create endpoint by design. Every field on this resource is camelCase on the wire.
Every field on this resource is camelCase on the wire, unlike Payment Links, Checkouts, Invoices, and Transactions (which are mostly snake\_case with a few camelCase extras). See [Naming convention](/api-reference/introduction#naming-convention).
Create a `type: "subscription"` link to start selling recurring plans.
The buyer behind each subscription.
# List transactions
Source: https://docs.agentaos.ai/api-reference/transactions/list
GET /gateway/all-transactions
One unified list of everything that moved money on your account: inbound payments (from checkouts and payment links) and outbound sends, across every rail (card, SEPA bank transfer, stablecoins on-chain), in one feed. Only `confirmed` rows are returned: a pending or failed attempt never appears here; `status` on each item is always `confirmed`.
Only `confirmed` rows are returned. A pending or failed attempt never appears here. There's no `status` filter to request otherwise; `status` on each item is always `confirmed`.
This row carries additional internal ledger fields (settlement batch references, dispute markers, raw card-processor fee) beyond what's documented here. Treat any field not listed as forward-compatible and safe to ignore. Don't build logic against undocumented keys.
Every settled inbound transaction has a matching invoice.
Get notified the moment a transaction settles, instead of polling this list.
# Webhooks
Source: https://docs.agentaos.ai/api-reference/webhooks
Events AgentaOS sends to your server, payload shapes, and HMAC-SHA256 signature verification.
AgentaOS pushes events to a URL you configure, rather than making you poll. Configure `webhookUrl` when you [create a checkout](/api-reference/checkouts/create) or a [payment link](/api-reference/payment-links/create), or set one org-wide in the dashboard (**Settings → Developers → Webhooks**). A checkout's own `webhookUrl` wins if set, then its payment link's, then your org default, whichever is found first is the only one that fires, they don't all fire.
If none of the three is set, nothing is sent. That's not an error, it's silent by design, so double check you've configured a URL somewhere in the chain before relying on webhooks.
## Events
Fired when a checkout's payment is confirmed, on any rail (card, wallet, stablecoin). Resolved via the checkout's own webhook, then its payment link's, then your org default.
Fired when an outbound on-chain send you initiated is confirmed. Only your org-level webhook URL is checked for this event (there's no per-checkout scope for outbound sends).
Fired when an outbound send's broadcast fails. Same org-level-only resolution as `send.completed`.
### `checkout.session.completed`
```json theme={null}
{
"id": "evt_8c7d6e5f-4a3b-2c1d-0e9f-8a7b6c5d4e3f",
"type": "checkout.session.completed",
"data": {
"link_id": "mZrESFyR7RC9RPsJfZCVkg",
"session_id": "kR9pQwErTyUiOpAsDfGh",
"amount": "49.99",
"currency": "EUR",
"rail": "card",
"tx_hash": null,
"vendor_reference": "pi_3P...",
"payer": null,
"payer_type": "human",
"network": "stripe",
"testnet": false,
"overpaid_by_cents": null,
"metadata": { "orderId": "order-123" }
}
}
```
The payment link's `secure_link_id`, `null` for a standalone checkout.
The checkout's public `session_id`.
Currency units, as a **string** (`"49.99"`), not a number. See [Money model](/api-reference/introduction#money-model).
`card`, `sepa`, `bank_transfer`, `bridge`, or `wallet`. How the buyer actually paid.
On-chain hash. `null` for card/bank rails, use `vendor_reference` instead.
Off-chain audit reference (card-processor payment reference, bank reference). `null` for on-chain rails.
On-chain payer address. `null` for card/bank rails.
`human` or `agent`.
CAIP-2 network ID for on-chain rails, or `stripe` for card/bank.
`true` on a test-mode checkout.
Set when a bank-transfer buyer sent more than the amount due (beyond a 1-cent tolerance). `null` otherwise.
Whatever you set on the checkout (or its link), unchanged.
### `send.completed`
```json theme={null}
{
"id": "evt_9d8e7f6a-5b4c-3d2e-1f0a-9b8c7d6e5f4a",
"type": "send.completed",
"data": {
"transaction_id": "9d8e7f6a-5b4c-3d2e-1f0a-9b8c7d6e5f4a",
"tx_hash": "0xabc123...",
"from": "0xYourOrgWallet...",
"to": "0xRecipient...",
"amount": "100.00",
"token": "USDC",
"chain_id": 8453,
"network": "eip155:8453",
"testnet": false,
"description": null
}
}
```
### `send.failed`
Identical shape to `send.completed`, with `tx_hash: null`.
```json theme={null}
{
"id": "evt_1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"type": "send.failed",
"data": {
"transaction_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"tx_hash": null,
"from": "0xYourOrgWallet...",
"to": "0xRecipient...",
"amount": "100.00",
"token": "USDC",
"chain_id": 8453,
"network": "eip155:8453",
"testnet": false,
"description": null
}
}
```
The raw payload above is the literal snake\_case JSON body AgentaOS `POST`s to your `webhookUrl`, this is what you'll parse in any language other than the SDK. `agentaos.webhooks.verify()` (Node.js) parses it and returns a camelCased, typed object instead: `linkId`, `sessionId`, `txHash`, `payerType`, etc. `rail`, `vendor_reference`, `testnet`, and `overpaid_by_cents` are additive fields not yet reflected in the SDK's TypeScript types, they arrive on the wire regardless of language.
## Delivery
```mermaid theme={null}
sequenceDiagram
participant A as AgentaOS
participant S as Your server
A->>A: Sign payload with HMAC-SHA256
A->>S: POST webhookUrl (X-AgentaOS-Signature header)
alt 2xx response
S-->>A: 200 OK
else non-2xx, timeout, or network error
A->>A: Wait, exponential backoff (1s, 2s, ...)
A->>S: Retry, up to 3 attempts total
end
```
* Delivered as `POST`, `Content-Type: application/json`, body is the exact JSON shown above.
* A `10` second timeout per attempt, up to `3` attempts total, exponential backoff between retries.
* Redirects are not followed (a `3xx` response counts as a failed attempt).
* URLs resolving to private or internal addresses are rejected before any delivery attempt (SSRF guard).
* Return `2xx` quickly. Do slow work (emails, fulfillment) asynchronously after responding, a slow handler risks the delivery timing out and retrying.
## Signature verification
Every delivery includes an `X-AgentaOS-Signature` header:
```
X-AgentaOS-Signature: t=1770379200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```
| Part | Meaning |
| ---- | ------------------------------------------------------------------------------------------- |
| `t` | Unix timestamp (seconds) when the signature was generated. |
| `v1` | HMAC-SHA256 hex digest of `{t}.{raw request body}`, keyed with your webhook signing secret. |
Get your signing secret (`whsec_...`) from **app.agentaos.ai → Settings → Developers → Webhooks → Reveal signing secret**. It's stable across URL changes; rotate it from the same screen if it's ever exposed.
```typescript theme={null}
import { AgentaOS, WebhookVerificationError } from '@agentaos/pay';
import express from 'express';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
// Use express.raw(): verification needs the exact raw body bytes.
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = agentaos.webhooks.verify(
req.body,
req.headers['x-agentaos-signature'] as string,
process.env.AGENTAOS_WEBHOOK_SECRET!,
);
if (event.type === 'checkout.session.completed') {
fulfillOrder(event.data.sessionId);
}
res.sendStatus(200);
} catch (err) {
if (err instanceof WebhookVerificationError) {
return res.status(400).send('Invalid signature');
}
res.status(500).send('Webhook processing failed');
}
});
```
```python theme={null}
import hmac, hashlib, time
def verify_webhook(raw_body: str, signature: str, secret: str) -> bool:
parts = dict(p.split('=', 1) for p in signature.split(','))
timestamp = int(parts['t'])
# Reject signatures older than 5 minutes
if abs(time.time() - timestamp) > 300:
return False
expected = hmac.new(
secret.encode(),
f"{timestamp}.{raw_body}".encode(),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, parts['v1'])
```
Verify against the **raw** request body bytes, before any JSON parsing or re-serialization. Re-stringifying a parsed object can reorder keys or change whitespace and silently break the signature check.
## Best practices
A checkout's `successUrl` is best-effort, the buyer's browser might close first. Webhooks are the source of truth for "did this actually get paid."
A retried delivery can arrive more than once. Deduplicate on `id` (the event id) or `data.session_id`/`data.transaction_id`.
Return `200` immediately, then do the slow work. A handler that blocks risks a retry and a duplicate delivery.
Never act on the raw body without checking `X-AgentaOS-Signature` first, anyone can `POST` to a public URL.
## Next steps
Set `webhookUrl` when creating a checkout.
Set a default `webhookUrl` for every checkout created from a link.
# Changelog
Source: https://docs.agentaos.ai/changelog/overview
What's new in the AgentaOS docs, SDK, and API.
## Dashboard-first onboarding and real product screenshots
* **Accept your first payment** is now dashboard-first: create a product, share its link, and take a test payment with no code. The SDK, CLI, and API path is still here for programmatic use.
* Added real screenshots of the **checkout**, the **product builder**, and the **invoice builder**, alongside the Home, Go live, and Payments views.
* Rewrote the introduction and clarified **[How Merchant of Record works](/mor/how-it-works)**, including what a payment processor does under the hood.
* Standardized balance states to match the app: **Available**, **Incoming**, and **In reserve**.
* Retired the legacy self-custody EUR IBAN pages. Bank payouts reach your account in EUR and USD through our banking partner.
## First public release
* Merchant of Record overview, [supported countries](/mor/supported-countries), and [tax handling](/mor/tax).
* [Payments](/payments/payment-links), [subscriptions](/payments/subscriptions), [invoices](/payments/invoices), [receipts](/payments/receipts), [customers](/payments/customers), and [webhooks](/payments/webhooks).
* **[@agentaos/pay](/sdk/pay-overview)** TypeScript SDK, the **[`agenta` CLI](/cli/install)**, and the **[MCP server](/mcp/setup)** so your AI agents can run the store.
* Full [REST API reference](/api-reference/introduction) with live "Try it".
# Customers
Source: https://docs.agentaos.ai/cli/customers
List the people who have paid you, from the terminal.
`agenta customers` is a quick lookup for anyone who has ever completed a checkout with you: email, name, country, VAT number if they gave one. Read-only from the CLI today.
```bash theme={null}
agenta customers list
```
## Usage
```bash theme={null}
agenta customers list [--limit ] [--json]
```
Results per page, 1 to 100.
Output a single JSON object instead of a table.
```bash Output theme={null}
Customers (2 total)
jane@startup.io Jane Cooper DE 10f6f205-0f31-44c3-ad1a-240af6177e74
ana@example.com Ana Reyes ES f5100f77-fd73-4c33-a6e7-c1c5db53804c
2 of 2 shown.
```
### `--json`
```json theme={null}
{
"total": 2,
"hasMore": false,
"items": [
{
"id": "10f6f205-0f31-44c3-ad1a-240af6177e74",
"email": "jane@startup.io",
"name": "Jane Cooper",
"country": "DE",
"vatNumber": "DE123456789",
"stripeCustomerId": "cus_Q4f...",
"createdAt": "2026-06-11T09:15:00.000Z"
}
]
}
```
## Fields
| Field | Description |
| ------------------ | ------------------------------------------------------------------------ |
| `id` | Customer ID, e.g. `10f6f205-0f31-44c3-ad1a-240af6177e74`. |
| `email` | Buyer's email. |
| `name` | Buyer's name, `null` if never provided. |
| `country` | ISO 3166-1 alpha-2 country code (`DE`, `US`), `null` if unknown. |
| `vatNumber` | Buyer's VAT number if they entered one at checkout. |
| `stripeCustomerId` | Underlying customer from the card processor, for card and bank payments. |
| `createdAt` | When this customer record was first created. |
Piping `agenta customers list` to a file or another command switches to JSON automatically, no `--json` needed: `agenta customers list --limit 100 > customers.json`.
## Next steps
See what a customer is subscribed to.
Pull a customer's paid invoices and receipts.
Look up customers from your own backend.
How customer records are created and used.
# Install
Source: https://docs.agentaos.ai/cli/install
Install the agenta CLI with one command, or via npm if you already run Node.
The `agenta` CLI ships as the `agentaos` npm package and installs two equivalent binary names: `agenta` (used throughout these docs) and `agentaos`. It talks to the same API your `@agentaos/pay` SDK integration uses, so anything you can do in code you can also do from a terminal: create checkouts, list subscriptions, pull receipts, check readiness.
## Requirements
Node.js 20 or later. The installer scripts work on macOS, Linux, and Windows (WSL).
## Install
```bash curl theme={null}
curl -fsSL https://agentaos.ai/install | bash
```
```bash npm theme={null}
npm install -g agentaos
```
The curl script downloads a prebuilt binary and puts `agenta` on your `PATH`. The npm install builds the same CLI from the published package and works anywhere `npm install -g` works.
## Verify
```bash theme={null}
agenta --version
```
```bash Output theme={null}
1.2.0
```
If `agenta` isn't found after a curl install, open a new shell so your `PATH` picks up the change.
## No Node.js?
If you're going the npm route and don't have Node 20+ installed:
```bash theme={null}
curl -fsSL https://fnm.vercel.app/install | bash
fnm install 20
fnm use 20
```
Then re-run `npm install -g agentaos`.
## AI agent setup
Building an agent or automation that should use AgentaOS on your behalf? Install the skill for guided setup instead of hand-rolling the integration:
```bash theme={null}
npx skills add AgentaOS/skills@agenta
```
Or read it directly at [agentaos.ai/SKILL.md](https://agentaos.ai/SKILL.md).
## Next steps
Authenticate the CLI with your AgentaOS account.
Create your first checkout end to end.
# Invoices
Source: https://docs.agentaos.ai/cli/invoices
List invoices and manage receipts from the terminal with agenta invoices.
`agenta invoices` covers the paper trail: list what's been issued, download a receipt PDF, or re-send one to a buyer who lost the email. Every paid checkout gets an invoice, issued by our Estonian entity as Merchant of Record.
```bash theme={null}
agenta invoices list
agenta invoices receipt
agenta invoices send-receipt
```
## List invoices
```bash theme={null}
agenta invoices list [--limit ] [--json]
```
Results per page, 1 to 100.
Output a single JSON object instead of a table.
```bash Output theme={null}
Invoices (2 total)
issued INV-2026-0142 49.99 EUR jane@startup.io 3332e42e-14ae-4d59-9171-28372c2ac632
issued INV-2026-0141 199.00 USD ana@example.com 5fd23a28-8c66-4f32-bb7e-964bcce3117d
2 of 2 shown.
```
### `--json`
`invoices list --json` returns the full invoice record for each item, not a narrowed subset, so tax and settlement detail come along for the ride:
```json theme={null}
{
"total": 2,
"hasMore": false,
"items": [
{
"id": "3332e42e-14ae-4d59-9171-28372c2ac632",
"invoiceNumber": "INV-2026-0142",
"amount": 49.99,
"currency": "EUR",
"description": "Consulting fee, August",
"status": "issued",
"buyerEmail": "jane@startup.io",
"buyerName": "Jane Cooper",
"buyerCountry": "DE",
"buyerVat": null,
"buyerCompany": null,
"taxRate": 19,
"taxAmount": 7.98,
"taxInclusive": true,
"taxName": "VAT",
"merchantName": "Aristokrates OÜ",
"merchantVat": "EE102810130",
"fiatAmount": null,
"fiatCurrency": null,
"issuedAt": "2026-08-06T15:04:00.000Z",
"createdAt": "2026-08-06T15:04:00.000Z",
"voidedAt": null
}
]
}
```
`amount`, `taxAmount`, and `fiatAmount` are all plain decimal currency units, same as everywhere else in the API. `fiatAmount`/`fiatCurrency` are only populated on invoices settled from a stablecoin payment, showing the fiat-equivalent value at the exchange rate used.
## Download a receipt
```bash theme={null}
agenta invoices receipt [-o ]
```
Invoice ID, e.g. `3332e42e-14ae-4d59-9171-28372c2ac632`.
File path to save the PDF to.
```bash theme={null}
agenta invoices receipt 3332e42e-14ae-4d59-9171-28372c2ac632 -o ./receipts/august.pdf
```
```bash Output theme={null}
Saved: ./receipts/august.pdf
```
```json --json theme={null}
{ "saved": "./receipts/august.pdf" }
```
Receipts are only available for paid (issued) invoices.
## Re-send a receipt
```bash theme={null}
agenta invoices send-receipt
```
Invoice ID, e.g. `3332e42e-14ae-4d59-9171-28372c2ac632`.
Re-sends the receipt email to the buyer's address on file. Handy when a customer says they never got it.
```bash theme={null}
agenta invoices send-receipt 3332e42e-14ae-4d59-9171-28372c2ac632
```
```bash Output theme={null}
Receipt sent to jane@startup.io
```
```json --json theme={null}
{ "ok": true, "sentTo": "jane@startup.io" }
```
## Next steps
Look up who an invoice belongs to.
See the recurring plan behind a billing cycle's invoice.
Pull invoices and receipts from your own backend.
What's on a receipt and how VAT is shown.
# Login
Source: https://docs.agentaos.ai/cli/login
Authenticate the CLI with your AgentaOS account using the device-code browser flow.
Every CLI command that touches your data (`pay`, `subscriptions`, `customers`, `invoices`, `status`) runs against the account you're logged into, not an API key. Log in once and the session persists across terminal restarts.
```bash theme={null}
agenta login
```
## How it works
`agenta login` uses a device-code flow, the same pattern GitHub CLI and similar tools use: the terminal and the browser are two different places, so the CLI hands you a short code to confirm in the browser instead of asking for a password on the command line.
`agenta` calls your server and gets back a `deviceCode`, a short human-readable `userCode`, and a `verificationUrl`.
The CLI opens `verificationUrl` in your default browser and prints the same URL and `userCode` in the terminal, in case it can't open a browser for you (SSH sessions, headless boxes).
Sign in and approve the code. Nothing else happens in the browser: no wallet setup, no extra onboarding step, just confirming that this terminal should be allowed to act as you.
`agenta` polls the server every few seconds for up to 10 minutes. Once you approve, it receives a session token and a refresh token and stores them locally.
## Usage
```bash theme={null}
agenta login [--server ]
```
| Flag | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------- |
| `--server ` | Server to authenticate against. Defaults to `$AGENTA_SERVER`, or `https://api.agentaos.ai` if that's unset. |
If you're already logged in, `agenta login` doesn't re-authenticate. It tells you to run `agenta logout` first if you want to switch accounts.
## Example output
```bash Output theme={null}
Open this URL in your browser:
https://app.agentaos.ai/cli-auth?code=WDJK-QPXR
Verification code: WDJK-QPXR
✓ Logged in
✓ Authenticated as demo@founder.dev
Organization: Acme Freelance
Wallet: 0x71C7...976F
Session stored in /Users/you/.agenta
Next: agenta status
```
## Where the session lives
Login writes a session file to `~/.agenta/session.json` (mode `0600`, readable only by you) containing the session token, refresh token, and the server URL you logged into. It's plain file storage, not your OS keychain: a short-lived session token doesn't need biometric protection, and keychain prompts would add friction to every command.
Every command that needs a session (`agenta status`, `agenta pay ...`, and so on) reads this file and refreshes the token automatically in the background when it's close to expiring. You only need to run `agenta login` again if the refresh token itself has expired or been revoked, in which case the CLI tells you to.
The CLI authenticates with a browser-confirmed session, not an API key. If you need key-based auth for a server or script instead of an interactive login, use the `@agentaos/pay` SDK or the REST API directly with an `sk_live_`/`sk_test_` key. See [Payment SDK](/sdk/pay-overview).
## Logout
```bash theme={null}
agenta logout
```
Clears the local session file and best-effort revokes it on the server. Safe to run even if you're not sure whether you're logged in.
```bash Output theme={null}
✓ Logged out
```
## Next steps
Confirm your account, wallet, and tool readiness after logging in.
Create your first checkout from the terminal.
# Pay
Source: https://docs.agentaos.ai/cli/pay
Create, check, and list checkout sessions from the terminal with agenta pay.
`agenta pay` wraps the same checkout flow as the `@agentaos/pay` SDK and the dashboard: create a checkout, get back a payment link, and track its status, all without leaving the terminal. Useful for one-off invoicing, quick testing, and scripting payment creation into whatever tool you already use.
`agenta pay` commands authenticate with your `agenta login` session, not an API key. For key-based auth in a backend or script, use [`@agentaos/pay`](/sdk/pay-overview) or the REST API with an `sk_live_`/`sk_test_` key instead.
## Create a checkout
```bash theme={null}
agenta pay checkout -a [-c ] [-d ] [--email ] [--json]
```
Payment amount **in currency units**, e.g. `-a 49.99` for €49.99. Not cents.
Currency code, e.g. `EUR`, `USD`.
Shown to the buyer on the checkout page.
Pre-fills the buyer's email on the checkout page.
Output a single JSON object instead of the human-readable summary.
```bash theme={null}
agenta pay checkout -a 49.99 -c EUR -d "Consulting fee, August"
```
```bash Output theme={null}
Session Id: mZrESFyR7RC9RPsJfZCVkg
Status: open
Amount: 49.99
Currency: EUR
Checkout Url: https://app.agentaos.ai/checkout/mZrESFyR7RC9RPsJfZCVkg
Expires At: 2026-08-06T15:32:00.000Z
Hint: Share the checkoutUrl with your customer.
✓ Checkout created
```
### `--json`
```json theme={null}
{
"sessionId": "mZrESFyR7RC9RPsJfZCVkg",
"status": "open",
"amount": 49.99,
"currency": "EUR",
"checkoutUrl": "https://app.agentaos.ai/checkout/mZrESFyR7RC9RPsJfZCVkg",
"x402Url": null,
"expiresAt": "2026-08-06T15:32:00.000Z",
"hint": "Share the checkoutUrl with your customer."
}
```
Checkouts expire in 30 minutes by default. Share `checkoutUrl` with a human buyer, or `x402Url` (when present) if an AI agent is paying programmatically.
## Get a checkout
```bash theme={null}
agenta pay get [--json]
```
The checkout session ID, e.g. `mZrESFyR7RC9RPsJfZCVkg`.
Output a single JSON object instead of the human-readable summary.
```bash theme={null}
agenta pay get mZrESFyR7RC9RPsJfZCVkg
```
```json --json theme={null}
{
"sessionId": "mZrESFyR7RC9RPsJfZCVkg",
"status": "completed",
"amount": 49.99,
"currency": "EUR",
"checkoutUrl": "https://app.agentaos.ai/checkout/mZrESFyR7RC9RPsJfZCVkg",
"x402Url": null,
"expiresAt": "2026-08-06T15:32:00.000Z",
"createdAt": "2026-08-06T15:02:00.000Z"
}
```
`amount` here reflects a per-checkout override and can be `null` if this checkout used its payment link's own amount rather than overriding it. `status` is one of `open`, `completed`, `expired`, `cancelled`.
## List checkouts
```bash theme={null}
agenta pay list [--status ] [--limit ] [--json]
```
Filter by `open`, `completed`, `expired`, or `cancelled`. Omit to list all.
Results per page, 1 to 100.
Output a single JSON object instead of a table.
```bash theme={null}
agenta pay list --status completed --limit 5
```
```bash Output theme={null}
Checkouts (14 total)
completed 49.99 EUR mZrESFyR7RC9RPsJfZCVkg Consulting fee, August
completed 199.00 USD Oz1cpiHki8gAawFAHgYh1w Annual plan renewal
open 49.99 EUR lLd0Kl04lC3hvPH_7AQ20A
3 of 14 shown.
```
### `--json`
```json theme={null}
{
"total": 14,
"hasMore": true,
"items": [
{
"sessionId": "mZrESFyR7RC9RPsJfZCVkg",
"status": "completed",
"currency": "EUR",
"amount": 49.99,
"description": "Consulting fee, August",
"checkoutUrl": "https://app.agentaos.ai/checkout/mZrESFyR7RC9RPsJfZCVkg",
"expiresAt": "2026-08-06T15:32:00.000Z"
}
]
}
```
Every `agenta pay` command supports `--json`, and JSON output kicks in automatically whenever stdout isn't a terminal. Pipe to `jq` or a file without the flag: `agenta pay list --status open | jq '.items[].checkoutUrl'`.
## Next steps
List and cancel recurring subscribers.
Pull receipts once a checkout is paid.
Create checkouts from your own backend.
How checkouts, payment links, and settlement fit together.
# Status
Source: https://docs.agentaos.ai/cli/status
Check who you're logged in as, whether payment tools are ready, and what to do next.
`agenta status` is the command to run right after `agenta login`, and the one to reach for whenever something isn't working: it tells you exactly what's configured, what's missing, and the next command to run.
```bash theme={null}
agenta status
```
Alias: `agenta whoami`.
## What it checks
* **Account**: are you logged in, and as who (read from your session).
* **Organization and wallet**: your org name and whether a payout wallet is activated.
* **Payment tools**: whether `agenta pay checkout` will actually work right now.
* **Agent sub-accounts**: any `agenta sub create`d signer wallets on this machine, with live balance and status.
## Usage
```bash theme={null}
agenta status [--json]
```
| Flag | Description |
| -------- | ------------------------------------------------------------------ |
| `--json` | Output a single JSON object instead of the human-readable summary. |
## Example output
```bash Output theme={null}
AgentaOS CLI
───────────────────────────────────
Account: demo@founder.dev
Organization: Acme Freelance
Wallet: 0x71C7...976F
JWT expires: 2026-08-06 14:32 UTC (6h 40m remaining)
Server: https://api.agentaos.ai
Config: /Users/you/.agenta
Payment tools: ✓ Ready
Agent accounts: Run agenta sub create to create a sub-account
Next: agenta pay checkout -a 50 to create a checkout
agenta sub create to create an agent sub-account
```
If your wallet isn't activated yet, `Payment tools` shows what's blocking you instead of a green check, and `Next` points at `agenta login` to finish activation.
### `--json`
```json theme={null}
{
"account": {
"authenticated": true,
"email": "demo@founder.dev",
"server": "https://api.agentaos.ai",
"configDir": "/Users/you/.agenta",
"jwtExpiresAt": "2026-08-06T14:32:00.000Z",
"jwtSecondsRemaining": 24000,
"organization": "Acme Freelance",
"walletAddress": "0x71C7656EC7ab88b098defB751B7401B5f6d8976",
"walletActivated": true,
"paymentTools": {
"ready": true,
"hint": "Run agenta pay checkout -a 50 to create a checkout."
}
},
"subAccounts": {
"count": 0,
"hint": "Run agenta sub create --name to create a sub-account.",
"items": []
}
}
```
`--json` also kicks in automatically whenever stdout isn't a terminal, so piping to `jq` or writing to a file gives you JSON without the flag: `agenta status | jq .account.walletActivated`.
## Response fields
| Field | Description |
| ----------------------------- | ------------------------------------------------------------------- |
| `account.authenticated` | Whether you have a valid session. |
| `account.email` | Account email, decoded from your session token. |
| `account.organization` | Your organization's name. |
| `account.walletAddress` | Payout wallet address, `null` if not activated. |
| `account.walletActivated` | Whether the wallet is ready to receive funds. |
| `account.paymentTools.ready` | Whether `agenta pay checkout` will succeed right now. |
| `account.paymentTools.hint` | What to do if it isn't ready. |
| `account.jwtExpiresAt` | ISO 8601 timestamp your session token expires at. |
| `account.jwtSecondsRemaining` | Seconds left before that expiry. |
| `account.configDir` | Where the CLI stores its local session and signer config. |
| `subAccounts.count` | Number of agent sub-accounts (`agenta sub create`) on this machine. |
| `subAccounts.items[]` | Each sub-account's `name`, `address`, and `isDefault`. |
`subAccounts` are agent signer wallets created with `agenta sub` (for agent-to-agent payments and x402), not your MoR customers. For the people who've paid you, see [`agenta customers list`](/cli/customers).
## Not logged in
```json theme={null}
{
"account": {
"authenticated": false,
"reason": "not-logged-in",
"hint": "Run agenta login to get started."
},
"subAccounts": { "count": 0, "hint": "Run agenta sub create --name to create a sub-account.", "items": [] }
}
```
`reason` is either `not-logged-in` or `session-expired`, so scripts can tell the two apart.
## Next steps
Authenticate or re-authenticate the CLI.
Create and track checkouts.
# Subscriptions
Source: https://docs.agentaos.ai/cli/subscriptions
List and cancel recurring subscribers from the terminal with agenta subscriptions.
`agenta subscriptions` is a read-and-cancel view onto everyone paying you on a recurring plan (created from a subscription payment link). It's the fast path for support work: look up a subscriber, check their status, cancel on request.
```bash theme={null}
agenta subscriptions list
agenta subscriptions cancel
```
## List subscriptions
```bash theme={null}
agenta subscriptions list [--limit ] [--json]
```
Results per page, 1 to 100.
Output a single JSON object instead of a table.
```bash Output theme={null}
Subscriptions (3 total)
active €19.99/month jane@startup.io 253dbe6a-6b4d-4c40-a16f-aae31cfea271
past_due $49.00/month ana@example.com 0fb760fa-bce8-48e6-a51a-d1c5c06075f5
canceled €199.00/year team@bigco.eu 46d8d6cf-5281-4fed-b7c2-af1065b21ecc
3 of 3 shown.
```
### `--json`
```json theme={null}
{
"total": 3,
"hasMore": false,
"items": [
{
"id": "253dbe6a-6b4d-4c40-a16f-aae31cfea271",
"customerEmail": "jane@startup.io",
"customerName": null,
"planName": "Pro plan",
"billingInterval": "month",
"status": "active",
"unitAmountMinor": 1999,
"currency": "EUR",
"currentPeriodEnd": "2026-09-06T00:00:00.000Z",
"stripeSubscriptionId": "sub_1PQr..."
}
]
}
```
The human table formats the amount for you (`€19.99/month`). In JSON, `unitAmountMinor` is an **integer of the smallest currency unit** (`1999` = €19.99), the one exception to plain decimal amounts elsewhere in the API. Every other money field you'll see (checkouts, invoices, payment links) is a plain decimal number.
### Status values
| Status | Meaning |
| -------------------- | ----------------------------------------------- |
| `trialing` | In a free trial period. |
| `active` | Paying, current. |
| `past_due` | Latest charge failed, retrying. |
| `unpaid` | Retries exhausted, not yet cancelled. |
| `paused` | Billing paused. |
| `canceled` | Ended. |
| `incomplete` | First payment hasn't completed yet. |
| `incomplete_expired` | First payment window expired before completing. |
## Cancel a subscription
```bash theme={null}
agenta subscriptions cancel [--now] [--json]
```
Subscription ID, e.g. `253dbe6a-6b4d-4c40-a16f-aae31cfea271`.
Cancel immediately instead of at the end of the current billing period. Without this flag, the subscriber keeps access until their paid period ends and nothing is refunded.
Output a single JSON object instead of the human-readable summary.
```bash theme={null}
agenta subscriptions cancel 253dbe6a-6b4d-4c40-a16f-aae31cfea271
```
```json --json theme={null}
{
"status": "active",
"cancelAtPeriodEnd": true,
"effectiveCancelDate": "2026-09-06",
"currentPeriodEnd": "2026-09-06T00:00:00.000Z"
}
```
Cancel immediately:
```bash theme={null}
agenta subscriptions cancel 253dbe6a-6b4d-4c40-a16f-aae31cfea271 --now
```
```json --json theme={null}
{
"status": "canceled",
"cancelAtPeriodEnd": false,
"effectiveCancelDate": "2026-08-06",
"currentPeriodEnd": "2026-09-06T00:00:00.000Z"
}
```
Default to the no-flag form. `--now` skips the grace period the subscriber already paid for, so reach for it only when that's actually the ask (fraud, duplicate signup, an explicit "cancel me right now").
## Next steps
Look up who's behind a subscription.
Pull receipts for a paid billing cycle.
Manage subscriptions from your own backend.
How subscription payment links and billing cycles work.
# Why was I charged by AgentaOS?
Source: https://docs.agentaos.ai/for-customers/why-charged
You see a charge from AgentaOS on your statement. Here is what it is and what to do.
## A charge from AgentaOS
This charge is for a digital product or subscription you bought. AgentaOS is the Merchant of Record that handled the payment on the seller's behalf, so our name appears on your statement instead of theirs. This is normal and expected.
## Find your purchase
Search your inbox for your AgentaOS receipt. It lists the product name, the seller, and the amount.
Compare the amount and date on the receipt to the charge on your statement.
If you use more than one email address, try the others, you may have purchased with a different one.
## I still do not recognize the charge
[Contact us](https://agentaos.ai) with the exact amount, the date it appeared, and the last 4 digits of the card or account charged. We will identify the product and seller for you.
## I want to cancel a subscription
Use the cancellation link in your receipt, or contact the seller directly with the support details on their website or receipt. Cancelling stops future renewals, and you keep access until the end of the period you already paid for.
## I want a refund
Refund policies are set by each seller. Contact the seller first, using the support email on their website or your receipt. If the seller does not respond within 7 days, [contact us](https://agentaos.ai) and we will help.
## I think this charge is fraudulent
If you believe your payment details were used without your permission, [contact us immediately](https://agentaos.ai) with the amount, the date, and the last 4 digits of the card.
## Why does it say "AgentaOS" and not the seller?
AgentaOS is the Merchant of Record, the legal seller on the transaction, so our name appears on your statement instead of the seller's. [Learn more about Merchant of Record](/mor/how-it-works).
# Dashboard tour
Source: https://docs.agentaos.ai/getting-started/dashboard
A tour of app.agentaos.ai: where to find your products, payments, customers, and settings. No code required.
[app.agentaos.ai](https://app.agentaos.ai) is where you run the business side of AgentaOS: create things to sell, watch money come in, and manage your team and API keys. Everything below is reachable by click, no code needed.
Every page is scoped by the **Test / Live** switch at the top of the sidebar. Toggle it to see test-mode data (safe to explore, see [Test mode and live mode](/getting-started/test-mode)) or live data (real money, gated on verification).
## Home
Your landing page after sign-in. Sales volume over the last 7 or 30 days, subscription MRR, customer count, and a recent-activity feed. If you haven't gone live yet, a checklist card walks you through what's left.
## Catalog → Products
Where you create what you sell: one-time payment links or subscription plans. Each product is a shareable checkout URL, no code required to publish one. See [Payment links](/payments/payment-links) for the concept in depth, or the [Payment SDK](/sdk/pay-overview) to create them from code instead.
## Finance → Payments
Your balance, in EUR and USD: **Available**, **Incoming**, **In reserve**, and your next payout date. Every confirmed payment and payout also lands in the activity feed on this page, this is the one place to answer "where's my money."
## Finance → Invoices
Every completed payment issues a tax-correct invoice automatically. Download the PDF, void one if you need to, or export a batch for your accountant.
## Finance → Subscriptions
Recurring billing your buyers started by paying a subscription payment link. List active subscriptions, check status, and cancel from here, the same actions the SDK's `subscriptions` resource exposes.
## Insights → Customers
Everyone who has paid you: email, country, and VAT number where provided. This list builds itself; there's nothing to import.
## Go live
The sidebar's durable readiness tracker, with a progress chip that shows what's left before you can accept live payments. Business verification (KYB) is the main step. See [How Merchant of Record works](/mor/how-it-works) for why it exists.
## Settings → Developers
Your API keys live here, scoped to whichever environment you're viewing (Test or Live), alongside webhooks and your account ID. This is also where you generate a new key or revoke an old one.
## Settings, the rest
A few more tabs round out account management:
Your signed-in identity and security (passkey) settings.
Company details, VAT number, and custom tax rates.
Invite teammates to your account.
Your pricing plan and the fees that apply to each payment method.
## Next steps
First payment in test mode, start to finish.
Test keys, live keys, and what going live requires.
Why verification exists and what we handle for you.
Every resource behind the dashboard, in code.
# Quickstart
Source: https://docs.agentaos.ai/getting-started/quickstart
Start accepting payments in minutes with AgentaOS. Everything below runs in test mode, with test money.
Both paths below run in **test mode** with test money, so there is nothing to verify and nothing to lose. Pick the one that fits how you build:
Integrate AgentaOS into your app with the SDK, CLI, or REST API.
Create a payment link and start selling without writing code.
## Code integration
Sign up at [app.agentaos.ai](https://app.agentaos.ai). No card required. Then go to **Settings → Developers → API Keys** and click **Generate Test Key**. It is shown once, so copy it now.
Test keys are prefixed `sk_test_` and only ever touch test-mode data. Prefer the terminal? Skip the key and run `agenta login` instead.
Amounts are in currency units: `49.99` means €49.99, never cents.
```typescript TypeScript SDK theme={null}
// npm install @agentaos/pay
import { AgentaOS } from '@agentaos/pay';
const agentaos = new AgentaOS('sk_test_...');
const checkout = await agentaos.checkouts.create({
amount: 49.00,
currency: 'EUR',
description: 'Pro plan',
successUrl: 'https://yoursite.com/success',
});
console.log(checkout.checkoutUrl); // send your customer here
```
```bash CLI theme={null}
# curl -fsSL https://agentaos.ai/install | bash && agenta login
agenta pay checkout -a 49 -c EUR -d "Pro plan"
```
```bash REST API theme={null}
curl https://api.agentaos.ai/api/v1/gateway/sessions \
-H "x-api-key: sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "amount": 49.00, "currency": "EUR", "description": "Pro plan" }'
```
Open the `checkoutUrl`. In test mode the page renders the secure card form. Pay with the standard test card:
| Field | Value |
| ----------- | --------------------- |
| Card number | `4242 4242 4242 4242` |
| Expiry | Any future date |
| CVC | Any 3 digits |
Only ever type a card number into the checkout page (the secure card form). Never send a card number to any API, in test mode or live.
After payment the buyer is redirected to your `successUrl`. For anything that matters (granting access, provisioning, emails), do not rely on the redirect. Listen for the `checkout.session.completed` [webhook](/payments/webhooks) on your server instead.
### Complete working example
A full Node.js and Express app: create a checkout, and receive the payment event reliably over a signed webhook. Copy, set two env vars, and run.
```bash .env theme={null}
AGENTAOS_API_KEY=sk_test_your_key
AGENTAOS_WEBHOOK_SECRET=whsec_your_webhook_secret
APP_URL=http://localhost:3000
```
```typescript server.ts theme={null}
import express from 'express';
import { AgentaOS } from '@agentaos/pay';
const app = express();
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
// 1. Create a checkout and hand back its URL.
app.post('/checkout', express.json(), async (_req, res) => {
const checkout = await agentaos.checkouts.create({
amount: 49.00,
currency: 'EUR',
description: 'Pro plan',
successUrl: `${process.env.APP_URL}/success`,
});
res.json({ checkoutUrl: checkout.checkoutUrl });
});
// 2. Receive payment events. Use the RAW body so the signature verifies.
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = agentaos.webhooks.verify(
req.body, // raw Buffer
req.header('X-AgentaOS-Signature')!, // format: t=,v1=
process.env.AGENTAOS_WEBHOOK_SECRET!,
);
if (event.type === 'checkout.session.completed') {
const { amount, currency } = event.data; // amount is a string
console.log(`Paid ${amount} ${currency}`);
// Fulfil the order: grant access, send an email, update your database.
// `payer` and `txHash` are also on event.data, but only populated on
// stablecoin checkouts; they are null for card payments.
}
res.json({ received: true });
} catch {
res.status(401).json({ error: 'Invalid signature' });
}
});
app.listen(3000, () => console.log('Listening on :3000'));
```
To run it: `npm install express @agentaos/pay`, add a webhook endpoint pointing at `/webhooks` in **Settings → Developers → Webhooks** and copy its signing secret into `AGENTAOS_WEBHOOK_SECRET`, then `node server.ts`. `POST /checkout` returns a `checkoutUrl`, pay it with the test card, and your `/webhooks` handler fires.
## No-code
Perfect for creators and anyone who wants to start selling without writing code.
Open the **Products** page in the dashboard and create your first product with a name, description, and price.
Click **Share** to copy the product's payment link. Send it by email, social, or anywhere else. That is it, you are ready to get paid.
## Next steps
How test and live keys differ, and what going live requires.
Checkout sessions and payment links in depth.
Recurring billing, renewals, and cancellation.
Receive and verify real-time payment events.
# Test mode and live mode
Source: https://docs.agentaos.ai/getting-started/test-mode
Build and test your integration safely in test mode, with test money, before you go live.
Test mode lets you build and test your AgentaOS integration in a completely isolated environment. Payments, webhooks, customers, and invoices are kept separate from live, so you can develop with confidence and never risk a real charge.
Always build and test in test mode first. Every new account starts there, so there is nothing to switch on.
## Test and live are chosen by your key
AgentaOS has one base URL. The **key prefix** decides which environment you hit, so you never point at the wrong host.
| Key prefix | Environment | Data |
| ---------- | ----------- | -------------------- |
| `sk_test_` | Test mode | Test money, isolated |
| `sk_live_` | Production | Real money |
```typescript theme={null}
import { AgentaOS } from '@agentaos/pay';
const test = new AgentaOS('sk_test_...'); // test mode
const live = new AgentaOS('sk_live_...'); // production
```
Find both keys under **Settings → Developers → API Keys**. Store them as environment variables and never commit them.
```bash .env theme={null}
AGENTAOS_API_KEY=sk_test_your_key
```
## Testing payments
Test-mode checkouts render the secure card form. Use these test cards with any future expiry and any CVC:
| Card number | Behavior |
| --------------------- | --------------------------------- |
| `4242 4242 4242 4242` | Successful payment |
| `4000 0000 0000 0002` | Card declined |
| `4000 0000 0000 9995` | Insufficient funds |
| `4000 0025 0000 3155` | Requires 3D Secure authentication |
Only type a card number into the checkout page (the secure card form). Never send a card number to any API, in test mode or live.
## Testing webhooks
In test mode, events go to the webhook URL you register in **Settings → Developers → Webhooks**. To receive them on your machine, expose your local server with a tunnel like ngrok and register the tunnel URL. This lets you verify signature validation and your event handlers before going live. See [Webhooks](/payments/webhooks).
## Going live
Run your full flow with the test cards above, including a webhook round-trip, until every path behaves.
Complete business verification (KYB) in the dashboard. This is required before you can accept real money as Merchant of Record.
Add a bank account or wallet so your earnings have somewhere to settle. See [Payouts](/payouts/overview).
Replace `sk_test_` with `sk_live_` in your environment variables. Nothing else changes.
Add your production webhook URL and copy its live signing secret.
Keep an eye on your first live transactions to confirm everything behaves as it did in test mode.
Never use a `sk_test_` key in production. Test-key calls never touch live money and will not settle.
## Next steps
Create checkout sessions and payment links.
The full test-to-production checklist.
Recurring billing and cancellation.
Receive and verify real-time events.
# Accept Your First Payment
Source: https://docs.agentaos.ai/guides/accept-your-first-payment
Create a product, share its link, and watch a test payment land. No code required.
By the end of this guide you'll have a shareable payment link, a completed test payment against it, and a confirmed record in your dashboard. Every step runs in **test mode**: test money, no real card, nothing to lose.
You can do all of this from the dashboard. No code required. If you'd rather create links and detect payments programmatically, jump to [Do it in code](#do-it-in-code).
All you need is an AgentaOS account. Sign up at [app.agentaos.ai](https://app.agentaos.ai), no card required.
## From the dashboard
Open **Catalog → Products** and click **New product**. Give it a name and a price (for example, `Pro plan`, `49.00`), choose **one-time** or **subscription**, and save. Amounts are plain currency units: `49.00` means €49.00, never cents.
The moment you save, the product gets its own reusable payment link and a QR code.
On the product page, click **Copy link**. It looks like `https://app.agentaos.ai/pay/{id}`. Paste it anywhere: a button, an email, a chat message, an invoice, or share the QR code. Every visitor who opens it gets their own checkout, and there's nothing for the buyer to install.
Open the link in a browser. This is the page your buyer sees. AgentaOS is the merchant of record, so the checkout collects the buyer's country, calculates the right VAT, and shows one clear total before payment.
In test mode, the checkout renders the real secure card form, not a mock. Pay with the standard test card:
| Field | Value |
| ----------- | --------------------- |
| Card number | `4242 4242 4242 4242` |
| Expiry | Any future date |
| CVC | Any 3 digits |
Type the test card directly into the checkout page. Never send a card number to any AgentaOS API call yourself, in test mode or in live. We (and our card processor) never accept a raw card number over the API.
Open [app.agentaos.ai](https://app.agentaos.ai) → **Home** or **Finance → Payments**. Your test payment shows up in the balance and the activity feed within seconds. A tax-correct invoice appears under **Finance → Invoices**, and a customer record is created for the email that paid.
## What you just built
That one payment created a customer, a transaction, and a tax-correct invoice, all visible in the dashboard. No code, no integration, no card data touching your systems.
## Do it in code
The dashboard is enough to get paid. Reach for the API when you want to **create links or checkouts programmatically**, or **detect a payment from your own system** (fulfil an order, provision access, update your database) the moment it clears. Everything the dashboard does is available over the SDK, CLI, and REST API.
```bash theme={null}
npm install @agentaos/pay
```
`@agentaos/pay` is server-side only (Node.js 20+, ESM). Never ship your API key to a browser. Grab a test key (`sk_test_...`) from **Settings → Developers → API Keys**.
```bash Install theme={null}
curl -fsSL https://agentaos.ai/install | bash
```
```bash Log in theme={null}
agenta login
```
`agenta login` opens your browser to sign in, no API key needed. See [Login](/cli/login) for the details.
A payment link is a reusable, shareable URL. Every visitor who opens it gets their own checkout. Amounts are plain currency units: `49.99` means €49.99, never cents.
```typescript theme={null}
import { AgentaOS } from '@agentaos/pay';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!); // sk_test_...
const link = await agentaos.paymentLinks.create({
amount: 49.99,
currency: 'EUR',
description: 'Pro plan',
successUrl: 'https://myshop.com/success',
webhookUrl: 'https://myshop.com/webhooks',
});
console.log(link.checkoutUrl);
// → https://app.agentaos.ai/pay/7rr6S9ml4BMp829wV5WeAA
```
```bash theme={null}
curl -X POST https://api.agentaos.ai/api/v1/gateway/payment-links \
-H "x-api-key: sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 49.99,
"currency": "EUR",
"description": "Pro plan",
"successUrl": "https://myshop.com/success",
"webhookUrl": "https://myshop.com/webhooks"
}'
```
```bash theme={null}
agenta pay checkout -a 49.99 -c EUR -d "Pro plan" --json
```
There's no CLI command to create a reusable payment link yet. `agenta pay checkout` creates a one-off checkout instead. To create a real reusable link, use the SDK, the REST API, or the dashboard's **Catalog → Products** page.
Share the `checkoutUrl` exactly as you'd share a link from the dashboard. The buyer's checkout page is identical.
If you set a `webhookUrl`, AgentaOS POSTs a `checkout.session.completed` event the moment payment clears, with `sessionId`, `amount` (a string, `"49.99"`), and `currency` in the payload. This is the reliable way to react to a payment server-side. See [Handle webhooks](/guides/handle-webhooks) to build a verified handler.
Poll the checkout with `checkouts.retrieve()` (SDK), `agenta pay get ` (CLI), or a `GET` against the REST API. It moves from `open` to `completed` when the payment clears.
Don't trust the `successUrl` redirect as proof of payment. The buyer's browser might close before it fires. A webhook or a status check against the checkout is the source of truth.
## Next steps
Turn this into a recurring plan buyers pay into every month or year.
Build a verified handler so your server reacts to payments automatically.
Move from test mode to accepting real money.
Every parameter, response field, and the checkout-fields feature, in full.
# Let an AI Agent Run Your Store
Source: https://docs.agentaos.ai/guides/ai-agent
Point a coding agent at the SKILL.md prompt, or connect the MCP server, so an AI assistant can create checkouts, check status, and manage subscriptions for you.
By the end of this guide an AI assistant, running in your terminal or inside Claude Desktop / Cursor, can create checkouts, check payment status, and manage subscriptions for your store, in plain language instead of code you write yourself.
There are two ways in, and you can set up both, they don't conflict:
Claude Code, Cursor's agent mode, or anything else that can run shell commands. Give it the SKILL.md prompt and the `agenta` CLI directly.
Claude Desktop, Cursor's chat, Windsurf, or any MCP-compatible client. Connect the AgentaOS MCP server instead.
## Before you start
An AgentaOS account. Neither path needs an API key up front, `agenta login` and the MCP server's gateway key both come from the same dashboard you'd use anyway.
## Path A: a coding agent with the CLI
```bash theme={null}
curl -fsSL https://agentaos.ai/install | bash
agenta login
```
`agenta login` is the one step that needs you: it opens a browser for sign-in. Every other command an agent runs afterward is non-interactive.
Tell your coding agent to fetch and follow the skill prompt:
```bash theme={null}
curl -fsSL https://agentaos.ai/SKILL.md
```
It teaches the agent the exact `agenta pay` and `agenta sub` commands, their flags, and how to handle the response, including always running `agenta status --json` first and always passing `--json` so output is machine-parseable instead of full of ANSI color codes.
Ask it something like:
> Create a checkout for 50 EUR for my consulting invoice.
The agent runs `agenta status --json` to confirm payment tools are ready, then `agenta pay checkout -a 50 --json`, and hands you back the `checkoutUrl` to share with your client.
## Path B: a chat assistant with MCP
A gateway key for payment tools, from [app.agentaos.ai](https://app.agentaos.ai) → **Settings → Developers → API Keys**. If you also want the agent to touch an agent wallet (send tokens, sign messages), you'll additionally need an API key and secret from a sub-account.
For Claude Desktop, add this to your MCP server configuration:
```json theme={null}
{
"mcpServers": {
"agenta": {
"command": "npx",
"args": ["-y", "agentaos"],
"env": {
"AGENTA_API_KEY": "your-api-key",
"AGENTA_API_SECRET": "your-api-secret",
"AGENTAOS_GATEWAY_KEY": "sk_live_your-gateway-key"
}
}
}
}
```
See [MCP setup](/mcp/setup) for Cursor and Windsurf equivalents.
> List my last 5 checkouts.
> Cancel subscription sub\_4f81c0 at the end of the current period.
The assistant calls the matching tool, `agenta_pay_list_checkouts` or `agenta_pay_cancel_subscription`, and reports back in the chat.
## What the assistant can do
The MCP server exposes **25 tools** total: wallet operations, contract calls, x402 payments, and the payment tools below. All 25 are documented in full at [MCP setup](/mcp/setup); the payment-specific ones are the ones most relevant to running a store:
| Tool | What it does |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `agenta_pay_create_checkout` | Create a checkout session. Returns a `checkoutUrl` for a human buyer and an `x402Url` for an AI agent payer. |
| `agenta_pay_get_checkout` | Get a checkout's status, amount, currency, and expiry. Use it to check whether a payment completed. |
| `agenta_pay_list_checkouts` | List checkout sessions, filterable by status (`open`, `completed`, `expired`, `cancelled`). |
| `agenta_pay_list_subscriptions` | List subscriptions: status, amount, billing interval, and subscriber. |
| `agenta_pay_cancel_subscription` | Cancel a subscription. Defaults to cancel-at-period-end; pass `atPeriodEnd: false` to cancel immediately. |
| `agenta_pay_list_customers` | List the customers who have paid you: email, name, country, and ID. |
| `agenta_pay_send_receipt` | Re-send the receipt email for a paid invoice to the buyer on file. |
Both paths use the same underlying API and respect the same test/live key scoping. An assistant working against a test key can only see and create test-mode data, exactly like a human integration would.
## Verify it worked
Ask the assistant directly:
> What's my AgentaOS status?
On the CLI path, it should run `agenta status --json` and report `paymentTools.ready: true`, along with your organization and wallet. On the MCP path, ask it to list your most recent checkout and confirm the amount and status match what you see in the dashboard.
## Next steps
Every tool the MCP server exposes, and Cursor/Windsurf configuration.
Let agents discover, check, and pay for 402-protected resources automatically.
The full `agenta` CLI reference, for scripting outside an AI assistant too.
Checkouts an agent creates still need your server to fulfil them on completion.
# Go Live
Source: https://docs.agentaos.ai/guides/go-live
Move from test mode to real money: verify your business, add a payout account, and swap your test key for a live one.
By the end of this guide your account is verified, has somewhere to send money, and is running on a live API key instead of a test one. Test mode and live are fully separate environments in AgentaOS, nothing you built carries over automatically, only your code does, so this is a short, deliberate switch, not a background setting.
## Before you start
* You've already tested your integration end to end in test mode: checkout creation, webhook delivery, and (if you're selling recurring plans) subscription creation. See [Accept your first payment](/guides/accept-your-first-payment) and [Sell a subscription](/guides/sell-a-subscription) if you haven't.
* Your real business details on hand: legal name, address, and whatever your business verification asks for.
Confirm the basics work with test data before you touch real money: a checkout completes, your webhook handler verifies the signature and fulfills correctly, and (if relevant) a subscription activates and cancels the way you expect. Test mode is free and has no verification gate, so there's no reason to skip this.
The sidebar's **Go live** item is your durable readiness tracker, with a progress chip showing what's left. It stays visible until every step below is done, and it's the fastest way to see what's blocking you.
AgentaOS is the Merchant of Record on every live transaction, we need to know who we're selling on behalf of before real money moves. Submit your business details for review from the **Go live** page.
See [Account review](/mor/account-review) for what we check and how to prepare. Reviews are usually completed within a day.
You need somewhere for your balance to go. Set up bank, wallet, or both.
Add a bank account to receive payouts in EUR or USD across the supported countries. See [Supported countries](/mor/supported-countries).
Have a wallet address you control ready to receive stablecoin payouts. No separate institution or verification is required for this path, since you're moving your own balance to your own address.
See [Payouts](/payouts/overview) for how your balance moves through **Incoming**, **Available**, and **In reserve** before it pays out.
Once verification is approved, flip the Test/Live switch at the top of the dashboard sidebar to **Live**, then generate a new key from **Settings → Developers → API Keys**. Live keys are prefixed `sk_live_`.
```typescript theme={null}
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!); // now sk_live_...
```
The SDK detects the environment from the key's prefix automatically, there's no separate flag to flip. Swap the environment variable in your deploy, and every call now reads and writes live data.
Update `successUrl`, `cancelUrl`, and `webhookUrl` on your live checkouts and payment links to your real domain, not `localhost` or a tunnel URL left over from testing. Then check **Developer → Webhooks** while the dashboard is switched to Live, and confirm your production endpoint and signing secret are set. See [Handle webhooks](/guides/handle-webhooks) if you haven't wired this up yet.
## Verify it worked
* The **Go live** progress chip in the sidebar shows every step complete.
* **Settings → Developers → API Keys** shows an active `sk_live_` key, and your server is using it.
* Your payout destination (bank IBAN or wallet address) appears on the **Payouts** page.
* Optionally, run one small real checkout end to end: create it with your live key, pay it with a real card, and confirm it lands in **Finance → Payments** with an invoice under **Finance → Invoices**. Unlike test mode, live mode doesn't accept the `4242...` test card, only real payment methods clear.
## Go-live checklist
* [ ] Test-mode integration tested end to end (checkout, webhook, subscriptions if used)
* [ ] Business verification (KYB) submitted and approved
* [ ] Payout account added: bank IBAN, wallet address, or both
* [ ] Live API key (`sk_live_...`) generated and deployed
* [ ] `successUrl` / `cancelUrl` / `webhookUrl` point at production, not localhost
* [ ] Live webhook endpoint and signing secret confirmed in **Developer → Webhooks**
## Next steps
How test and live keys differ, in depth.
Why verification exists and what it unlocks.
How your balance clears and moves to your payout destination.
Make sure your production endpoint is verified and ready before you flip the switch.
# Handle Webhooks
Source: https://docs.agentaos.ai/guides/handle-webhooks
Register a webhook URL, verify the signature, and fulfil an order the moment checkout.session.completed fires. A complete, runnable Express example.
By the end of this guide you'll have a real Express endpoint that verifies AgentaOS's signature and marks an order paid the moment a checkout completes, safely, even if the same event is delivered more than once.
## Before you start
* Node.js 20+, Express, and `@agentaos/pay` installed (`npm install @agentaos/pay express`).
* A URL AgentaOS can reach over HTTPS. For local development, run a tunnel (ngrok or similar) so `http://localhost:3000` gets a public HTTPS address to register.
* A payment link or checkout to test against, see [Accept your first payment](/guides/accept-your-first-payment) if you don't have one yet.
Open [app.agentaos.ai](https://app.agentaos.ai) → **Developer** → **Webhooks**, and enter the HTTPS URL you want events sent to, for example `https://myshop.com/webhooks`. Click **Reveal signing secret**, copy the `whsec_...` value, and add it to your server's environment as `AGENTAOS_WEBHOOK_SECRET`. Never commit it, never log it, never send it to the client.
Signature verification signs the **raw** request body. If a JSON body parser runs first, the reserialized body won't byte-for-byte match what was signed, and every event will fail verification. Mount `express.raw()` on the webhook route only, and keep `express.json()` for everything else.
```typescript theme={null}
import express from 'express';
const app = express();
app.post('/webhooks', express.raw({ type: 'application/json' }), webhookHandler);
app.use(express.json()); // every other route can parse JSON normally
```
`webhooks.verify()` parses the `t=...,v1=...` header, rejects it if older than 5 minutes, recomputes the HMAC-SHA256 digest in constant time, and returns a typed `WebhookEvent`, or throws `WebhookVerificationError` if anything doesn't check out.
```typescript theme={null}
import { AgentaOS, WebhookVerificationError } from '@agentaos/pay';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
function webhookHandler(req: express.Request, res: express.Response) {
let event;
try {
event = agentaos.webhooks.verify(
req.body,
req.headers['x-agentaos-signature'] as string,
process.env.AGENTAOS_WEBHOOK_SECRET!,
);
} catch (err) {
if (err instanceof WebhookVerificationError) {
return res.status(400).send('Invalid signature');
}
return res.status(500).send('Webhook processing failed');
}
// Signature is valid, safe to act on event.data now.
}
```
Key your fulfillment logic off `event.data.sessionId`, and check whether you've already processed it before doing anything. Retries mean the same event can arrive more than once, your handler needs to be safe to run twice.
```typescript theme={null}
// Simple in-memory store keyed by sessionId. Use a real database in production,
// with a unique constraint on sessionId to make this safe under concurrent delivery.
const fulfilledSessions = new Set();
switch (event.type) {
case 'checkout.session.completed': {
const { sessionId, amount, currency } = event.data;
if (fulfilledSessions.has(sessionId)) {
break; // already handled this payment, skip
}
fulfilledSessions.add(sessionId);
console.log(`Order ${sessionId} paid: ${amount} ${currency}`);
// YOUR BUSINESS LOGIC HERE:
// - grant access, send a confirmation email, trigger shipping
break;
}
}
```
`event.data.amount` is a **string** (`"49.99"`), not a number. Every other `amount` you pass into a create call is a plain number, webhook payloads are the one place it's serialized as a string. Parse it before doing arithmetic.
Verify, queue or record the event, then respond. Do slow work, emails, external API calls, outside the request so AgentaOS doesn't time out waiting for you.
```typescript theme={null}
res.sendStatus(200);
```
## Full example
```typescript webhook-server.ts theme={null}
import { AgentaOS, WebhookVerificationError } from '@agentaos/pay';
import express from 'express';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
const app = express();
const fulfilledSessions = new Set();
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = agentaos.webhooks.verify(
req.body,
req.headers['x-agentaos-signature'] as string,
process.env.AGENTAOS_WEBHOOK_SECRET!,
);
} catch (err) {
if (err instanceof WebhookVerificationError) {
return res.status(400).send('Invalid signature');
}
return res.status(500).send('Webhook processing failed');
}
if (event.type === 'checkout.session.completed') {
const { sessionId, amount, currency } = event.data;
if (!fulfilledSessions.has(sessionId)) {
fulfilledSessions.add(sessionId);
console.log(`Order ${sessionId} paid: ${amount} ${currency}`);
// fulfillOrder(sessionId);
}
}
res.sendStatus(200);
});
app.use(express.json()); // other routes can parse JSON normally
app.listen(3000, () => console.log('Webhook server listening on :3000'));
```
Not using Node? The signing algorithm is plain HMAC-SHA256 over `{timestamp}.{raw_body}`, straightforward to reimplement in any language. See [manual verification in Python, Go, and PHP](/payments/webhooks#manual-verification-no-sdk).
## Test it end to end
Run the example above, and make sure your tunnel or production URL points at it.
Use the link or checkout from [Accept your first payment](/guides/accept-your-first-payment), or create a new one with `webhookUrl` set to your endpoint.
`4242 4242 4242 4242`, any future expiry, any CVC, typed directly into the hosted checkout page.
You should see `Order paid: 49.99 EUR` in your logs within seconds of the payment clearing.
## Verify it worked
* Your endpoint returned `200` for the delivery (check **Developer → Webhooks** in the dashboard for delivery status).
* Your logs show exactly one fulfillment for that `sessionId`, even if AgentaOS retries the delivery.
* An invalid or missing `X-AgentaOS-Signature` header gets rejected with `400`, not silently processed. Try POSTing a fake payload without a valid signature to confirm `webhooks.verify()` throws as expected.
## Next steps
Every event type, its full payload, and a JSON example.
Manual verification in Python, Go, and PHP, plus retry and delivery details.
Create the checkout that triggers this handler.
What happens to your balance after the payment lands.
# Guides
Source: https://docs.agentaos.ai/guides/introduction
Task-based, end-to-end walkthroughs. Pick the outcome you want and follow the steps to get there.
Guides are different from the Documentation tab. Documentation explains concepts and lists every parameter on every resource, the reference you keep open in a second tab. Guides are task-based: each one starts from nothing and ends with a working result, one goal, one path, start to finish.
Follow a guide top to bottom in order. Where a concept deserves more depth than the guide has room for, it links out to the relevant Documentation page, come back and pick up where you left off.
Every guide runs in **test mode** by default: a full test environment with test money, so there's nothing to verify and nothing to lose. See [Test mode and live mode](/getting-started/test-mode) if you haven't set that up yet.
## Who these are for
Developers integrating AgentaOS into a real product, and merchants comfortable following a code block even if they don't write code day to day. If you're brand new to AgentaOS, start with the [Quickstart](/getting-started/quickstart) first, it covers account setup in more depth than these guides assume.
## Pick a guide
Create a payment link, share it, and confirm the money landed. The fastest path from zero to a paid checkout.
Create a recurring payment link, let a buyer subscribe at checkout, then list and cancel what's running.
Move from test mode to real money: business verification, a payout account, and a live API key.
Register a URL, verify every signature, and fulfil an order the moment a checkout completes.
Point a coding agent at the SKILL.md prompt, or connect the MCP server, and manage payments in plain language.
## Next steps
New to AgentaOS? Start here before the guides above.
Full reference for every resource and method in `@agentaos/pay`.
# Sell a Subscription
Source: https://docs.agentaos.ai/guides/sell-a-subscription
Create a recurring payment link, let a buyer subscribe at the hosted checkout, then list and cancel what's running.
By the end of this guide you'll have a subscription plan buyers can pay into, a real test subscriber, and you'll know how to list who's subscribed and cancel someone who asks. This runs in **test mode**, so the "buyer" is you, paying with the test card.
**There is no `subscriptions.create()`.** A subscription is created by the buyer, not by you. You create a payment link with `type: 'subscription'`, the buyer pays it at the hosted checkout, and that payment is what starts the subscription. This guide walks through exactly that flow.
## Before you start
* An AgentaOS account with a test API key, or the CLI logged in. See [Accept your first payment](/guides/accept-your-first-payment) if you haven't set that up yet.
* Your account needs to accept card and bank (via Merchant of Record), which is the default for most accounts. Only wallet-only, on-chain accounts can't create subscription links. Business verification (KYB) is a separate, later requirement for going **live**; it isn't needed to build and test subscriptions in test mode. See [Going live](/guides/go-live).
Set `type: 'subscription'` and a `billingInterval`. Every other field works the same as a one-time link.
```typescript theme={null}
import { AgentaOS } from '@agentaos/pay';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!); // sk_test_...
const plan = await agentaos.paymentLinks.create({
amount: 19.99,
currency: 'EUR',
description: 'Pro plan, monthly',
type: 'subscription',
billingInterval: 'month', // 'month' | 'year', required for subscriptions
});
console.log(plan.checkoutUrl);
// → https://app.agentaos.ai/pay/7rr6S9ml4BMp829wV5WeAA
```
```bash theme={null}
curl -X POST https://api.agentaos.ai/api/v1/gateway/payment-links \
-H "x-api-key: sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 19.99,
"currency": "EUR",
"description": "Pro plan, monthly",
"type": "subscription",
"billingInterval": "month"
}'
```
There's no CLI command for payment links yet, one-time or subscription. `agenta pay checkout` only creates single, non-recurring checkouts. Create a subscription link from the SDK, the REST API, or the dashboard's **Catalog → Products** page.
Share `plan.checkoutUrl` exactly as you would a one-time link. The buyer opens it and pays with a card at the hosted checkout, using the test card in test mode:
| Field | Value |
| ----------- | --------------------- |
| Card number | `4242 4242 4242 4242` |
| Expiry | Any future date |
| CVC | Any 3 digits |
Behind the scenes, AgentaOS creates a customer and subscription with our card processor, charges the first cycle, and the subscription becomes visible to `subscriptions.list()`.
```typescript theme={null}
const page = await agentaos.subscriptions.list({ limit: 20, offset: 0 });
const sub = page.items.find((s) => s.planName === 'Pro plan, monthly');
console.log(sub?.status); // 'active'
console.log(sub?.unitAmountMinor); // 1999
```
```bash theme={null}
agenta subscriptions list --json
```
```bash theme={null}
curl "https://api.agentaos.ai/api/v1/gateway/subscriptions?limit=20" \
-H "x-api-key: sk_test_..."
```
**Money model exception:** every other amount in this API, including this same payment link's `amount: 19.99`, is decimal currency units. A subscription's `unitAmountMinor` is the one field that breaks that pattern: it's an **integer of the smallest currency unit**. `1999` means €19.99, mirroring how our card processor represents recurring prices internally. The field name is the signal, anything ending in `Minor` is an integer, everything else is already decimal.
Defaults to cancel-at-period-end: the subscriber keeps access until `currentPeriodEnd`, no refund. Pass `atPeriodEnd: false` to cancel immediately instead.
```typescript theme={null}
// At period end (default)
await agentaos.subscriptions.cancel(sub!.id);
// Immediately, no grace period
await agentaos.subscriptions.cancel(sub!.id, { atPeriodEnd: false });
```
```bash At period end theme={null}
agenta subscriptions cancel 7c1e9b2a-4f6d-4a3b-9c8e-2d5f0a1b3c4d
```
```bash Immediately theme={null}
agenta subscriptions cancel 7c1e9b2a-4f6d-4a3b-9c8e-2d5f0a1b3c4d --now
```
```bash theme={null}
curl -X POST https://api.agentaos.ai/api/v1/gateway/subscriptions/7c1e9b2a-4f6d-4a3b-9c8e-2d5f0a1b3c4d/cancel \
-H "x-api-key: sk_test_..." \
-H "Content-Type: application/json" \
-d '{ "atPeriodEnd": true }'
```
Cancellation never issues a refund, it only stops future renewals. Calling cancel again on an already-canceled subscription is a no-op, safe to retry.
## Verify it worked
* `subscriptions.list()` shows your test subscription with `status: 'active'` and the right `unitAmountMinor` and `billingInterval`.
* After cancelling at period end, `status` is still `active` but `cancelAtPeriodEnd` is `true` and `effectiveCancelDate` is set. After cancelling immediately, `status` becomes `canceled` right away.
* The buyer now shows up under [Customers](/payments/customers), and the first cycle's charge issued its own invoice.
## Next steps
The full parameter and response reference, one-time and subscription both.
Get notified the moment a subscription payment lands.
See everyone subscribed to you, with email, country, and VAT number.
Take this plan from test mode to real, recurring revenue.
# Monetize your product in a day.
Source: https://docs.agentaos.ai/introduction
Set up in minutes and start selling the same day. Card, Apple Pay, and Google Pay, with subscriptions, tax, invoices, and payouts all handled for you.
Getting paid shouldn't be harder than building, but for most founders it is: subscriptions, global cards, sales tax, chargebacks, invoices. None of it is your product, and all of it stands between you and revenue. AgentaOS runs the whole money layer so you never build a billing team to get paid.
Create a product and take your first payment in minutes.
Point any AI agent at `SKILL.md` and it wires up AgentaOS for you.
## What is a Merchant of Record?
It means we become the legal seller of your product. Your buyer pays AgentaOS, and we handle the payment, the VAT, the invoice, and the payout to you. You never register for tax abroad, file a cross-border return, or reconcile three processors. As the registered seller of record we carry the payment and tax liability, not you, and we are PCI-compliant and bank-grade secure. See [How it works](/mor/how-it-works).
## Why founders switch
Cross-border VAT means registering and filing in every country you sell to. As your Merchant of Record, AgentaOS calculates, collects, and remits it, so you never file a cross-border return again.
Card, Apple Pay, and Google Pay settle to one balance through one typed SDK, with tax and invoicing built in. No stitching a processor, a tax engine, and an invoicing tool together yourself.
Everything, from creating a product to sending a receipt, works over a type-safe SDK, a CLI, and an MCP server, so your AI agents can run the store while you ship.
## Core features
Create a shareable link or reusable product. One-time or subscription. No code required.
Recurring billing with automatic renewals. List, inspect, and cancel from the API, CLI, or dashboard.
Tax-correct invoices issued automatically, with a PDF and a CSV export for accounting.
Every payment gets an emailed receipt. Download or resend the PDF any time.
Every buyer who paid you, with email, country, and VAT number.
Get notified the moment a checkout completes. Signed and verifiable.
## Developer tools
`@agentaos/pay`. Type-safe, zero runtime dependencies, full API coverage.
`agenta`. Manage payments, subscriptions, customers, and invoices from the terminal, with `--json` for scripts.
Connect Claude, Cursor, or Windsurf and let your AI agents run your store.
## AI agent integration
Your AI agents can set up and operate AgentaOS on their own. No dashboards, no clicking.
```text theme={null}
Read https://agentaos.ai/SKILL.md and set up AgentaOS for me
```
Give this to any AI coding agent (Claude, Cursor, Copilot, Windsurf) and it will install the CLI, sign you in, create your first product and share the link, and wire up webhooks in your codebase.
## Quick start
Sign up at [app.agentaos.ai](https://app.agentaos.ai). No card required. You start in test mode.
Grab your test key from Settings. Keys prefixed `sk_test_` use test mode; `sk_live_` goes to production.
```bash SDK theme={null}
npm install @agentaos/pay
```
```bash CLI theme={null}
curl -fsSL https://agentaos.ai/install | bash
agenta login
```
```typescript SDK theme={null}
import { AgentaOS } from '@agentaos/pay';
const agenta = new AgentaOS('sk_test_...');
const link = await agenta.paymentLinks.create({
amount: 49.00, // decimal currency units, not cents
currency: 'EUR',
description: 'Pro plan',
});
console.log(link.checkoutUrl); // share this anywhere
```
```bash CLI theme={null}
agenta pay checkout -a 49 -c EUR -d "Pro plan"
```
Listen for `checkout.session.completed` to sync your app. See [Webhooks](/payments/webhooks) for signature verification.
## FAQ
Minutes in test mode. Create an account, grab a test key, and create your first checkout. Going live needs a short business verification, see [Account review](/mor/account-review).
Card, Apple Pay, and Google Pay. Cards work in 135+ currencies, from buyers worldwide.
Cards reach buyers in 135+ currencies. We handle destination VAT for the EU (via Estonia OSS) today, and add jurisdictions as we grow. See [Supported countries](/mor/supported-countries).
Yes. Point any AI agent at `SKILL.md`, or connect the MCP server, and it can create products, read customers and subscriptions, and send receipts on your behalf.
AgentaOS is the Merchant of Record, so we appear as the seller. See [Why was I charged](/for-customers/why-charged).
# Stablecoin payouts (legacy)
Source: https://docs.agentaos.ai/legacy/stablecoin-payouts
An optional, advanced way to withdraw your AgentaOS balance on-chain to a wallet you control, from anywhere.
Stablecoin payout is an **optional, advanced** way to move your available balance on-chain to a wallet you control. You are moving your own balance to your own address, so no third-party bank sits in between and no extra verification is required. It works from anywhere, which makes it a useful fallback when bank payouts do not yet reach your country.
Most merchants don't need this. Your standard payout account already receives **EUR and USD** by bank transfer through our banking partner. See [Payouts](/payouts/overview) for the primary flow.
## How it works
On the **Payouts** page, add a wallet address you control as a payout destination.
Sales move from **Incoming** to **Available** the same way they do for bank payouts.
Your available balance is sent on-chain to your address. You pay network gas only, with no AgentaOS markup.
## Good to know
Funds go to an address you control. No third-party bank sits in the middle.
Not limited by country, so it reaches places bank payouts don't yet cover.
You pay network gas only. AgentaOS adds no markup on top.
Your balance still clears over about 14 days on newer accounts before it's payable.
## Next steps
The primary bank payout flow, in EUR and USD.
Where bank payouts reach, and the payout fee table.
# MCP Server
Source: https://docs.agentaos.ai/mcp/setup
Connect AI assistants to AgentaOS via MCP.
The AgentaOS MCP server exposes your account to any Model Context Protocol client, so an AI assistant can create checkouts, read customers and subscriptions, send receipts, and operate your wallet on your behalf. Use it when you want Claude Desktop, Cursor, or Windsurf to run AgentaOS directly, instead of calling the API or CLI by hand.
25 tools for Claude Desktop, Cursor, Windsurf, and any MCP-compatible client.
## Claude Desktop
```json theme={null}
{
"mcpServers": {
"agenta": {
"command": "npx",
"args": ["-y", "agentaos"],
"env": {
"AGENTA_API_KEY": "your-api-key",
"AGENTA_API_SECRET": "your-api-secret",
"AGENTAOS_GATEWAY_KEY": "sk_live_your-gateway-key"
}
}
}
}
```
* **API Key + Secret**: For wallet tools. From sub-account creation.
* **Gateway Key**: For payment tools. From [app.agentaos.ai](https://app.agentaos.ai) → API Keys.
## Tools
18 wallet/agent tools plus 7 `agenta_pay_*` payment tools.
### Wallet & agent tools
#### Discovery
| Tool | What it does |
| ------------------------ | ------------------------------------------ |
| `agenta_wallet_overview` | Address, balances, and recent transactions |
| `agenta_list_networks` | List available networks and chain IDs |
| `agenta_list_signers` | List signers accessible with the API key |
| `agenta_resolve_address` | Resolve an ENS name to an address |
#### Common operations
| Tool | What it does |
| --------------------- | --------------------- |
| `agenta_send_eth` | Send ETH |
| `agenta_send_token` | Send ERC-20 tokens |
| `agenta_get_balances` | ETH + ERC-20 balances |
#### Contract interaction
| Tool | What it does |
| ---------------------- | --------------------------------------------------- |
| `agenta_call_contract` | Write to a contract |
| `agenta_read_contract` | Read contract state |
| `agenta_execute` | Execute a raw transaction with pre-encoded calldata |
| `agenta_simulate` | Simulate a transaction |
#### Signing
| Tool | What it does |
| ------------------------ | ----------------------- |
| `agenta_sign_message` | Sign a message |
| `agenta_sign_typed_data` | Sign EIP-712 typed data |
#### Management and audit
| Tool | What it does |
| ---------------------- | ---------------------------------------------- |
| `agenta_get_status` | Server health and signer status |
| `agenta_get_audit_log` | Recent signing activity and policy evaluations |
#### x402 payments
| Tool | What it does |
| ---------------------- | ------------------------------- |
| `agenta_x402_check` | Check if a URL requires payment |
| `agenta_x402_discover` | Find x402 endpoints |
| `agenta_x402_fetch` | Pay and fetch a resource |
### Payment tools (`agenta_pay_*`)
| Tool | What it does |
| -------------------------------- | ------------------------- |
| `agenta_pay_create_checkout` | Create a checkout session |
| `agenta_pay_get_checkout` | Get checkout status |
| `agenta_pay_list_checkouts` | List checkouts |
| `agenta_pay_list_subscriptions` | List subscriptions |
| `agenta_pay_cancel_subscription` | Cancel a subscription |
| `agenta_pay_list_customers` | List paying customers |
| `agenta_pay_send_receipt` | Re-send a receipt email |
## Next steps
The same operations as typed methods in `@agentaos/pay`.
Run AgentaOS from your terminal with `agenta`.
Let an agent discover, check, and pay for 402-protected APIs.
Create a product and take your first payment.
# Account review
Source: https://docs.agentaos.ai/mor/account-review
How business verification works before you accept live payments, and what you need to get approved.
Before you can accept live payments, your business goes through a short verification review (KYB). Test mode needs no review, so you can build and test the full flow first, then submit for review when you are ready to go live.
The fastest approvals happen when your product is already live, your legal pages are visible, and your support email matches the one shown on your website.
## Approval checklist
| Requirement | Details |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------ |
| **Product is live** | Your product is ready for real customers. Still building? Use [Test mode](/getting-started/test-mode) first. |
| **No false information** | No fake reviews, testimonials, or inflated user or customer counts on your website. |
| **Privacy Policy and Terms** | Both legal pages must be present and reachable on your website. |
| **Product clearly visible** | We must be able to understand what you are selling from your website or landing page. |
| **No trademark conflicts** | Your product name must not infringe an existing trademark or create customer confusion. |
| **Pricing is visible** | Your pricing must be clearly displayed and easy to find. |
| **Acceptable use** | No high-risk, illegitimate, or prohibited use cases (see below). |
| **Support email** | A reachable support email, shown on your website and on receipts. |
## Review process
### How long does it take?
Reviews are usually completed within a day. You will get an email the moment the review is complete or if we need a change.
### What happens after the review?
* **Approved:** your account can start accepting live payments.
* **Changes requested:** fix the flagged items, then resubmit from the same place you started.
### Where do I start?
Open **Go live** and start **Verify your business**, or go to **Payments → Payout account**. Verification and payout setup are part of the same go-live flow.
### What information is needed?
* Your legal name, or your business entity name
* Your product name and its website URL
* A short description of your business and how you operate
* A description of the products you intend to sell
* Your country of tax residence
### Common reasons for a change request
1. **Support email mismatch:** the email in your business details does not match the one on your website.
2. **Website not reachable:** your site is down, password-protected, or returning errors during review.
3. **Missing legal pages:** your website needs a Privacy Policy and Terms.
4. **False information:** fake reviews, testimonials, or inflated counts on your website.
5. **Product not ready:** if your product is not live yet, keep using [Test mode](/getting-started/test-mode) until it is.
## Why we review accounts
We review accounts to keep the platform legitimate and compliant. As your Merchant of Record we are the legal seller, so a light review up front protects both sides against fraud, misuse, and high-risk activity.
## Product guidelines
### Acceptable products
We support digital goods and services that we can sell as your Merchant of Record. Examples:
* Software and SaaS
* Digital subscriptions and memberships
* eBooks and PDFs
* Design assets and templates
* Photos, audio, and video
* Online courses
### Prohibited and high-risk products
Selling a prohibited product, or otherwise violating our terms, can place your account in review or lead to suspension.
* Adult or sexually explicit content of any kind, including AI-generated
* Deepfake, face-swap, or face-manipulation tools
* Illegal or age-restricted goods (drugs, weapons, alcohol, tobacco, vaping)
* Counterfeit goods, or content you do not hold the rights or license to
* Regulated financial services (gambling, lending, debt relief, banking)
* Spyware, stalkerware, or covert monitoring apps
* Multi-level marketing, pyramid, or similar schemes
* Physical goods (we are a digital Merchant of Record)
* Anything our payment or banking partners consider too high-risk
### Restricted (extra due diligence)
* Services such as marketing, design, development, and consulting
* Job boards and paid advertising placements
* API reselling (established resellers only, with prior-processor and chargeback history)
If you are unsure whether your product qualifies, contact support with a short description before you start selling. The full [Terms](https://agentaos.ai) govern in all cases.
## Customer support requirements
Good support is a requirement for every merchant.
* **A visible support email is required**, on your public website and in your dashboard.
* **Use a branded support email.** If your product is `MintAI`, use `support@mintai.com`, not a generic address.
* **Buyers must be able to cancel a subscription** from your product or from the link in their receipt. See [Subscriptions](/payments/subscriptions).
* **Respond to customer requests promptly.** If a buyer cannot reach you, we may issue a refund on your behalf.
## Next steps
The full path from test mode to your first live payment.
Why we review, and what we take on as the legal seller.
# How Merchant of Record Works
Source: https://docs.agentaos.ai/mor/how-it-works
What a Merchant of Record is, why it matters when you sell software across borders, and how AgentaOS becomes the legal seller on every transaction.
When you sell digital products across borders, you don't just owe tax at home. You typically owe VAT or sales tax in your buyer's country too, and you're responsible for registering, collecting, and filing it correctly in every one of those countries. AgentaOS removes that problem by becoming the legal seller of record on every transaction you make through us. If you're new to AgentaOS, start with the [introduction](/introduction).
## What a Merchant of Record is
A Merchant of Record (MoR) is the company that is legally responsible for a sale. It appears on the receipt, it collects the payment, and it owes the tax authority for that transaction. Most payment processors don't do this: they move money for you, but you stay the seller of record and you stay responsible for the tax.
AgentaOS is different. We stand in as the seller of record on every payment. Our Estonian entity is the legal seller your buyer transacts with, not you. That one change is what lets us calculate, collect, and remit tax on your behalf instead of leaving it to you.
## Why it matters
Cross-border tax compliance doesn't scale for a small team. Tracking rates, registering in each country, filing returns, and issuing tax-correct invoices in every buyer's jurisdiction is a job most founders don't have time for, and getting it wrong carries real financial and legal risk.
A Merchant of Record takes that job off your plate entirely. You sell. We handle the tax.
## How a payment flows
```mermaid theme={null}
flowchart LR
A[Buyer pays
card or digital wallet] --> B[AgentaOS is seller of record
Estonia]
B --> C[Tax calculated
and collected]
B --> D[Your balance
EUR or USD]
D --> E[You pay out
bank or wallet]
```
Your customer checks out by card or digital wallet (Apple Pay, Google Pay). They see AgentaOS as the seller on the payment page and the receipt.
We calculate the tax due based on what's being sold and where the buyer is, and collect it as part of the payment.
Your share of the payment, after fees, settles into your AgentaOS balance in EUR or USD. Tax is already accounted for, you don't set it aside yourself.
Move your balance to your bank account, in EUR or USD, or your wallet, on your schedule. See [Payouts](/payouts/overview).
## Merchant of Record vs. payment processor
| | Payment processor (used directly) | Merchant of Record (AgentaOS) |
| --------------------------- | ---------------------------------------- | --------------------------------------------- |
| Legal seller on the receipt | You | AgentaOS |
| Who owes VAT / sales tax | You, in every country your buyers are in | AgentaOS registers, collects, and remits |
| Who issues the invoice | You | AgentaOS, tax itemized on every invoice |
| Compliance filings | Your responsibility | Off your plate |
| What you integrate | Payments only | Payments, tax, invoicing, and payouts, in one |
A payment processor and a Merchant of Record aren't the same thing, and they aren't mutually exclusive. Under the hood we use a payment processor to move the money, the same rails you could plug into directly. The difference is that we sit in front of it as the legal seller, so we carry the tax, compliance, and chargeback liability that going direct would leave with you.
## What stays yours
* Your product, your pricing, your customer relationship.
* Your payout schedule and destination, bank or wallet.
* Your own company's income tax and bookkeeping. AgentaOS handles the tax on the sale itself, not your business's own tax return.
## Refunds and chargebacks
Disputes are part of selling online. As your Merchant of Record, AgentaOS also handles refunds and chargebacks on your behalf: when a buyer disputes a charge with their card issuer or bank, AgentaOS responds to it as the seller of record, so you are not the one fielding the dispute or wiring money back yourself.
## FAQ
No. A payment processor used directly leaves you as the seller of record, responsible for tax. AgentaOS is a Merchant of Record: we become the seller of record and handle VAT, sales tax, and invoicing for every sale.
Our Estonian entity appears as the seller of record on the payment page, the receipt, and the invoice. See [Invoices](/payments/invoices).
Probably, for your own company's books and income tax. What you won't need to do is register for or file VAT and sales tax on the sales you make through AgentaOS. We handle that as the seller of record.
It settles into your AgentaOS balance in EUR or USD. From there you pay out to your bank account or your wallet. See [Payouts](/payouts/overview).
See [Supported countries](/mor/supported-countries) for where you can sell and get paid, and [Tax](/mor/tax) for where we're registered to collect and remit.
## Next steps
The AgentaOS overview, if you're just getting started.
Where you can sell and where you can get paid.
Destination VAT, where we're registered, and what shows up on the invoice.
How the per-transaction fee works.
What a tax-correct invoice looks like, and how to export one.
Move your balance to your bank or wallet.
# Pricing
Source: https://docs.agentaos.ai/mor/pricing
Transparent per-transaction fees plus an optional plan. No setup costs, no hidden charges. You pay when you get paid.
AgentaOS charges a fee on each payment you accept, plus an optional monthly plan that lowers your rate. Tax registration, collection, remittance, invoicing, and payouts are all included in what you pay as your Merchant of Record. There are no setup costs and no hidden charges.
**Founding 30.** The first 30 founders and teams to go live get Pro rates with **no monthly subscription, forever.** [Open an account](https://app.agentaos.ai) to claim a spot.
## Plans
| Plan | Bank & stablecoin | Card | Monthly |
| ----------------- | ----------------- | ------------------ | -------- |
| **Pay as you go** | 1.5% | 4.5% + €0.50 | €0 |
| **Pro** | 1.0% | 4.0% + €0.40 | €49 / mo |
| **Scale** | \~0.5% | Cost-plus (custom) | Custom |
* **Pay as you go** is the no-commitment entry lane. No monthly fee, slightly higher per-transaction rate.
* **Pro** lowers every rate by \~0.5%. It pays for itself at roughly €10,000/mo in volume, and the dashboard tells you when you cross that line.
* **Scale** is for high-volume merchants and agencies (sub-accounts, higher limits, negotiated rates).
Transaction fees apply to the gross amount and are netted from your settlement, so there is nothing to invoice or pay separately.
## What a card payment costs
The card rate depends on where your buyer's card was issued. AgentaOS settles each charge in its own currency and lets the buyer cover FX, so you are not paying a conversion markup.
| Buyer's card | Typical cost to us |
| -------------------- | ------------------------------------ |
| EEA (Europe) | Lowest |
| US and other non-EEA | Higher (an issuer surcharge applies) |
The Pro card rate (4.0% + €0.40) is a flat price to you regardless of your buyer's origin.
## Payouts
Where your payout method charges a fee, it passes straight through to you at cost. AgentaOS does not mark it up. See [Payouts](/payouts/overview).
## FAQ
Only on Pro (€49/mo) and Scale. Pay as you go has no monthly fee. And the Founding 30 program waives the Pro subscription forever for the first 30 founders and teams.
Pro lowers every rate by about 0.5%, so it pays for itself at roughly €10,000/mo in volume. Below that, Pay as you go is cheaper. The dashboard nudges you the moment Pro would save you money.
Yes. The transaction fee is what funds AgentaOS acting as your Merchant of Record: tax registration, collection, remittance, and invoicing. You do not pay separately for those.
No. Where a payout method has a fee, it passes through to you at cost.
The cost to us varies by your buyer's card origin, but the Pro card rate (4.0% + €0.40) is flat to you. See [Supported countries](/mor/supported-countries).
## Next steps
What a Merchant of Record is and why it matters.
Where you can sell and get paid.
Destination VAT, where we are registered, and what shows on the invoice.
# Supported Countries & Currencies
Source: https://docs.agentaos.ai/mor/supported-countries
Where you can sell, which payment methods reach which buyers, and the countries where you can get paid out with AgentaOS.
AgentaOS accepts purchases from buyers in almost every country, and pays merchants out by bank transfer across the countries listed below. For how the money moves end to end, see [How it works](/mor/how-it-works).
Do not see your country in the payout list? Bank payout coverage expands over time. In the meantime, [stablecoin payouts (legacy)](/legacy/stablecoin-payouts) let you withdraw your balance on-chain from anywhere. Reach out and we will confirm your options.
## Accepting payment (your buyer's side)
135+ currencies, from buyers worldwide. Your buyer pays in their local currency, and you do not need a merchant account in their country.
Purchases are accepted from buyers worldwide, except the sanctioned or embargoed jurisdictions in the [unsupported list](#unsupported-countries-for-purchases) below.
## Payout methods (your side)
Whichever method your buyer uses, your AgentaOS balance settles in **EUR or USD**, then pays out to your bank account:
Available across every supported country below, through our banking partner. Payout fees pass through at cost, with no markup.
### Payout fees
We pass payout costs through at cost. AgentaOS adds no markup of its own. What you pay depends on the route your money takes:
| Route | Typical fee per payout |
| --------------------------------- | ------------------------------------------------------ |
| Local bank transfer | A small flat fee, often free |
| Cross-border (Swift), shared cost | About €5, and the recipient may bear onward bank fees |
| Cross-border (Swift), fixed cost | About €25, and the recipient receives the exact amount |
Cross-border Swift fees vary by destination and payout currency, typically between €5 and €35 per transfer. Currency conversion, where it applies, is charged separately.
## Supported payout countries
You can receive a bank payout in the following countries. This list follows our banking partner's live coverage, so it grows over time.
| | | | |
| ------------------ | -------------- | -------------- | ---------------------- |
| Albania | Andorra | Argentina | Australia |
| Austria | Bangladesh\*\* | Belgium | Bosnia and Herzegovina |
| Brazil | Bulgaria | Canada | Cayman Islands |
| Chile | China\* | Colombia\*\* | Costa Rica |
| Croatia | Cyprus | Czech Republic | Denmark |
| Dominican Republic | Egypt | Estonia | Finland |
| France | Georgia | Germany | Gibraltar |
| Greece | Guatemala | Hong Kong | Hungary |
| Iceland | India | Indonesia | Ireland |
| Israel | Italy | Japan | Kenya |
| Latvia | Liechtenstein | Lithuania | Luxembourg |
| Malaysia | Malta | Mexico | Moldova |
| Monaco | Montenegro | Morocco | Nepal\*\* |
| Netherlands | New Zealand | Nigeria | North Macedonia |
| Norway | Pakistan\*\* | Peru | Philippines |
| Poland | Portugal | Romania | San Marino |
| Serbia | Singapore | Slovakia | Slovenia |
| South Africa | South Korea | Spain | Sri Lanka |
| Sweden | Switzerland | Taiwan | Tanzania\*\* |
| Thailand | Turkey | Ukraine\*\* | United Arab Emirates |
| United Kingdom | United States | Uruguay | Vietnam |
| Zambia | | | |
### Bank transfer partner restrictions
Some local bank payouts carry extra rules from our banking partner. Countries marked `**` may have restrictions, for example only individual (personal) bank accounts being supported, or business transfers not being available. China (`*`) has per-transfer limits. These come from the partner and can change over time, so check with us before you rely on a specific route.
## Unsupported countries for purchases
We cannot accept payments from customers or merchants in the following countries:
* Afghanistan
* Antarctica
* Belarus
* Burma (Myanmar)
* Central African Republic
* Cuba
* Crimea (Region of Ukraine)
* Democratic Republic of Congo
* Donetsk (Region of Ukraine)
* Haiti
* Iran
* Kherson (Region of Ukraine)
* Libya
* Luhansk (Region of Ukraine)
* Mali
* Netherlands Antilles
* Nicaragua
* North Korea
* Russia
* Somalia
* South Sudan
* Sudan
* Syria
* Venezuela
* Yemen
* Zaporizhzhia (Region of Ukraine)
* Zimbabwe
## FAQ
Cards reach 135+ currencies through our card processor's global network. Purchases are blocked only in the sanctioned or embargoed jurisdictions listed above.
Your AgentaOS balance always settles in EUR or USD, whatever currency your buyer paid in and whatever method they used.
No. Bank payouts reach the countries listed above, and coverage expands over time. See [Payouts](/payouts/overview).
Bank payout coverage expands over time as our banking partner adds routes. Reach out and we will confirm your options.
## Next steps
What a Merchant of Record is and why it matters.
Destination VAT, where we are registered, and what shows on the invoice.
Transaction fees, plans, and the Founding 30 offer.
Move your balance to your bank or wallet.
# How Tax Works
Source: https://docs.agentaos.ai/mor/tax
How AgentaOS calculates, collects, and remits VAT and sales tax as your Merchant of Record.
Tax on a sale is normally owed based on where your buyer is, not where you are. As your Merchant of Record, AgentaOS calculates that tax at checkout, collects it as part of the payment, and remits it to the right authority. You don't register, file, or send a payment to a tax office yourself.
This page explains how tax works on sales made through AgentaOS. It isn't advice on your own company's corporate or income tax, talk to your accountant for that.
## Destination tax, in short
Most consumption taxes (VAT in the EU, sales tax or GST elsewhere) are charged based on the buyer's location and what's being sold, not the seller's. Sell a subscription to a buyer in Germany, and German VAT rules apply, even if your company has never set foot there. This is why cross-border software sales create a tax obligation in every country you have customers, not just your own.
## How AgentaOS handles it
Based on the buyer's location and what's being sold, before the payment is confirmed.
Your buyer pays one total price, tax included, in a single transaction.
We register with tax authorities, file returns, and remit what's owed in the jurisdictions we cover. You never file anything yourself.
Every payment gets a tax-correct invoice with our Estonian entity as the seller of record, and tax broken out as its own line. See [Invoices](/payments/invoices).
## Where we're registered today
Destination VAT via Estonia's OSS (One-Stop Shop) scheme. Our Estonian entity is the registered seller.
We calculate VAT for the jurisdictions where we are registered today, the EU (via Estonia OSS), and add more as we grow.
## Selling to businesses (B2B)
When your buyer is a business rather than a consumer, VAT treatment can differ. In some jurisdictions, the reverse-charge mechanism applies, where the buyer accounts for the VAT instead of AgentaOS collecting it. Business buyers can typically provide a VAT or tax number at checkout, which the invoice then reflects.
## FAQ
No. AgentaOS is the seller of record, so we register, collect, and remit VAT and sales tax on the sales you make through us.
It depends on the buyer's country and what's being sold, and is calculated automatically at checkout. Exact rates and thresholds by country are being finalized before we publish a full table here.
B2B sales can be treated differently for VAT purposes, including reverse charge in some jurisdictions. We're finalizing the exact rules to publish here, see the note above.
Our Estonian entity, your Merchant of Record, appears as the seller of record. See [Invoices](/payments/invoices).
We register there and start collecting and remitting automatically. You don't need to do anything on your side.
No. This covers tax on the sales made through AgentaOS. Your own company's income tax and bookkeeping are still yours to handle.
## Next steps
The AgentaOS overview, if you're just getting started.
What a Merchant of Record is and why it matters.
Where you can sell and where you can get paid.
How the per-transaction fee works.
What a tax-correct invoice looks like, and how to export one.
Move your balance to your bank or wallet.
# Checkouts
Source: https://docs.agentaos.ai/payments/checkouts
Create a checkout session to collect a single payment. Standalone, or from a payment link.
A checkout is one attempt to collect one payment. Create one directly from your backend when you already know the amount, or create one from a [payment link](/payments/payment-links) to reuse a shared template. Either way you get a `checkoutUrl`: send your buyer there to pay.
```mermaid theme={null}
stateDiagram-v2
[*] --> open: checkouts.create()
open --> completed: Payment confirmed
open --> expired: expiresIn elapsed
open --> cancelled: checkouts.cancel()
completed --> [*]
expired --> [*]
cancelled --> [*]
```
## Create a checkout
```typescript theme={null}
import { AgentaOS } from '@agentaos/pay';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
const checkout = await agentaos.checkouts.create({
amount: 49.99,
currency: 'EUR',
description: 'Order #123',
successUrl: 'https://myshop.com/success',
cancelUrl: 'https://myshop.com/cart',
webhookUrl: 'https://myshop.com/webhooks',
});
// Redirect your buyer to pay
res.redirect(checkout.checkoutUrl);
```
```bash theme={null}
agenta pay checkout --amount 49.99 --currency EUR --description "Order #123"
```
```bash theme={null}
curl -X POST https://api.agentaos.ai/api/v1/gateway/sessions \
-H "x-api-key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 49.99,
"currency": "EUR",
"description": "Order #123",
"successUrl": "https://myshop.com/success",
"cancelUrl": "https://myshop.com/cart",
"webhookUrl": "https://myshop.com/webhooks"
}'
```
## Money model
A plain `amount` is always in currency units: `49.99` means €49.99, never cents. This holds for every create call, payment link, checkout, and invoice amount in the API. The one exception is `subscription.unitAmountMinor`, which is an integer of the smallest unit (`1999` = €19.99). See [Subscriptions](/payments/subscriptions) for why that field is different. Webhook payloads carry `amount` as a string (`"49.99"`), since JSON numbers lose trailing zeros.
## Create from a payment link
Pass `linkId` to inherit the link's `amount`, `currency`, `description`, `taxRateId`, and URLs. Override anything per-checkout with `amountOverride` or the other fields.
```typescript theme={null}
const checkout = await agentaos.checkouts.create({
linkId: link.id,
metadata: { customerId: '12345' },
});
```
**Payment links vs. standalone checkouts:** use a payment link for anything reusable or shareable, a subscription plan, a donation button, a link you paste in chat. Use a standalone checkout when your backend already knows the amount and buyer at the moment of creation, like an e-commerce order at cart checkout.
A standalone (linkless) checkout is always a **one-time** payment, there's no `type` field on checkout create. To sell a subscription, create a subscription payment link (`type: 'subscription'`) and the buyer subscribes at the hosted checkout, or spin up a checkout from that link with `linkId`.
## Pre-populate buyer info
If you already know the buyer (from your own account system), pre-fill their details to skip the checkout form:
```typescript theme={null}
const checkout = await agentaos.checkouts.create({
amount: 49.99,
currency: 'EUR',
buyerEmail: 'john@example.com',
buyerName: 'John Doe',
buyerCompany: 'Acme Corp',
buyerCountry: 'DE',
buyerVat: 'DE123456789',
buyerAddress: '123 Main St, Berlin',
});
```
If you don't pre-populate, the hosted checkout asks the buyer for name and email directly. Company, VAT, and address are optional but recommended: they land on the invoice.
## How the buyer pays
The hosted checkout offers card, Apple Pay, and Google Pay through our card processor. Card details are entered directly into the secure card form, never handled by your server or ours. Some checkouts also expose an `x402Url` for agent-to-agent payment over the [x402 protocol](/sub/x402); most integrations can ignore it.
Don't trust `successUrl` as proof of payment. The buyer's browser might close before the redirect fires. Use [webhooks](/payments/webhooks) as the source of truth for "did this checkout actually get paid."
## Retrieve a checkout
```typescript theme={null}
const checkout = await agentaos.checkouts.retrieve('mZrESFyR7RC9RPsJfZCVkg');
console.log(checkout.status); // 'open' | 'completed' | 'expired' | 'cancelled'
```
```bash theme={null}
agenta pay get mZrESFyR7RC9RPsJfZCVkg
```
```bash theme={null}
curl https://api.agentaos.ai/api/v1/gateway/sessions/mZrESFyR7RC9RPsJfZCVkg \
-H "x-api-key: sk_live_..."
```
## List checkouts
Paginated: every list call returns `{ items, total, hasMore }`.
```typescript theme={null}
const page = await agentaos.checkouts.list({
status: 'completed',
limit: 10,
offset: 0,
});
```
```bash theme={null}
agenta pay list --status completed --limit 10
```
```bash theme={null}
curl "https://api.agentaos.ai/api/v1/gateway/sessions?status=completed&limit=10" \
-H "x-api-key: sk_live_..."
```
## Cancel a checkout
```typescript theme={null}
await agentaos.checkouts.cancel('mZrESFyR7RC9RPsJfZCVkg');
```
```bash theme={null}
curl -X POST https://api.agentaos.ai/api/v1/gateway/sessions/mZrESFyR7RC9RPsJfZCVkg/cancel \
-H "x-api-key: sk_live_..."
```
Cancelling stops the buyer from paying. If a payment already cleared before the cancel call lands, it still completes; cancelling doesn't reach into the card processor or on-chain state.
## Parameters
Amount in currency units (e.g. `49.99`). Required if no `linkId`.
Create from a payment link template. Inherits its amount, currency, and configuration.
`EUR` or `USD`.
Shown on the checkout page. Max 1000 characters.
Override the link's amount for this checkout only.
Redirect the buyer here after payment. HTTPS only.
"Cancel" link on the checkout page. HTTPS only.
Server notification URL for payment events. HTTPS only.
UUID of a pre-created tax rate.
Pre-populate buyer email. Max 320 characters.
Pre-populate buyer name. Max 200 characters.
Pre-populate company name. Max 200 characters.
ISO 3166-1 alpha-2 (e.g. `DE`).
Pre-populate buyer address. Max 500 characters.
Pre-populate VAT number (e.g. `DE123456789`).
ISO date (`YYYY-MM-DD`). Presentation only, stamped onto the invoice once issued.
Seconds until expiry (300 to 86400).
Custom key-value data, returned unchanged in webhooks. Max 8KB.
## Response
These are the SDK (camelCase) field names; the raw REST response uses snake\_case (e.g. `session_id`, `seller_mode`).
Session UUID.
Public session ID, used in the checkout URL.
URL to send your buyer to.
x402 protocol URL, for agent payments.
`open`, `completed`, `expired`, or `cancelled`.
`mor` (card + bank) or `crypto` (on-chain). Inherited from the link, or derived from your account for standalone checkouts.
Amount for this checkout, if overridden.
Settlement currency.
Issued invoice UUID. `null` until the payment is confirmed.
Human-readable invoice number, once issued.
ISO 8601.
ISO 8601.
## Next steps
Turn a checkout into a reusable, shareable template.
Paying a subscription-type link at checkout starts a subscription.
Every completed checkout issues a VAT-correct invoice.
Verify `checkout.session.completed` server-side, don't rely on the redirect.
# Customers
Source: https://docs.agentaos.ai/payments/customers
The buyers who have paid you. Read-only, populated automatically from checkouts.
A customer record is created the moment someone pays you for the first time: email, name, country, and VAT number, pulled from what they entered at checkout. This is a read surface, the same list the [dashboard](https://app.agentaos.ai) Customers page shows. There's no `create` or `update`: you can't add a customer that hasn't paid, and buyer details come from the checkout, not from you editing a record after the fact.
## List customers
Paginated: every list call returns `{ items, total, hasMore }`.
```typescript theme={null}
const page = await agentaos.customers.list({ limit: 20, offset: 0 });
console.log(page.total, page.hasMore);
```
```bash theme={null}
agenta customers list --limit 20
```
```bash theme={null}
curl "https://api.agentaos.ai/api/v1/gateway/customers?limit=20&offset=0" \
-H "x-api-key: sk_live_..."
```
## Response
Customer UUID.
Customer email.
Customer name.
ISO 3166-1 alpha-2 country code.
VAT number on file, if provided.
Underlying customer ID from the card processor, for card/bank payers.
ISO 8601, when this buyer first paid you.
```json theme={null}
{
"items": [
{
"id": "a1b2c3d4-...",
"email": "john@example.com",
"name": "John Doe",
"country": "DE",
"vatNumber": "DE123456789",
"stripeCustomerId": "cus_...",
"createdAt": "2026-03-17T02:00:00.000Z"
}
],
"total": 1,
"hasMore": false
}
```
## Next steps
See which customers have an active subscription.
Every invoice a customer has been issued.
Pre-fill buyer details to skip the checkout form next time.
# Invoices
Source: https://docs.agentaos.ai/payments/invoices
VAT-correct invoices for every payment. List, retrieve, void, download the PDF, and export for accounting.
Every confirmed payment issues an invoice automatically: seller and buyer details, tax breakdown, and (for stablecoin payments) the exchange rate used to convert to fiat. The seller of record on every invoice is **Aristokrates OÜ** (Estonia), AgentaOS's Merchant of Record entity, unless the payment settled to your own on-chain wallet, in which case you are the seller.
Amounts on the `Invoice` object (`amount`, `fiatAmount`, `taxAmount`) are decimal currency units, same as everywhere else in this API. `19.99` means €19.99, not cents.
## Create and send an invoice
Bill a customer directly from the dashboard, no code required. Open **Finance → Invoices** and click **New invoice**. Add your line items, fill in the customer's details and country (we calculate the right VAT), set your payment terms, and send. Your customer pays on the same secure checkout, by card, Apple Pay, or Google Pay.
Every invoice you send, and every invoice issued automatically when a payment clears, is available over the API below.
## List invoices
Paginated: every list call returns `{ items, total, hasMore }`. Filter by date range and status.
```typescript theme={null}
const page = await agentaos.invoices.list({
from: '2026-03-01',
to: '2026-03-31',
status: 'issued', // 'all' | 'issued' | 'paid' | 'voided'
limit: 50,
});
```
```bash theme={null}
agenta invoices list --limit 50
```
```bash theme={null}
curl "https://api.agentaos.ai/api/v1/gateway/invoices?from=2026-03-01&to=2026-03-31&status=issued" \
-H "x-api-key: sk_live_..."
```
## Retrieve an invoice
```typescript theme={null}
const invoice = await agentaos.invoices.retrieve('invoice-uuid');
```
```bash theme={null}
curl https://api.agentaos.ai/api/v1/gateway/invoices/invoice-uuid \
-H "x-api-key: sk_live_..."
```
## Void an invoice
Voiding marks the accounting record as void. It never touches the payment itself: the buyer already paid, and nothing is refunded. Use it to correct a bookkeeping mistake, not to reverse a charge.
```typescript theme={null}
await agentaos.invoices.void('invoice-uuid');
```
```bash theme={null}
curl -X POST https://api.agentaos.ai/api/v1/gateway/invoices/invoice-uuid/void \
-H "x-api-key: sk_live_..."
```
Voiding is irreversible. The invoice stays void forever; there's no "un-void."
## Download the invoice PDF
```typescript theme={null}
import { writeFileSync } from 'fs';
const pdf = await agentaos.invoices.downloadPdf('invoice-uuid');
writeFileSync('invoice.pdf', pdf);
```
```bash theme={null}
curl https://api.agentaos.ai/api/v1/gateway/invoices/invoice-uuid/pdf \
-H "x-api-key: sk_live_..." \
-o invoice.pdf
```
The PDF includes merchant and buyer details, the amount (plus the fiat equivalent and exchange rate for stablecoin payments), a tax breakdown (subtotal, tax, total), and a link to the on-chain transaction where relevant.
## Download a statement
A monthly-statement-style PDF covering a date range: opening/closing balance, every transaction in the ledger, and a VAT summary grouped by rate.
```typescript theme={null}
const statement = await agentaos.invoices.downloadStatement({
from: '2026-03-01',
to: '2026-03-31',
});
writeFileSync('march-statement.pdf', statement);
```
```bash theme={null}
curl "https://api.agentaos.ai/api/v1/gateway/invoices/statement?from=2026-03-01&to=2026-03-31" \
-H "x-api-key: sk_live_..." \
-o statement.pdf
```
## Export CSV
A 23-column CSV built for accounting software: date, direction, invoice number, description, crypto amount, token, and token address, chain, exchange rate (and source and timestamp), fiat amount and currency, tax name/rate/amount, `total_eur` (always in EUR), buyer name/company/country/VAT, transaction hash, and status.
```typescript theme={null}
const csv = await agentaos.invoices.exportCsv({
from: '2026-03-01',
to: '2026-03-31',
status: 'issued',
});
writeFileSync('invoices.csv', csv);
```
```bash theme={null}
curl "https://api.agentaos.ai/api/v1/gateway/invoices/export?from=2026-03-01&to=2026-03-31" \
-H "x-api-key: sk_live_..." \
-o invoices.csv
```
## Response
Invoice UUID.
Human-readable, e.g. `INV-2026-0001`.
Amount in the payment token/currency.
Currency or token code.
EUR/USD equivalent, for stablecoin payments.
`EUR` or `USD`.
Rate applied at settlement time.
Where the rate came from.
Applied tax rate, e.g. `19` for 19%.
Tax amount in currency units.
e.g. `DE VAT`.
Whether `amount` already includes tax.
Seller name on the invoice.
Buyer email.
Buyer name.
Buyer company.
ISO 3166-1 alpha-2.
Buyer VAT number, if provided.
`issued`, `paid`, or `voided`.
ISO 8601.
ISO 8601, if voided.
## Next steps
Every paid invoice also gets a receipt, emailed automatically.
The buyer behind each invoice.
How destination VAT is calculated and remitted.
Know the moment a new invoice is issued.
# Payment Links
Source: https://docs.agentaos.ai/payments/payment-links
Create a reusable, shareable link to sell a product. One-time or subscription, no code required to share it.
A payment link is a reusable product. Create one and share the URL anywhere: email, a landing page, a chat message. Every visitor who opens it gets their own checkout. One link can be paid many times.
**Payment link vs. checkout:** a payment link is a template you create once and share. A [checkout](/payments/checkouts) is a single payment attempt, either created standalone or from a link. Opening a payment link's URL creates a new checkout behind the scenes.
## Without code
Do not want to touch the API? Create a product in the dashboard and share its link.
Open the **Products** page and add a name, description, price, and an optional image.
Click **Share** to copy the product's payment link. Send it by email, social, or a QR code. Every visit starts a fresh checkout.
Prefer code? Create links programmatically below.
## Create a payment link
```typescript theme={null}
import { AgentaOS } from '@agentaos/pay';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
const link = await agentaos.paymentLinks.create({
amount: 49.99,
currency: 'EUR',
description: 'Pro plan',
successUrl: 'https://myshop.com/success',
cancelUrl: 'https://myshop.com/cancel',
webhookUrl: 'https://myshop.com/webhooks',
});
console.log(link.checkoutUrl);
// → https://app.agentaos.ai/pay/7rr6S9ml4BMp829wV5WeAA
```
```bash theme={null}
curl -X POST https://api.agentaos.ai/api/v1/gateway/payment-links \
-H "x-api-key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 49.99,
"currency": "EUR",
"description": "Pro plan",
"successUrl": "https://myshop.com/success",
"cancelUrl": "https://myshop.com/cancel",
"webhookUrl": "https://myshop.com/webhooks"
}'
```
There's no CLI command to create a payment link yet. Create one from the SDK, the REST API, or the [dashboard](https://app.agentaos.ai). The CLI's `agenta pay checkout` creates a one-off [checkout](/payments/checkouts), not a reusable link.
`amount` is in currency units, not cents. `49.99` means €49.99. See the [money model](/payments/checkouts#money-model) if you're wiring this up against real numbers.
## One-time vs. subscription
A payment link is `type: 'one_time'` by default: every payment is independent, and the link can be reused indefinitely. Set `type: 'subscription'` to turn it into a recurring plan. Subscription links require a `billingInterval` and a verified account that accepts card and bank; they can't run in on-chain-only mode.
```typescript theme={null}
const plan = await agentaos.paymentLinks.create({
amount: 29.99,
currency: 'EUR',
description: 'Pro plan, monthly',
type: 'subscription',
billingInterval: 'month', // 'month' | 'year', required for subscriptions
});
```
Creating a subscription link does not create a subscription. A subscription is created only when a buyer pays it at the hosted checkout. See [Subscriptions](/payments/subscriptions) for how the buyer-pays-in flow works and how to manage the result.
## The checkout URL
Every payment link returns a `checkoutUrl` in the shape `https://app.agentaos.ai/pay/{id}`. Share it as-is: a button, an email, a QR code. Each visit starts a fresh checkout scoped to that link's amount, currency, and configuration.
## Custom checkout fields
Collect extra information from the buyer before they pay by passing `checkoutFields`. Each field renders on the hosted checkout page and is required or optional per field.
```typescript theme={null}
const link = await agentaos.paymentLinks.create({
amount: 15.00,
currency: 'EUR',
description: 'Event ticket',
checkoutFields: [
{ key: 'attendeeName', label: 'Attendee name', type: 'text', required: true },
{ key: 'company', label: 'Company', type: 'text', required: false },
{ key: 'ticketTier', label: 'Ticket tier', type: 'select', required: true, options: ['Standard', 'VIP'] },
],
});
```
## Retrieve a payment link
```typescript theme={null}
const link = await agentaos.paymentLinks.retrieve('link-uuid');
```
```bash theme={null}
curl https://api.agentaos.ai/api/v1/gateway/payment-links/link-uuid \
-H "x-api-key: sk_live_..."
```
## List payment links
Paginated: every list call returns `{ items, total, hasMore }`.
```typescript theme={null}
const page = await agentaos.paymentLinks.list({ limit: 20, offset: 0 });
console.log(page.total, page.hasMore);
```
```bash theme={null}
curl "https://api.agentaos.ai/api/v1/gateway/payment-links?limit=20&offset=0" \
-H "x-api-key: sk_live_..."
```
## Cancel a payment link
Cancelling stops new checkouts from being created against the link. It does not affect checkouts already in progress or subscriptions already running from it.
```typescript theme={null}
await agentaos.paymentLinks.cancel('link-uuid');
```
```bash theme={null}
curl -X DELETE https://api.agentaos.ai/api/v1/gateway/payment-links/link-uuid \
-H "x-api-key: sk_live_..."
```
## Parameters
Amount in currency units (e.g. `49.99` = €49.99).
`EUR` or `USD`.
Shown on the checkout page. Max 1000 characters.
`one_time` or `subscription`.
`month` or `year`. Required when `type` is `subscription`, omit otherwise.
Redirect the buyer here after payment. HTTPS only.
"Cancel" link on the checkout page. HTTPS only.
Server notification URL for payment events. HTTPS only.
UUID of a pre-created tax rate.
Custom fields to collect from the buyer at checkout. Each item has `key`, `label`, `type` (`text` | `email` | `tel` | `select`), `required`, and optionally `placeholder` or `options` (for `select`).
ISO 8601. The link stops accepting new checkouts after this time.
Custom key-value data, returned unchanged on every checkout created from this link. Max 8KB.
## Response
Link UUID.
Shareable payment URL.
Amount in currency units.
Settlement currency.
`active` or `cancelled`.
`mor` (card + bank) or `crypto` (on-chain). Derived, never set by you.
`one_time` or `subscription`.
`month` or `year` for subscription links; `null` for one-time.
Times this link has been paid.
Custom fields configured on this link.
ISO 8601.
ISO 8601.
## Next steps
A single payment attempt, standalone or created from a link.
How a `type: 'subscription'` link turns into a running subscription.
Get notified the moment someone pays your link.
Why we can handle card, bank, and tax as your seller of record.
# Receipts
Source: https://docs.agentaos.ai/payments/receipts
Every paid invoice gets a receipt automatically. Download the PDF, or resend the email to the buyer.
The moment a payment is confirmed, AgentaOS emails the buyer a receipt. You don't trigger it and don't need to build it: it's automatic, tied to the invoice, and always available to re-send if the buyer loses it.
A receipt exists for every paid invoice. If an invoice was issued before receipts existed on your account, `getReceipt` falls back to the invoice PDF, so the method always returns something useful.
## Download the receipt PDF
```typescript theme={null}
import { writeFileSync } from 'fs';
const pdf = await agentaos.invoices.getReceipt('invoice-uuid');
writeFileSync('receipt.pdf', pdf);
```
```bash theme={null}
agenta invoices receipt invoice-uuid -o receipt.pdf
```
```bash theme={null}
curl https://api.agentaos.ai/api/v1/gateway/invoices/invoice-uuid/receipt \
-H "x-api-key: sk_live_..." \
-o receipt.pdf
```
## Resend the receipt email
Re-sends to whichever email address is on file for the buyer, the same address the original receipt went to. Only works for paid invoices: there's nothing to send a receipt for on an unpaid or voided one.
```typescript theme={null}
const result = await agentaos.invoices.sendReceipt('invoice-uuid');
console.log(result.sentTo); // the email it was sent to
```
```bash theme={null}
agenta invoices send-receipt invoice-uuid
```
```bash theme={null}
curl -X POST https://api.agentaos.ai/api/v1/gateway/invoices/invoice-uuid/send-receipt \
-H "x-api-key: sk_live_..."
```
## Response
Always `true` on success.
The email address the receipt was (re-)sent to.
## Next steps
Every receipt is tied to an underlying invoice.
Where the buyer's email on file comes from.
React to `checkout.session.completed` the moment a receipt goes out.
# Subscriptions
Source: https://docs.agentaos.ai/payments/subscriptions
Recurring billing. Created when a buyer pays a subscription payment link, managed from the API, CLI, or dashboard.
A subscription is a running, recurring commitment: card on file, billed automatically every cycle. AgentaOS manages the billing for you (retries on failed cards, prorations, cancellations) via our card processor under the hood.
**There is no `subscriptions.create()`.** Subscriptions are created by the buyer, not by you. You create a [payment link](/payments/payment-links) with `type: 'subscription'`, the buyer pays it at the hosted checkout, and that payment starts the subscription. This resource is the merchant-side management surface: list what's running, cancel what shouldn't be.
## How a subscription gets created
```typescript theme={null}
const plan = await agentaos.paymentLinks.create({
amount: 19.99,
currency: 'EUR',
description: 'Pro plan, monthly',
type: 'subscription',
billingInterval: 'month',
});
```
The buyer opens `plan.checkoutUrl` and pays with a card at the hosted checkout, exactly like a one-time payment.
AgentaOS creates a customer and subscription with our card processor behind the scenes, charges cycle 1, and the subscription becomes visible to `subscriptions.list()`.
Every `billingInterval`, the card on file is charged again. No action from you unless the charge fails or the buyer cancels.
## Money model: `unitAmountMinor`
Every other amount in this API is decimal currency units (`amount: 49.99` means €49.99). Subscriptions are the one exception: `unitAmountMinor` is an **integer of the smallest currency unit**, mirroring how our card processor represents recurring prices internally.
```typescript theme={null}
subscription.unitAmountMinor; // 1999
// → €19.99 per cycle (1999 minor units, EUR has 2 decimals)
```
The field name is the signal: anything ending in `Minor` is an integer of cents/pence, not decimal currency. Everywhere else in this API (payment links, checkouts, invoices), a plain `amount` is already in currency units, no conversion needed.
## Statuses
`status` is the raw subscription status from the card processor, passed through unchanged:
| Status | Meaning |
| -------------------- | ---------------------------------------------------------------------------------------- |
| `incomplete` | First payment hasn't succeeded yet (e.g. card requires authentication). |
| `incomplete_expired` | First payment failed and the authentication window lapsed. Subscription never activated. |
| `trialing` | In a free trial period, not yet charged. |
| `active` | Paid and current. |
| `past_due` | A renewal charge failed; the card processor is retrying. |
| `canceled` | Ended, no further charges. |
| `unpaid` | Retries exhausted without a successful charge. |
| `paused` | Billing paused; no charges while in this state. |
## List subscriptions
Paginated: every list call returns `{ items, total, hasMore }`.
```typescript theme={null}
const page = await agentaos.subscriptions.list({ limit: 20, offset: 0 });
console.log(page.total, page.hasMore);
```
```bash theme={null}
agenta subscriptions list --limit 20
```
```bash theme={null}
curl "https://api.agentaos.ai/api/v1/gateway/subscriptions?limit=20&offset=0" \
-H "x-api-key: sk_live_..."
```
## Cancel a subscription
Defaults to cancel-at-period-end: the buyer keeps what they already paid for until `currentPeriodEnd`, and there's no refund. Pass `atPeriodEnd: false` to cancel immediately instead. Calling cancel on an already-canceled subscription is a no-op, safe to retry.
```typescript theme={null}
// Cancel at period end (default): buyer keeps access until currentPeriodEnd
await agentaos.subscriptions.cancel('sub-uuid');
// Cancel immediately: access revoked now, no refund
await agentaos.subscriptions.cancel('sub-uuid', { atPeriodEnd: false });
```
```bash theme={null}
# At period end (default)
agenta subscriptions cancel sub-uuid
# Immediately
agenta subscriptions cancel sub-uuid --now
```
```bash theme={null}
curl -X POST https://api.agentaos.ai/api/v1/gateway/subscriptions/sub-uuid/cancel \
-H "x-api-key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "atPeriodEnd": true }'
```
Cancellation never issues a refund. It only stops future renewals. If you owe the buyer money back for the current period, that's a separate, manual step on your side.
## Response
**Subscription object** (from `list`):
Subscription UUID.
Subscriber email.
Subscriber name.
The plan's name or description, from the payment link.
`month` or `year`.
One of the statuses above.
Per-cycle amount in integer minor units (e.g. `1999` = €19.99).
Settlement currency.
ISO 8601 end of the current paid period. `null` before the first cycle books.
Underlying subscription ID from the card processor.
**Cancel result:**
Status after cancellation.
ISO 8601 end of the current paid period.
Whether the subscription is scheduled to end at period end.
ISO date the cancellation takes effect.
## Next steps
Create the `type: 'subscription'` link that starts this flow.
See who's subscribed to you.
Every renewal charge issues its own invoice.
Get notified when a subscription payment lands.
# Webhooks
Source: https://docs.agentaos.ai/payments/webhooks
Get notified the moment a payment completes. Register a URL, verify the HMAC-SHA256 signature, and never trust an unverified payload.
AgentaOS sends a webhook to your server for every payment event: a checkout completing, an outbound send confirming or failing. Webhooks are how your backend finds out about a payment without polling, and they're the only source of truth you should build on, not the browser redirect after checkout.
**Always verify the signature before you act on a webhook body.** Anyone who knows your webhook URL can POST a fake payload to it. Verification is what proves the request actually came from AgentaOS.
## Register a webhook URL
Go to [app.agentaos.ai](https://app.agentaos.ai) → **Developer** → **Webhooks**.
Enter the HTTPS URL you want events sent to, for example `https://myshop.com/webhooks`.
Click **Reveal signing secret** and copy the `whsec_...` value.
Add it to your server's environment as `AGENTAOS_WEBHOOK_SECRET`. Never commit it, never log it, never send it to the client.
The signing secret is stable, it doesn't change when you update the URL. If you suspect it's been exposed, click **Rotate** to issue a new one. Rotating invalidates the old secret immediately, so deploy the new value before you rotate if you can't afford downtime.
You can also pass a one-off `webhookUrl` when creating a [checkout](/payments/checkouts) or [payment link](/payments/payment-links) to route that specific payment's event to a different endpoint, useful for per-integration or per-customer routing without touching your account-wide URL.
## Verify the signature
Every webhook request carries an `X-AgentaOS-Signature` header. Verifying it proves two things: the payload came from AgentaOS, and it hasn't been replayed from an old request.
```typescript SDK (@agentaos/pay) theme={null}
import { AgentaOS, WebhookVerificationError } from '@agentaos/pay';
import express from 'express';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
const app = express();
// IMPORTANT: use express.raw() here. Verification signs the RAW body.
// If a JSON body parser runs first, the reserialized body won't match
// the signature and every event will fail verification.
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = agentaos.webhooks.verify(
req.body,
req.headers['x-agentaos-signature'] as string,
process.env.AGENTAOS_WEBHOOK_SECRET!,
);
} catch (err) {
if (err instanceof WebhookVerificationError) {
return res.status(400).send('Invalid signature');
}
return res.status(500).send('Webhook processing failed');
}
// Signature is valid: safe to act on event.data now.
switch (event.type) {
case 'checkout.session.completed':
fulfillOrder(event.data.sessionId, event.data.amount);
break;
case 'send.completed':
console.log('Send confirmed:', event.data.txHash);
break;
case 'send.failed':
console.log('Send failed:', event.data.transactionId);
break;
}
res.sendStatus(200);
});
```
`webhooks.verify()` does four things, in order: parses the `t=...,v1=...` header, rejects it if the timestamp is older than 5 minutes (replay protection), recomputes the HMAC-SHA256 digest and compares it in constant time, then parses and returns a typed `WebhookEvent`. Any failure throws `WebhookVerificationError` rather than returning a falsy value, so you can't accidentally skip the check.
### Manual verification (no SDK)
The algorithm is plain HMAC-SHA256 over `{timestamp}.{raw_body}`, so it's straightforward to reimplement in any language that can do an HMAC and a constant-time compare.
```python Python theme={null}
import hmac, hashlib, time
def verify_webhook(body: str, signature: str, secret: str, tolerance_sec: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in signature.split(","))
timestamp = int(parts["t"])
# Reject old signatures (replay protection)
if abs(time.time() - timestamp) > tolerance_sec:
return False
expected = hmac.new(
secret.encode(),
f"{timestamp}.{body}".encode(),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
```
```go Go theme={null}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"strings"
"time"
)
func verifyWebhook(body []byte, signature, secret string, toleranceSec int64) bool {
parts := map[string]string{}
for _, p := range strings.Split(signature, ",") {
if kv := strings.SplitN(p, "=", 2); len(kv) == 2 {
parts[kv[0]] = kv[1]
}
}
timestamp, err := strconv.ParseInt(parts["t"], 10, 64)
if err != nil || parts["v1"] == "" {
return false
}
if age := time.Now().Unix() - timestamp; age > toleranceSec || age < 0 {
return false // expired or in the future
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(fmt.Sprintf("%d.%s", timestamp, body)))
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(parts["v1"]))
}
```
```php PHP theme={null}
$toleranceSec) {
return false; // expired
}
$expected = hash_hmac('sha256', "{$timestamp}.{$body}", $secret);
return hash_equals($expected, $parts['v1']);
}
```
Use a **constant-time comparison** (`hmac.compare_digest`, `hmac.Equal`, `hash_equals`, `crypto.timingSafeEqual`), never `==` or `===`. A naive string comparison leaks timing information an attacker can use to forge a valid signature byte by byte.
### Signature format
```
X-AgentaOS-Signature: t=1710791400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```
| Part | Description |
| ---- | ---------------------------------------------------------------------------------- |
| `t` | Unix timestamp (seconds) when AgentaOS signed the payload |
| `v1` | HMAC-SHA256 hex digest of `{t}.{raw_body}`, keyed with your webhook signing secret |
The `t` prefix exists so the same payload signed twice never produces the same signature, and so a captured request can't be replayed indefinitely, verification rejects anything older than 5 minutes by default.
## Delivery and retries
```mermaid theme={null}
sequenceDiagram
participant P as Payment rail
participant A as AgentaOS
participant S as Your server
P->>A: Payment confirmed
A->>A: Sign payload (HMAC-SHA256)
A->>S: POST your webhook URL (X-AgentaOS-Signature)
alt 2xx response
S-->>A: 200 OK
Note over A: Delivered
else Non-2xx or timeout
A->>A: Wait (exponential backoff)
A->>S: Retry, up to 3 attempts total
end
```
Each delivery attempt gets a 10-second response timeout. If your endpoint doesn't return a `2xx` status in time, AgentaOS retries with exponential backoff, up to 3 attempts total. After the last attempt fails, the event is marked failed and isn't retried further.
Return `200` as soon as you've verified the signature and durably queued the event (a job, a database row). Do the actual work, fulfillment emails, external API calls, asynchronously. A slow handler is the most common reason webhooks fail and retry unnecessarily.
## Events
A checkout was paid, by card, bank transfer, or stablecoin.
An outbound send confirmed on-chain.
An outbound send failed to broadcast.
See the [event reference](/webhooks/events) for the full payload shape of each one.
Every webhook payload's `amount` field is a **string** (e.g. `"49.99"`), not a number. This is different from `amount` on SDK create calls, which is a plain number. Parse it before doing arithmetic.
## Best practices
The `successUrl` redirect is best-effort. The customer might close their browser before it fires. Always treat the webhook, not the redirect, as the source of truth that a payment happened.
Retries mean the same event can arrive more than once. Key your fulfillment logic off `event.data.sessionId` or `event.data.transactionId` and make it safe to process twice.
Verify, queue, return `200`. Do the slow work outside the request.
Never branch on `event.data` before `webhooks.verify()` (or the manual equivalent) has returned successfully.
## Next steps
Every event type, its full payload, and a JSON example.
The `webhooks` resource in `@agentaos/pay`, in full.
Set a per-checkout `webhookUrl` when you create one.
What happens to your balance after a payment lands.
# Payouts
Source: https://docs.agentaos.ai/payouts/overview
How your AgentaOS balance clears, and how payouts reach your bank account or your wallet.
Every payment you accept, card, bank transfer, or stablecoin, settles to one AgentaOS balance. You don't reconcile three settlement accounts for three payment methods. Your balance settles in **EUR or USD**, and from that one balance payouts run automatically to your bank account.
## Your balance
Money doesn't move from "buyer paid" to "in your bank account" instantly. It passes through a few states first, mirroring how card networks and banking rails actually clear funds.
A payment has been confirmed, but it's still clearing. New card and bank sales clear over about 14 days before they're payable, the same way they would through any card acquirer.
Cleared and yours to pay out. This is what moves on your next payout.
A small reserve is held on newer accounts to cover refunds and chargebacks. It releases over time as your payment history builds.
The date your available balance is scheduled to move. Shown per currency on the **Payouts** page.
```mermaid theme={null}
flowchart LR
A[Buyer pays
card, bank, or stablecoin] --> B[Incoming
clearing ~14 days]
B --> C[Available]
C --> D[Payout
bank, EUR or USD]
B -.->|newer accounts| E[In reserve]
E -.->|releases over time| C
```
## Where payouts go
Your balance pays out by bank transfer, same-currency: EUR to a EUR account and USD to a USD account, with no cross-currency FX.
Paid through our banking partner to the countries listed in [Supported countries](/mor/supported-countries), in the currency your balance holds. Cross-border routes carry a Swift fee (see the fee table on that page).
## Schedule
Payouts **run automatically twice a month**. Your available balance moves to your bank account on the scheduled payout date, shown per currency on the **Payouts** page. There's nothing to trigger by hand.
## Fees
Payout fees are a **pass-through, at cost, with no markup**. AgentaOS doesn't add anything on top of what the banking or on-chain rail actually costs to move your money. For bank payouts, cross-border routes carry a Swift fee, see the fee table on [Supported countries](/mor/supported-countries) for the current routes and amounts.
## Getting paid out
Any completed checkout, card, bank, or stablecoin, adds to your balance. See [Checkouts](/payments/checkouts) and [Payment links](/payments/payment-links).
Funds move from **Incoming** to **Available** automatically, over about 14 days for new card and bank sales. No action needed on your end.
Add the bank account where you want to receive your payouts, in EUR or USD.
Your available balance moves to your bank account automatically, twice a month, on the schedule shown on the **Payouts** page.
## FAQ
Your balance settles in EUR or USD. Bank payouts are same-currency, EUR to a EUR account and USD to a USD account, with no cross-currency FX.
Automatically, twice a month. Your available balance moves on the scheduled payout date shown on the **Payouts** page.
Yes. Add the bank account you want to receive payouts to first.
Yes, refunded or disputed amounts are deducted from your balance rather than paid out. On newer accounts a small reserve is held to cover them.
## Legacy
An optional, advanced way to withdraw your balance on-chain to a wallet you control, from anywhere. You're moving your own balance to your own address.
## Next steps
Where bank payouts reach, and the payout fee table.
How a payment turns into money on your balance in the first place.
Create the payments that fund your balance.
Get notified the moment a checkout completes.
# Checkouts
Source: https://docs.agentaos.ai/sdk/pay-checkouts
Create a single payment session with agentaos.checkouts. Standalone or from a payment link template.
A checkout is a single payment attempt: one amount, one buyer, one `checkoutUrl`. Create one from your backend when you know the amount at request time (an e-commerce order, an invoice you're collecting on). For a reusable, shareable URL, use a [payment link](/sdk/pay-payment-links) instead, its `checkouts.create({ linkId })` call is how each visitor gets their own session.
A standalone (linkless) checkout is always a **one-time** payment, there's no `type` field on `checkouts.create()`. To sell a subscription, create a subscription payment link (`type: 'subscription'`) and the buyer subscribes at the hosted checkout, or open a session from that link with `checkouts.create({ linkId })`.
```mermaid theme={null}
stateDiagram-v2
[*] --> open: checkouts.create()
open --> completed: Payment confirmed
open --> expired: expiresIn elapsed
open --> cancelled: checkouts.cancel()
completed --> [*]
expired --> [*]
cancelled --> [*]
```
## `checkouts.create(params)`
```typescript theme={null}
const checkout = await agentaos.checkouts.create({
amount: 100.00,
currency: 'EUR',
description: 'Order #123',
successUrl: 'https://shop.com/success',
cancelUrl: 'https://shop.com/cart',
webhookUrl: 'https://shop.com/webhooks',
});
res.redirect(checkout.checkoutUrl);
```
### Parameters
Amount in currency units, e.g. `49.99`. **Required if `linkId` is omitted.** Min `0.01`, max `1,000,000`.
`'EUR'` or `'USD'`. Defaults to your org's settlement currency.
UUID of an existing payment link to create this session from. The session inherits the link's `amount`, `currency`, `description`, `taxRateId`, `successUrl`, `cancelUrl`, and `webhookUrl`, all overridable per-field. Omit for a standalone checkout.
When creating from a `linkId`, override the link's amount for this session only. Min `0.01`, max `1,000,000`.
Shown on the checkout page. Max 1000 characters.
UUID of a pre-created tax rate.
Redirect target after payment. **HTTPS only**, max 2048 characters.
Target for the checkout page's "cancel" link. **HTTPS only**, max 2048 characters.
Where AgentaOS POSTs the `checkout.session.completed` event. **HTTPS only**, max 2048 characters. See [Webhooks](/sdk/pay-webhooks).
Seconds until the session expires. Range `300`–`86400`.
`YYYY-MM-DD`. Presentation only, stamped onto the invoice issued for this session once payment completes. Never affects the money path.
CAIP-2 network IDs, e.g. `['eip155:8453']`. Defaults to Base mainnet. Only relevant for on-chain (crypto) settlement.
Arbitrary key-value data, round-tripped onto the session and onto the webhook payload. Max 8KB serialized.
### Pre-populate buyer info
Skip the checkout form's fields by pre-filling what you already know:
```typescript theme={null}
const checkout = await agentaos.checkouts.create({
amount: 100.00,
currency: 'EUR',
buyerEmail: 'john@example.com',
buyerName: 'John Doe',
buyerCompany: 'Acme Corp',
buyerCountry: 'DE',
buyerVat: 'DE123456789',
buyerAddress: '123 Main St, Berlin',
});
```
Max 320 characters.
Max 200 characters.
Max 200 characters.
ISO 3166-1 alpha-2, e.g. `'DE'`. Max 2 characters.
Max 500 characters.
Max 20 characters, e.g. `'DE123456789'`.
Company, VAT, and address are optional but recommended, they land directly on the invoice AgentaOS issues for EU VAT compliance. If you skip buyer fields entirely, the checkout page collects name and email itself before payment.
### Response
Session UUID.
Public session ID, embedded in `checkoutUrl`.
Set when created from a `linkId`; `null` for a standalone checkout.
Your organization's UUID.
URL to send your human customer to.
x402 protocol URL for AI-agent payers.
Current session status.
How this session settles. Resolved server-side, see the note above.
The amount for this session (currency units), or `null` if unset.
Settlement currency.
Whatever you passed in `metadata`.
Set once an invoice is issued for this session; `null` until then.
Human-readable invoice number, e.g. `'INV-2026-0001'`.
ISO 8601.
ISO 8601.
ISO 8601.
```json theme={null}
{
"id": "a1b2c3d4-...",
"paymentLinkId": null,
"orgId": "b7e2a1c4-...",
"sessionId": "mZrESFyR7RC9RPsJfZCVkg",
"checkoutUrl": "https://app.agentaos.ai/checkout/mZrESFyR7RC9RPsJfZCVkg",
"x402Url": "https://api.agentaos.ai/gateway/v1/x402/mZrESFyR7RC9RPsJfZCVkg",
"status": "open",
"sellerMode": "mor",
"amountOverride": 100.00,
"currency": "EUR",
"metadata": {},
"successUrl": "https://shop.com/success",
"cancelUrl": "https://shop.com/cart",
"invoiceId": null,
"invoiceNumber": null,
"expiresAt": "2026-08-06T02:30:00.000Z",
"createdAt": "2026-08-06T02:00:00.000Z",
"updatedAt": "2026-08-06T02:00:00.000Z"
}
```
## `checkouts.retrieve(sessionId)`
```typescript theme={null}
const checkout = await agentaos.checkouts.retrieve('mZrESFyR7RC9RPsJfZCVkg');
console.log(checkout.status); // 'open' | 'completed' | 'expired' | 'cancelled'
```
Returns the same shape as `create()`. Poll this as a fallback if your webhook endpoint is ever unreachable, see [Webhooks](/sdk/pay-webhooks).
## `checkouts.list(params?)`
```typescript theme={null}
const page = await agentaos.checkouts.list({
status: 'completed',
limit: 10,
offset: 0,
});
console.log(page.items, page.total, page.hasMore);
```
Filter by status. Omit to list all statuses.
Max 100.
Returns `PaginatedList` (`{ items, total, hasMore }`), see [Pagination](/sdk/pay-overview#pagination).
## `checkouts.cancel(sessionId)`
```typescript theme={null}
await agentaos.checkouts.cancel('mZrESFyR7RC9RPsJfZCVkg');
// → { success: true }
```
Cancelling prevents the customer from paying through that session. If a payment was already in flight (card authorized, on-chain broadcast) when you cancel, it may still complete, cancel is a status change, not a mid-transaction abort.
## Next steps
Reusable templates. Each visit calls `checkouts.create({ linkId })` under the hood.
Get notified server-side the moment a checkout completes.
Every completed checkout issues an invoice you can pull by `invoiceId`.
What `create()` throws on bad params, and how retries work.
# Customers
Source: https://docs.agentaos.ai/sdk/pay-customers
Read the people who have paid you with agentaos.customers.
`agentaos.customers` mirrors the dashboard's Customers list: everyone who has completed a checkout against your org, scoped to the environment (test or live) your API key belongs to. It's read-only. A customer record is created automatically the first time someone pays, there's no `create`, `update`, or `retrieve` by ID, only `list`.
## `customers.list(params?)`
```typescript theme={null}
const page = await agentaos.customers.list({
limit: 20, // default 20, max 100
offset: 0,
});
console.log(page.items.length, page.total, page.hasMore);
```
Max 100.
Returns `PaginatedList`. See [Pagination](/sdk/pay-overview#pagination) for the `{ items, total, hasMore }` shape and how to walk every page.
### Customer fields
Customer UUID.
ISO 3166-1 alpha-2, e.g. `'DE'`.
VAT number on file, if any.
The underlying customer ID from the card processor, set for card and bank (Merchant of Record) payments.
ISO 8601.
```json theme={null}
{
"id": "9c8b7a6d-5e4f-3a2b-1c0d-e9f8a7b6c5d4",
"email": "jane@example.com",
"name": "Jane Doe",
"country": "DE",
"vatNumber": "DE123456789",
"stripeCustomerId": "cus_1H...",
"createdAt": "2026-08-06T02:00:00.000Z"
}
```
`country`, `vatNumber`, and `name` come from whatever the buyer entered at checkout, or from what you pre-populated via `buyerCountry`/`buyerVat`/`buyerName` on [`checkouts.create()`](/sdk/pay-checkouts#pre-populate-buyer-info). They can be `null` if the buyer skipped optional fields.
## Next steps
Pre-populate buyer info so it lands here correctly.
`customerEmail`/`customerName` on a subscription reference this record.
Every invoice carries its own snapshot of buyer details at time of payment.
# Errors
Source: https://docs.agentaos.ai/sdk/pay-errors
The AgentaOSError hierarchy, what triggers each subclass, and exactly how retries and timeouts work.
Every failure from `@agentaos/pay`, a bad request, an auth problem, a network blip, throws a subclass of `AgentaOSError`. Retries for transient failures (`5xx`, network errors, some `429`s) happen automatically inside the SDK before anything is thrown, so if you see an error, it's already survived the retry budget.
## Hierarchy
```typescript theme={null}
class AgentaOSError extends Error {
readonly status: number;
readonly code: string;
readonly requestId?: string;
}
```
Every SDK-thrown error extends `AgentaOSError`, so `err instanceof AgentaOSError` catches all of them, `err.status`, `err.code`, and `err.requestId` (from the response's `x-request-id` header, when present) are always there for logging.
| Class | `status` | `code` | Thrown when |
| -------------------------- | --------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AuthenticationError` | `401` | `authentication_error` | Invalid or expired API key. |
| `PermissionError` | `403` | `permission_error` | The key is valid but can't access this resource. |
| `NotFoundError` | `404` | `not_found` | The resource doesn't exist, or doesn't belong to your org. |
| `ValidationError` | `400` | `validation_error` | Request params failed server-side validation. Carries `errors: Array<{ field, message }>`. |
| `RateLimitError` | `429` | `rate_limit` | Too many requests, exceeded the inline retry budget (see below). Carries `retryAfter` in **milliseconds**. |
| `IdempotencyError` | `409` | `idempotency_error` | Reserved for a duplicate idempotency-key conflict. In practice, create calls replay the original success instead of throwing this, see [Idempotency](#idempotency) below. |
| `ApiError` | the actual `5xx` code | `api_error` | A `5xx` response survived every retry attempt. |
| `TimeoutError` | `0` | `timeout_error` | The request didn't finish within `timeout` ms. Never retried. |
| `WebhookVerificationError` | `0` | `webhook_verification_error` | `webhooks.verify()` rejected a signature. Never comes from an HTTP call. |
| `AgentaOSError` (generic) | varies | `network_error` \| `unknown_error` | A connection failure after retries, or a status code with no dedicated subclass. |
## Catching errors
Catch specific subclasses before the generic `AgentaOSError`, they all extend it, so a broad catch first would shadow the specific ones:
```typescript theme={null}
import {
AgentaOSError,
AuthenticationError,
NotFoundError,
RateLimitError,
ValidationError,
} from '@agentaos/pay';
try {
await agentaos.checkouts.create({ amount: -1 });
} catch (err) {
if (err instanceof ValidationError) {
console.log('Invalid params:', err.errors); // [{ field: 'amount', message: '...' }]
} else if (err instanceof AuthenticationError) {
console.log('Bad API key:', err.message);
} else if (err instanceof RateLimitError) {
console.log('Rate limited, retry after:', err.retryAfter, 'ms');
} else if (err instanceof NotFoundError) {
console.log('Not found:', err.message);
} else if (err instanceof AgentaOSError) {
console.log('API error:', err.status, err.code, err.message);
} else {
throw err; // not an SDK error at all
}
}
```
## Retry behavior
Retries are controlled by the `maxRetries` [client option](/sdk/pay-overview#options) (default `2`), and only apply to `checkouts`, `paymentLinks`, `transactions`, `invoices`, `subscriptions`, and `customers`, `webhooks.verify()` never touches the network.
Retried with exponential backoff: `min(1000 × 2^attempt, 10000)` ms, so `1s`, `2s`, `4s`, `8s`, capped at `10s`. After `maxRetries` is exhausted, throws `ApiError`.
Retried **once inline**, and only if the response's `Retry-After` header is `60` seconds or less and a retry attempt remains. If `Retry-After` exceeds `60s`, or retries are exhausted, it throws `RateLimitError` immediately with `retryAfter` set from the header (in ms, defaulting to `60000` if the header is missing).
Same exponential backoff as `5xx`. After `maxRetries`, throws a generic `AgentaOSError` (`code: 'network_error'`, `status: 0`).
**Never retried.** Each attempt (including retries of other error types) gets its own `timeout`-ms `AbortController`; if it fires, `TimeoutError` is thrown immediately, no backoff, no further attempts.
Because every attempt gets a fresh timeout window, worst-case latency for a single call is roughly `(maxRetries + 1) × timeout`, plus backoff delay between attempts. With the defaults (`timeout: 30000`, `maxRetries: 2`), a call that keeps hitting `5xx` can take up to roughly 90 seconds plus a few seconds of backoff before it finally throws.
## Idempotency
Every `POST` the SDK sends (`checkouts.create()`, `paymentLinks.create()`, `subscriptions.cancel()`, and the rest) automatically carries an `idempotency-key` header, a random UUID generated fresh per call. Retry the same call with the same key and the server does not reject it: it replays the original success and hands back the same resource, including its already-issued invoice, if any, instead of creating a second one. There is no global `409` on a duplicate key.
`subscriptions.cancel()` is a `POST`, so it carries an auto-generated key like any other. You rarely need to think about it: the call is safe to retry because cancelling an already-cancelled subscription is a no-op on the server, not an error. The key is sent, but the operation is naturally idempotent either way.
`GET` reads carry no key, they're naturally idempotent.
## Debugging
Set `debug: true` on the client to log every request and retry to `stderr` (or your own `logger`):
```typescript theme={null}
const agentaos = new AgentaOS('sk_live_...', {
debug: true,
logger: (level, message) => myLogger[level]('agentaos', message),
});
```
Debug logs are sanitized: they never include your API key or request/response bodies, only method, path, status, timing, and retry/backoff notices.
## Next steps
Client options, auth, and the resource map.
`WebhookVerificationError` and signature verification in detail.
# Invoices
Source: https://docs.agentaos.ai/sdk/pay-invoices
Read tax-compliant invoice records with agentaos.invoices. PDFs, statements, CSV exports, and receipts.
Every confirmed payment, checkout or subscription renewal, issues a tax-compliant invoice: buyer details, applied tax rate and amount, exchange rate at settlement, and the merchant record AgentaOS billed as. `agentaos.invoices` reads those records and downloads their documents. There's no `create`, invoices are generated by AgentaOS when a payment confirms.
## `invoices.list(params?)`
```typescript theme={null}
const page = await agentaos.invoices.list({
from: '2026-03-01',
to: '2026-03-31',
status: 'issued',
limit: 50,
});
```
ISO 8601 date, inclusive lower bound.
ISO 8601 date, inclusive upper bound.
Max 5000.
Returns `PaginatedList`.
### Invoice fields
e.g. `'INV-2026-0001'`.
Currency units, in `paymentToken`.
The token the buyer paid with, e.g. `'EURC'`.
On-chain settlement transaction, if applicable.
EUR/USD equivalent, currency units.
`'EUR'` or `'USD'`.
Rate applied at settlement.
ISO 8601.
e.g. `19` for 19%.
Currency units.
e.g. `'DE VAT'`.
ISO 3166-1 alpha-2.
ISO 8601.
ISO 8601.
ISO 8601.
`amount`, `fiatAmount`, and `taxAmount` are plain decimal currency-unit numbers, not minor units. `19.99` means €19.99. See the [money model](/sdk/pay-overview#the-money-model).
## `invoices.retrieve(id)`
```typescript theme={null}
const invoice = await agentaos.invoices.retrieve('uuid');
```
Returns the full `Invoice` shape above for a single record.
## `invoices.void(id)`
```typescript theme={null}
await agentaos.invoices.void('uuid');
// → { success: true }
```
Sets `status` to `'voided'` and stamps `voidedAt`. The record is retained, `retrieve()` still returns it.
## Downloads
Every download method returns raw bytes, not a parsed JSON object. PDFs come back as `Buffer`, the CSV export comes back as `string`.
```typescript downloadPdf(id) theme={null}
const pdf = await agentaos.invoices.downloadPdf('uuid'); // Promise
fs.writeFileSync('invoice.pdf', pdf);
```
```typescript downloadStatement(params) theme={null}
// Monthly statement PDF: balance reconciliation, VAT summary, transaction ledger.
const statement = await agentaos.invoices.downloadStatement({
from: '2026-03-01',
to: '2026-03-31',
}); // Promise
fs.writeFileSync('march-statement.pdf', statement);
```
```typescript exportCsv(params?) theme={null}
const csv = await agentaos.invoices.exportCsv({
from: '2026-03-01',
to: '2026-03-31',
status: 'issued',
}); // Promise
fs.writeFileSync('invoices.csv', csv);
```
```typescript getReceipt(id) theme={null}
// Receipt PDF for a paid invoice. Falls back to the invoice PDF for
// invoices issued before receipts existed.
const receipt = await agentaos.invoices.getReceipt('uuid'); // Promise
fs.writeFileSync('receipt.pdf', receipt);
```
ISO 8601 date. Required for `downloadStatement`, optional for `exportCsv`.
ISO 8601 date. Required for `downloadStatement`, optional for `exportCsv`.
`exportCsv` only, optional.
`exportCsv`'s params are all optional: `{ from?: string; to?: string; status?: string }`. `downloadStatement` requires both `from` and `to`.
## `invoices.sendReceipt(id)`
Re-sends the receipt email to the buyer on file. Paid invoices only.
```typescript theme={null}
const result = await agentaos.invoices.sendReceipt('uuid');
console.log(result.sentTo); // buyer email the receipt was sent to
```
The email address the receipt was sent to.
## Next steps
`invoiceId`/`invoiceNumber` on a completed checkout point back here.
The buyers these invoices are billed to.
What a missing or already-actioned invoice ID throws.
# Overview
Source: https://docs.agentaos.ai/sdk/pay-overview
@agentaos/pay is the server-side TypeScript SDK for AgentaOS. Install, authenticate, and call any resource in a few lines.
`@agentaos/pay` is a server-side TypeScript SDK for the AgentaOS Payment API. AgentaOS is a Merchant of Record: create a checkout or a payment link, and AgentaOS collects the payment (card or wallet), calculates and remits tax, issues the invoice, and settles to your balance. The SDK is a thin, typed wrapper over the REST API, one class per resource, camelCase in, camelCase out.
`@agentaos/pay` is **backend-only**. The constructor throws if it detects `window`/`document`, so it cannot run in a browser bundle. Your API key grants full read/write access to your payments, customers, and invoices, never ship it to client-side code.
## Install
```bash theme={null}
npm install @agentaos/pay
```
Requires **Node.js 20+**. The package ships as **ESM only** (`"type": "module"`) with zero runtime dependencies, its HTTP layer is the built-in `fetch`.
## Initialize
```typescript theme={null}
import { AgentaOS } from '@agentaos/pay';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
const checkout = await agentaos.checkouts.create({
amount: 49.99,
currency: 'EUR',
description: 'Pro Plan: Monthly',
successUrl: 'https://myshop.com/success',
cancelUrl: 'https://myshop.com/cart',
webhookUrl: 'https://myshop.com/webhooks',
});
console.log(checkout.checkoutUrl);
// → https://app.agentaos.ai/checkout/mZrESFyR7RC9RPsJfZCVkg
```
Get a key from the dashboard: **Settings → Developers → API Keys**. See [Test mode and live mode](/getting-started/test-mode) for the difference between a `sk_test_` and `sk_live_` key.
## Authentication
The constructor takes one positional argument, your key, and detects which auth mode to use from its shape. There is no separate flag to set:
| Key looks like | Auth mode | Header sent |
| ------------------------------------ | ------------- | ------------------------------------------- |
| Starts with `sk_live_` or `sk_test_` | API key | `x-api-key: ` |
| Three dot-separated segments (a JWT) | Session token | `Authorization: Bearer ` |
| Anything else | - | Constructor throws `Invalid API key format` |
```typescript theme={null}
// API key: the common case for a backend integration
const agentaos = new AgentaOS('sk_live_51H...');
// JWT: used internally by `agenta login` sessions (CLI), rarely passed directly
const agentaos = new AgentaOS('eyJhbGciOi....header.payload'); // 3 dot-separated segments
```
A JWT-mode client is how the AgentaOS CLI authenticates its own SDK calls after `agenta login`. If you're building a normal backend integration, use an API key.
## Options
The second constructor argument configures transport behavior. Every field is optional.
```typescript theme={null}
const agentaos = new AgentaOS('sk_live_...', {
baseUrl: 'https://api.agentaos.ai',
timeout: 30_000,
maxRetries: 2,
debug: false,
});
```
API origin. Each resource appends its own path (e.g. `/api/v1/gateway/sessions`) to this origin, so set this to override the host, not to add a path prefix. Useful for pointing at a local or staging server.
Per-attempt request timeout in milliseconds. On timeout the call throws [`TimeoutError`](/sdk/pay-errors), which is **not** retried. If `maxRetries` is set, each retry attempt gets its own fresh timeout window, worst-case latency is roughly `(maxRetries + 1) × timeout` plus backoff delay.
Max retries on `5xx` responses and network errors, with exponential backoff (`1s, 2s, 4s...`, capped at `10s`). Set `0` to disable. A `429` is retried inline at most once when the server's `Retry-After` is 60s or less; otherwise it throws immediately. See [Errors → Retry behavior](/sdk/pay-errors#retry-behavior).
Logs each request (`METHOD path -> status (Nms)`) and retry/backoff events to `stderr`. Never logs your API key or request/response bodies.
Custom sink for debug output instead of `stderr`. Only called when `debug: true`.
## Resources
The client exposes one property per resource. All of them share the same auth and retry configuration from the constructor.
| Resource | Access | What it's for |
| ------------------------ | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `agentaos.checkouts` | `create` `list` `retrieve` `cancel` | Single payment sessions. [Docs →](/sdk/pay-checkouts) |
| `agentaos.paymentLinks` | `create` `list` `retrieve` `cancel` | Reusable, shareable payment URLs (one-time or subscription). [Docs →](/sdk/pay-payment-links) |
| `agentaos.subscriptions` | `list` `cancel` | Recurring billing created by buyers at checkout. [Docs →](/sdk/pay-subscriptions) |
| `agentaos.invoices` | `list` `retrieve` `void` `downloadPdf` `downloadStatement` `exportCsv` `getReceipt` `sendReceipt` | Tax-compliant invoice records and their PDFs/CSV. [Docs →](/sdk/pay-invoices) |
| `agentaos.customers` | `list` | Buyers who have paid you. [Docs →](/sdk/pay-customers) |
| `agentaos.transactions` | `list` | Unified ledger of confirmed inbound and outbound payments. |
| `agentaos.webhooks` | `verify` | Local HMAC signature verification, no network call. [Docs →](/sdk/pay-webhooks) |
`transactions.list()` takes `{ direction?: 'all' | 'inbound' | 'outbound', from?, to?, limit?, offset? }` and returns a `PaginatedList`. It has no `create`, transactions are a read-only ledger produced by checkouts, subscription charges, and outbound sends.
## The money model
The SDK is strict about units, and the field **name** tells you which one you're holding:
| Representation | Where it shows up | Example |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| **Decimal currency units** (`number`) | `amount` on every create call, `PaymentLink.amount`, `Checkout.amountOverride`, `Invoice.amount` / `fiatAmount` / `taxAmount`, `Transaction.amount` | `amount: 49.99` means €49.99 |
| **Integer minor units** (`number`) | `Subscription.unitAmountMinor`, the only field with a `Minor` suffix | `unitAmountMinor: 1999` means €19.99 |
| **String** | `amount` inside every webhook event payload | `"49.99"` |
Never pass `amount: 4900` to `checkouts.create()` or `paymentLinks.create()` expecting cents, that call means **4,900 units** of the currency. A plain `amount` field is always decimal currency units. Only a field whose name ends in `Minor` is an integer of the smallest unit, and today that's exactly one field: `Subscription.unitAmountMinor`.
## Pagination
Every `list()` method returns the same shape:
```typescript theme={null}
interface PaginatedList {
items: T[];
total: number; // total matching records, not just this page
hasMore: boolean; // true if offset + items.length < total
}
```
```typescript theme={null}
const page = await agentaos.customers.list({ limit: 20, offset: 0 });
console.log(page.items.length, page.total, page.hasMore);
// Walk every page
let offset = 0;
const all = [];
while (true) {
const page = await agentaos.customers.list({ limit: 100, offset });
all.push(...page.items);
if (!page.hasMore) break;
offset += page.items.length;
}
```
Page size. Capped server-side at **100** for `checkouts`, `paymentLinks`, `subscriptions`, and `customers`; capped at **5000** for `transactions` and `invoices`.
Number of records to skip, for the next page pass `offset + items.length` from the previous response.
## Field casing
The REST API is snake\_case (`checkout_url`); the SDK is camelCase (`checkoutUrl`) both ways: request bodies you pass in are already camelCase and go over the wire as-is (server DTOs accept camelCase), and every response body is deep-transformed from the server's snake\_case back to camelCase before it reaches your code, including nested objects and webhook payloads.
## Error handling
Every failed call throws a subclass of `AgentaOSError` (itself an `Error`) with a typed `status`, `code`, and optional `requestId`:
```typescript theme={null}
import { AgentaOSError, ValidationError, RateLimitError } from '@agentaos/pay';
try {
await agentaos.checkouts.create({ amount: -1 });
} catch (err) {
if (err instanceof ValidationError) {
console.log(err.errors); // [{ field: 'amount', message: '...' }]
} else if (err instanceof RateLimitError) {
console.log('retry after', err.retryAfter, 'ms');
} else if (err instanceof AgentaOSError) {
console.log(err.status, err.code, err.requestId);
}
}
```
See [Errors](/sdk/pay-errors) for the full hierarchy and the exact retry/backoff behavior.
## Next steps
Create, list, retrieve, and cancel single payment sessions.
Reusable one-time and subscription payment URLs.
Verify signed event payloads server-side.
The full error hierarchy and retry/timeout behavior.
# Payment Links
Source: https://docs.agentaos.ai/sdk/pay-payment-links
Reusable, shareable payment URLs with agentaos.paymentLinks. One-time or recurring subscriptions.
A payment link is a reusable template: create it once, share the `checkoutUrl`, and every visitor gets their own [checkout](/sdk/pay-checkouts) session at that link's amount and settings. Use a link for anything you'd otherwise re-create per customer, a pricing page button, an emailed invoice, a subscription plan.
## `paymentLinks.create(params)`
```typescript theme={null}
const link = await agentaos.paymentLinks.create({
amount: 29.99,
currency: 'EUR',
description: 'Pro plan',
successUrl: 'https://shop.com/success',
cancelUrl: 'https://shop.com/cancel',
webhookUrl: 'https://shop.com/webhooks',
});
console.log(link.checkoutUrl);
// → https://app.agentaos.ai/pay/7rr6S9ml4BMp829wV5WeAA
```
### Parameters
Amount in currency units, e.g. `29.99`. Min `0.01`, max `1,000,000`.
`'EUR'` or `'USD'`. Defaults to your org's settlement currency.
Max 1000 characters.
See [One-time vs subscription](#one-time-vs-subscription) below.
**Required when `type: 'subscription'`, omit otherwise.**
UUID of a pre-created tax rate.
HTTPS only, max 2048 characters.
HTTPS only, max 2048 characters.
HTTPS only, max 2048 characters. Inherited by checkouts created from this link.
ISO 8601. After this instant, new checkouts can't be created from the link.
Max 8KB serialized. Carried onto every checkout created from this link.
Extra fields to collect on the checkout page. Each entry:
Max 64 characters.
Max 128 characters.
Max 256 characters.
For `type: 'select'` only.
```typescript theme={null}
const link = await agentaos.paymentLinks.create({
amount: 29.99,
currency: 'EUR',
description: 'Pro plan',
checkoutFields: [
{ key: 'email', label: 'Work email', type: 'email', required: true },
{ key: 'company', label: 'Company', type: 'text', required: false },
],
});
```
### One-time vs subscription
```typescript theme={null}
const link = await agentaos.paymentLinks.create({
amount: 29.99,
currency: 'EUR',
description: 'Pro plan: lifetime',
// type defaults to 'one_time'
});
```
Each checkout is a single payment. This is the default, `type` and `billingInterval` don't need to be set.
```typescript theme={null}
const link = await agentaos.paymentLinks.create({
amount: 29.99,
currency: 'EUR',
description: 'Pro plan: monthly',
type: 'subscription',
billingInterval: 'month', // 'month' | 'year', required
});
```
A buyer paying this link creates a [`Subscription`](/sdk/pay-subscriptions) that bills every `billingInterval` going forward. Subscriptions bill through Merchant of Record (card and bank), so this requires a verified account; wallet-only, on-chain accounts can't create subscription links.
### Response
Link UUID.
Currency units.
How this link settles.
`null` for one-time links.
Shareable payment URL.
Times this link has been paid.
```json theme={null}
{
"id": "7rr6S9ml-...",
"orgId": "org_...",
"amount": 29.99,
"currency": "EUR",
"description": "Pro plan",
"status": "active",
"sellerMode": "mor",
"type": "one_time",
"billingInterval": null,
"checkoutUrl": "https://app.agentaos.ai/pay/7rr6S9ml4BMp829wV5WeAA",
"metadata": {},
"checkoutFields": [],
"webhookUrl": null,
"successUrl": null,
"cancelUrl": null,
"taxRateId": null,
"paymentCount": 0,
"expiresAt": null,
"createdAt": "2026-08-06T02:00:00.000Z",
"updatedAt": "2026-08-06T02:00:00.000Z"
}
```
## `paymentLinks.retrieve(id)`
```typescript theme={null}
const link = await agentaos.paymentLinks.retrieve('7rr6S9ml-...');
console.log(link.paymentCount); // how many times it's been paid
```
## `paymentLinks.list(params?)`
```typescript theme={null}
const page = await agentaos.paymentLinks.list({ limit: 20, offset: 0 });
```
Takes plain `ListParams` (`limit`, `offset`), no status filter. Returns `PaginatedList`, max `limit` is 100. See [Pagination](/sdk/pay-overview#pagination).
## `paymentLinks.cancel(id)`
```typescript theme={null}
await agentaos.paymentLinks.cancel('7rr6S9ml-...');
// → { success: true }
```
This is a soft cancel, the link's `status` becomes `'cancelled'` and it stops accepting new checkouts, but the record and its payment history stay retrievable via `retrieve()`. It does not affect subscriptions already created from the link.
## Next steps
How `checkouts.create({ linkId })` inherits a link's settings.
Managing subscriptions created from a `type: 'subscription'` link.
The buyers who paid your links.
`checkout.session.completed` fires for every paid link visit.
# Subscriptions
Source: https://docs.agentaos.ai/sdk/pay-subscriptions
Read and manage recurring billing with agentaos.subscriptions. List and cancel, list and cancel only, by design.
`agentaos.subscriptions` is a read/manage surface, not a creation API. A subscription comes into existence when a buyer pays a [payment link](/sdk/pay-payment-links) created with `type: 'subscription'`, AgentaOS bills them every `billingInterval` from then on through Merchant of Record (card or bank). There is no `subscriptions.create()`: to start recurring billing, create a subscription payment link and let the buyer check out.
```typescript theme={null}
// This is how a subscription is born, on the hosted checkout, not the SDK:
const link = await agentaos.paymentLinks.create({
amount: 29.99,
currency: 'EUR',
description: 'Pro plan: monthly',
type: 'subscription',
billingInterval: 'month',
});
// → share link.checkoutUrl. Every buyer who pays it gets their own Subscription.
```
## `subscriptions.list(params?)`
Mirrors the dashboard's Subscriptions list, scoped to the environment (test or live) your API key belongs to.
```typescript theme={null}
const page = await agentaos.subscriptions.list({
limit: 20, // default 20, max 100
offset: 0,
});
console.log(page.total, page.hasMore);
```
Max 100.
Returns `PaginatedList`.
### Subscription fields
Subscription UUID.
The plan's name/description, taken from the subscription payment link.
See statuses below.
Per-cycle amount in **integer minor units**. `1999` means €19.99. This is the one field in the entire SDK named `*Minor`, see the [money model](/sdk/pay-overview#the-money-model).
ISO 8601. `null` before the first billing cycle has booked.
The underlying subscription ID from the card processor.
```json theme={null}
{
"id": "b7e2a1c4-...",
"customerEmail": "jane@example.com",
"customerName": "Jane Doe",
"planName": "Pro plan: monthly",
"billingInterval": "month",
"status": "active",
"unitAmountMinor": 1999,
"currency": "EUR",
"currentPeriodEnd": "2026-09-06T00:00:00.000Z",
"stripeSubscriptionId": "sub_1H..."
}
```
### Statuses
`SubscriptionStatus` is the raw subscription status from the card processor, mirrored onto the local record by AgentaOS's polling reconciliation (there are no inbound webhooks from the card processor in this system, statuses update on the next poll cycle):
| Status | Meaning |
| -------------------- | ------------------------------------------------------------------------------ |
| `incomplete` | First payment attempt hasn't succeeded yet. |
| `incomplete_expired` | First payment never completed within the window; subscription never activated. |
| `trialing` | In a trial period, no charge yet. |
| `active` | Billing normally. |
| `past_due` | A renewal charge failed; the card processor is retrying. |
| `canceled` | Ended, no further charges. |
| `unpaid` | Renewal retries exhausted without a successful charge. |
| `paused` | Billing paused (e.g. a trial-without-payment-method configuration ending). |
## `subscriptions.cancel(id, params?)`
Defaults to cancel-at-period-end: the subscriber keeps access through the period they already paid for, no refund. Pass `{ atPeriodEnd: false }` to cancel immediately instead. Calling it again on an already-canceled subscription is a no-op, not an error.
```typescript theme={null}
// Cancel at period end (default): subscriber keeps access until currentPeriodEnd
await agentaos.subscriptions.cancel('sub_...');
// Cancel immediately: access revoked now, no refund
await agentaos.subscriptions.cancel('sub_...', { atPeriodEnd: false });
```
`true` schedules cancellation for `currentPeriodEnd`. `false` cancels now.
### Response
Status after the cancellation call.
ISO 8601.
Whether it's scheduled to cancel at period end vs. already canceled.
ISO 8601 date the cancellation takes (or took) effect.
```json theme={null}
{
"status": "active",
"currentPeriodEnd": "2026-09-06T00:00:00.000Z",
"cancelAtPeriodEnd": true,
"effectiveCancelDate": "2026-09-06"
}
```
## Next steps
Create the `type: 'subscription'` link that gives birth to a subscription.
The people paying your subscriptions.
Each renewal charge issues its own invoice.
What `cancel()` throws on an unknown subscription ID.
# Webhooks
Source: https://docs.agentaos.ai/sdk/pay-webhooks
Verify signed webhook payloads locally with agentaos.webhooks.verify(). HMAC-SHA256, timing-safe.
`agentaos.webhooks` makes no network call, `verify()` runs entirely in your process. It checks a payload's HMAC-SHA256 signature against your webhook secret, rejects stale or malformed signatures, and returns the parsed, camelCased event. Configure the destination URL and copy the signing secret from **Settings → Developers → Webhooks** in the dashboard (or set `webhookUrl` per checkout/link, see [Checkouts](/sdk/pay-checkouts)).
## `webhooks.verify(payload, signature, secret, toleranceSec?)`
```typescript theme={null}
import express from 'express';
import { AgentaOS, WebhookVerificationError } from '@agentaos/pay';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
// IMPORTANT: express.raw(), not express.json(). Verification needs the
// exact bytes that were signed. Parsing JSON first breaks the signature.
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = agentaos.webhooks.verify(
req.body, // raw body: string | Buffer
req.headers['x-agentaos-signature'] as string, // signature header
process.env.AGENTAOS_WEBHOOK_SECRET!, // whsec_... from the dashboard
);
} catch (err) {
if (err instanceof WebhookVerificationError) {
return res.status(400).send('Invalid signature');
}
throw err;
}
switch (event.type) {
case 'checkout.session.completed':
console.log('Paid:', event.data.amount, event.data.currency);
break;
case 'send.completed':
console.log('Sent:', event.data.amount, event.data.token);
break;
case 'send.failed':
console.log('Send failed:', event.data.transactionId);
break;
}
res.sendStatus(200);
});
```
The raw, unparsed request body. If it's a `Buffer`, it's decoded as UTF-8 before verifying.
The `x-agentaos-signature` header value.
Your webhook signing secret (`whsec_...`).
Max age of the signature's timestamp, in seconds. A signature older than this (or with a timestamp in the future) throws `WebhookVerificationError`.
Returns a typed `WebhookEvent`, throws [`WebhookVerificationError`](/sdk/pay-errors) on any failure.
### Signature format
The header is `t=,v1=`. `verify()`:
1. Parses `t` and `v1` out of the header, throws if either is missing.
2. Checks `now - t` is between `0` and `toleranceSec` seconds, throws `Webhook signature expired` if not (this rejects both stale replays and clock-skewed future timestamps).
3. Recomputes `HMAC-SHA256(secret, "${t}.${payload}")` and compares it to `v1` with `crypto.timingSafeEqual` (constant-time, no early-exit on mismatch).
4. `JSON.parse`s the payload and deep-transforms it from snake\_case to camelCase before returning it as a `WebhookEvent`.
Every failure at any step throws `WebhookVerificationError`, there's no partial or "unverified" event returned.
## `WebhookEvent`
A discriminated union on `type`. `switch`/narrow on it to get typed `data`.
Unique event ID, `evt_`. Present on every event, both on the wire and on the object `verify()` returns.
Inside every event's `data`, **`amount` is a string**, e.g. `"49.99"`, not the `number` you get back from `checkouts.create()` or `paymentLinks.create()`. Parse it before doing math. See the [money model](/sdk/pay-overview#the-money-model).
### `checkout.session.completed`
Fires when a checkout session's payment confirms.
The payment link's secure ID.
e.g. `"49.99"`.
On-chain settlement hash.
Payer wallet address.
CAIP-2 network ID.
Whatever `metadata` you passed at `checkouts.create()`.
### `send.completed`
Fires when an outbound send (a payout or transfer you initiated) is broadcast successfully.
### `send.failed`
Same shape as `send.completed`, but `txHash` is always `null` and no broadcast succeeded.
`successUrl` is best-effort, the customer might close their browser before the redirect lands. Fulfill orders from the webhook (server-to-server, reliable), not the redirect.
Treat delivery as at-least-once. Use `event.data.sessionId` (or `transactionId`) to check whether you've already processed it before fulfilling again.
If your endpoint is ever down, poll [`checkouts.retrieve()`](/sdk/pay-checkouts) for `status === 'completed'` as a backstop. Don't build your primary flow on polling, it's slower and burns rate limit budget.
## Next steps
Set `webhookUrl` to receive `checkout.session.completed`.
`WebhookVerificationError` and the rest of the error hierarchy.
# x402 Payments
Source: https://docs.agentaos.ai/sub/x402
Pay for 402-protected resources from your sub-account.
x402 is the pay-per-request standard for APIs that answer with HTTP 402 Payment Required. From your sub-account, an agent can probe those endpoints, see exactly what each one charges, settle the payment inline, and read the response. Use these commands when a resource you want to fetch is gated behind x402 and you want the payment handled for you.
AI agents can discover, check, and pay for x402-protected APIs.
## Check
```bash theme={null}
agenta sub x402 check
```
Returns `requires402`, payment options (scheme, network, amount, asset).
## Discover
```bash theme={null}
agenta sub x402 discover
```
Probes a domain for all x402 endpoints.
## Fetch & Pay
```bash theme={null}
agenta sub x402 fetch
agenta sub x402 fetch --max-amount 1000000
```
Automatically pays and returns the response. `--max-amount` caps spending in atomic units (1000000 = 1 USDC).
Returns: `paid`, `transaction`, `status`, `body`.
## Next steps
The same x402 discover, check, and fetch flow as MCP tools for your AI assistant.
Install `agenta` and sign in to your sub-account.
# Events
Source: https://docs.agentaos.ai/webhooks/events
Every webhook event AgentaOS sends: full payload fields, types, and a JSON example for each.
AgentaOS sends three event types today. Every event shares the same envelope, an `id`, a `type`, and a `data` object whose shape depends on the type. See [Webhooks](/payments/webhooks) for how to register a URL and verify the signature before trusting any of this.
Every `amount` field below is a **string** (e.g. `"49.99"`), not a number. This differs from `amount` on SDK create calls (checkouts, payment links), which is a plain number in currency units. Parse it before doing arithmetic.
The field names below are the literal snake\_case keys AgentaOS `POST`s to your `webhookUrl`, what you parse in any language. As returned by the SDK `webhooks.verify()` (Node.js) the same fields come back camelCased and typed: `link_id` becomes `linkId`, `session_id` becomes `sessionId`, `tx_hash` becomes `txHash`, `payer_type` becomes `payerType`, `chain_id` becomes `chainId`, `transaction_id` becomes `transactionId`, and so on. This page mirrors the raw body documented in [Webhooks](/api-reference/webhooks).
Unique event ID, e.g. `evt_550e8400-e29b-41d4-a716-446655440000`. Always present. For `checkout.session.completed` it is independent of `data.session_id`. For `send.completed` and `send.failed` it is derived as `evt_${data.transaction_id}`, so a redelivered send always carries the same event id. Either way, use `id` to deduplicate the *delivery*, and the data-level ID (`session_id` or `transaction_id`) to deduplicate the *underlying payment or send*.
One of `checkout.session.completed`, `send.completed`, `send.failed`. Switch on this to decide how to parse `data`.
Event-specific payload. See each event below.
***
## `checkout.session.completed`
Fired when a checkout is paid, by card, wallet, or stablecoin. This is the event to listen for to fulfill an order.
The payment link's `secure_link_id`. `null` for a standalone checkout created without a link.
The checkout's public session ID. The stable identifier to key your fulfillment and idempotency logic off.
Amount paid, in currency units, as a string. e.g. `"49.99"`.
Settlement currency, e.g. `"EUR"`.
How the buyer actually paid: `card`, `sepa`, `bank_transfer`, `bridge`, or `wallet`.
On-chain transaction hash for stablecoin checkouts. `null` for card/bank rails, use `vendor_reference` instead.
Off-chain audit reference (card-processor payment reference, bank reference). `null` for on-chain rails.
The paying wallet address for stablecoin checkouts. `null` for card/bank rails.
`human` or `agent`, whether a person or an autonomous agent (e.g. an x402 client) initiated the payment.
CAIP-2 network ID for on-chain rails, e.g. `"eip155:8453"` for Base, or `stripe` for card/bank.
`true` on a test-mode checkout, `false` in live.
Set when a bank-transfer buyer sent more than the amount due (beyond a 1-cent tolerance). `null` otherwise.
The custom key-value data you attached when creating the checkout or payment link.
`tx_hash`, `payer`, and `network` describe on-chain settlement. For checkouts paid by card, these fields are empty, use `vendor_reference` for the off-chain reference and don't assume the on-chain fields are always populated. Key fulfillment off `session_id` and `amount`/`currency`, which are present for every payment method.
```json Example theme={null}
{
"id": "evt_8c7d6e5f-4a3b-2c1d-0e9f-8a7b6c5d4e3f",
"type": "checkout.session.completed",
"data": {
"link_id": "mZrESFyR7RC9RPsJfZCVkg",
"session_id": "kR9pQwErTyUiOpAsDfGh",
"amount": "49.99",
"currency": "EUR",
"rail": "card",
"tx_hash": null,
"vendor_reference": "pi_3P...",
"payer": null,
"payer_type": "human",
"network": "stripe",
"testnet": false,
"overpaid_by_cents": null,
"metadata": { "orderId": "order-123" }
}
}
```
## `send.completed`
Fired when an outbound stablecoin send you initiated confirms on-chain.
Internal transaction ID. Use this to deduplicate retried deliveries.
On-chain transaction hash of the confirmed send.
Sending wallet address (your organization's wallet).
Recipient wallet address.
Amount sent, in token units, as a string. e.g. `"49.99"`.
Token symbol, e.g. `"USDC"`.
EVM chain ID, e.g. `8453` for Base.
CAIP-2 network ID, e.g. `"eip155:8453"`.
`true` on a test-mode send, `false` in live.
Optional description you attached to the send. `null` if none was set.
```json Example theme={null}
{
"id": "evt_9d8e7f6a-5b4c-3d2e-1f0a-9b8c7d6e5f4a",
"type": "send.completed",
"data": {
"transaction_id": "9d8e7f6a-5b4c-3d2e-1f0a-9b8c7d6e5f4a",
"tx_hash": "0xabc123...",
"from": "0xorgwallet...",
"to": "0xrecipient...",
"amount": "100.00",
"token": "USDC",
"chain_id": 8453,
"network": "eip155:8453",
"testnet": false,
"description": "Payout to contractor"
}
}
```
## `send.failed`
Fired when an outbound send fails to broadcast, network error, insufficient balance, or the transaction reverted.
Internal transaction ID. Use this to deduplicate retried deliveries.
Always `null`, the send never confirmed on-chain.
Sending wallet address (your organization's wallet).
Intended recipient wallet address.
Amount that was attempted, in token units, as a string.
Token symbol, e.g. `"USDC"`.
EVM chain ID, e.g. `8453` for Base.
CAIP-2 network ID, e.g. `"eip155:8453"`.
`true` on a test-mode send, `false` in live.
Optional description you attached to the send. `null` if none was set.
```json Example theme={null}
{
"id": "evt_8fae0d2b-7d54-4b3e-9a41-2b3f9c1e0aa2",
"type": "send.failed",
"data": {
"transaction_id": "8fae0d2b-7d54-4b3e-9a41-2b3f9c1e0aa2",
"tx_hash": null,
"from": "0xorgwallet...",
"to": "0xrecipient...",
"amount": "100.00",
"token": "USDC",
"chain_id": 8453,
"network": "eip155:8453",
"testnet": false,
"description": null
}
}
```
`send.failed` means the transaction never landed on-chain. It's distinct from a payment dispute or a card decline, those don't go through this event, they affect your balance directly. See [Payouts](/payouts/overview).
## Next steps
How to register a URL and verify the HMAC-SHA256 signature, in TypeScript, Python, Go, and PHP.
The `webhooks` resource in `@agentaos/pay`.