# Build with AI agents
Source: https://docs.agentaos.ai/agents/overview
Give your AI agent the AgentaOS skill file. The agent installs the CLI, creates a product, and returns its buyer link.
Give your AI agent the AgentaOS skill file, and the agent does the integration for you.
```text theme={"system"}
Read https://agentaos.ai/SKILL.md and set up AgentaOS for me:
install the CLI, log me in, create a test product, and give me the checkout link.
```
It reads [`SKILL.md`](https://agentaos.ai/SKILL.md) (the exact commands, flags, and response shapes), installs the `agenta` CLI, signs you in, creates a product in test mode, and returns the product's buyer link. You approve the browser sign-in once; everything after that is non-interactive.
## Connect the MCP server
In Claude Code, Cursor, ChatGPT or Claude.ai, connect the AgentaOS MCP server instead. You approve it once in the browser and paste no key. Your agent can then create products and checkouts, submit your verification, and manage subscriptions, discount codes, customers and invoices. [MCP server](/mcp/setup) has the setup for each client and the full list of tools.
## Feed the docs to your model
The whole documentation is available as plain Markdown for LLMs. Point your model at it or paste it into context:
A compact index of every page, for grounding a model.
The entire docs as one Markdown file.
## Next steps
The full walkthrough: coding agent with the CLI, or a chat agent with MCP.
Setup for each client, and every tool your agent can use.
Prefer to do it by hand first? Create a product and get paid.
# Add a bank account
Source: https://docs.agentaos.ai/api-reference/bank-accounts/create
POST /gateway/bank-accounts
Save a bank account for the business. Where the bank offers a name check, it runs at once: a clear mismatch is saved but never paid, and `payableReason` says so. Needs a live key. For a platform acting on a business it manages, bank accounts by API must have been opened for your platform; ask us. Every account added through a platform's key is emailed to the platform's owner and to the business's owner. The account number is never stored: we keep the last four characters and a hash.
Needs a live key. For a platform acting on a business it manages, ask us to open bank accounts by API for your platform first; until then the call gets `403 bank_accounts_by_api_closed`. Where the bank offers a name check, it runs at once: a clear mismatch is saved but never paid, and `payableReason` says so; `partial` and `unknown` are paid. Every account added through a platform's key is emailed to the platform's owner and to the business's owner. We keep the last four digits and a hash, never the number.
The flow end to end, with the SDK.
Managing businesses, start to finish.
# Retire a bank account
Source: https://docs.agentaos.ai/api-reference/bank-accounts/deactivate
DELETE /gateway/bank-accounts/{id}
The account stops being paid to. A currency that pointed at it falls back to the newest payable account. Needs a live key; the same platform rule as adding one.
The account stops being paid to. A currency that pointed at it falls back to the newest usable account. Needs a live key, and the same platform rule as adding one.
The flow end to end, with the SDK.
Managing businesses, start to finish.
# List bank accounts
Source: https://docs.agentaos.ai/api-reference/bank-accounts/list
GET /gateway/bank-accounts
The active bank accounts of the business, thin references only, each with whether the next payout can go to it.
Thin references only, each with whether the next payout can go to it and, when not, why.
The flow end to end, with the SDK.
Managing businesses, start to finish.
# Refresh the fields after an answer
Source: https://docs.agentaos.ai/api-reference/bank-accounts/refresh-requirements
POST /gateway/bank-accounts/requirements/refresh
Some answers change the fields that follow (a field marked `refreshRequirementsOnChange`, such as the legal type). Send the `quoteId` and the answers so far; the fields come back updated.
Some answers change the fields that follow, such as the legal type. Send the `quoteId` from the first call and the answers so far.
The flow end to end, with the SDK.
Managing businesses, start to finish.
# Get the fields a currency needs
Source: https://docs.agentaos.ai/api-reference/bank-accounts/requirements
GET /gateway/bank-accounts/requirements
The form schema for a bank account in this currency. Render it, then send the answers to `POST /gateway/bank-accounts`. Works with a test or a live key.
The fields depend on the currency: an IBAN for EUR, a routing and an account number for USD. Render the schema, then send the answers to add the account. When a field is marked `refreshRequirementsOnChange`, send the answers so far to the refresh call and render the fields again.
The flow end to end, with the SDK.
Managing businesses, start to finish.
# Create a business
Source: https://docs.agentaos.ai/api-reference/businesses/create
POST /gateway/businesses
Add a business you manage. It starts in test mode and goes live after its own verification. With `clientEmail`, your client is invited as its admin, and the invitation expires after 7 days. We email the invitation, or with `sendInvitationEmail: false` we send nothing and you send `inviteUrl` yourself (white-label).
The business starts in test mode and goes live after its own verification. With `clientEmail`, we email your client an invitation. The invitation expires after 7 days. With `sendInvitationEmail: false` we send nothing: send `inviteUrl` yourself, under your own brand.
**Webhook:** none. `account.updated` fires later, when the business's verification changes. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
Store the returned `business.id` on your side, next to your record of the client. There is no metadata on a business: the id is the link between the two.
End to end.
Set your share in the dashboard.
# List businesses
Source: https://docs.agentaos.ai/api-reference/businesses/list
GET /gateway/businesses
Every business you manage, with its status and your share. Not paginated: returns a plain array. The platform is always the caller's own account; an owner or admin of it.
Every field on this resource is camelCase on the wire. See [Naming convention](/api-reference/introduction#naming-convention).
Returns a plain array, not the paginated envelope. These are the businesses **you** manage as a platform. To act as one of them on any other endpoint, send its `id` in the [`AgentaOS-Account` header](/api-reference/introduction#acting-for-a-business).
What a platform is, and who does what.
The same call from TypeScript.
# Resend the invitation
Source: https://docs.agentaos.ai/api-reference/businesses/resend-invitation
POST /gateway/businesses/{id}/invitation
Send your client's invitation again, to the person last invited. It is a new link that expires after 7 days: the old one stops working. Refused with `409 client_joined` once your client has joined the business.
Each call makes a new link that expires after 7 days; the previous one stops working. Once your client has joined the business, its invitations are theirs to manage and this returns `409 client_joined`.
**Webhook:** none. An invitation changes no verification state. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# Retrieve a business
Source: https://docs.agentaos.ai/api-reference/businesses/retrieve
GET /gateway/businesses/{id}
One business you manage, the same shape as a row of the list. A business you do not manage and an id that does not exist get the same `403`, so the response never confirms an id.
The same shape as a row of [List businesses](/api-reference/businesses/list). Read `status` and `identityVerified` to see where the business's verification stands, or listen for [`account.updated`](/connect/webhooks).
# Revoke the invitation
Source: https://docs.agentaos.ai/api-reference/businesses/revoke-invitation
DELETE /gateway/businesses/{id}/invitation
Withdraw your client's open invitation. Its link stops working. Refused with `409 client_joined` once your client has joined the business.
The open invitation's link stops working. Returns `204` with no body.
**Webhook:** none. An invitation changes no verification state. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# Create a verification link
Source: https://docs.agentaos.ai/api-reference/businesses/verification-link
POST /gateway/businesses/{id}/verification-link
The identity check link for your client. You send it; only your client can complete it. Needs a live key: a test key gets `403 live_key_required`. While a check is open, calling again returns the same check. `url` is `null` when the client is already verified. For another app of your own company it returns `409 identity_from_company`: its identity check is your company's.
Needs a live key (`sk_live_...`): the identity check is real. A test key gets `403 live_key_required`.
You send the link; only your client can complete the identity check. `url` is `null` when they are already verified. Another app of your own company has no identity check of its own: `409 identity_from_company`.
**Webhook:** none. `account.updated` fires when your client completes the identity check and AgentaOS reads the result. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# 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.
**Webhook:** none. An unpaid checkout has no payment to report. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# 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.
**Webhook:** none. `checkout.session.completed` fires later, when AgentaOS confirms the buyer's payment. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
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={"system"}
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.
# Stop a discount code
Source: https://docs.agentaos.ai/api-reference/discount-codes/archive
POST /gateway/discount-codes/{id}/archive
The code stops working immediately. Subscribers who already redeemed it keep the discount they were given.
**Webhook:** none. Stopping a code changes no payment or subscription. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# Create a discount code
Source: https://docs.agentaos.ai/api-reference/discount-codes/create
POST /gateway/discount-codes
Set at most one of `percentOff` and `amountOffMinor`. Set neither and the code carries no discount at all: the buyer pays full price and you still see who it brought in. A code with no discount cannot carry a `name`, because the code itself is its name. Terms are fixed once created: to change them, archive this code and create another.
**Webhook:** none. A discount code moves no money until a buyer uses it at checkout. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# List discount codes
Source: https://docs.agentaos.ai/api-reference/discount-codes/list
GET /gateway/discount-codes
Paginated, archived codes included. What each code takes off, and how many people used it, is on the single-code route.
# Retrieve a discount code
Source: https://docs.agentaos.ai/api-reference/discount-codes/retrieve
GET /gateway/discount-codes/{id}
The only route that reports what a code takes off and how many subscribers used it.
# 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, which error bodies repeat as `requestId`. 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={"system"}
{
"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. |
| `code` | string | On some errors only: a stable, machine-readable code such as `key_no_longer_valid`. Branch on it when it is present. |
## Status codes
| Status | Meaning | When it occurs |
| - | - | - |
| `400` | Bad request | Missing or invalid parameters. See the `errors` array. |
| `401` | Unauthorized | Missing or invalid API key, or a key whose creator no longer administers the business (`key_no_longer_valid`). |
| `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 | The request conflicts with the record's current state: another request changed it, a product reached its purchase limit, or a state code such as `client_joined` applies. For a concurrent change, read the record again, then retry the write. A repeated idempotency key never returns `409`. |
| `429` | Rate limited | Too many requests from your IP address to one endpoint. Honor the `Retry-After` header. See [Rate limits](/api-reference/introduction#rate-limits). |
| `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={"system"}
{
"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 live mode.
### 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. The `Retry-After` header tells you how many seconds to wait.
## Handling errors with the SDK
The [TypeScript SDK](/sdk/pay-errors) maps every error response to a typed `AgentaOSError` subclass with `status`, `code`, `message`, `requestId`, and (on validation) `errors`. A `409` becomes `IdempotencyError`, whatever its cause. The SDK retries a `429`, a `5xx` and a network error for you.
```typescript theme={"system"}
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 live mode. See [Test mode](/getting-started/test-mode).
## Support
Include the `requestId` (or the `x-request-id` header) from the failed response when you contact support. It lets us find the exact request.
# Introduction
Source: https://docs.agentaos.ai/api-reference/introduction
Base URL, authentication, test and live keys, request format, pagination, errors, rate limits, idempotency, and versioning 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
```text theme={"system"}
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={"system"}
curl https://api.agentaos.ai/api/v1/gateway/payment-links \
-H "x-api-key: sk_test_..."
```
Your secret key. In the dashboard, open **Developers → API keys** in the sidebar and click **Generate Test Key** (in test mode) or **Generate Live Key** (in live mode).
A missing or invalid key returns `401 Unauthorized`, with the message `Missing or invalid authentication`. Server-to-server calls always send the key in `x-api-key`; API keys have no bearer-token form. A key stops working when the person who created it is no longer an owner or admin of the business. The server then returns `401` with the code `key_no_longer_valid`:
```json 401 theme={"system"}
{
"statusCode": 401,
"message": "This API key no longer works: the person who created it no longer administers this business.",
"code": "key_no_longer_valid",
"timestamp": "2026-09-28T12:00:00.000Z",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
```
Only the CLI's browser login session sends `Authorization: Bearer`: the [`agenta` CLI](/cli/login) signs in through your browser and sends a session token as `Authorization: Bearer `, not an API key. A request made with a session token runs in test mode unless it sends the header `X-Environment: live`. Use an API key for your own servers and scripts.
## Acting for a business
A [platform](/connect/overview) can call any endpoint as a business it manages. Send the business's id in the `AgentaOS-Account` header, and the request runs as that business: the checkout, product or invoice is the business's, not yours.
```bash theme={"system"}
curl https://api.agentaos.ai/api/v1/gateway/sessions \
-H "x-api-key: sk_live_..." \
-H "AgentaOS-Account: 5b1f0c2e-8a4d-4c7b-9e21-3f6a7d8c9b10" \
-H "Content-Type: application/json" \
-d '{"amount": 49, "currency": "EUR"}'
```
The id of a business you manage. Leave it out, or send your own id, to act as yourself.
Only a platform's key, or a signed-in owner or admin of the platform, may send it, and only for a business that platform manages. Any other value (another platform's business, an account nobody manages, an id that does not exist) returns the same `403`:
```json 403 theme={"system"}
{
"statusCode": 403,
"message": "You cannot act as this business.",
"timestamp": "2026-09-28T12:00:00.000Z"
}
```
A header that is present but empty returns `400` with the message `AgentaOS-Account must name a business.`
The SDK sends this header for you with `{ business }` and the CLI with `--business `. See [Acting for a business](/sdk/pay-businesses#acting-for-a-business).
## Test and live keys
The key's prefix sets the mode of the request. There is no mode parameter.
| Prefix | Mode | Data |
| - | - | - |
| `sk_test_` | Test | Test data only. No real money moves, and no business verification is needed. |
| `sk_live_` | Live | Live data and real money. Your business must be verified. |
Test data and live data are separate. A test key reads and writes only records created with a test key, and a live key only records created with a live key. A record from the other mode returns `404`, as if it did not exist. See [Test mode and testing](/getting-started/test-mode).
## Request format
Send request bodies as JSON with `Content-Type: application/json`. Body fields are camelCase, for example `successUrl`, `billingInterval` and `trialPeriodDays`. The server rejects a field that the endpoint does not define with a `400`, so check the spelling of every field. A body larger than 512 KB is rejected.
## Pagination
The list endpoints `GET /gateway/payment-links`, `/sessions`, `/subscriptions`, `/customers`, `/invoices` and `/all-transactions` page with `limit` and `offset` and return the same envelope. There is no cursor.
Items per page. The server caps it at 100 on payment links, checkouts, subscriptions and customers, and at 5000 on invoices and transactions. A larger value returns the capped number of items without an error.
The number of items to skip. For the next page, add the number of items you received to the current offset.
```json Pagination envelope theme={"system"}
{
"items": [ /* ... */ ],
"total": 137,
"hasMore": true
}
```
The page of results. The shape depends on the resource.The number of matching items across all pages.`true` when `offset` plus the number of items on this page is less than `total`.
`GET /gateway/businesses` is not paged. It returns a plain array.
## Naming convention
Most response fields are `snake_case`. A few computed fields are camelCase, and two resources are camelCase throughout. Responses have three kinds of fields:
A field stored on the record comes back in snake\_case: `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.
The server computes these fields when it builds the response. They are camelCase and have no snake\_case form:
checkoutUrl on Checkouts and 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 have no snake\_case fields: **Subscriptions** (`GET /gateway/subscriptions`, `POST /gateway/subscriptions/:id/cancel`) and **Customers** (`GET /gateway/customers`). Every field on these two is camelCase.
The TypeScript SDK removes this mix: it camelCases every response, so `checkoutUrl` stays `checkoutUrl` and `buyer_email` becomes `buyerEmail`. Outside Node.js, read the tables on each page of this reference as the literal JSON keys the server sends.
## Money model
AgentaOS writes money in three forms.
An amount you send is a decimal number in currency units: `49.99` means €49.99, never cents. The same holds for a plainly named amount in a response: a Payment Link's `amount`, a Checkout's `amount_override`, and an Invoice's `amount`, `fiat_amount` and `tax_amount`.
A field whose name ends in `Minor` is an integer count of the smallest currency unit: `1999` means €19.99. Examples are `unitAmountMinor` on a Subscription, `dueTodayMinor` on a plan-change quote, `amountMinor` on a credit, and the fields of the `money` breakdown on a Transaction and the `earnings` breakdown on a retrieved Invoice.
In a webhook event's `data`, `amount` is a string in currency units, e.g. `"49.99"`, so the value keeps its trailing zeros. `amount_minor` on subscription and dispute events is an integer in minor units, like the `Minor` fields above. The SDK's `webhooks.verify()` returns it as `amountMinor`.
## Errors
An error response is JSON. Every response, success or error, has an `x-request-id` header, and an error body repeats it as `requestId`. Include it when you contact support.
```json 400 theme={"system"}
{
"statusCode": 400,
"message": ["amount must be a positive number"],
"error": "Bad Request",
"errors": [{ "field": "amount", "message": "amount must be a positive number" }],
"timestamp": "2026-09-28T12:00:00.000Z",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
```
The HTTP status code.Human-readable text. A validation failure returns an array, one entry per problem.ISO 8601 time of the error.The same value as the `x-request-id` header.A short category, such as `Bad Request`. Most errors have it; some, such as a `429`, do not.On a validation `400` only: one `{ field, message }` per invalid field.On some errors only: a stable, machine-readable code, such as `key_no_longer_valid` or `account_on_hold`. Branch on `code` when it is present, and on `statusCode` when it is not. Never branch on `message`, which can change.
The status codes and how to handle each are on [Error codes](/api-reference/error-codes).
## Rate limits
The server counts requests per client IP address and per endpoint. The default limit is 60 requests per 60 seconds on each endpoint. These endpoints have a lower limit of 10 requests per 60 seconds:
* `POST /gateway/businesses`
* `POST /gateway/businesses/:id/invitation`
* `POST /gateway/businesses/:id/verification-link`
Each response has the headers `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (seconds until the count resets). Over the limit, the server returns `429` with a `Retry-After` header, in seconds:
```json 429 theme={"system"}
{
"statusCode": 429,
"message": "Too many tries in a row. Wait a minute, then try again.",
"timestamp": "2026-09-28T12:00:00.000Z",
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
```
Wait for the number of seconds in `Retry-After`, then send the request again. The SDK does this for you when `Retry-After` is 60 seconds or less.
The count is per IP address and endpoint, so all the checkouts your server creates share one budget: 60 `POST /gateway/sessions` calls per minute, across all your buyers. Create a checkout when a buyer asks to pay, not on every page view.
## Idempotency
Two endpoints read the `Idempotency-Key` header:
| Endpoint | Behavior |
| - | - |
| `POST /gateway/sessions` | Optional. A retry with the same key, in the same mode, returns the checkout created the first time, with the same invoice number, instead of a second checkout. You can also send the key as `idempotencyKey` in the body. |
| `POST /gateway/subscriptions/:id/credits` | Required. A request without it returns `400` with the message `This request needs an idempotency key.` A retry with the same key does not grant the credit a second time. |
No other endpoint reads the header. If a create request times out and you send it again, for example `POST /gateway/payment-links`, the server can create the record twice. Before you retry such a request, list the resource and check whether the first request succeeded.
The SDK sends a new random `Idempotency-Key` on every `POST` call, and uses the same key when it retries that call after a `429`, a `5xx` or a network error. You cannot choose the key: `RequestOptions` has no `idempotencyKey`. The one exception is `subscriptions.credit()`, which requires you to pass `idempotencyKey`. On the two endpoints above, a retry is safe. On other endpoints, the retry can create a duplicate.
The server does not answer a repeated key with `409`: it returns the first result. A `409 Conflict` means the request conflicts with the record's current state, for example a checkout changed by another request, or a product at its purchase limit. The SDK throws every `409` as `IdempotencyError`, whatever the cause, so read its `message`.
## Versioning
The API version is in the path: every endpoint is under `/api/v1`. The SDK (`@agentaos/pay`) and the CLI (`agentaos`) are published on npm with semantic version numbers. Pin the version you tested with, and read the [changelog](/changelog/overview) before you upgrade.
## 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 when 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.
**Webhook:** none. The call sends an email only. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# 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.
**Webhook:** none. Voiding changes the invoice document, not a payment. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
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.
**Webhook:** none. Cancelling a link moves no money. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
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.
**Webhook:** none. Creating a link moves no money. The buyer's payment on it sends `checkout.session.completed`. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
You do not pass a settlement mode. The server sets it from your account and returns it on the response. Buyers pay by card, and on EUR sales also by bank transfer.
# 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.
**Webhook:** none. Editing a link moves no money. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# 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`.
**Webhook:** `subscription.updated` fires right away, when the call schedules the cancel. `subscription.canceled` fires when the period ends, on AgentaOS's next check. A repeat call sends nothing. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# Give a subscriber credit
Source: https://docs.agentaos.ai/api-reference/subscriptions/credits-create
POST /gateway/subscriptions/{id}/credits
Puts credit on the subscriber's account with you. It comes off their next invoice, reducing what their card is charged; no money moves out, and no receipt already issued changes. The credit belongs to the CUSTOMER, so if the same buyer has two subscriptions with you in the same currency it goes to whichever invoice falls due first. The amount is positive integer minor units and may cover the whole of the next invoice — at most its amount due. A credit that covers it in full charges the card nothing, and that month is still recorded as a sale of zero with an invoice and a receipt saying the credit covered it. More than the amount due is refused, and the error names the largest amount you can give. The currency is the subscription's; there is no currency field. Only an owner or admin may do this, from a signed-in session: an API key cannot, because the credit must name the person who gave it.
**Webhook:** none. The credit changes no subscription state. A renewal that the credit pays sends `checkout.session.completed` and `subscription.renewed`. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# List credits given to a subscriber
Source: https://docs.agentaos.ai/api-reference/subscriptions/credits-list
GET /gateway/subscriptions/{id}/credits
Every credit given for this subscription, newest first, and what is left unspent. `balanceMinor` is the customer's whole unspent credit with you, which may include credit given for another of their subscriptions. `createdBy` is the id of the person who gave it. `currency` is the subscription's, and every minor-unit figure on the page is counted in it. Owner or admin, signed-in session only: an API key is refused here, same as on the grant.
# List a subscription's invoices
Source: https://docs.agentaos.ai/api-reference/subscriptions/invoices
GET /gateway/subscriptions/{id}/invoices
Every invoice minted at a renewal cycle for this subscription, newest first. Bounded (latest 100); not paginated. `amount`/`currency` is one already-resolved figure per invoice: the linked capture's settlement earnings when one is available in the invoice's own fiat currency, falling back to the merchant's fiat amount, then to the settlement-token amount. Every field on this resource is camelCase on the wire.
# 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.
# Change the plan
Source: https://docs.agentaos.ai/api-reference/subscriptions/plan-change
POST /gateway/subscriptions/{id}/plan-change
Apply a plan change quoted by the preview. Echo the preview's `prorationDate` so the amount charged matches the quote. An upgrade charges the prorated difference on the saved card now and the response carries the new per-cycle amount; a downgrade is scheduled for the current period end (`effectiveAt`) and charges nothing today. Idempotent on (subscription, target plan, prorationDate). Same plan, a non-active subscription, a different currency, or a different billing interval return `400`.
An **upgrade** charges the prorated difference on the subscriber's saved card **now**. Always show the [preview](/api-reference/subscriptions/plan-change-preview) first and echo its `prorationDate`; that is what makes the charge equal the quote. A **downgrade** charges nothing today and switches at the current period end.
Idempotent on (subscription, target plan, `prorationDate`): retrying the same request does not charge twice. Same plan, a subscription that is not `active` or `trialing`, a different currency, or a different billing interval return `400`.
**Webhook:** `subscription.updated` fires right away. An upgrade's prorated charge also sends `checkout.session.completed` and `subscription.renewed` on AgentaOS's next check. A downgrade sends a second `subscription.updated` when the new plan takes over at the period end. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# Preview a plan change
Source: https://docs.agentaos.ai/api-reference/subscriptions/plan-change-preview
GET /gateway/subscriptions/{id}/plan-change/preview
Quote moving a subscription to another plan. Read-only: nothing is charged or scheduled. The target must be a different subscription product in the same currency and billing interval, and the subscription must be `active` or `trialing`. An upgrade (higher price) is charged now, prorated; a downgrade switches at the current period end with nothing due today. Pass the returned `prorationDate` to the apply call so the charge matches this quote.
Read-only. Nothing is charged or scheduled until you call [Change the plan](/api-reference/subscriptions/plan-change) with the `prorationDate` from this response. The target must be a different subscription product in the same currency and billing interval; the subscription must be `active` or `trialing`. Anything else returns `400`.
**Webhook:** none. The preview changes nothing. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# Resume a subscription
Source: https://docs.agentaos.ai/api-reference/subscriptions/resume
POST /gateway/subscriptions/{id}/resume
Undoes a scheduled cancel-at-period-end before the current period elapses. Only valid on a live subscription that is currently scheduled to cancel; returns `400` if it was never scheduled to cancel or has already gone terminal.
**Webhook:** `subscription.updated` fires right away, with `cancel_at_period_end: false`. See [Which action fires which event](/webhooks/events#which-action-fires-which-event).
# 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 when a transaction settles, instead of polling this list.
# Webhooks
Source: https://docs.agentaos.ai/api-reference/webhooks
How AgentaOS signs and delivers webhook events, and how to verify the X-AgentaOS-Signature header.
AgentaOS signs every webhook it sends to your server. Verify the signature before you act on the body. The events and their payloads are in the [event reference](/webhooks/events).
## 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_...`) in **Settings → Developers → Webhooks**: click **Reveal signing secret**. The secret stays the same when you change the URL. If it is exposed, click **Rotate secret** on the same screen.
```typescript theme={"system"}
import { AgentaOS, WebhookVerificationError } from '@agentaos/pay';
import express from 'express';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
const app = express();
// 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); // your own code
}
res.sendStatus(200);
} catch (err) {
if (err instanceof WebhookVerificationError) {
res.status(400).send('Invalid signature');
return;
}
res.status(500).send('Webhook processing failed');
}
});
app.listen(3000);
```
```python theme={"system"}
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 a signature older than 5 minutes, or from the future
age = int(time.time()) - timestamp
if age < 0 or age > 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.
## Delivery
Timeouts, attempts, redirects and refused addresses are in [Delivery and retries](/webhooks/events#delivery-and-retries). Return `2xx` quickly and do slow work (emails, fulfillment) after you respond. A slow handler can time out and cause a retry.
# Changelog
Source: https://docs.agentaos.ai/changelog/overview
What's new in AgentaOS.
## Embedded checkout
Put the AgentaOS checkout on your own page, inline or as an overlay, with a script tag or the `@agentaos/checkout` package. Add your sites under **Approved sites**, and set the button color, font, corners, and background under **Checkout appearance**. It applies to the hosted checkout too. See [Embedded checkout](/payments/embedded-checkout).
## Platforms, in private preview
Run your clients' businesses from one account. Add a business, invite your client by email, and take a share of each sale, counted on the price before VAT. From code, send the `AgentaOS-Account` header to act as a business you manage. Your webhook receives your clients' events, every event names its `business`, and `account.updated` tells you when a business's verification changes. See [Build a platform](/guides/build-a-platform).
## Stablecoin payouts, in public preview
Be paid in USDC or EURC to a wallet instead of your bank account. See [Stablecoin payouts](/payouts/stablecoin-payouts).
## New pages
[Payout statements and self-billed invoices](/payouts/statements), and [Product release phases](/getting-started/release-phases), which explains the Private preview and Public preview tags.
## Discount codes
Create a code under **Discounts**, and a buyer types it at checkout to pay less. A code can also take nothing off and only track who sent the buyer. See [Discount codes](/payments/discount-codes).
## Downgrades at the period end
A downgrade keeps the plan the buyer paid for until the end of the period, then switches. Pick the current plan again before then to revert the change. See [Subscriptions](/payments/subscriptions).
## Free trials
A subscription plan can start with a free trial.
## Plan changes
Move a subscriber to another plan. An upgrade applies at once, and the buyer pays the prorated difference today. See [Subscriptions](/payments/subscriptions).
## Built for AI agents
Copy any page as Markdown, or open it in ChatGPT or Claude, right from the docs.
## No-code products and invoices
Create a product or send an invoice from the dashboard, share the link, get paid.
## Payments glossary
Added a plain-English payments glossary.
## Card, Apple Pay, and Google Pay
Accept **Card, Apple Pay, and Google Pay** as your merchant of record, with tax, invoices, and payouts handled.
## Subscriptions
**Subscriptions**, invoices, receipts, customers, and webhooks.
## TypeScript SDK, CLI, and MCP server
**TypeScript SDK, CLI, and MCP server** so your AI agents can run the store.
# Businesses
Source: https://docs.agentaos.ai/cli/businesses
Manage your clients' businesses from the terminal, and run any command as one of them with the business option.
Private preview Working end to end for the accounts that use it; details can still change. Tell us at [contact@agentaos.ai](mailto:contact@agentaos.ai) before you rely on it, and we support you directly. See [Product release phases](/getting-started/release-phases).
`agenta businesses` is for [platforms](/connect/overview): the businesses you manage, whether a client's business or another app of your own company.
```bash theme={"system"}
agenta businesses list
```
## Commands
| Command | What it does |
| - | - |
| `agenta businesses list` | Every business you manage, with its status. |
| `agenta businesses get ` | One business, with its status. |
| `agenta businesses create --name … --country … [--client-email …] [--same-company] [--no-invite-email]` | Add a business. |
| `agenta businesses invite [--no-email]` | Send your client's invitation again. A new link; the old one stops working. |
| `agenta businesses revoke-invite ` | Withdraw the open invitation. Its link stops working. |
| `agenta businesses id-link ` | The identity check link for your client. Only they can complete it. |
## List
```bash theme={"system"}
agenta businesses list
```
```bash Output theme={"system"}
2 businesses:
- ClientCo (in review, invitation open for founder@example.com) — 5b1f0c2e-8a4d-4c7b-9e21-3f6a7d8c9b10
- Acme Notes (live, your own company, identity verified) — 7c2e1d3f-9b5e-4d8c-0f32-4a7b8e9d0c21
```
## Get
```bash theme={"system"}
agenta businesses get
```
```bash Output theme={"system"}
ClientCo (in review, invitation open for founder@example.com) — 5b1f0c2e-8a4d-4c7b-9e21-3f6a7d8c9b10
Act as it: add --business 5b1f0c2e-8a4d-4c7b-9e21-3f6a7d8c9b10 to any command.
```
## Create
```bash theme={"system"}
agenta businesses create --name --country [--client-email ] [--same-company] [--no-invite-email]
```
The name buyers see.
Where it is registered, two-letter code, e.g. `DE`.
Invite your client as its admin. Leave it out to run the business yourself.
Another app of your own company: verified and priced with you, with no share and no identity check of its own.
We send no email. The command prints the link for you to send yourself.
```bash theme={"system"}
agenta businesses create --name ClientCo --country DE --client-email founder@example.com --no-invite-email
```
```bash Output theme={"system"}
Added ClientCo. It starts in test mode and goes live after its own verification.
Business id: 5b1f0c2e-8a4d-4c7b-9e21-3f6a7d8c9b10 — add --business 5b1f0c2e-8a4d-4c7b-9e21-3f6a7d8c9b10 to any command to act as it.
Send ClientCo this link to accept: https://app.agentaos.ai/invite/…
```
## Invite
```bash theme={"system"}
agenta businesses invite [--no-email]
```
Don't email it. The command prints the new link for you to send.
Once your client has joined, the invitation is theirs to manage and this is refused.
## Revoke invite
```bash theme={"system"}
agenta businesses revoke-invite
```
```bash Output theme={"system"}
Invitation withdrawn: its link no longer works.
```
## ID link
```bash theme={"system"}
agenta businesses id-link
```
```bash Output theme={"system"}
Send your client this link — only they can complete the identity check: https://…
```
If the client is already verified it says so and gives no link. The identity check is real, so a test API key is refused. Signed in with `agenta login`, the command works in test mode too, as the **ID check link** button in the dashboard does. Another app of your own company has no identity check of its own.
## `--business` on every command
Add `--business ` to **any** `agenta` command to run it as a business you manage. Leave it out to act as yourself.
```bash theme={"system"}
agenta products create … --business
agenta pay checkout -a 49 -c EUR --business
agenta verify submit … --business
agenta invoices list --business
```
Filing a client's verification with `agenta verify submit … --business ` is recorded as filed by the platform. A business you do not manage is refused with `You cannot act as this business.`
### File a client's verification
This is the same verification any account submits, sent as the business. We record it as filed by the platform, and our reviewers see that. File only what your client confirmed to you.
```bash theme={"system"}
agenta verify submit --entity business --legal-name "ClientCo GmbH" \
--registration-number "HRB 123456" --country DE \
--street "Hauptstrasse 1" --city Berlin --postal 10115 \
--url https://clientco.com --description "Scheduling software for clinics" \
--accept-declaration --business
```
Run `agenta verify declaration` first to read what `--accept-declaration` attests to. See [Verify](/cli/verify) for every flag.
Add `--json` to any command for a single JSON object, e.g. `agenta businesses create … --json` returns `business`, `inviteToken` and `inviteUrl`.
## Next steps
End to end.
Every method and field.
Set your share in the dashboard.
What a platform is, and who does what.
# 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={"system"}
agenta customers list
```
## Usage
```bash theme={"system"}
agenta customers list [--limit ] [--json]
```
Results per page, 1 to 100.
Output a single JSON object instead of a table.
```bash Output theme={"system"}
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={"system"}
{
"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.
# Discounts
Source: https://docs.agentaos.ai/cli/discounts
Create, list, inspect and stop the discount codes buyers type at checkout.
`agenta discounts` manages [discount codes](/payments/discount-codes): codes a buyer types at checkout. A code works on any product, one-time or subscription. A code takes a percentage or a fixed amount off the first invoice. A code with neither changes no price and records who it brought in, which is how a referral code works.
```bash theme={"system"}
agenta discounts list
```
## Commands
| Command | What it does |
| - | - |
| `agenta discounts create --code [options]` | Create a discount code. |
| `agenta discounts list` | List your discount codes. |
| `agenta discounts show ` | What one code takes off, and how many people used it. |
| `agenta discounts archive ` | Stop a code. |
Every command takes `--json` for one JSON object, and `--business ` to act as a [business you manage](/cli/businesses).
## Create
```bash theme={"system"}
agenta discounts create --code [--percent-off | --amount-off ] [options]
```
The code buyers type at checkout. Letters, numbers, dashes and underscores, up to 64.
Your label for the code, for your records. Buyers never see it.
The percentage off the first invoice, e.g. `20`.
A fixed amount off the first invoice, e.g. `5.00`.
The code stops working after this many subscribers use it.
The date the code stops working, as `YYYY-MM-DD`.
Use `--percent-off` or `--amount-off`, not both. Leave both out for a tracking-only code.
```bash theme={"system"}
agenta discounts create --code LAUNCH20 --percent-off 20
```
```bash Output theme={"system"}
Created LAUNCH20 — discount — active — all plans.
Buyers type it on the checkout page and the first invoice drops.
Its id is 3f6a7d8c-9b10-4c7b-9e21-5b1f0c2e8a4d (use it to show or archive the code).
```
A tracking-only code prints "It changes no price; it tells you who it brought in." on the second line.
## List
```bash theme={"system"}
agenta discounts list [--limit ]
```
Results per page, from 1 to 100.
```bash Output theme={"system"}
2 discount codes:
- LAUNCH20 — discount — active — all plans — 3f6a7d8c-9b10-4c7b-9e21-5b1f0c2e8a4d
- ALICE — tracking only — active — all plans — 7c2e1d3f-9b5e-4d8c-0f32-4a7b8e9d0c21
What each one takes off is on discounts show.
```
With no codes yet, it prints: "No discount codes yet. Create one with discounts create --code LAUNCH20 --percent-off 20."
## Show
```bash theme={"system"}
agenta discounts show
```
```bash Output theme={"system"}
LAUNCH20 — 20% off — used 14 of 100 times — active.
It works on every plan you sell.
```
`show` is the only command that reports what a code takes off and how many people used it.
## Archive
```bash theme={"system"}
agenta discounts archive
```
```bash Output theme={"system"}
LAUNCH20 stops working now; nobody new can type it.
Subscribers who already used it keep the discount they were given.
```
## Next steps
How codes work at checkout and in the dashboard.
List, cancel and change subscriptions.
# 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={"system"}
curl -fsSL https://agentaos.ai/install | bash
```
```bash npm theme={"system"}
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={"system"}
agenta --version
```
```bash Output theme={"system"}
3.1.0
```
If `agenta` isn't found after a curl install, open a new shell so your `PATH` picks up the change.
## Installing Node.js
If you're going the npm route and don't have Node 20+ installed:
```bash theme={"system"}
curl -fsSL https://fnm.vercel.app/install | bash
fnm install 20
fnm use 20
```
Then re-run `npm install -g agentaos`.
## AI agent setup
For an agent or automation that uses AgentaOS for you: in Claude Code, install the plugin; any other agent can read the skill directly:
```bash theme={"system"}
claude plugins marketplace add AgentaOS/agentaos
claude plugins install agentaos@agentaos
```
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 AgentaOS's Estonian entity as the merchant of record.
```bash theme={"system"}
agenta invoices list
agenta invoices receipt
agenta invoices send-receipt
```
## List invoices
```bash theme={"system"}
agenta invoices list [--limit ] [--json]
```
Results per page, 1 to 100.
Output a single JSON object instead of a table.
```bash Output theme={"system"}
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={"system"}
{
"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={"system"}
agenta invoices receipt [-o ]
```
Invoice ID, e.g. `3332e42e-14ae-4d59-9171-28372c2ac632`.
File path to save the PDF to.
```bash theme={"system"}
agenta invoices receipt 3332e42e-14ae-4d59-9171-28372c2ac632 -o ./receipts/august.pdf
```
```bash Output theme={"system"}
Saved: ./receipts/august.pdf
```
```json --json theme={"system"}
{ "saved": "./receipts/august.pdf" }
```
Receipts are only available for paid (issued) invoices.
## Re-send a receipt
```bash theme={"system"}
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={"system"}
agenta invoices send-receipt 3332e42e-14ae-4d59-9171-28372c2ac632
```
```bash Output theme={"system"}
Receipt sent to jane@startup.io
```
```json --json theme={"system"}
{ "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={"system"}
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={"system"}
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={"system"}
Open this URL in your browser:
https://app.agentaos.ai/cli-auth?code=WDJK-QPXR
Verification code: WDJK-QPXR
✓ Logged in
✓ Authenticated as demo@example.com
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={"system"}
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={"system"}
✓ Logged out
```
## Next steps
Confirm your account and your go-live progress 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 its checkout URL, and track its status from the terminal. Use it for one-off invoicing, quick testing, and scripts that create payments.
`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={"system"}
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={"system"}
agenta pay checkout -a 49.99 -c EUR -d "Consulting fee, August"
```
```bash Output theme={"system"}
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={"system"}
{
"sessionId": "mZrESFyR7RC9RPsJfZCVkg",
"status": "open",
"amount": 49.99,
"currency": "EUR",
"checkoutUrl": "https://app.agentaos.ai/checkout/mZrESFyR7RC9RPsJfZCVkg",
"expiresAt": "2026-08-06T15:32:00.000Z",
"hint": "Share the checkoutUrl with your customer."
}
```
Checkouts expire in 30 minutes by default. Share the `checkoutUrl` with your buyer.
## Get a checkout
```bash theme={"system"}
agenta pay get [--json]
```
The checkout session ID, e.g. `mZrESFyR7RC9RPsJfZCVkg`.
Output a single JSON object instead of the human-readable summary.
```bash theme={"system"}
agenta pay get mZrESFyR7RC9RPsJfZCVkg
```
```json --json theme={"system"}
{
"sessionId": "mZrESFyR7RC9RPsJfZCVkg",
"status": "completed",
"amount": 49.99,
"currency": "EUR",
"checkoutUrl": "https://app.agentaos.ai/checkout/mZrESFyR7RC9RPsJfZCVkg",
"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 product's own amount rather than overriding it. `status` is one of `open`, `completed`, `expired`, `cancelled`.
## List checkouts
```bash theme={"system"}
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={"system"}
agenta pay list --status completed --limit 5
```
```bash Output theme={"system"}
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={"system"}
{
"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, products, and settlement fit together.
# agenta products
Source: https://docs.agentaos.ai/cli/products
Create and list products and subscription plans from the terminal. A product is what the API calls a payment link; its ID is the `linkId`.
A **product** is a reusable, shareable checkout with a fixed price: one-time, or a subscription plan billed monthly or yearly. It is the same object the dashboard calls a product and the API calls a payment link. Create it once; every buyer uses the same one. Creating the first product is the merchant's first go-live milestone.
Requires `agentaos` 2.1 or newer (`npm install -g agentaos@latest`).
## Create a product or plan
```bash theme={"system"}
agenta products create --name -a [-c ] [-d ] \
[--subscription --interval month|year [--trial-days ]] \
[--success-url ] [--cancel-url ] [--json]
```
Product name buyers see.
Price in currency units: `29` means €29.00.
`EUR` or `USD`. Defaults to the org setting.
Shown on the checkout page.
Recurring plan instead of a one-time product. Needs `--interval`.
Billing cadence for a plan.
Free-trial length, 1 to 730 days. Plans only. The buyer's card is saved with nothing due today; the first charge happens when the trial ends.
`https://` URL the buyer lands on after paying. `?sessionId=` is appended.
`https://` URL the buyer lands on if they back out.
Output a single JSON object instead of the human-readable summary.
```bash theme={"system"}
agenta products create --name "Pro" -a 29 -c EUR --subscription --interval month \
--success-url https://yourapp.com/thanks --json
```
```json --json theme={"system"}
{
"id": "48d35c70-4a6c-4347-bff5-3a4aa073e642",
"name": "Pro",
"type": "subscription",
"billingInterval": "month",
"amount": 29,
"currency": "EUR",
"status": "active",
"checkoutUrl": "https://app.agentaos.ai/pay/mc8d_k7fxmkyMPC80ngtcA",
"successUrl": "https://yourapp.com/thanks",
"cancelUrl": null,
"hint": "Share the checkoutUrl with your customers. It works for every sale."
}
```
* `id` is the **`linkId`**: pass it to `checkouts.create({ linkId })` for logged-in users, or to `agenta subscriptions change-plan --to`.
* `checkoutUrl` is the **buyer link**: share it, or put it behind a Buy button. It carries no app user id.
* Return URLs must be `https://`; plain-http and localhost are refused before the request is sent.
* Products created from the CLI are test-mode products. Live products are created in the dashboard once the merchant is live.
A plan with a trial:
```bash theme={"system"}
agenta products create --name "Pro (annual)" -a 290 -c EUR --subscription --interval year --trial-days 14 --json
```
The hosted checkout then shows "Due today €0.00, then €290.00/yr after your 14-day free trial", saves the card, and the subscription starts as `trialing`.
## List products
```bash theme={"system"}
agenta products list [--limit ] [--json]
```
```json --json theme={"system"}
{
"total": 2,
"hasMore": false,
"items": [
{ "id": "ebc045bf-…", "name": "Pro (annual)", "type": "subscription", "billingInterval": "year", "amount": 290, "currency": "EUR", "status": "active", "checkoutUrl": "https://app.agentaos.ai/pay/v6wN…", "successUrl": null, "cancelUrl": null },
{ "id": "7e0f2852-…", "name": "Launch Kit", "type": "one_time", "billingInterval": null, "amount": 49, "currency": "EUR", "status": "active", "checkoutUrl": "https://app.agentaos.ai/pay/AgaG…", "successUrl": null, "cancelUrl": null }
]
}
```
## Next steps
Share the link, or check out a logged-in user with the linkId.
List subscribers, change plans, cancel.
# Status
Source: https://docs.agentaos.ai/cli/status
Check who you're logged in as, how far along going live you are, and the next command to run.
`agenta status` is the command to run right after `agenta login`, and the one to reach for whenever something isn't working. It says who you are signed in as, where going live stands, and the next command to run.
```bash theme={"system"}
agenta status
```
Alias: `agenta whoami`.
## Status checks
* **Account**: who you are signed in as, and for which business.
* **Going live**: how many go-live steps are done, your business verification, the free Revenue & Pricing Audit, and your payout account.
* **Next step**: the one command that moves you forward, when there is one.
Test payments work as soon as you are signed in. Live payments wait on a verified business. Payouts wait on a bank account.
## Usage
```bash theme={"system"}
agenta status [--business ] [--json]
```
| Flag | Description |
| - | - |
| `--business ` | Show the status of a [business you manage](/cli/businesses). |
| `--json` | Output a single JSON object instead of the human-readable summary. |
## Example output
```bash Output theme={"system"}
Signed in as demo@example.com for Acme Freelance on https://api.agentaos.ai.
Test payments work now. Live money waits on verification.
Go-live steps done: 1 of 2.
Business verification: Not submitted.
Free Revenue & Pricing Audit: Not requested.
Payout account: not yet added, needed before your first payout.
Next: start with the free audit (agenta audit request).
```
The two go-live steps are a first product or payment and a verified business. The dashboard's setup guide counts three items: the same two, and **Go live** as the third. A bank account is not a go-live step: you need it before your first payout, not before your first sale. The first line changes as you go:
| Where you are | First line |
| - | - |
| Not verified yet | Test payments work now. Live money waits on verification. |
| Verified | You can take live payments. |
The `Next` line names the step that blocks you first:
| Where you are | `Next` points to |
| - | - |
| AgentaOS asked for changes on your verification | `agenta verify resubmit` |
| Not verified, no audit requested | `agenta audit request` (the dashboard shows the audit sealed until you verify; the CLI can ask for it now) |
| Not verified, audit requested | `agenta verify submit` |
| Verified, no bank account | Adding a bank account in the dashboard, before your first payout |
In every other case there is no `Next` line, for example while your verification is in review.
### `--json`
```json theme={"system"}
{
"account": {
"authenticated": true,
"email": "demo@example.com",
"server": "https://api.agentaos.ai",
"configDir": "/Users/you/.agenta",
"orgId": "0b7c6a2e-4f1d-4e8a-9c3b-2d5e7f9a1b04",
"organization": "Acme Freelance",
"jwtExpiresAt": "2026-09-28T14:32:00.000Z",
"jwtSecondsRemaining": 24000,
"paymentTools": { "ready": true, "mode": "test", "next": "agenta pay checkout -a 50" }
},
"goLive": {
"canGoLive": false,
"progress": { "done": 1, "total": 2 },
"verification": {
"state": "unverified",
"label": "Not submitted",
"heldReason": null,
"rejectReason": null,
"cooldownUntil": null,
"changesRequested": []
},
"audit": { "label": "Not requested", "requested": false, "reportUrl": null, "grade": null },
"payouts": { "connected": false },
"milestones": {
"firstProductAt": "2026-09-27T09:10:00.000Z",
"firstTestPaymentAt": "2026-09-27T09:25:00.000Z",
"firstLivePaymentAt": null,
"livePaymentCount": 0
},
"next": { "command": "agenta audit request", "why": "start with the free audit" }
}
}
```
`--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 .goLive.next`.
## Response fields
| Field | Description |
| - | - |
| `account.authenticated` | Whether you have a valid session. |
| `account.email` | Account email, decoded from your session token. |
| `account.organization` | Your business's name. |
| `account.orgId` | Your business's ID. |
| `account.paymentTools.ready` | Always `true` once you are signed in: test payments work. |
| `account.paymentTools.next` | A command to try a test payment. |
| `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 config. |
| `goLive.canGoLive` | Whether you can take live payments. |
| `goLive.progress` | Go-live steps done, out of the total. |
| `goLive.verification.state` | `unverified`, `in_review`, `on_hold`, `verified` or `rejected`. |
| `goLive.verification.label` | The same state in words, including `Changes requested`. |
| `goLive.verification.changesRequested` | What AgentaOS asked you to change. Empty when it asked for nothing. |
| `goLive.audit` | Where the free audit stands, with the report link once it exists. |
| `goLive.payouts.connected` | Whether you have a bank account that can receive payouts. You need one before your first payout. |
| `goLive.milestones` | When you made your first product, test payment and live payment, and your live payment count. |
| `goLive.next` | The next `command` and `why`, or `null` when there is nothing to do. `command` is `null` when the step happens in the dashboard. |
## Not logged in
```json theme={"system"}
{
"account": {
"authenticated": false,
"reason": "not-logged-in",
"next": "agenta login"
}
}
```
The command prints this JSON with or without `--json`, and exits with code 0. `reason` is either `not-logged-in` or `session-expired`, so scripts can tell the two apart.
## Next steps
Authenticate or re-authenticate the CLI.
Submit your business verification.
# 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 product). Use it for support work: look up a subscriber, check their status, cancel on request.
```bash theme={"system"}
agenta subscriptions list
agenta subscriptions cancel
agenta subscriptions credit -a --reason
```
## List subscriptions
```bash theme={"system"}
agenta subscriptions list [--limit ] [--code ] [--json]
```
Results per page, 1 to 100.
Only subscribers who typed this [discount code](/payments/discount-codes).
Output a single JSON object instead of a table.
```bash Output theme={"system"}
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={"system"}
{
"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, fields whose name ends in `Minor` are integers in minor units (`1999` = €19.99). Plain `amount` fields are decimals. See the [money model](/api-reference/introduction#money-model).
### Status values
| Status | Meaning |
| - | - |
| `trialing` | In a free trial period. |
| `active` | Paying, current. |
| `past_due` | Latest charge failed, retrying. |
| `unpaid` | Retries exhausted, not yet canceled. |
| `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={"system"}
agenta subscriptions cancel [--json]
```
The cancellation takes effect at the end of the current period: the subscriber keeps access until their paid period ends, and nothing is refunded.
Subscription ID, e.g. `253dbe6a-6b4d-4c40-a16f-aae31cfea271`.
Output a single JSON object instead of the human-readable summary.
```bash theme={"system"}
agenta subscriptions cancel 253dbe6a-6b4d-4c40-a16f-aae31cfea271
```
```json --json theme={"system"}
{
"status": "active",
"cancelAtPeriodEnd": true,
"effectiveCancelDate": "2026-09-06",
"currentPeriodEnd": "2026-09-06T00:00:00.000Z"
}
```
## Change the plan (upgrade or downgrade)
```bash theme={"system"}
agenta subscriptions change-plan --to [--dry-run] [--json]
```
Subscription ID.
The ID of the target subscription product (`agenta products list`). It must be a subscription product in the same currency and billing interval as the current plan, and a different one.
Print the quote and apply nothing.
One command, two API calls: the quote, then the change with the quote's `prorationDate` echoed back, so what is charged is what was shown. Always look at the quote first:
```bash theme={"system"}
agenta subscriptions change-plan 9811c905-1f61-459d-af03-18fb5aad47d8 --to 3f0c2d61-... --dry-run --json
```
```json --json theme={"system"}
{
"applied": false,
"direction": "upgrade",
"currency": "eur",
"dueTodayMinor": 1234,
"dueTodayVatMinor": 239,
"effectiveAt": "2026-09-03T10:31:25.000Z",
"nextInvoiceMinor": 4900,
"nextInvoiceVatMinor": 1176,
"nextInvoiceAt": "2026-10-03T10:45:13.000Z",
"prorationDate": 1788434542
}
```
Then apply:
```bash theme={"system"}
agenta subscriptions change-plan 9811c905-1f61-459d-af03-18fb5aad47d8 --to 3f0c2d61-... --json
```
```json --json theme={"system"}
{
"direction": "upgrade",
"applied": true,
"status": "active",
"unitAmountMinor": 4900,
"currency": "eur",
"quote": { "...": "the quote above" }
}
```
* **Upgrade** (the new plan costs more): the prorated difference is charged on the saved card **now**; `dueTodayMinor` says how much.
* **Downgrade** (same or lower price): nothing today; the subscriber keeps the current plan until `effectiveAt`, the current period end, and `agenta subscriptions list` shows the scheduled switch as `pendingPlanChange` until then.
* **Cancel a scheduled downgrade**: run the command again with `--to` set to the current plan's id. The result says `direction: "revert"`, nothing is charged, and `pendingPlanChange` is null again.
* Money fields are integer minor units (`4900` = €49.00). The subscription must be `active` or `trialing`.
## Give a credit
```bash theme={"system"}
agenta subscriptions credit -a --reason [--json]
```
A credit takes money off the subscriber's next invoice. The rules on when you can give one are on [Subscriptions](/payments/subscriptions#credits). You must be signed in (`agenta login`) as an owner or admin.
Subscription ID.
The credit in the plan's currency, as a decimal (`5.00`).
Why you give it. The subscriber never sees it.
```bash theme={"system"}
agenta subscriptions credit 9811c905-1f61-459d-af03-18fb5aad47d8 -a 10 --reason "Two days of downtime"
```
```bash Output theme={"system"}
€10.00 credited.
It comes off their next invoice on Oct 12, 2026, leaving €50.76 to pay.
They now hold €10.00 in unspent credit with you.
```
With `--json`, the result has `amountMinor`, `currency`, `reason`, `creditBalanceMinor`, `nextInvoiceMinor`, `nextInvoiceDueAfterCreditMinor`, `nextInvoiceAt`, `createdBy`, `createdAt`, and the `idempotencyKey` the command used. Each run is a new credit: running the command twice gives two credits.
## List credits
```bash theme={"system"}
agenta subscriptions credits [--json]
```
Prints each credit given on the subscription (amount, reason, date) and the buyer's unspent balance. With `--json`, the result is `{ items: [{ id, amountMinor, reason, createdBy, createdAt }], balanceMinor }`.
## Next steps
Look up who's behind a subscription.
Pull receipts for a paid billing cycle.
Manage subscriptions from your own backend.
How subscription products and billing cycles work.
# Verify
Source: https://docs.agentaos.ai/cli/verify
Submit your business verification from the terminal, check where it stands, and send it back after changes.
`agenta verify` submits your business verification. A person reviews it, and once your business is verified you can accept live payments. Test payments work before that.
```bash theme={"system"}
agenta verify status
```
## Commands
| Command | What it does |
| - | - |
| `agenta verify declaration` | The five statements you attest to when you submit. |
| `agenta verify submit …` | Submit business verification. |
| `agenta verify status` | Where your verification stands. |
| `agenta verify resubmit` | Send it back for review after the changes AgentaOS asked for. |
Every command takes `--json` for one JSON object, and `--business ` to act as a [business you manage](/cli/businesses).
## Declaration
Read the five statements before you submit. `--accept-declaration` on `verify submit` attests to all five.
```bash theme={"system"}
agenta verify declaration
```
```bash Output theme={"system"}
Submitting verification attests that:
- My pricing is visible before checkout, not after.
- My product name doesn't borrow someone else's brand.
- Any reviews or user counts on my site are real.
- My site has a public Privacy Policy and Terms.
- My product isn't built for spam, fraud or harassment.
Not supported (we do not sell these): Adult content, Gambling, Weapons, Drugs & supplements, Financial advice, Counterfeit goods, Data scraping, Engagement farming.
An application for one of these is refused, not reviewed.
If one turns out not to be true, we stop payouts and may close the account. A review rejected for a prohibited product or fraud cannot be resubmitted for three months.
```
If an agent runs this for you, it reads the statements to you and you confirm each one. Only you can attest to them.
## Submit
```bash theme={"system"}
agenta verify submit --legal-name --registration-number --country --street --city --url --accept-declaration [options]
```
The command checks every required flag before it sends anything. If flags are missing, it names all of them in one message:
```bash Output theme={"system"}
✕ Missing required flags: --legal-name, --country, --street, --city, --url, --registration-number, --accept-declaration. Run verify declaration to read what --accept-declaration attests to.
```
`business` or `individual`.
The registered company name, or your full legal name.
The company registration number. Required when `--entity` is `business`.
The name buyers see on their statement. Defaults to `--legal-name`.
The country of registration, two-letter code, e.g. `EE`.
The registered address.
The city.
The postal code.
The country of the address, two-letter code. Defaults to `--country`.
Your product's page on your own site, e.g. `https://example.com`.
One sentence on what your product does.
`saas`, `digital`, `services`, `marketplace`, `physical` or `other`.
How buyers get what they pay for: `instant_digital`, `email_delivery`, `subscription_access`, `manual`, `scheduled_service`, `physical_shipped` or `other`.
Your sales volume band: `under_1k`, `1k_10k`, `10k_50k` or `over_50k`.
You already have paying customers.
Your site shows reviews or user counts.
Attest to the five statements in `agenta verify declaration`.
```bash theme={"system"}
agenta verify submit \
--legal-name "Acme OÜ" --registration-number 12345678 \
--country EE --street "Sepapaja 6" --city Tallinn --postal 15551 \
--url https://example.com --description "Invoicing for freelancers" \
--category saas --delivery instant_digital --volume under_1k \
--accept-declaration
```
```bash Output theme={"system"}
Your business verification is with us; a person reviews it, usually within 24 to 48 hours.
Nothing to do until we write back. verify status tells you where it has got to.
```
```json --json theme={"system"}
{
"verification": {
"state": "in_review",
"label": "In review",
"submitted": true,
"reviewedBy": "a person, usually within 24 to 48 hours"
}
}
```
A submitted application cannot be edited. If you run `verify submit` again, nothing is sent:
```bash Output theme={"system"}
Nothing was sent: your verification is already on file (in review).
Nothing to do until we write back.
```
If you run a platform, `agenta verify submit … --business ` files your client's verification for them. AgentaOS records that the platform filed it.
## Status
```bash theme={"system"}
agenta verify status
```
```bash Output theme={"system"}
Your business verification is with us; nothing to do until we write back.
```
The first line depends on where your verification stands:
| State | First line |
| - | - |
| Not submitted | You have not submitted business verification yet. Read verify declaration, then verify submit. |
| In review | Your business verification is with us; nothing to do until we write back. |
| Changes requested | Your verification is waiting on changes from you. |
| Verified | Your business is verified. |
| On hold | Your account is on hold. |
| Rejected | Your verification was rejected. |
When AgentaOS asks for changes, the next line lists them and ends with "Send it back with verify resubmit once done." On hold and rejected add the reason, and a rejection can add the date after which you can apply again.
## Resubmit
Make the changes AgentaOS asked for, then send the application back. You retype nothing: the application on file is reused.
```bash theme={"system"}
agenta verify resubmit
```
```bash Output theme={"system"}
Your verification is back with us for review; the application already on file was reused.
Nothing to do until we write back.
```
## Next steps
The steps to live payments.
Your go-live overview and the next command to run.
# Bank accounts by API
Source: https://docs.agentaos.ai/connect/bank-accounts
Save a bank account for a business you manage from your own app. The bank confirms the holder name; we keep only the last four digits.
Private preview Working end to end for the accounts that use it; details can still change. Tell us at [contact@agentaos.ai](mailto:contact@agentaos.ai) before you rely on it, and we support you directly. See [Product release phases](/getting-started/release-phases).
A marketplace or a platform whose sellers never log in here can save each seller's bank account from its own app. The account is the seller's: payouts for that business go to it, and where the bank offers a name check, a clear mismatch is saved but never paid.
## Before you start
* Ask us to open bank accounts by API for your platform. It is off until we switch it on, because a key that can add a payout account can move money. We open it for one platform at a time, after a conversation.
* Use a live key. A test key gets `403 live_key_required`.
* Act as the business: pass `{ business: id }` on the call or create the client with it. See [Build a platform](/guides/build-a-platform).
Without the switch, adding and retiring an account get `403 bank_accounts_by_api_closed`; asking for the requirements and listing still work. The business's bank account can still be added from the dashboard: open the business and go to Balances, Payout accounts.
## Add an account
The fields depend on the currency: an IBAN for EUR, a routing and an account number for USD.
```typescript theme={"system"}
const forSeller = new AgentaOS(process.env.AGENTAOS_API_KEY!, { business: seller.id });
const { quoteId, requirements } = await forSeller.bankAccounts.requirements('EUR');
```
`requirements` is a form schema, one entry per account type, each with its fields and validation. When a field is marked `refreshRequirementsOnChange`, such as the legal type, send the answers so far to `refreshRequirements({ quoteId, details })` and render the fields again.
```typescript theme={"system"}
const account = await forSeller.bankAccounts.create({
currency: 'EUR',
type: 'iban',
accountHolderName: 'Seller GmbH',
legalType: 'BUSINESS',
details: { IBAN: 'DE89370400440532013000' },
});
```
You get the account back as a thin reference: `accountIdentifierLast4`, `accountHolderName`, `nameMatchStatus` and `payable`. We keep the last four digits and a hash. The account number stays with our bank partner.
Where the bank offers a name check, it runs at once; not every currency has one. `payable: true` means the next payout can go there. `nameMatchStatus: "failed"` means the account is saved but never paid: the name on the account is not the business's. `partial` and `unknown` are paid. `payableReason` says why in a sentence you can show.
## What happens next
* **We email the owners.** Every account added through your key is emailed to your owners and, when the business has any, to the business's owners, one email each: the business, the currency, the last four digits, the holder name, that it came through your API key, whether the next payout goes there, and to reply at once if nobody at the platform did this.
* **Payouts use the newest usable account per currency**, unless the business chose one. A seller who logs in sees the account on their Payout accounts page like any other.
* **Retire an account** with `deactivate(id)`. A currency that pointed at it falls back to the newest usable account.
## The calls
| Call | What it does |
| - | - |
| `bankAccounts.requirements(currency)` | The fields this currency needs |
| `bankAccounts.refreshRequirements({ quoteId, details })` | The fields again after an answer that changes them |
| `bankAccounts.create(params)` | Save an account, needs a live key |
| `bankAccounts.list()` | The active accounts, thin references |
| `bankAccounts.deactivate(id)` | Retire one, needs a live key |
The same calls are in the [API reference](/api-reference/bank-accounts/create) as `/gateway/bank-accounts`.
# Platform fees
Source: https://docs.agentaos.ai/connect/fees
Take a share of each client sale: a percentage of the price before VAT plus a fixed amount, paid to you once the sale clears.
Private preview Working end to end for the accounts that use it; details can still change. Tell us at [contact@agentaos.ai](mailto:contact@agentaos.ai) before you rely on it, and we support you directly. See [Product release phases](/getting-started/release-phases).
As a platform you can take a **share** of every sale a client business makes. You set it per business. Another app of your own company carries no share.
## How a business you manage is priced
Managing businesses is not tied to a plan: platforms work on every plan. A client's business pays the AgentaOS fees of its own plan, which is pay as you go unless it upgrades. Another app of your own company is priced with you, on your plan. See [Pricing](/mor/pricing).
## How the share is worked out
The share is a **percentage of the sale's price before VAT, plus a fixed amount** per sale.
On a €100 sale (before VAT) with a 10% + €0.50 share, you get €10.50.
* **VAT is left out.** We are the merchant of record and remit the VAT; it is never part of your share.
* **Our own fee stays on the total paid**, as for any sale. See [Pricing](/mor/pricing). The client keeps what remains after our fee and your share.
* **The share is fixed on each sale when it is made.** Changing it later affects new sales only. Past sales keep the share they were made with.
* **Every sale carries it**: a one-time sale, each subscription renewal, and each paid invoice.
* **The fixed part is in the sale's currency.** A fixed part of 0.50 is €0.50 on a EUR sale and \$0.50 on a USD sale.
* **The buyer sees nothing of it.** The buyer's invoice and receipt show the product and the price. The share is between you and your client. Your client sees it on each sale page as **Platform fee** with your name, for example **Platform fee (AgencyOne)**.
## Set the share
The share is set in the dashboard only. There is no API for it. You can set it before the client is verified; the fees then wait, as described under [When you are paid](#when-you-are-paid). Your client cannot change or refuse the share: only you set it.
In the sidebar, click **Businesses**, then click the business.
Under **Your share**, click **Change the share**, enter the percentage and the fixed amount. The editor shows what the client keeps on an example sale, after our fee and yours. The card shows the terms and what they earned you this month.
You can read the current share on each business as `platformFee` (`{ bps, fixedMinor }`: basis points, where `1000` is 10%, and the fixed part in minor units). With no share set, it is `{ bps: 0, fixedMinor: 0 }`, never `null`. See [Business fields](/sdk/pay-businesses#business-fields).
## When you are paid
Your share is paid to you with **your own payout**, once that sale's reserve period ends: 120 days after the sale, unless the client's account has a different reserve period. The client's own money from the sale clears sooner, 14 days after the sale. The **Platform fees** card on your **Balances** page shows the date for each share. Platform fees ride in the same payout as your own sales: one payout per currency. The 1% payout fee applies to the whole payout, fees included. See [Payouts](/payouts/overview#fees).
The client must be in good standing at that point: verified, and not on hold. A share from a client that is not yet verified waits until they are.
A payout that contains only platform fees gets no self-billed invoice. The section **Platform fees collected** on its payout statement is the record of that income. See [Payout statements](/payouts/statements).
## Refunds, disputes and holds
* **Refund.** The buyer gets the money back. If you already received your share of that sale, you return it. A share not yet paid to you is dropped.
* **Lost chargeback.** We take back the share that was paid to you for that sale. A share not yet paid to you is dropped.
* **Hold.** If we put a client on hold after it went live, its fees wait until we resume it. Fees you already received are not taken back because of a hold.
See [Refunds, disputes and chargebacks](/mor/refunds-and-disputes) for the client's side.
## See what is coming
The **Platform fees** card is on the **Balances** page, in the **Overview** tab. It shows one line per day for the next five days, soonest first, then one **Later** line. A day where the client is not yet in good standing says it is paid once that client is verified.
For a quick total per business, `feesThisMonth` on each [business](/sdk/pay-businesses#business-fields) gives your share of its live sales this month, per currency, ready to show.
## Next steps
What a platform is, and who does what.
How your balance moves before it pays out.
# Platforms
Source: https://docs.agentaos.ai/connect/overview
Run sales for your clients, or for other apps of your own company, from one AgentaOS account. What a platform is and who does what.
Private preview Working end to end for the accounts that use it; details can still change. Tell us at [contact@agentaos.ai](mailto:contact@agentaos.ai) before you rely on it, and we support you directly. See [Product release phases](/getting-started/release-phases).
A **platform** is an AgentaOS account that manages other businesses. You build software for other people: an agency that launches products for clients, a site builder, a marketplace of apps. Each of your clients gets its own **business** on AgentaOS. It sells under its own name, gets its own invoices and payouts, and you run it with your own API key.
AgentaOS stays the merchant of record for every sale, yours and your clients'.
## Two kinds of business
Someone else's company. It is verified on its own, including an identity check that only the client can complete. You can take a share of each of its sales.
A second product you own ("same company"). It is verified and priced with you. It carries no share and no identity check of its own.
You choose when you create the business: `sameLegalEntity: true` in the SDK, `--same-company` in the CLI.
## Who does what
| Step | Who does it |
| - | - |
| Create the business | You |
| Invite the client to log in (optional) | You. We email the invitation, or you send the link yourself. |
| File the business details, product and declarations | You or the client. If you file it, the review records that the platform filed it, and our reviewers see that. |
| Identity check | **Only the client.** You can generate the link and send it; you cannot complete it for them. |
| Create products, checkouts and payment links | You, acting as the business, or the client |
| Set your share of its sales | You, in the dashboard only |
| Receive its webhook events | Your own webhook endpoint, automatically |
Most clients never log in. You can run everything for them and only send them the identity check link.
Removing a business you manage, or transferring it to another platform, is not in the dashboard today. Contact us at [contact@agentaos.ai](mailto:contact@agentaos.ai).
White-label: create the business with `sendInvitationEmail: false` and we send no email. You get `inviteUrl` back and send it to your client yourself, under your own brand.
## Acting as a business
Every API call can run as a business you manage. Send its id in the `AgentaOS-Account` header, or pass `{ business }` in the SDK, or `--business ` in the CLI. Without it, the call acts on your own account.
```typescript theme={"system"}
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
// One call only
await agentaos.checkouts.create({ amount: 49, currency: 'EUR' }, { business: biz.id });
// Or a client on which every call runs as the business
const client = new AgentaOS(process.env.AGENTAOS_API_KEY!, { business: biz.id });
```
Only your own key or session may act for a business you manage. Any other id returns `403 You cannot act as this business.` See [Acting for a business](/sdk/pay-businesses#acting-for-a-business).
## Next steps
The five steps, in the SDK and the CLI.
Your share of each client sale, and when it is paid.
One endpoint for all your clients' events.
Every method, parameter and field.
# Platform webhooks
Source: https://docs.agentaos.ai/connect/webhooks
One webhook endpoint for all your clients. Every event says which business it happened in, and account.updated tells you when a client's verification changes.
Private preview Working end to end for the accounts that use it; details can still change. Tell us at [contact@agentaos.ai](mailto:contact@agentaos.ai) before you rely on it, and we support you directly. See [Product release phases](/getting-started/release-phases).
As a platform you do not set up a webhook for each client. **Your own webhook endpoint also receives every event of every business you manage**, signed with your own signing secret. It works in test mode too. `account.updated` is always a live event (`livemode: true`), because it is about your client's real verification. It still reaches your endpoint while you build in test mode, because an account has one webhook endpoint.
Set your endpoint once, as any account does: see [Webhooks](/payments/webhooks).
## Which business an event is from
Every event carries `business`: the id of the business it happened in.
* An event from your own sales carries your own id.
* An event from a client's sale carries that client's business id.
```json theme={"system"}
{
"id": "evt_8c7d6e5f-4a3b-2c1d-0e9f-8a7b6c5d4e3f",
"type": "checkout.session.completed",
"business": "5b1f0c2e-8a4d-4c7b-9e21-3f6a7d8c9b10",
"data": { "session_id": "kR9pQwErTyUiOpAsDfGh", "amount": "49.99", "currency": "EUR", "livemode": true }
}
```
Route on `business` to the right client in your own system. Store our business id on your side when you create the business; it is the only link between the two.
If the client has its own webhook URL, it still gets its own copy, signed with its own secret. Yours is separate.
## When a client's verification changes
`account.updated` fires when a business's verification changes: its identity check lands (verified, or declined), or our reviewers approve, reject, hold or resume it, or ask for changes.
```json theme={"system"}
{
"id": "evt_4e1b9a0c2d3f5a67",
"type": "account.updated",
"business": "5b1f0c2e-8a4d-4c7b-9e21-3f6a7d8c9b10",
"data": {
"verification": "verified",
"changes_requested": false,
"identity_verified": true,
"livemode": true
}
}
```
`unverified`, `in_review`, `verified`, `on_hold` or `rejected`.
We asked the business for a change and are waiting on it.
The identity check is done. For another app of your own company, this is your company's check.
Always `true`: verification is about live money.
A typical use: when `identity_verified` turns `true`, stop reminding your client to complete the identity check. To read the current state at any time, call [`businesses.retrieve(id)`](/sdk/pay-businesses) and look at `status` and `identityVerified`. [Business status](/sdk/pay-businesses#business-status) shows how `verification` matches `status` and the status in the dashboard.
## Next steps
Every event and its fields.
Check each delivery before you trust it.
# Agent wallet
Source: https://docs.agentaos.ai/experimental/agent-wallet
An on-chain wallet that an AI agent operates. Private preview, available on request.
Private preview Working end to end for the accounts that use it; details can still change. Tell us at [contact@agentaos.ai](mailto:contact@agentaos.ai) before you rely on it, and we support you directly. See [Product release phases](/getting-started/release-phases).
The agent wallet is an on-chain wallet that an AI agent operates, holding USDC or EURC. It is in private preview and available on request only: the CLI, the MCP server, and the Payment SDK do not include agent wallet tools. To ask for access, write to [contact@agentaos.ai](mailto:contact@agentaos.ai) and tell us what you need the wallet for.
The agent wallet is separate from stablecoin payouts. To receive your balance in USDC or EURC at a wallet address you control, see [Stablecoin payouts](/payouts/stablecoin-payouts).
To get paid, use the [payment tools](/mcp/setup), the [Payment SDK](/sdk/pay-overview), or the [CLI](/cli/install).
# 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 your statement shows `AGENTAOS*` followed by the seller's name, for example `AGENTAOS* KNEEGUARD`. 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 bought 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
Contact the seller to cancel, or cancel in the seller's own software or app. Your receipt names the seller: the email is titled "Receipt from" and the seller's name. To reach the seller, reply to the receipt email: your reply goes to the seller. Canceling 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.
## The AgentaOS name on your statement
AgentaOS is the merchant of record, the legal seller on the transaction, so your statement shows `AGENTAOS*` followed by the seller's name, for example `AGENTAOS* KNEEGUARD`. See [How merchant of record works](/mor/how-it-works).
# Dashboard tour
Source: https://docs.agentaos.ai/getting-started/dashboard
A tour of app.agentaos.ai: what each page in the sidebar does, and where to find your products, money, invoices, and settings.
[app.agentaos.ai](https://app.agentaos.ai) is where you run your sales: you create products, follow the money, send invoices, and manage your team and API keys. This page walks through the sidebar from top to bottom.
The **Test** / **Live** switch at the bottom of the sidebar decides which data every page shows. Test mode is free to explore and processes no real payments. Live mode handles real money and opens after your business is verified. See [Test mode and testing](/getting-started/test-mode).
## Home
Home is the page you see after you sign in. One card names the next step to your first real sale: **Create your first product**, then **Verify your business**, then **Go live**. Under the step, a count says how many of the three are done, and **All steps** opens the full list on the **Go live** page. After you go live, the card says "Setup done. Next: your first sale." **New product** at the top right opens the product form.
Below the card, one strip shows **Revenue**, **Sales**, **Customers**, **Active subs**, and **MRR**. Under the strip, **Gross volume** charts your sales for the last 7 days or 30 days, and **Recent activity** lists your latest payments. When you have money in your balance, a card beside the chart shows **Available now** and your payout schedule.
## Go live
**Go live** tracks what is left before you can take real payments. The chip next to it counts the steps done, such as **1/3**. The main step is to verify your business. See [Go live](/guides/go-live) and [Account review](/mor/account-review).
## Products
**Products** lists everything you sell. Each product has a price, one-time or subscription, and a buyer link you can copy from its row. The **Checkouts** tab lists each checkout a buyer started. See [Products and payment links](/payments/payment-links).
## Discounts
**Discounts** holds the codes a buyer can type at checkout for a lower price. See [Discount codes](/payments/discount-codes).
## Transactions
**Transactions** lists every payment and payout on your account. Search by name, amount, or ID. Filter by period, by **Payments** or **Payouts**, and by currency. **Export CSV** downloads the list.
## Balances
**Balances** shows your money in EUR and USD. It has three tabs:
* **Overview**: your balance, split into **Available now**, **On the way**, and **Reserve**, with the **Total** at the bottom.
* **Payouts**: the payouts sent to you.
* **Payout accounts**: the bank accounts and wallets that receive your payouts.
See [Payouts](/payouts/overview) and [Payout accounts](/payouts/payout-accounts).
## Invoices
**Invoices** lists every invoice in four tabs: **All**, **Open**, **Paid**, and **Void**. Every sale gets an invoice automatically, and **Create invoice** bills a customer by hand. See [Invoices](/payments/invoices).
## Subscriptions
**Subscriptions** lists the subscriptions your buyers started by paying a subscription product. From here you can change a plan or cancel. See [Subscriptions](/payments/subscriptions).
## Customers
**Customers** lists everyone who paid you or whom you billed, with their revenue and number of payments. See [Customers](/payments/customers).
## Businesses
**Businesses** lists the businesses you run from this account: another app of your company, or a client's business. The figures above the list say how many there are, your share this month, and how many need you. To add one, click **Add a business** on that page or in the account menu at the top of the sidebar. Click a business for its page, or **Open** to switch into it. A business that a platform manages does not have this entry. See [Platforms](/connect/overview).
## Developers, Support, and Guides
**Developers** opens a menu with links to **AI agents**, **API keys**, and **Webhooks**, and to the docs, the API reference, the SDK, the CLI, and the MCP server. **Support** opens an email to the AgentaOS team. **Guides** opens these docs.
## Settings
**Settings** has six tabs:
Your sign-in email and your passkey.
Your business details, the logo on invoices and pay pages, and whether prices include VAT.
Invite teammates to your account.
API keys, webhooks, and the sites allowed to show your embedded checkout.
Your plan and the fee for each payment method.
A legacy tab. You can ignore it.
API keys belong to the mode you are in: a test key works only in test mode, and a live key only in live mode.
## Next steps
Your first payment in test mode, start to finish.
Create a product and pay it yourself.
Why verification exists and what AgentaOS handles for you.
Everything in the dashboard, from code.
# Glossary
Source: https://docs.agentaos.ai/getting-started/glossary
Plain-English definitions for the payments and tax terms you'll meet in AgentaOS.
New to payments? These are the terms that show up across the docs, in one place.
The legal seller of your product. Your buyer pays AgentaOS, not you, so AgentaOS collects the money, charges and remits the tax, issues the invoice, and pays you out. AgentaOS carries the tax liability as the legal seller. A lost dispute costs you the sale plus the dispute fee: see [Refunds and disputes](/mor/refunds-and-disputes).
VAT charged at the rate of the **buyer's** country, not yours. While AgentaOS sells under the EU distance-selling threshold, the checkout charges EU consumers the Estonian rate instead. See [How tax works](/mor/tax#the-rate-on-eu-sales-today).
The EU scheme that lets one registration cover VAT across all EU countries. AgentaOS does not use OSS: it sells under the EU distance-selling threshold from its registration in Estonia. Either way, you never register in each country you sell to.
For a B2B sale to a VAT-registered business in another EU country, VAT shifts to the buyer, so the invoice shows no VAT. A buyer who enters a valid EU VAT ID at checkout is handled this way.
The thing you sell, with its price. The API calls a product a payment link; there is no separate `product` API. The product's API ID is the `linkId`.
The payment link UUID (`paymentLinks.id`). Pass it to `checkouts.create({ linkId })`. On the product page, open **Building this into your site?** and click **Copy ID**. It is not the `/pay/…` URL and not the checkout `sessionId`.
The public `/pay/…` URL of one product, which every buyer can open. In the API it is the payment link's `checkoutUrl`. The `id` / `linkId` is for the API.
A single attempt to collect one payment. Created on its own or from a product with `checkouts.create({ linkId })`. Every checkout has a `checkoutUrl` you send the buyer to (`/checkout/{sessionId}`).
A recurring charge on a card on file, billed automatically every cycle until canceled.
An **invoice** is the tax document for a sale (seller, buyer, VAT breakdown), issued automatically on every payment. A **receipt** is the emailed confirmation the buyer gets that they paid.
The behind-the-scenes step where a completed payment becomes money in your balance, after the card or bank network clears it.
**Available now** is cleared money that the next payout sends. **On the way** is sales that are still clearing, for 14 days after each sale, with the date each amount becomes available. **Reserve** is a safety hold on new accounts to cover refunds and chargebacks; the row shows the date it frees up. See [Payouts](/payouts/overview).
A portion of your balance held temporarily on newer accounts to cover potential refunds or chargebacks. It releases automatically.
Money moving from your **Available now** balance to your bank account. Payouts go out in two windows each month, the 1st and the 15th. A new account starts on **You ask**: click **Request payout** and the next window pays it. Choose **Automatic** to be paid every window. See [Payouts](/payouts/overview).
When a buyer's bank reverses a card payment. While the dispute is open, the sale's money shows in **Paused (disputed)** on your balance and is not paid out. See [Refunds and disputes](/mor/refunds-and-disputes).
**Test mode** uses fake money and test cards, safe to explore, no verification needed. **Live mode** takes real money and unlocks after business verification.
A signed HTTP call AgentaOS makes to your server when something happens (like a completed checkout), so your app does not have to keep asking.
# 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:
Create a product and start selling without writing a line of code.
Integrate AgentaOS into your app with the SDK, CLI, or REST API.
## No-code
The fastest way to get paid: create a product in the dashboard and share its link.
In the sidebar, click **Products**, then click **New product**. Give it a name and a price, choose one-time or subscription, and save. The product gets its buyer link when you save it.
Click **Copy link** and send it anywhere: an email, a chat message, social, or a QR code. Every visitor gets their own checkout, and one link can be paid many times.
Your buyer pays on the secure checkout by card, with Apple Pay and Google Pay on devices that support them, or by bank transfer on EUR sales. The payment, tax, invoice, and payout are handled for you. Watch it land on **Home** or under **Transactions**. To be paid out, add a payout account under **Balances → Payout accounts**.
Fees and plans are on [Pricing](/mor/pricing).
## Code integration
Everything the dashboard does is available over the SDK, CLI, and REST API.
Sign up at [app.agentaos.ai](https://app.agentaos.ai). No card required. Then open **Developers → API keys** in the sidebar 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={"system"}
// 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={"system"}
# curl -fsSL https://agentaos.ai/install | bash && agenta login
agenta pay checkout -a 49 -c EUR -d "Pro plan"
```
```bash REST API theme={"system"}
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 three env vars, and run.
```bash .env theme={"system"}
AGENTAOS_API_KEY=sk_test_your_key
AGENTAOS_WEBHOOK_SECRET=whsec_your_webhook_secret
APP_URL=http://localhost:3000
```
```typescript server.ts theme={"system"}
import express from 'express';
import { AgentaOS, WebhookVerificationError } 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) => {
let event;
try {
event = agentaos.webhooks.verify(
req.body, // raw Buffer
req.header('X-AgentaOS-Signature')!, // format: t=,v1=
process.env.AGENTAOS_WEBHOOK_SECRET!,
);
} catch (err) {
if (err instanceof WebhookVerificationError) {
res.status(400).json({ error: 'Invalid signature' });
return;
}
throw err;
}
if (event.type === 'checkout.session.completed') {
const { amount, currency } = event.data; // amount is a string
console.log(`Paid ${amount} ${currency}`);
// Fulfill the order: grant access, send an email, update your database.
// `payer` and `txHash` are also on event.data, but only set on
// stablecoin checkouts; they are null for card payments.
}
res.json({ received: true });
});
app.listen(3000, () => console.log('Listening on :3000'));
```
To run it, you need Node.js 20.6 or later:
1. Run `npm install express @agentaos/pay`.
2. AgentaOS does not deliver webhooks to `localhost`, so give port 3000 a public HTTPS address with a tunnel service.
3. In **Settings → Developers → Webhooks**, set the webhook URL to the tunnel address plus `/webhooks`, and copy the signing secret into `AGENTAOS_WEBHOOK_SECRET`.
4. Start the server with `npx tsx --env-file=.env server.ts`. `tsx` runs the TypeScript file directly, and `--env-file` loads the `.env` values.
`POST /checkout` returns a `checkoutUrl`. Pay it with the test card, and your `/webhooks` handler logs the payment.
## 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.
# Product release phases
Source: https://docs.agentaos.ai/getting-started/release-phases
What Private preview, Public preview and Generally available mean on an AgentaOS feature, and what to expect from each.
Every feature in the dashboard, the API and these docs is in one of three release phases. A page carries the phase at the top of the page, under its title. A page without it is generally available.
| Phase | Who can use it | How complete it is | Support |
| - | - | - | - |
| **Private preview** | By arrangement with us: tell us, and we support you directly. Some features need switching on for your account; the page says so. | Working end to end for the accounts that use it. Details can still change, and a change can break an integration. | Directly from us, at [contact@agentaos.ai](mailto:contact@agentaos.ai). |
| **Public preview** | Everyone with an AgentaOS account. Some features need us to switch them on for your account; the page says so. | Not feature-complete. New functionality can still arrive before release, and a change can break an integration. | [contact@agentaos.ai](mailto:contact@agentaos.ai) and these docs. |
| **Generally available** | Everyone with an AgentaOS account. | Feature-complete. No breaking changes; any future change arrives through the SDK and CLI release notes. | [contact@agentaos.ai](mailto:contact@agentaos.ai) and these docs. |
## In preview today
| Feature | Phase |
| - | - |
| [Platforms](/connect/overview): run your clients' businesses from one account and take a share of their sales. | Private preview |
| [Agent wallet](/experimental/agent-wallet): the crypto wallet an AI agent operates. | Private preview |
| [Stablecoin payouts](/payouts/stablecoin-payouts): be paid in USDC or EURC to a wallet instead of your bank account. | Public preview |
Before you use a feature in private preview, tell us at [contact@agentaos.ai](mailto:contact@agentaos.ai) with the account it is for, so we can support you directly. If the feature's page says it needs switching on, we switch it on for that account and confirm by email.
## Preview terms
* The feature is on for your account, in test mode and in live mode, like any other feature.
* We tell you before a change that affects your integration, by email.
* The money rules do not change with the phase: AgentaOS is the merchant of record for every sale, and a preview feature's fees are on its own page.
# Test mode and testing
Source: https://docs.agentaos.ai/getting-started/test-mode
Try every part of AgentaOS with test money: switch modes, pay with test cards, check test invoices and webhooks, then go live.
Test mode is a full copy of AgentaOS that uses test money. You create products, pay your own checkout with a test card, and get invoices, receipts, emails and webhooks, and no real money moves. Every new account starts in test mode, and you do not need to verify your business to use it.
Test data and live data are separate. A product, checkout, customer or invoice that you create in test mode is not visible in live mode, and the reverse is also true.
## Switch between test and live
The **Test** / **Live** switch is at the bottom of the sidebar. It sets which data every page of the dashboard shows.
While you are in test mode, a strip across the top of the dashboard says **Test mode · No real payments are processed**. When you switch to test, the dashboard confirms with **Switched to Test mode**: "You are now viewing test data. Payments use test funds."
If your business is not verified yet, **Live** takes you to the go-live page instead of an empty live view. See [Go live](/guides/go-live).
## Pay a test checkout
Open any test product's checkout link. A banner at the top of the checkout shows the test card:
**Test payment · 4242 4242 4242 4242 · any expiry · any CVC**
Click the number to copy it, then paste it into the card field on the checkout. Use any future expiry date and any three-digit CVC. The payment succeeds, and the checkout shows its success page.
Type or paste a card number only into the card field on the checkout page. Never send a card number to the API, in a script, or in a support message, in test mode or in live mode.
## Test a failed payment
Use these cards on a test checkout to see what a buyer sees when a payment fails. Each one takes any future expiry date and any CVC. Every row below was run on the AgentaOS checkout.
| Card number | What the buyer sees |
| - | - |
| `4242 4242 4242 4242` | The payment succeeds. |
| `4000 0000 0000 0002` | "Your card has been declined." |
| `4000 0000 0000 9995` | The card is declined for insufficient funds. |
| `4000 0025 0000 3155` | The bank asks the buyer to confirm the payment (3D Secure): a test confirmation window opens on the checkout. Complete it and the payment succeeds. Fail it and the checkout shows "Your bank couldn't confirm it's you. Try again, or use a different card." |
| `4000 0000 0000 0259` | The payment succeeds, and the buyer's bank disputes it later. Use it to see a dispute on the sale. |
## Test invoices, receipts and emails
AgentaOS issues invoices and receipts for test payments, and marks each one so that nobody mistakes it for a real document:
* The checkout success page says "Test payment: no money was paid. This invoice and receipt are not valid."
* A test invoice PDF has a **TEST** badge next to its title and the line "TEST MODE: not a tax invoice. No money was paid."
* A test receipt PDF has the same badge and the line "TEST MODE: not a valid receipt. No money was paid."
* Emails about a test payment have `[TEST]` at the start of the subject line.
## Money, payouts and verification
Test cards are not charged, so test payments bring no real money into your balance and nothing is paid out to your bank account. You do not need to verify your business to sell in test mode; verification applies to live mode only.
## For developers
### Test keys and live keys
An API key belongs to one mode. The key's prefix tells you which one:
| Prefix | Mode | Data it reads and writes |
| - | - | - |
| `sk_test_` | Test | Test data only |
| `sk_live_` | Live | Live data and real money |
To create a test key, switch to **Test**, open **Developers → API keys** in the sidebar, and click **Generate Test Key**. The same button reads **Generate Live Key** in live mode. There is one base URL for both modes; the key decides the mode, so you do not set a mode anywhere else. Keep keys in environment variables and never commit them.
```typescript theme={"system"}
import { AgentaOS } from '@agentaos/pay';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!); // sk_test_... while you build
```
The API reference explains how the key and the mode work on each request: see [Authentication](/api-reference/introduction#authentication) and [Test and live keys](/api-reference/introduction#test-and-live-keys).
### Webhooks in test mode
Test payments send webhooks, the same as live payments. Test events and live events go to the same webhook endpoint. Each event has a `livemode` field: `false` for a test event and `true` for a live event. Check `livemode` in your handler so that a test event never triggers a real fulfillment.
To receive test events on your own machine, expose your local server with a tunnel and register the tunnel URL as your webhook endpoint. See [Webhooks](/payments/webhooks).
### Embed a test product on localhost
A test product's [embedded checkout](/payments/embedded-checkout) loads on any page served from `localhost` or `127.0.0.1`, on any port and over `http` or `https`. You do not need to add these to your approved sites. A live product loads only on the approved sites you add.
## Go live
When your tests pass, verify your business and replace your `sk_test_` key with an `sk_live_` key. Add a bank account before your first payout. The [Go live guide](/guides/go-live) lists each step.
Verify your business and take real payments.
Receive and verify events.
# Revenue & Pricing Audit
Source: https://docs.agentaos.ai/grow/pricing-audit
Our AI pricing agent reads your pricing page and writes up how your model compares, where you lose revenue after the sale, and what to change first. Included with your account.
The Revenue & Pricing Audit is your free AI pricing review. In the words of the **Go live** page, our pricing agent reads your pricing page and tells you what your pricing model is costing you, where you lose revenue after the sale, and what refunds and chargebacks take back.
The audit has a list price of €299. It is included with your AgentaOS account: the dashboard shows the price struck out, next to the label **Included**. It usually arrives within 24 hours, as a PDF, in the app and in your inbox.
In the dashboard the audit is yours once you verify your business: the **Go live** page shows it sealed until then, and it unlocks the moment your ID check is handed in. If you asked for it on the agentaos.ai home page, it is already yours and nothing is held back.
## A growth merchant of record
A merchant of record sells your product in its own name, and AgentaOS is the merchant of record on every sale. See [How merchant of record works](/mor/how-it-works). The agentaos.ai home page defines the growth merchant of record:
> A Merchant of Record sells your product in its own name: it takes the payment, files the tax and handles disputes. A growth Merchant of Record also helps you earn more. AgentaOS includes an AI pricing agent that finds why users don't pay and tells you what to charge.
The home page FAQ adds: "Our AI pricing agent looks at your product and pricing page, scores seven areas, and sends you a score out of 100 with the fixes worth the most." The Revenue & Pricing Audit is that report. Your published audit carries a score from 0 to 100 and a grade.
## Get your audit
There are three ways to ask for the audit. Each one records the same request on your account.
### From the agentaos.ai home page
On [agentaos.ai](https://agentaos.ai), the home page says "Paste your product link. Get your free AI pricing review." Paste your product link into the box under "Turn your users into paying customers." and click **Review my pricing**.
The sign-in page says "Your free pricing review for yourapp.com starts after you sign in."
When you arrive, a notice says "Your pricing review for yourapp.com has started. Usually within 24 hours. It lands on your Go live page and in your inbox." The report on **Go live** says **Being written**.
Only the link from the home page is queued this way. Anyone else asks from the **Go live** page.
### From the Go live page
In the sidebar, click **Go live**. The report sits above the three steps, with the price struck out and **Included**. If you have not asked for it yet, it is sealed until you verify: it says **Unlocks when you finish step 1**, and once the first two screens are filled in, **Unlocks after your ID check**.
Step 1 is the business verification. The report unlocks as soon as your ID check is handed in, while we are still checking. See [Verify your business](/mor/verification).
Click **Get my audit** on the unlocked report. The form opens and asks one question per screen; on the last screen click **Get my audit**. The report then says **Being written**.
### From Home
Until your first live sale, **Home** carries one card with your next step. While the audit is still sealed for you, the line under the step says what verifying unlocks: real payments, payouts to your bank, and your free Revenue & Pricing Audit. From the moment it is unlocked, the line is the link **Get your Revenue & Pricing Audit**, which opens the same form as on the **Go live** page; once asked for, it shows where the audit stands.
## Information we ask for
We need a live web page where we can see your price. The form asks four questions. Only the first one is required.
| Screen | Question | Your answer |
| - | - | - |
| 1 | **Where can we see it?** | The full address of your product page, starting with `https://` |
| 2 | **What does it do?** | One sentence. Optional. |
| 3 | **What kind of business is it?** | **SaaS / software**, **Digital goods or content**, **Professional services**, **A marketplace**, **Physical goods**, or **Something else**. Optional. |
| 4 | **How do buyers get it?** | **Instantly, online**, **As a service, over time**, **Shipped to them**, or **Something else**. Optional. |
The form says the last question is "The biggest single driver of dispute risk, so it shapes the report." The home-page path sends your link only.
Your answers also fill the product part of the business verification form, so you do not type them twice. See [Verify your business](/mor/verification).
## The pricing agent and the turnaround
Our AI pricing agent reads your pricing page and writes the audit. The form says "Usually within 24 hours."
Until the audit is ready, the report on **Go live** shows **Being written**, the day you asked, and "Usually lands within 24 hours, here and in your inbox."
## The finished audit
The audit is a PDF. When it is ready, three things change:
* You get an email with the subject "Welcome. Your pricing audit is in." It lists what the report covers, and its **Read your audit** button opens the **Go live** page.
* The report on **Go live** changes to **Your audit is ready**, with your grade on its cover and the score beside it, and the button **Read your audit**, which opens the PDF.
* **Home** shows **Your audit is ready** and **Read it**, until your first live sale.
The email says the report covers:
* How your pricing compares to the rest of your category.
* Where you lose revenue after the sale.
* How much refunds and chargebacks take back.
The audit has a score out of 100 and a grade. It ends with the changes to make first. The example below is an audit for a product with one plan at €19 a month.
The audit is yours to keep. It changes nothing about your account, and it is not part of the steps to real money.
## Use the findings
To act on a recommendation, change your products and plans:
* **A new price or a new plan.** Each price is its own product. A monthly plan and a yearly plan are two products. See [Products and payment links](/payments/payment-links).
* **A trial, or an upgrade path.** Set the trial on the plan, and move subscribers between plans. See [Subscriptions](/payments/subscriptions).
* **A launch offer.** Give buyers a code to type at checkout. See [Discount codes](/payments/discount-codes).
To ask a question about your audit, reply to the email it came with.
## One audit per business
Each business gets one audit. When you send the form again, we keep your first request and update the link and answers that you gave. After the audit is published, the report on **Go live** shows **Read your audit** in place of **Get my audit**.
## Next steps
Create a product at the price the audit suggests.
Add a yearly plan, a trial, or an upgrade path.
Verify your business and take real money.
AgentaOS is the legal seller on every sale.
# Accept your first payment
Source: https://docs.agentaos.ai/guides/accept-your-first-payment
Create a product in the dashboard, share its buyer link, pay it yourself in test mode, and see the sale, the VAT, and your net. No code.
This guide takes you from no product to a first sale in the dashboard. You create a product, pay for it yourself as a test buyer, and then find the sale, the invoice, and your net amount. Everything runs in **Test mode**, so no real money moves.
The full product settings are on the [Products](/payments/payment-links) page. The buyer's checkout, field by field, is on the [Checkouts](/payments/checkouts) page.
## Before you start
* An AgentaOS account. Sign up at [app.agentaos.ai](https://app.agentaos.ai). The dashboard opens in **Test mode**.
* The test card `4242 4242 4242 4242`, with any future expiry date and any CVC. Type it only into the card form on the checkout page.
## Create the product
In the sidebar, click **Products**, then click **New product**. The form opens with the title **Create a new product**.
Under **Details**, type a **Product name**, for example `Starter guide`, and a **Description**. The buyer sees both on the checkout.
Under **Pricing**, type the price, for example `19`, and pick **EUR** or **USD**. Leave **One-time** selected.
Under the price, the form shows what a buyer pays with VAT. For a €19 product, it reads "EU consumers pay €23.56 (€19.00 + €4.56 VAT), you receive €19.00 before fees. Everyone else pays €19.00." Select **Prices include VAT** if your price already contains the VAT.
Click **Create product**. The next screen says "Starter guide is live". Live here is the product's word for published. You are still in test mode.
## Share the buyer link
Click **View product** to open the product's page. Every product has one buyer link, and one link works for every buyer.
* **Copy link** copies the buyer link. Send it in an email or a chat, or put it behind a button on your site.
* **QR code** shows a code that opens the same link on a phone.
* **Preview** opens the checkout in a new tab, the way a buyer sees it.
## Pay it yourself
Click **Preview** on the product's page, or **Try a test payment** on the screen after you create the product. The checkout opens. A banner at the top reads **Test payment · 4242 4242 4242 4242 · any expiry · any CVC**.
Type an email such as `buyer@example.com` and pick a **Country**. The order summary on the left adds the tax for that country and shows the **Total due**.
The 24% is the Estonian rate. The checkout charges it to every EU consumer while AgentaOS sells under the EU distance-selling threshold. See [How tax works](/mor/tax#the-rate-on-eu-sales-today).
Click **Continue to payment**.
Type `4242 4242 4242 4242` in **Card number**, any future date in **Expiration date**, and any three digits in **Security code**.
Click **Pay** followed by the amount, here **Pay €23.56**.
## The buyer's confirmation page
The checkout shows **Payment complete** with the amount, the invoice number, and the lines of the invoice. In test mode, it also says "Test payment: no money was paid. This invoice and receipt are not valid." The buyer can click **Download invoice** or **Download receipt**.
The buyer also gets a receipt by email, with the invoice attached. The subject line is "Receipt from" followed by your business name. In test mode, the subject starts with `[TEST]`, and the invoice number starts with `TEST-`. See [Test mode and testing](/getting-started/test-mode#test-invoices-receipts-and-emails) for how each test document is marked.
## See the sale
In the sidebar, click **Home**. A strip at the top says "First test sale cleared", and the card under it moves on to **Verify your business**, with **1 of 3 done**.
Click **Transactions**. The sale is at the top of the list as **Received** with the amount the buyer paid. The image below shows an earlier test sale of €12.40.
Click the sale to open it. Under **Buyer & payment**, three rows show where the buyer's money goes:
* **VAT** is the tax on the sale. AgentaOS collects and handles it.
* **AgentaOS fee** is the fee for the sale. See [Pricing](/mor/pricing).
* **Your net** is what is left for you.
Click **Invoices**. The sale's invoice is in the list as **Paid**, with the buyer's email. Open it and click **Download invoice**. The PDF of a test invoice has a **TEST** badge next to its title. The image below shows the invoice for an earlier test sale of €12.40.
## From code
To create products and checkouts from your server, and to know when a buyer pays, use the SDK:
* [Checkouts in the SDK](/sdk/pay-checkouts)
* [Payment links in the SDK](/sdk/pay-payment-links)
* [Quickstart for developers](/getting-started/quickstart#code-integration)
## Next steps
Create a monthly plan with a free trial and take your first subscriber.
Verify your business and take real payments.
Let your server react when a buyer pays.
# Let an AI agent run your store
Source: https://docs.agentaos.ai/guides/ai-agent
Connect the AgentaOS MCP server, or give a coding agent the CLI, so an AI agent can create checkouts, check payments and manage subscriptions for you.
By the end of this guide an AI agent can create checkouts, check payments and manage subscriptions for your store. You ask in plain words; the agent does the work.
There are two ways in. You can set up both; they do not conflict.
Claude.ai, ChatGPT, Claude Code or Cursor. You approve the connection once in the browser and paste no key.
Any agent that runs shell commands, including one in CI. It uses the `agenta` CLI and the SKILL.md prompt.
## Before you start
You do not need an account up front. When you approve the connection or run `agenta login`, you sign in, and a new email creates your account.
## Path A: connect the MCP server
In the dashboard, open **Developers → API keys** in the sidebar and click **Connect an AI agent**. The **Set up your AI agent** dialog asks "Where does your agent run?" Pick your client: **Claude Code**, **Cursor**, **ChatGPT**, **Claude.ai**, or **Other**. The dialog shows the steps for that client.
Follow [MCP server](/mcp/setup) for your client. For Claude.ai or ChatGPT, follow [Connect Claude.ai or ChatGPT](/mcp/remote-connector). Pick **Test mode** on the approval page while you try things out.
Ask your agent:
> Create a checkout for 50 EUR for my consulting invoice.
The agent calls `agenta_pay_checkout` and answers with the link to share, in a sentence like this one: "Checkout for €50.00 created. Share https\://… with your customer."
Ask it directly:
> List my last 5 checkouts.
> Cancel the subscription for [ana@example.com](mailto:ana@example.com) at the end of the current period.
The agent calls the matching tools, such as `agenta_pay_list`, `agenta_subscriptions_list` and `agenta_subscriptions_cancel`, and reports back in the chat.
[MCP server](/mcp/setup#tools) lists every tool your agent can use.
## Path B: a coding agent with the CLI
Install the `agenta` CLI with the command on [Install the CLI](/cli/install), then sign in with `agenta login`. See [Login](/cli/login).
`agenta login` is the one step that needs you: it opens a browser for sign-in. Every command an agent runs after that is non-interactive.
Tell your coding agent to fetch and follow the skill prompt:
> Read [https://agentaos.ai/SKILL.md](https://agentaos.ai/SKILL.md) and set up AgentaOS for me
In Claude Code, you can install the plugin instead. See [AI agent setup](/cli/install#ai-agent-setup).
It teaches the agent the `agenta` commands and their flags. It also tells the agent to run `agenta status --json` first and to pass `--json` to every command, so the output is JSON the agent can read.
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.
An agent connected in test mode, or running on a test key, sees and creates test data only. It cannot move real money.
## Verify it worked
Ask the agent:
> What's my AgentaOS status?
On the MCP path, the agent calls `agenta_status_get`. Before you go live, the answer starts with "Test payments work now." On the CLI path, the agent runs `agenta status --json` and reports `paymentTools.ready: true`, along with your business.
## Next steps
Setup for each client, and every tool your agent can use.
The full `agenta` CLI reference, for scripts outside an AI agent too.
Checkouts an agent creates still need your server to fulfill them on completion.
# Build a platform
Source: https://docs.agentaos.ai/guides/build-a-platform
Run your clients' businesses from one AgentaOS account. Add a business, invite the client, get it verified, set your share, and let it sell. Dashboard first, code second.
Private preview Working end to end for the accounts that use it; details can still change. Tell us at [contact@agentaos.ai](mailto:contact@agentaos.ai) before you rely on it, and we support you directly. See [Product release phases](/getting-started/release-phases).
A platform is an AgentaOS account that manages other businesses. An agency that launches products for clients is a platform. So is a studio with three apps of its own.
Each business you manage gets its own name on the checkout, its own invoices, and its own bank account and payouts. You run all of them from your account, and you can take a share of every sale a client makes. AgentaOS is the merchant of record for every sale, yours and theirs.
Tell us first; nothing needs switching on in the dashboard.
New to platforms? [Platforms](/connect/overview) explains the two kinds of business and who does what.
## In the dashboard
You need an AgentaOS account. Every step below works in test mode, including the **ID check link** button.
In the sidebar, click **Businesses**. The page lists the businesses you run from here and the figures above them: how many, your share this month, and how many need you. Click **Add a business**. The same button is in the account menu at the top of the sidebar, under your business name.
Enter the brand name buyers will see and the country where the business is registered. Then answer **Who is it?**
* **A client's business** is another company. It verifies on its own, and you can take a share of its sales.
* **Another app of your own company** is the same company. It uses your verification and your plan, so only its product gets reviewed. It has its own bank account, balance and payouts. It carries no share, because a company does not pay itself.
The dialog asks **Who runs it?**
Leave the email empty to run the business yourself. Most clients never log in.
Enter your client's email to give them their own login. They become an admin of this business and see only this business. We email them the invitation. The dialog also shows you the link, so you can send it yourself. The link signs them in and stays valid for 7 days.
Click a business to open its page. Until the client joins, the page starts with **Next: the invitation is still open**, with **Email it again**, **New link** and **Revoke**. The list says **Invitation waiting on** their email.
The invitation link opens a page that names you and the business:
After they accept, they land inside their business. Every page of theirs carries the line **Managed by AgencyOne. They can manage products and settings; payouts always go to ClientCo's own bank account.**
From here the client works like any AgentaOS merchant: products, sales, invoices, and their own bank account. A platform whose sellers never log in can save their bank accounts from its own app: see [Bank accounts by API](/connect/bank-accounts).
A business sells in test mode until its verification is approved. After that, real money flows. The business needs a bank account before its first payout, not before its first sale.
Verification has two parts:
* Your client files the business details, the product and the declarations from **Go live** in their dashboard. You can also file them for the client, from what the client confirmed to you, and our reviewers see that you filed them.
* Only the client can complete the identity check. Click **ID check link** on the business's row and send the link to the client.
Another app of your own company skips both parts. It is verified with you, and only its product is reviewed.
The row shows the status: **Test only**, **In review**, **Changes needed**, **On hold**, **Rejected** or **Live**. If you use webhooks, an `account.updated` event tells you when the status changes. See [Business status](/sdk/pay-businesses#business-status) for how the three sets of names match.
On a client's row, click **Set share**. The share is a percentage of each sale's price before VAT, plus a fixed amount per sale. The screen shows the outcome as you type.
The share is fixed on each sale at the moment of the sale. A change applies to future sales only. Your client sees the share on every sale as **Platform fee**, before their net.
See [Platform fees](/connect/fees) for the rules.
Click **Open** on a row. Your dashboard switches into that business, and the banner reads **You're working in ClientCo · managed by AgencyOne**. Everything you create there belongs to the client: products, checkouts, invoices, customers. Click **Back** to return to your own account.
Your fees appear on your own **Balances** page, in the **Overview** tab, in the **Platform fees** card. The card shows the total not yet paid to you, then **When you get it**: one line per day for the next five days, soonest first, then one **Later** line.
We pay the fees with your own payout, on or after the date shown. They are never part of **Available now**, because they are not your own sales money.
## From code
Every step above is also an API call or a CLI command, made with your own key. To add clients from your own product, use these pages:
* [Businesses in the SDK](/sdk/pay-businesses): create a business, file its verification, send the identity check link, sell as the business, and receive every business's events on one webhook.
* [Businesses in the CLI](/cli/businesses): the same steps from the terminal, with `--business` on every command.
* [Platform webhooks](/connect/webhooks): the `business` field on every event, and `account.updated`.
## Next steps
Your share and when we pay it.
Put the checkout on your client's own page.
Every method and field.
The six commands and `--business`.
# Go live
Source: https://docs.agentaos.ai/guides/go-live
Take real money: verify your business, switch to live mode, and copy your products to live. Add a bank account before your first payout.
By the end of this guide your business is verified and sells for real money. You do not need a bank account to go live. You need one before your first payout. Test mode and live mode are separate: each has its own products, sales, customers, and invoices. Your products do not move to live by themselves. You copy each one with the **Copy to live** button on its product page, and the live copy gets a new buyer link. If you built an integration, only your code carries over, with a live API key in place of the test key.
## Before you start
* A product that you paid for yourself in test mode. The first row on the **Go live** page, **Create your first product**, shows as done after that test payment. See [Test mode and testing](/getting-started/test-mode).
* Your business details: your legal name, your registration number if you are a company, your address, and a live web page where buyers can see the price.
## Verify your business
Open **Go live** in the sidebar. The page lists the steps to real money and counts them, for example "1 of 3 done". The same count shows next to **Go live** in the sidebar, for example "1/3". The three rows are:
1. **Create your first product**
2. **Verify your business**
3. **Go live**
Above the rows sits your **Revenue & Pricing Audit**, sealed until you verify, unless you already asked for it, and unlocked the moment your ID check is handed in. It is not a step and holds nothing up. See [Revenue & Pricing Audit](/grow/pricing-audit).
Below the rows, **Getting paid** has two rows: **Bank account** and **Stablecoin payouts**. They are not steps, and your first sale does not wait for them. On the right, **Your account** shows your business name, when the identity check comes, and your payout schedule.
AgentaOS is the merchant of record on every live sale, so we check whose product we sell before real money moves. To start, click **Start** on the **Verify your business** row. The form asks one question per screen, in three parts: **You**, **Your product**, and **The rules**. It takes about 5 minutes and saves as you go, so you can click **Save & finish later** and come back.
On the last screen, click **Submit for review**. The form says: "Reviewed by a person, usually within 24 to 48 hours. No documents to upload and no third-party ID check."
After you submit, the dashboard shows "Submitted for review", and the row shows **In review**. You do not need to do anything while we review. When we approve your business, the row says "Verified. Card and bank payments are on." For what we check, and for what to do if we ask for changes, see [Verify your business](/mor/verification).
## Switch to live mode
When your business is verified, the **Go live** page says "Everything is ready. Turn on real payments." Click **Go live** on the **Go live** row. The dashboard switches to live mode and shows "You're live". If you have no bank account yet, that message has the link **Add a bank account to get paid, about two minutes.**
You can also use the switch at the bottom of the sidebar, below **Settings**. It has two options, **Test** and **Live**. Click **Live**, and the dashboard shows "Switched to Live mode" with "You are now viewing live data. Real funds apply." While your business is not verified, clicking **Live** opens the **Go live** page instead.
In test mode, a dark strip across the top of every page says "Test mode" and "No real payments are processed". In live mode the strip is gone.
## Copy your products to live
Your test products stay in test mode. For each product that you want to sell:
1. Switch to **Test**.
2. Open **Products** and click the product.
3. Click **Copy to live**. The dashboard shows your product name followed by "is now in live mode." and a **View it** button.
The live copy has the same name, price, description, image, tax setting, success and cancel pages, and trial. It starts with no sales and no customers. Your test sales, customers, invoices, and subscriptions stay in test mode.
After you copy a product, its test page shows **In live mode** in place of **Copy to live**, and that button opens the live copy. Later changes to the test product do not reach the live copy. To change a live product, switch to **Live** and edit it there.
The live copy has its own buyer link. The test link stays a test link, so replace it everywhere you shared it:
1. Switch to **Live**.
2. Open **Products** and click the product.
3. Click **Copy link**, and put this link on your site, in your emails, and in your social posts.
4. Click **Preview** to open the live checkout page and check it. A payment on this page charges a real card.
## Add a bank account for payouts
Your sales collect in your AgentaOS balance from the first live sale. To move that balance to your bank, add a bank account. You can add it at any time, also before your verification is done.
On the **Go live** page, the **Bank account** row under **Getting paid** says "Where we pay you. Not needed to sell. Add it before your first payout, about two minutes." Click **Add** on that row. After you go live, the row is highlighted, its last sentence is "Your balance waits until then.", and its button is **Add bank account**. Each button opens the **Payout accounts** tab on **Balances** with the form. See [Add a bank account](/payouts/payout-accounts) for the fields and the name check.
Before the first payout, the owner or an admin completes the identity check, in live mode, from the **Home** or **Balances** page. See [The identity check](/mor/verification#the-identity-check). See [Payouts](/payouts/overview) for how your balance moves to your account.
Payouts to a stablecoin wallet are in public preview. The **Stablecoin payouts** row counts the conditions you meet, for example "0 of 3 done". See [Stablecoin payouts](/payouts/stablecoin-payouts).
## Changes for your buyers
In live mode, your buyers pay real money:
* **Charges.** The card processor charges the buyer's card. Test cards such as `4242 4242 4242 4242` do not work.
* **Card statement.** The charge shows as `AGENTAOS*` followed by your business name, shortened to fit, for example `AGENTAOS* KNEEGUARD`. The buyer's receipt email says "On your card statement this shows as" and gives the same line.
* **Invoices and receipts.** Test invoices carry a TEST badge and a `TEST-` number, and test emails start with `[TEST]`. Live invoices, receipts, and emails do not.
* **VAT.** The checkout calculates VAT for each buyer and adds it to the price. See [How tax works](/mor/tax).
## If you built an integration
This part is for developers. Your code stays the same, and the key decides the mode.
1. Switch the dashboard to **Live**.
2. In the sidebar, open **Developers → API keys**. Under **API Keys**, click **Generate Live Key**. Live keys start with `sk_live_`.
3. Put the live key in your deploy in place of the `sk_test_` key. Every call with that key reads and writes live data. See [Code integration](/getting-started/quickstart#code-integration) for where the key goes.
Your webhook endpoint, under **Webhooks** on the same page, receives both test and live events. Live events carry `livemode: true`, so check that field before you fulfill an order. See [Receive webhooks in Express](/sdk/handle-webhooks).
`successUrl`, `cancelUrl`, and `webhookUrl` must be `https` URLs on your live site. A product's own `webhookUrl` is not copied to live. Events for the live copy go to your endpoint under **Webhooks**.
Checklist before your first live sale:
* [ ] Business verified.
* [ ] Each product copied to live, and the live buyer links shared.
* [ ] `sk_live_` key deployed in place of the test key.
* [ ] `successUrl` and `cancelUrl` point at your live site.
* [ ] Webhook handler checks the signature and `livemode`.
To verify your business from the CLI, run `agenta login`, then `agenta verify declaration` to read the five statements you attest to, then `agenta verify submit` with your details. `agenta verify status` shows where the review is. An agent connected over MCP files the same form with the `agenta_verify_submit` tool. See [Verify](/cli/verify) for every option.
## Next steps
How your balance moves to your bank account or your wallet.
What each live sale costs.
Check signatures and `livemode` on live events.
# 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.
Each guide starts from nothing and ends with a working result: one goal, one path. The Documentation tab is the reference to keep open in a second tab: it explains concepts and lists every parameter on every resource. Follow a guide from top to bottom. Where a concept needs more depth, the guide links to its Documentation page; read it, then return to the step you left.
Every guide runs in **test mode** by default: a full test environment with test money, so there is nothing to verify and nothing to lose. See [Test mode and testing](/getting-started/test-mode) if you haven't set that up yet.
## Who these are for
These guides are for founders first: most of them start in the dashboard, in the screen's own words, and need no code. Developers come second: after the dashboard steps, a guide links to the SDK, CLI, or API pages, and Receive webhooks in Express is for developers only.
If you're brand new to AgentaOS, start with the [Quickstart](/getting-started/quickstart). It covers account setup in more depth than these guides assume.
## Pick a guide
Create a product, share its buyer link, and confirm the money landed.
Create a product, share the link or check out a logged-in user, then list, invoice, and cancel.
Move from test mode to real money: business verification, live mode, and a live API key.
Register a URL, verify every signature, and fulfill an order when a checkout completes.
Point a coding agent at the SKILL.md prompt, or connect the MCP server, and manage payments in plain language.
Run your clients' businesses from one account.
## 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 monthly plan with a free trial, share it, take your first subscriber, change their plan, and cancel. All from the dashboard, in test mode.
This guide takes you from no plan to a running subscription in the dashboard. You create a monthly plan with a 14-day free trial, pay for it yourself as a test buyer, then move the subscriber to another plan and cancel. Everything runs in **Test mode**, so no real money moves.
How subscriptions behave over their life, and the same actions from code, are on the [Subscriptions](/payments/subscriptions) page.
## Before you start
* An AgentaOS account. The dashboard opens in **Test mode**. You only need to verify your business to take real money: see [Go live](/guides/go-live).
* A second browser, or a private window, to act as the buyer.
* The test card `4242 4242 4242 4242`, with any future expiry date and any CVC. Type it only into the card form on the checkout page.
## Create the plan
In the sidebar, click **Products**, then click **New product**.
Under **Details**, type a **Product name**, for example `Pro`. Buyers see the name and the description on the checkout.
Under **Pricing**, type the price, for example `29`, and pick **EUR** or **USD**. Click **Subscription**, then **Monthly**.
In **Free trial (days)**, type `14`. Leave **Trial price** empty for a free trial.
Click **Create product**. The next screen says "Pro is live". Live here is the product's word for published. You are still in test mode.
Click **View product** to open the plan's page.
## Share the link or embed it
Click **Copy link** and send the link to a buyer: in an email, on your pricing page, or in a post. One link works for every buyer.
To show the checkout on your own site instead, open **Building this into your site?** on the plan's page. Under **Embed on your site**, click **Copy snippet** and paste it into your page. The snippet works only on sites you approve under **Settings → Developers → Approved sites**. See [Embedded checkout](/payments/embedded-checkout).
## Take the first subscriber
Open the link in your second browser and pay as the buyer.
Type an email such as `buyer@example.com` and pick a **Country**. The checkout shows **Due today** €0.00 and the monthly price after the trial, with any VAT for that country.
The 24% is the Estonian rate. The checkout charges it to every EU consumer while AgentaOS sells under the EU distance-selling threshold. See [How tax works](/mor/tax#the-rate-on-eu-sales-today).
Click **Continue to payment**.
Type `4242 4242 4242 4242` in **Card number**, any future date in **Expiration date**, and any three digits in **Security code**.
Click **Start 14-day free trial**. The checkout confirms "Trial started" and €0.00 due today.
The buyer and you each get an email that the trial started. The first charge happens on the day the trial ends.
## See the subscriber
In the sidebar, click **Subscriptions**. The new subscriber shows with the status **Trialing** and the date of the first charge under **Renews**.
The price in the list includes any VAT the buyer pays, so it can be higher than the plan price. Click the subscriber's name to open the subscription.
## Change their plan
A plan change needs a second plan in the same currency and with the same interval. Create a product named `Pro Plus` at `49`, **Subscription**, **Monthly**, the same way as above.
On the subscription page, click **Change plan**.
Pick **Pro Plus** from the list. The dialog shows what is due today and the price from the next renewal.
Click **Confirm change**. An upgrade applies at once, so the subscription now shows Pro Plus.
A move to a cheaper plan waits for the end of the period. The dialog says "No charge today" and the date the change takes effect.
After you confirm a downgrade, the subscription shows **Scheduled change** with the new plan and its start date. To drop the scheduled change, click **Change plan** again and pick the plan marked "current plan".
## Cancel
On the subscription page, click **Cancel subscription**.
Read the dialog, then click **Cancel subscription** again.
The status changes to **Cancels** followed by the end date. The subscriber keeps access until then, and nothing more is charged. To keep them after all, click **Resume subscription** before that date.
## From code: subscribe a signed-in user
When the buyer already has an account in your app, create the checkout from your server with the plan's link ID and your own user ID in `metadata`. Use these pages:
* [Subscriptions in the SDK](/sdk/pay-subscriptions): the checkout call for a signed-in user, and how to match renewals to your user.
* [Paywall for logged-in users](/sdk/paywall-for-logged-in-users): the complete integration, as a runnable demo app with the user's invoices and plan management.
* [Subscriptions](/payments/subscriptions#from-code): creating plans, listing subscriptions, plan changes, and canceling from code.
## Next steps
Trials, plan changes, cancellation, and the code for each.
Give new subscribers money off their first invoice.
Unlock access when a subscriber pays.
Take real payments.
# 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.
AgentaOS runs billing, tax, invoices, and payouts, so you do not build a billing team to get paid.
Create a product, share its link, and watch the test payment land.
Paste one line into Cursor or Claude Code. The agent sets up billing and returns a buyer link.
Payment links, checkouts, subscriptions, invoices, and customers over REST.
Our AI pricing agent reads your pricing page and tells you what to change first. Included with your account.
## AgentaOS as your merchant of record
AgentaOS is a billing and payments platform for AI founders, and your **merchant of record**: the legal seller of your product. Your buyer pays AgentaOS, and AgentaOS takes the payment, charges the VAT, issues the invoice and pays you out. As the registered seller, AgentaOS carries the tax liability, so you do not register for tax abroad, file a cross-border return, or reconcile three processors. A lost dispute costs you the sale plus the dispute fee: see [Refunds and disputes](/mor/refunds-and-disputes). AgentaOS is PCI compliant. See [How it works](/mor/how-it-works).
## Start where you are
A shareable URL you can charge many times. No code to share it.
Recurring billing with automatic renewals and tax handled.
Send a tax-correct invoice, paid on the same secure checkout.
Run your clients' businesses from one AgentaOS account.
Fees and plans are on [Pricing](/mor/pricing).
## Checkout, tax, and payouts
Card, with Apple Pay and Google Pay on devices that support them, and bank transfer on EUR sales. Cards work in 135+ currencies, from buyers worldwide.
AgentaOS calculates the VAT, collects it, and pays it to the tax office.
Your buyer pays AgentaOS, and AgentaOS pays you out.
## Reasons founders switch
Cross-border VAT means registering and filing in every country you sell to. As your merchant of record, AgentaOS calculates it, collects it, and pays it to the tax office, so you do not file a cross-border return.
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 product and share its buyer link. 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 a signed event you can verify when a checkout completes.
## 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 Code, Cursor, ChatGPT, or Claude.ai and let your AI agents run your store.
A runnable demo app: a plan behind a login, the webhook, access checks, the user's invoices, cancel and upgrade.
## AI agent integration
Your AI agents can set up and operate AgentaOS on their own, without the dashboard.
```text theme={"system"}
Read https://agentaos.ai/SKILL.md and set up AgentaOS for me
```
Give this to any AI coding agent, such as Claude Code or Cursor, 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 is required, and you start in test mode with test money.
Open **Products** and click **New product**. Give it a name and a price, choose one-time or subscription, and save.
Click **Copy link** and send it anywhere: an email, a chat message, or a QR code. One link can be paid many times.
Open the link and pay with the test card `4242 4242 4242 4242`, any future expiry, and any CVC. The payment shows on **Home** and in **Transactions**.
To integrate with code, follow the [code integration](/getting-started/quickstart#code-integration) steps in the quickstart.
## 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, with Apple Pay and Google Pay on devices that support them, and bank transfer on EUR sales. Cards work in 135+ currencies, from buyers worldwide.
AgentaOS charges EU consumers VAT at the Estonian rate while its EU sales are under the €10,000 threshold, and charges no tax to buyers outside the EU. See [How tax works](/mor/tax#the-rate-on-eu-sales-today) and [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 AgentaOS appears as the seller. See [Why was I charged](/for-customers/why-charged).
# Connect Claude.ai or ChatGPT
Source: https://docs.agentaos.ai/mcp/remote-connector
Add the AgentaOS connector to Claude.ai or ChatGPT, approve it once, and run your account from chat.
The AgentaOS connector lets a hosted assistant run your AgentaOS account from chat: create products and checkouts, submit your verification, and manage subscriptions, customers and invoices. You add one URL to your assistant and approve the connection on an AgentaOS page. You paste no key.
The connector URL is:
```text theme={"system"}
https://mcp.agentaos.ai/mcp
```
If you do not have an AgentaOS account yet, sign in with a new email during the approval step. That creates your account.
## Add the connector
In Claude.ai or the Claude desktop app, open **Customize → Connectors → Add custom connector** and paste the connector URL.
In ChatGPT, open **Settings → Connectors → Create** and paste the connector URL.
The dashboard shows the same steps: open **Developers → API keys** in the sidebar, click **Connect an AI agent** and choose your assistant in the **Set up your AI agent** dialog.
## Approve the connection
When you add the connector, your assistant opens an AgentaOS page.
If you are not signed in to AgentaOS, click **Sign in to continue**. After you sign in, the page comes back.
The page shows **Connect** followed by your assistant's name, then **to AgentaOS**. Pick **Test mode** or **Live mode**:
* **Test mode**: "Nothing is charged. Good for trying it out."
* **Live mode**: "Real payments on your live account."
The page starts on the mode your dashboard is in. If you have never switched the dashboard to live, it starts on Test mode.
Click **Approve**. The page shows "Sending you back to" your assistant, and you return to the chat.
When you approve, AgentaOS creates an API key on your account for the mode you picked. The key carries your assistant's name, and every tool call from the assistant uses it. A connection in test mode sees only test data.
## Switch from Test to Live
The mode is fixed when you approve. To take live payments from chat, [disconnect](#disconnect), add the connector again and pick **Live mode**. Live payments also need a verified business: see [Go live](/guides/go-live).
## Available tools
Your assistant gets all 31 AgentaOS tools, the same set as every other client. [MCP server](/mcp/setup#tools) lists each one.
Ask in plain words. For example:
> What is my AgentaOS status?
> Create a monthly plan called Pro at 29 euros.
> Create a discount code LAUNCH20 for 20% off.
> Is my business verification done yet?
Each tool answers with a plain sentence, which your assistant passes on to you.
## Disconnect
In the sidebar, open **Developers → API keys**. Under **API Keys**, find the key with your assistant's name.
Click the delete icon on that key, then **Confirm**.
After that, every tool call from the assistant fails. The error ends with: "The key may have been revoked in AgentaOS. Create a new key, or reconnect the connector." To use the assistant again, add the connector again and approve it.
## Troubleshooting
| Message | What to do |
| - | - |
| This approval expired. Start the connection again. | Add the connector again and approve on the new page. |
| This approval link expired. Start the connection again. | More than 10 minutes passed between adding the connector and approving it. Add the connector again. |
| The connection was declined in AgentaOS. | Add the connector again and click **Approve** this time. |
| AgentaOS has not confirmed the approval yet. Try connecting again. | Add the connector again. |
| The key may have been revoked in AgentaOS. Create a new key, or reconnect the connector. | The key was revoked. Add the connector again and approve it. |
## Next steps
Every tool, and the setup for Claude Code and Cursor.
The walkthrough, from the first prompt to a paid checkout.
# MCP server
Source: https://docs.agentaos.ai/mcp/setup
Connect Claude, ChatGPT, Cursor or any MCP client to AgentaOS, and every tool your agent can then use.
The AgentaOS MCP server gives your AI agent the same operations as the `agenta` CLI: account status, the Revenue & Pricing Audit, products, checkouts, verification, subscriptions, discount codes, customers, invoices, and the businesses you manage. The CLI commands and the MCP tools come from one catalog, so each tool has a matching command with the same inputs and the same answer.
## Connect your agent
Start in the dashboard: open **Developers → API keys** in the sidebar and click **Connect an AI agent**. The **Set up your AI agent** dialog asks "Where does your agent run?" and shows the steps for the client you pick.
Every client that can open a browser uses the hosted server at `https://mcp.agentaos.ai/mcp`. You approve the connection once, on an AgentaOS page, and you pick **Test mode** or **Live mode** there. You paste no key.
Install the AgentaOS plugin. It adds the hosted server and a skill that teaches your agent the tools.
```bash theme={"system"}
claude plugins marketplace add AgentaOS/agentaos
claude plugins install agentaos@agentaos
```
In Claude Code, type `/mcp`, choose `agentaos`, then **Authenticate**. An AgentaOS page opens in your browser: pick Test or Live and press **Approve**.
To check the connection, run:
```bash theme={"system"}
claude mcp list
```
The list shows `agentaos: https://mcp.agentaos.ai/mcp (HTTP) - Connected`.
In Cursor, open **Settings → MCP → Add server** and paste this config:
```json theme={"system"}
{
"mcpServers": {
"agentaos": {
"url": "https://mcp.agentaos.ai/mcp"
}
}
}
```
An AgentaOS page opens in your browser: pick Test or Live and press **Approve**.
Add `https://mcp.agentaos.ai/mcp` as a custom connector, then approve it on the AgentaOS page that opens. [Connect Claude.ai or ChatGPT](/mcp/remote-connector) has the steps for each assistant.
[Connect Claude.ai or ChatGPT](/mcp/remote-connector) also covers the approval page, how to switch from Test to Live, and how to disconnect. Those steps are the same for every client on the hosted server.
### Agents that cannot open a browser
A headless agent or a CI job runs the server on its own machine instead, with an API key. The server needs Node.js 20 or later.
With the dashboard in test mode, open **Developers → API keys** in the sidebar, click **Connect an AI agent** and choose **Other**. Click **Generate one named Other agent**, then **Create key**. Copy the key: the dashboard shows it only once. It stays under **API Keys** with the name "Other agent".
Add an MCP server named `agentaos` that runs `npx -y agentaos`, with the key in `AGENTAOS_GATEWAY_KEY`:
```json theme={"system"}
{
"mcpServers": {
"agentaos": {
"command": "npx",
"args": ["-y", "agentaos"],
"env": {
"AGENTAOS_GATEWAY_KEY": "sk_test_your-key"
}
}
}
}
```
In Claude Code, one command does the same:
```bash theme={"system"}
claude mcp add agentaos-local -e AGENTAOS_GATEWAY_KEY=sk_test_your-key -- npx -y agentaos
```
Point your agent at `https://agentaos.ai/SKILL.md`. It teaches the agent the tools and when to use each one.
A test key (`sk_test_`) sees test data only, so the agent cannot move real money.
## Tool answers
Every tool answers twice: a plain sentence written for you, and the same result as JSON for code. The sentence is the one the CLI prints. The JSON is what the CLI prints with `--json`.
## Tools
The server has 31 tools. The CLI column is the same operation in the terminal.
### Account and going live
| Tool | CLI | What it does |
| - | - | - |
| `agenta_status_get` | `agenta status` | Your account and go-live overview, with the next step to take. |
| `agenta_audit_request` | `agenta audit request` | Ask for the free Revenue & Pricing Audit. |
| `agenta_audit_show` | `agenta audit show` | Where the audit stands, and the report once it exists. |
| `agenta_verify_declaration` | `agenta verify declaration` | The five statements you attest to when you submit verification. |
| `agenta_verify_submit` | `agenta verify submit` | Submit business verification, so you can accept live payments. |
| `agenta_verify_status` | `agenta verify status` | Where your verification stands. |
| `agenta_verify_resubmit` | `agenta verify resubmit` | Send your verification back for review after the changes AgentaOS asked for. |
### Products and checkouts
| Tool | CLI | What it does |
| - | - | - |
| `agenta_products_create` | `agenta products create` | Create a product (one-time) or a subscription plan. |
| `agenta_products_list` | `agenta products list` | List your products and plans. |
| `agenta_pay_checkout` | `agenta pay checkout` | Create a checkout and get the link to share. |
| `agenta_pay_get` | `agenta pay get ` | Whether a checkout is paid, still open, expired or canceled. |
| `agenta_pay_list` | `agenta pay list` | List your checkouts, optionally by status. |
### Subscriptions and discount codes
| Tool | CLI | What it does |
| - | - | - |
| `agenta_subscriptions_list` | `agenta subscriptions list` | List your subscriptions. |
| `agenta_subscriptions_cancel` | `agenta subscriptions cancel ` | Cancel a subscription at the end of the current period. |
| `agenta_subscriptions_change_plan` | `agenta subscriptions change-plan ` | Move a subscription to another plan. An upgrade charges now; a downgrade starts at the end of the period. |
| `agenta_subscriptions_credit` | `agenta subscriptions credit ` | Put credit on a subscriber's account. It comes off their next invoice. |
| `agenta_subscriptions_credits` | `agenta subscriptions credits ` | The credits given on a subscription, and what is still unspent. |
| `agenta_discounts_create` | `agenta discounts create` | Create a discount code buyers type at checkout. |
| `agenta_discounts_list` | `agenta discounts list` | List your discount codes. |
| `agenta_discounts_show` | `agenta discounts show ` | One discount code: what it takes off and how many people used it. |
| `agenta_discounts_archive` | `agenta discounts archive ` | Stop a discount code. Subscribers who already used it keep their discount. |
### Customers and invoices
| Tool | CLI | What it does |
| - | - | - |
| `agenta_customers_list` | `agenta customers list` | List your customers. |
| `agenta_invoices_list` | `agenta invoices list` | List your invoices. |
| `agenta_invoices_receipt` | `agenta invoices receipt ` | The receipt PDF for a paid invoice. The CLI saves it as a file. |
| `agenta_invoices_send_receipt` | `agenta invoices send-receipt ` | Send the receipt email again to the buyer on file. |
### Businesses you manage
For [platforms](/connect/overview) only.
| Tool | CLI | What it does |
| - | - | - |
| `agenta_businesses_list` | `agenta businesses list` | List the businesses you manage. |
| `agenta_businesses_get` | `agenta businesses get ` | One business you manage, with its status. |
| `agenta_businesses_create` | `agenta businesses create` | Add a business you manage: a client's business, or another app of your company. |
| `agenta_businesses_invite` | `agenta businesses invite ` | Send your client's invitation again. The new link replaces the old one. |
| `agenta_businesses_revoke_invite` | `agenta businesses revoke-invite ` | Withdraw your client's open invitation. |
| `agenta_businesses_id_link` | `agenta businesses id-link ` | The identity check link for your client. Only they can complete it. |
## Act as a business you manage
If you run a platform, every tool takes an optional `business` input: the id of a business you manage. The tool then acts as that business instead of as you. Leave `business` out to act as yourself. In the CLI, the same input is `--business `.
## Next steps
Approve the connector, switch to Live, and disconnect.
The walkthrough, from the first prompt to a paid checkout.
The same operations from your terminal.
Manage your clients' businesses.
# 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, short for know your business). 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
### Review time
Reviews are usually done within 24 to 48 hours. You will get an email when the review is complete or if we need a change.
### Review outcomes
* **Approved:** your account can start accepting live payments.
* **Changes requested:** fix the flagged items, then resubmit from the same place you started.
### Where to start
Open **Go live** and start **Verify your business**.
### Information you provide
* 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
### Businesses managed by a platform
A client's business files its own review. Its [platform](/connect/overview) may file the review for the client, and our reviewers see that the platform filed it. The identity check is the client's alone. Another app of the same company is verified with that company, and we review only its product.
### 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.
## Reasons for the review
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
* Drugs and supplements, weapons, alcohol, tobacco, vaping, and other illegal or adults-only goods
* Counterfeit goods, or content you do not hold the rights or license to
* Gambling, financial advice, and regulated financial services (lending, debt relief, banking)
* Spyware, stalkerware, or covert monitoring apps
* Multi-level marketing, pyramid, or similar schemes
* Data scraping and engagement farming
* Physical goods (we are a digital merchant of record)
* Anything our payment or banking partners consider too high-risk
An application for a product on this list is refused, not reviewed.
### Reviewed case by case
* 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.** A buyer cancels by contacting you, or in your own software or app. 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
The role of a merchant of record 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 also owe VAT or sales tax in your buyer's country, and you're responsible for registering, collecting, and filing it correctly in every one of those countries. AgentaOS removes that problem by becoming the merchant of record, the legal seller, on every sale you make through AgentaOS. If you're new to AgentaOS, start with the [introduction](/introduction).
## The merchant of record role
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 merchant of record and you stay responsible for the tax.
AgentaOS is the merchant of record on every payment. The legal seller your buyer pays is AgentaOS's Estonian entity, not you. Because AgentaOS is the seller, AgentaOS calculates, collects, and pays the tax on your behalf.
## A growth merchant of record
The agentaos.ai home page says: "A Merchant of Record sells your product in its own name: it takes the payment, files the tax and handles disputes. A growth Merchant of Record also helps you earn more. AgentaOS includes an AI pricing agent that finds why users don't pay and tells you what to charge." With your account, the pricing agent reads your pricing page and writes up what to change first. See [Revenue & Pricing Audit](/grow/pricing-audit).
## The cost of doing it yourself
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 from you: you sell, and AgentaOS handles the tax.
## How a payment flows
```mermaid theme={"system"}
flowchart LR
A[Buyer pays card or bank transfer] --> B[AgentaOS is merchant 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, with Apple Pay and Google Pay on devices that support them, or by bank transfer on EUR sales. They see AgentaOS as the seller on the payment page and the receipt.
AgentaOS calculates the tax due based on what's being sold and where the buyer is, and collects it as part of the payment.
What you earn from the payment, after fees, settles into your AgentaOS balance in EUR or USD. Tax is already accounted for: you do not 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 compared with a payment processor
| | Payment processor (used directly) | Merchant of record (AgentaOS) |
| - | - | - |
| Legal seller on the receipt | You | AgentaOS |
| Who owes VAT | You, in every country your buyers are in | AgentaOS collects and remits it |
| 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 are different things, and one can use the other. AgentaOS uses a card processor to move the money. AgentaOS is the legal seller in front of it, so AgentaOS carries the tax liability that you would carry if you used the processor directly. A lost dispute costs you the sale plus the dispute fee: see [Refunds and disputes](/mor/refunds-and-disputes).
## Your responsibilities
* 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 merchant 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 merchant of record, responsible for tax. AgentaOS is a merchant of record: AgentaOS is the legal seller and handles VAT and invoicing for every sale.
AgentaOS's Estonian entity appears as the seller on the payment page, the receipt, and the invoice. See [Invoices](/payments/invoices).
Yes, for your own company's books and income tax. What you won't need to do is register for or file VAT on the sales you make through AgentaOS. AgentaOS handles that as the merchant 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 AgentaOS is registered to collect and pay tax.
## Next steps
The AgentaOS overview, if you're just getting started.
Where you can sell and where you can get paid.
Where AgentaOS is registered for VAT, 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
Per-transaction fees plus an optional plan, and a 1% payout fee. No setup costs. 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 and invoicing are included in that fee, because AgentaOS is your merchant of record. Each payout carries a payout fee of 1% (minimum €1 or \$1); see [Payouts](/payouts/overview#fees). There are no setup costs.
## Plans
| Plan | Bank transfer | 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 (higher limits, negotiated rates).
Your plan and rates are on **Settings → Billing**. To move to Pro, click **Upgrade to Pro** on **Settings → Billing**, then click **Email us** and send the email it opens. You can also write to [contact@agentaos.ai](mailto:contact@agentaos.ai). AgentaOS switches your account, and the new rate applies from your next payment.
[Platforms](/connect/overview), which manage businesses for clients or for other apps of the same company, work on every plan.
Transaction fees apply to the gross amount and are netted from your settlement, so there is nothing to invoice or pay separately.
## 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 | Surcharge on Pay as you go |
| - | - |
| EEA (Europe) | None |
| UK, US and other non-EEA | 1.5% on top of the card rate |
On Pay as you go, a card issued outside the EEA costs 6.0% + €0.50 in total. The Pro card rate (4.0% + €0.40) is a flat price to you regardless of your buyer's origin.
## Payouts
Each payout carries a payout fee of 1% of the amount paid out, with a minimum of €1 or \$1. A bank payout also carries the cost of the bank transfer, passed through at cost. See the payout fee table on [Supported countries](/mor/supported-countries) and [Payouts](/payouts/overview#fees).
## FAQ
Only on Pro (€49/mo) and Scale. Pay as you go has no monthly fee.
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. **Settings → Billing** shows when 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.
A payout fee of 1% of the amount paid out, with a minimum of €1 or \$1. A bank transfer cost, where the route has one, passes through at cost.
On Pay as you go, a card issued outside the EEA adds 1.5% to the card rate. 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.
# Refunds, disputes and chargebacks
Source: https://docs.agentaos.ai/mor/refunds-and-disputes
How you ask for a refund, what a refund or a chargeback takes from your balance, what the reserve covers, and what you do when a buyer disputes a charge.
AgentaOS is the merchant of record, so AgentaOS sends every refund and answers every dispute. You ask for a refund from the sale page. A refund always returns the whole amount to the buyer, and it takes back only what that sale paid you. A dispute pauses the sale's money until the buyer's bank decides. If you lose the dispute, you also pay a flat dispute fee.
## Refunds
### Ask for a refund
In the dashboard, click **Transactions**. Click the sale.
Below the payment details, the page says: "Need to undo this sale? Ask for a refund — the buyer gets the whole amount back once AgentaOS approves it. Nothing moves until then." Click **Request refund**.
The button changes to a **Refund requested** label. No money moves at this step.
### Approval or decline
AgentaOS reviews the request and does one of two things:
* **Refunds the buyer.** The buyer gets the whole amount back on the card they paid with. The sale shows **Refunded**.
* **Declines the request.** The sale shows **Refund declined**, with AgentaOS's note below it. You can read the note and ask again.
While a request is open, AgentaOS holds that sale out of your payouts. This stops a payout of money that a refund then takes back.
You cannot ask for a refund on a sale that is already refunded or in dispute. A disputed sale follows the dispute steps below.
### Refunds are always in full
A refund returns the whole amount the buyer paid, VAT included. You cannot refund part of a sale.
### Refunds and your balance
A refund takes back only what the sale paid you. The AgentaOS fee on the sale is not charged to you again, and it is not returned to you. A refund has no extra fee.
| When AgentaOS refunds the sale | What happens to your money |
| - | - |
| Before its money reached you | The sale leaves your balance. It is never paid out. |
| After it was paid out | The amount you were paid for the sale, reserve included if it was released, comes out of your next payout. |
The payout statement shows the amount taken back as a **Returned · Refunded sale** line. See [Payout statements](/payouts/statements).
### If a platform manages your account
If a platform takes a share of your sales, the platform returns its share too. The platform pays back what it already received from that sale. A share that was not paid yet is never paid.
### Ask with the API
Send `POST /gateway/transactions/{id}/refund-request` with the sale's id, signed with your secret key. The request follows the same steps as the button.
## Disputes and chargebacks
A dispute starts when a buyer asks their bank to reverse a charge. If the bank decides for the buyer, the reversal is a chargeback. AgentaOS finds new disputes automatically and answers the buyer's bank as the merchant of record.
### Dispute notices
* **An email** with the subject "A buyer wants their €12.40 back. We need a few things.", with the amount of your sale. When the bank sets a date for an answer, the email gives it.
* **On the sale**, in the transaction list, the status **Disputed**.
* **On your balance**, a **Paused (disputed)** row: "We dispute these for you. Back if we win." Open the row to see **Sales on hold** and **Dispute fees charged**.
The money from a disputed sale stays out of your payouts until the dispute ends. See [Payouts](/payouts/overview).
### Send your evidence
You do not file anything from the dashboard. Reply to the dispute email with what you have, before the date in the email, or as soon as you can when the email has no date. The email asks for:
* What the buyer bought, and when they got it.
* Any logs that show they used it.
* Your messages with them.
* Your refund and delivery terms as they stood that day.
### If you win
AgentaOS gives back everything the dispute took: the sale's money and the dispute fee. The sale returns to your balance and goes out in a later payout, like any other sale. You get an email: "You won that dispute. The money is back."
### If you lose
The sale shows **Charged back**, and its money does not come back. What you owe depends on whether the sale was already paid to you:
| When the dispute opened | What you owe |
| - | - |
| Before the sale was paid to you | The dispute fee only. The sale's money was never paid to you. |
| After the sale was paid to you | What the sale paid you, plus the dispute fee, minus any reserve AgentaOS still holds on that sale. |
The dispute fee is €35 on a EUR sale and \$35 on a USD sale, once per dispute. AgentaOS takes what you owe from your next payouts. The payout statement shows it as a **Returned · Charged back sale** line.
If a platform manages your account and a dispute opens after its share was paid, the platform returns that share. A share that was not paid yet is held, and it is never paid if you lose. The platform does not pay the dispute fee.
### How the reserve covers a chargeback
New accounts have a **Reserve** row on the balance: "Safety hold on new accounts". AgentaOS holds back part of each sale for a period. If a sale is charged back while its reserve is still held, AgentaOS keeps that reserve and takes it off what you owe.
## Costs at a glance
| Event | Cost to you |
| - | - |
| Refund | What the sale paid you. No fee. |
| Dispute you win | Nothing. The sale and the dispute fee come back. |
| Dispute you lose | What the sale paid you, if it was paid, plus the €35 or \$35 dispute fee, minus any reserve still held. |
## How to avoid disputes
A buyer who does not recognize a charge may dispute it. AgentaOS helps the buyer recognize the charge and find you:
* **The card statement names you.** The charge shows as `AGENTAOS*` followed by your business name, for example `AGENTAOS* CLIENTCO`. The sale page shows it as **Card Statement Descriptor**.
* **The buyer gets a receipt by email.** The receipt is from your business, says how the charge shows on the card statement, and lets the buyer reply to you directly.
* **Buyers have a help page.** [Why was I charged by AgentaOS?](/for-customers/why-charged) tells a buyer how to find the purchase and to contact you first for a refund.
Answer buyer emails quickly, and refund when a buyer asks. A refund costs less than a lost dispute.
# Supported countries and 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).
If your country is not in the payout list: bank payout coverage expands over time. In the meantime, [stablecoin payouts (public preview)](/payouts/stablecoin-payouts) pay your balance to a wallet in USDC or EURC. Ask us at [contact@agentaos.ai](mailto:contact@agentaos.ai) and we confirm your options.
## Accepting payment (your buyer's side)
Card, with Apple Pay and Google Pay on devices that support them, in 135+ currencies, from buyers worldwide. Bank transfer on EUR sales. A card 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. See [Payout fees](#payout-fees).
### Payout fees
Each payout carries the [payout fee](/payouts/overview#fees). AgentaOS adds the bank transfer cost at cost, and that cost depends on the route your money takes:
| Route | Fee per payout |
| - | - |
| Local bank transfer | A small flat fee |
| 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 depend on the destination and the payout currency, from €5 to €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 coverage, which 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 | | | |
### Marks in the table
* `**` Personal bank accounts only, in the recipient's own name. Our banking partner does not pay a business account in these countries. In Bangladesh the partner also refuses transfers sent by a business, so ask us before you count on a bank payout there.
* `*` China: a business is paid to a CNY account at a bank in mainland China, with a transfer reason on file; the recipient's bank can ask for the contract and the invoice. A person is paid through a card or wallet network, with a cap on each payout.
* `†` No local route. The payout travels by Swift, so the cross-border fee in the table above applies.
Some routes cap one payout. Brazil: 10,000 USD to a business account and 3,000 USD to a personal account, and the account holder's tax number is required. Argentina: 18,000 USD per calendar month. India: 1.5 million INR per working day. These caps are our banking partner's and can change. If your balance is above a cap, ask us before the payout window.
## Unsupported countries for purchases
AgentaOS 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
Yes, through the 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. Ask us at [contact@agentaos.ai](mailto:contact@agentaos.ai) and we confirm your options.
## Next steps
What a merchant of record is and what it does for you.
Where AgentaOS is registered for VAT, and what shows on the invoice.
Transaction fees and plans.
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 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
VAT in the EU is charged based on the buyer's location and what's being sold, not the seller's. Once a seller passes the EU threshold, a sale to a consumer in Germany follows German VAT rules, even if the seller 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.
## The rate on EU sales today
AgentaOS is VAT-registered in Estonia and sells under the EU distance-selling threshold of €10,000. Under that threshold, the checkout charges every EU consumer the Estonian VAT rate of 24%, whichever EU country they buy from. A German consumer therefore pays 24% on the checkout, not the German rate.
* **EU consumer**: the Estonian rate, 24%.
* **EU business in another EU country, with a valid VAT number**: no VAT (reverse charge). The buyer accounts for the VAT.
* **Buyer outside the EU**: no VAT.
Once AgentaOS's EU sales pass the €10,000 threshold, the buyer's country rate applies.
## 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.
AgentaOS files the returns and remits what is owed where it is registered. You never file anything yourself.
Every payment gets a tax-correct invoice with AgentaOS's Estonian entity as the seller, and the VAT on its own line with the rate applied, for example "VAT 24% of €29.00". On a reverse-charge sale, the invoice says "Reverse charge applies per Article 196 of EU VAT Directive 2006/112/EC." See [Invoices](/payments/invoices).
## Where AgentaOS is registered today
VAT-registered in Estonia. AgentaOS's Estonian entity is the registered seller. See [The rate on EU sales today](#the-rate-on-eu-sales-today).
AgentaOS charges VAT only in the EU today, from its registration in Estonia. Buyers outside the EU are charged no tax.
## Selling to businesses (B2B)
A business buyer can enter its VAT number at checkout. AgentaOS checks an EU number against the EU VAT register. A business in another EU country with a valid VAT number pays no VAT: the invoice shows the reverse charge, and the buyer accounts for the VAT itself. A business in Estonia pays the Estonian rate. A buyer outside the EU pays no VAT, with or without a tax number.
## FAQ
No. AgentaOS is the merchant of record, so AgentaOS registers, collects, and remits VAT on the sales you make through AgentaOS.
Today an EU consumer pays the Estonian rate of 24%, and a buyer outside the EU pays no VAT. See [The rate on EU sales today](#the-rate-on-eu-sales-today).
A business in another EU country with a valid VAT number pays no VAT: the invoice shows the reverse charge, and the business accounts for the VAT itself. A business in Estonia pays the Estonian rate. A business outside the EU pays no VAT.
AgentaOS's Estonian entity, your merchant of record, appears as the seller. See [Invoices](/payments/invoices).
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 what it does for you.
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.
# Verify your business
Source: https://docs.agentaos.ai/mor/verification
The two checks before real money moves: the business review unlocks live sales, and the identity check unlocks payouts.
Verification has two parts. The **business review** (KYB, short for know your business) checks your business and what it sells, and it must be approved before you take live card and bank payments. The **identity check** (KYC, short for know your customer) confirms the person we pay with a photo of an ID document, and it must pass before your first payout. Test mode needs neither, so you can build and test everything first.
| Check | What it unlocks | Where you start it | Who does it |
| - | - | - | - |
| Business review | Live card and bank payments | **Go live** in the sidebar, the **Verify your business** row | An owner or admin of the account |
| Identity check | Payouts | **Home** or **Balances**, in live mode | An owner or admin of the account |
The identity check runs once. When it passes, it covers every later payout.
## The business review
The business review asks who you are, where your business is registered, and what you sell. A person reads your answers and your website. See [Account review](/mor/account-review) for the checklist we review against and the details to have ready.
### Submit the review
In the dashboard sidebar, click **Go live**.
On the **Verify your business** row, click **Start**. The row says it takes "About 5 minutes".
The form asks one question at a time. When you submit, the dashboard shows **Submitted for review**.
### After you submit
A person reviews your business, usually within 24 to 48 hours. You cannot edit the application while it is in review. The **Verify your business** row on **Go live** shows where the review is:
| Status | What the row shows | What you do |
| - | - | - |
| In review | An **In review** label and "You're done here." | Nothing. We email you when the review is done. |
| Changes requested | "We need a few details changed before we can approve you." and a **See changes** button | Click **See changes**, make each change, then click **Send back for review**. |
| Rejected | The reason, and a **Resubmit** button | Fix the reason, then click **Resubmit**. If the row shows a date, the **Resubmit** button comes back after that date. |
| On hold | An **On hold** label and the reason | Reply to our email. |
| Verified | "Verified. Card and bank payments are on." | Nothing. |
Each decision also comes by email. The subject line tells you which one it is:
| Subject | What happened |
| - | - |
| One thing missing, or the number of things missing | We need changes before we can approve you. The email lists them. |
| You’re approved. Go get paid. | The review is approved. You can take live payments. |
| We said no. Here’s why. | The review is rejected. The email gives the reason, and the date after which you can apply again when there is one. |
| We’ve paused your account. Your balance is fine. | Your account is on hold. New payments, subscription charges and payouts stop. Your balance stays yours. |
| You’re back on | The hold is lifted, and payments and payouts start again. |
## The identity check
The identity check confirms who we pay. It exists only in live mode, because only a live payout sends real money. An owner or admin of the account completes it. A team member with a lower role cannot start it.
### Documents for the check
* **An ID document**: you take a photo of it.
* **A selfie**: you take it during the check. You can finish the check on your phone.
The dashboard says the check is "checked automatically, usually in under a minute, and you only do this once." For a business registered as a company, the check verifies the company register and its key people.
### Start the check
Before you have money to pay out, the **Payout accounts** tab on **Balances** shows a note about the check. The note has no button. The **Go live** page says the same under **Your account**: "We ask when your first payout is ready".
When you have money to pay out, a message about the check shows on **Home** and at the top of every **Balances** tab. Click **Verify and get paid** to open the check. When you finish, the check returns you to the **Payouts** tab on **Balances**.
If that money has no bank account to go to, the message asks for the bank account first: "Add the bank account we pay into, about two minutes." Click **Add bank account**. The identity check is the second step.
If your country has no local bank route, you can start the check before you have money to pay out: the **Stablecoin payouts** card on the **Payout accounts** tab has the link **Start the identity check**. See [Stablecoin payouts](/payouts/stablecoin-payouts#who-can-be-paid-in-stablecoins).
### Check status messages
The same message on **Home** and **Balances** tells you where your check is:
| Where the check is | The message | The button |
| - | - | - |
| Not started, with money to pay out | Names your balance and what the check takes | **Verify and get paid** |
| Started, not finished | **Picking up where you left off** | **Continue** opens the same check again |
| Submitted | **Checking your documents now**, "This usually takes a few minutes" | None |
| Did not go through | Says your money is safe and still yours | **Try again** |
| Passed | Says "We’ll pay" and the name we pay | None |
If you request a payout before the check passes, we keep the request. The payout goes out in the first payout run after the check passes, and you do not need to request it again. See [Payouts](/payouts/overview) for the payout schedule.
The confirmation names who we pay. Read that name. If it is wrong, contact support before your first payout. For a company, the ID names its owner and we pay the company. That difference is expected.
### Identity check emails
| Subject | When we send it |
| - | - |
| The amount, then "is waiting on one boring step" | You have money to pay out and the check has not passed. We send it a few days before each payout run, at most twice a month. |
| Your money is safe, we just need to try that check again | The check did not go through. |
### If the check does not go through
The usual cause is a blurry photo or a document the check cannot read. Click **Try again** to do the check again. If it still does not go through, reply to the email and a person works it out with you.
### If you cannot use the identity check
Some people cannot use the check: for example, a person with no phone that has a camera, or with a document the check does not read. Contact support. A person then confirms your identity another way. A person's confirmation counts the same as the automatic check.
## Bank accounts and your name
The holder name on your bank account should match your verified legal name. When you save a bank account, a separate name check compares the name with your bank's records. See [Add a bank account](/payouts/payout-accounts) for its labels and what to do about each one.
## Businesses managed by a platform
A business that a [platform](/connect/overview) manages goes through the same two checks. The platform can file the business review for its client, and our reviewers see that the platform filed it. The identity check is the client's own: the platform gets an **ID check link** and sends it, and only the client can complete it. The platform finds the link on the business's page, under **Businesses** in the sidebar, in the **For you** card. When the check passes, the list shows **Identity verified** under the business's status. Another app of the same company is verified with that company: we review only its product, and it has no identity check of its own.
## From code
* **Business review**: `agenta verify` submits it, shows its status, and sends it back after changes. See [Verify](/cli/verify).
* **Identity check for a client**: a platform gets the client's link with a live key. See [Create a verification link](/api-reference/businesses/verification-link).
## Next steps
The checklist we review against, and the common reasons for a change request.
Every step from test mode to your first live payment.
# Checkouts
Source: https://docs.agentaos.ai/payments/checkouts
The hosted checkout your buyers pay on, step by step, and how to create a checkout from your server.
A checkout is one attempt to collect one payment. Each time a buyer opens a product's buyer link, AgentaOS creates a new checkout for that buyer. You can also create a checkout from your server when your app already knows who the buyer is. In both cases the buyer pays on the same hosted page at `https://app.agentaos.ai/…`.
The **Checkouts** tab on the Products page lists every checkout with its status: **Open**, **Completed**, or **Expired**.
## The buyer's checkout
The hosted checkout has two steps. The order summary stays on the left: the product, the subtotal, the tax, and the total due. Under the summary, the page states **Sold by AgentaOS, merchant of record.**
### Step 1: Your details
The buyer fills in:
* **Email**, required. The receipt goes to this address.
* **Full name**, marked **Optional**.
* **Country**, required. The hint under it reads "Used to calculate tax on this purchase." After the buyer picks a country, the hint shows the new total, such as "Total updates to €12.40 including tax." For a country outside the scope of VAT, it reads "No tax for this country. Total stays the list price."
* **Buying for a business? Add VAT ID**, a link that opens the VAT ID field. A valid EU VAT ID from another EU country is checked live, and reverse charge (0% VAT) applies only after that check passes.
* **Discount code**, optional. See [Discount codes](/payments/discount-codes).
If the product has [custom checkout fields](/payments/payment-links#custom-checkout-fields), they appear under **Asked by** followed by your business name. The buyer then clicks **Continue to payment**.
### Step 2: Review & pay
The top of the step shows **Paying as** with the buyer's email and the total. The buyer can open it to check the country, VAT ID, and tax, or click **Edit details** to go back. Under **Card details**, the payment form offers **Card** and, for EUR payments, **Bank transfer**. The buyer clicks **Pay** followed by the amount. Apple Pay and Google Pay appear above the form on devices that support them. The buyer types card details into the secure card form; your server and ours never receive them.
After the payment clears, the buyer gets a receipt by email. If the product has a Success URL, the checkout sends the buyer there.
### Test mode
A test-mode checkout shows a banner at the top: **Test payment · 4242 4242 4242 4242 · any expiry · any CVC**. Click the card number to copy it, then paste it into the card field. See [Test mode and testing](/getting-started/test-mode).
## Create a checkout from your server
Sharing the buyer link is enough for most sales. Create a checkout from your server when:
* The buyer is logged in to your app, and you must know which of your users paid.
* You want to attach your own data, such as a user ID or an order number, with `metadata`. The data comes back in the [webhook](/payments/webhooks).
* You already have the buyer's email or country and want to fill them in.
Pass the product's ID as `linkId`. On the product page, open **Building this into your site?** and click **Copy ID**.
```typescript theme={"system"}
import { AgentaOS } from '@agentaos/pay';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
const checkout = await agentaos.checkouts.create({
linkId: 'link-uuid',
buyerEmail: 'buyer@example.com',
metadata: { userId: 'user_123' },
});
res.redirect(checkout.checkoutUrl);
```
```bash theme={"system"}
curl -X POST https://api.agentaos.ai/api/v1/gateway/sessions \
-H "x-api-key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"linkId": "link-uuid",
"buyerEmail": "buyer@example.com",
"metadata": { "userId": "user_123" }
}'
```
A checkout created from a product takes the product's amount, currency, description, and URLs. Set `amountOverride` to charge a different amount on this checkout only.
Do not treat the Success URL redirect as proof of payment. The buyer can close the browser before the redirect. Use [webhooks](/payments/webhooks) or the checkout's `status` to confirm the payment.
### Create a checkout without a product
When your server knows the amount, you can create a checkout without a product. This checkout is always a one-time payment. To sell a subscription, use a subscription product and pass its `linkId`.
```typescript theme={"system"}
const checkout = await agentaos.checkouts.create({
amount: 49.99,
currency: 'EUR',
description: 'Order #123',
successUrl: 'https://myshop.com/success',
});
```
```bash theme={"system"}
agenta pay checkout --amount 49.99 --currency EUR --description "Order #123"
```
### Fill in the buyer's details
```typescript theme={"system"}
const checkout = await agentaos.checkouts.create({
linkId: 'link-uuid',
buyerEmail: 'buyer@example.com',
buyerName: 'Jane Doe',
buyerCompany: 'Acme GmbH',
buyerCountry: 'DE',
buyerVat: 'DE123456789',
buyerAddress: 'Hauptstrasse 1, Berlin',
});
```
The company, VAT number, and address are printed on the invoice. The buyer cannot change a field you fill in: the checkout marks it **Set by** followed by your business name. A field you leave out stays editable. If you do not set `buyerCountry`, the buyer picks the country, and that choice sets the VAT.
### Retrieve, list, and cancel
```typescript theme={"system"}
const checkout = await agentaos.checkouts.retrieve('mZrESFyR7RC9RPsJfZCVkg');
console.log(checkout.status); // 'open' | 'completed' | 'expired' | 'cancelled'
const page = await agentaos.checkouts.list({ status: 'completed', limit: 10 });
await agentaos.checkouts.cancel('mZrESFyR7RC9RPsJfZCVkg');
```
```bash theme={"system"}
agenta pay get mZrESFyR7RC9RPsJfZCVkg
agenta pay list --status completed --limit 10
```
```bash theme={"system"}
curl https://api.agentaos.ai/api/v1/gateway/sessions/mZrESFyR7RC9RPsJfZCVkg \
-H "x-api-key: sk_live_..."
curl "https://api.agentaos.ai/api/v1/gateway/sessions?status=completed&limit=10" \
-H "x-api-key: sk_live_..."
curl -X POST https://api.agentaos.ai/api/v1/gateway/sessions/mZrESFyR7RC9RPsJfZCVkg/cancel \
-H "x-api-key: sk_live_..."
```
An open checkout expires after `expiresIn` seconds: 1800 by default, from 300 to 86400. Canceling stops the buyer from paying. A payment that cleared before the cancel still completes.
The full parameter and response reference is in [Checkouts (SDK)](/sdk/pay-checkouts) and [agenta pay](/cli/pay).
## Money model
A plain `amount` is in currency units (`49.99` means €49.99), and a field whose name ends in `Minor` is an integer in minor units. The full rules are in the [money model](/api-reference/introduction#money-model).
## Next steps
Create the product a checkout sells.
The invoice every checkout issues.
Show the checkout on your own page.
Confirm the payment on your server.
# Customers
Source: https://docs.agentaos.ai/payments/customers
Everyone who paid you or whom you billed, with their revenue and payments. Built for you from checkouts and invoices.
The Customers page lists the people who paid you and the people you billed. AgentaOS adds a customer when someone pays you for the first time. It also adds one when you bill someone by email, on an invoice or on a checkout your server creates. You do not add or import customers yourself.
A customer's details come from what the buyer typed at checkout, or from what you entered on the invoice: email, name, country, and VAT number. Test mode and live mode keep separate customer lists.
## The Customers page
In the sidebar, click **Customers**. Each row shows:
* The customer's name or email.
* **Revenue**: what the customer paid you, before VAT.
* **Payments**: how many payments they made.
* **Last active**: their most recent activity.
A customer you billed who has not paid yet shows a dash for revenue and payments. Type in **Search customers** to find a customer by name or email. Click **Create invoice** in a row to bill that customer.
Click a row to open the customer. The customer page shows their details, when you first saw them, and their invoices.
## From code
The API returns the same list. It is read-only: there is no call to create or update a customer. The full reference is in [Customers (SDK)](/sdk/pay-customers) and [agenta customers](/cli/customers).
```typescript theme={"system"}
const page = await agentaos.customers.list({ limit: 20, offset: 0 });
console.log(page.total, page.hasMore);
```
```bash theme={"system"}
agenta customers list --limit 20
```
```bash theme={"system"}
curl "https://api.agentaos.ai/api/v1/gateway/customers?limit=20&offset=0" \
-H "x-api-key: sk_live_..."
```
The list is newest first, and each call returns `{ items, total, hasMore }`.
```json theme={"system"}
{
"items": [
{
"id": "a1b2c3d4-...",
"email": "buyer@example.com",
"name": "Jane Doe",
"country": "DE",
"vatNumber": "DE123456789",
"stripeCustomerId": "cus_...",
"createdAt": "2026-03-17T02:00:00.000Z"
}
],
"total": 1,
"hasMore": false
}
```
Customer UUID.Customer email.Customer name.ISO 3166-1 alpha-2 country code.VAT number, if the customer gave one.The card processor's customer ID. Set when the customer first subscribes or saves a card.ISO 8601. When AgentaOS first added this customer.
## Next steps
Every invoice a customer has.
Customers with a running subscription.
# Discount codes
Source: https://docs.agentaos.ai/payments/discount-codes
Codes a buyer types at checkout to pay less, or to tell you who sent them. Create them in the dashboard or the CLI.
A discount code lets a buyer pay less at checkout. You can also make a code that takes nothing off and only tells you who sent the buyer, for example a referral or a podcast code. A code works on any product, one-time or subscription.
## Create a code
In the dashboard sidebar, click **Discounts**. The page lists your codes, and **New code** starts a new one.
* **Code**: the text the buyer types, for example `LAUNCH20`. Use letters, numbers, dashes, and underscores, up to 64 characters. Buyers can type it in upper or lower case.
* **What it gives**: pick **Percent off**, **Amount off**, or **No discount — just track who it brings in**. A percentage is between 1 and 99. For a full first period at no charge, use a free trial instead.
* **Name**: optional. "For your records. Buyers never see it."
* **Only for some products**: tick it to pick the products the code works on. Leave it clear and the code works on every product you sell.
The code works at checkout right away.
An **Amount off** code takes a fixed amount off the total the buyer pays, VAT included. If you limit an amount-off code to some products, those products must all use the same currency. To discount products in different currencies with one code, use a percentage.
## Your codes
Each code has one row: the code, the products it works on, its kind (**Discount** or **Tracking**), and its state (**Running** or **Stopped**). Click a code to see what it takes off and how many times buyers used it.
Click **Stop** to end a code. A stopped code no longer works at checkout. Subscribers who already used it keep the discount they got. You cannot change a code after you create it: stop it and create a new one.
Only an owner or admin of your business can create or stop a code.
## At checkout
On the hosted checkout, the buyer sees an optional **Discount code** field under **Your details**. After **Continue to payment**, the order summary shows the discount as its own line, and the pay step confirms the code, for example "LAUNCH20 · €2.00 off".
A percentage comes off the price before tax, and AgentaOS calculates the tax on the lower amount. In the example above, 24% tax on €8.00 is €1.92.
On a subscription, the discount applies to the first payment only. Every renewal charges the full price.
Discount codes work the same way on the [embedded checkout](/payments/embedded-checkout).
## From code
Create and stop codes from the CLI, signed in as an owner or admin. The CLI also sets two limits the dashboard form does not have: a maximum number of uses and an end date. With an API key, the SDK can read your codes and list who used one, but it cannot create or stop codes.
```bash theme={"system"}
# 20% off the first payment
agenta discounts create --code LAUNCH20 --percent-off 20
# 5.00 off in your currency, stops after 100 uses or on Dec 31, 2026
agenta discounts create --code SAVE5 --amount-off 5.00 \
--max-redemptions 100 --expires-at 2026-12-31
# No discount: only track who the code brings in
agenta discounts create --code PODCAST
agenta discounts list
agenta discounts show # what it takes off, times used
agenta discounts archive # stop the code
# Subscribers who used a code
agenta subscriptions list --code LAUNCH20
```
```typescript theme={"system"}
const page = await agentaos.discountCodes.list({ limit: 20 });
const code = await agentaos.discountCodes.get('code-uuid');
console.log(code.code, code.termsLabel, code.timesRedeemed, code.active);
// LAUNCH20 20% off 14 true
// Subscribers who used a code (matched in upper or lower case)
const subscribers = await agentaos.subscriptions.list({ discountCode: 'LAUNCH20' });
```
## Next steps
See who subscribed and manage running subscriptions.
Start a checkout from your own code.
# Embedded checkout
Source: https://docs.agentaos.ai/payments/embedded-checkout
Put the AgentaOS checkout on your own page, inline or as an overlay, with a script tag, npm, or React.
The embedded checkout is the AgentaOS checkout on your own page: **inline** in a spot you choose, or as an **overlay** over the page. Card, bank transfer, trials, discount codes, VAT and 3D Secure work exactly as on the hosted checkout. AgentaOS stays the merchant of record.
It runs in the browser only. For server work, such as creating checkouts or reading subscriptions, use [`@agentaos/pay`](/sdk/pay-overview).
## Before you start
In the dashboard, **Settings → Developers → Approved sites**, add each site you'll embed on, e.g. `https://acme.com`. The browser refuses to show your checkout anywhere else. Test products also work on `localhost` without adding it.
**Settings → Business → Checkout appearance**: button color, font, corners, background. It applies to the hosted and the embedded checkout alike.
The `checkoutUrl` from `agenta products create`, the SDK, or the product page. **Embed on your site** on the product page has a ready snippet.
## Script tag
No build step:
```html theme={"system"}
```
## npm
```bash theme={"system"}
pnpm add @agentaos/checkout
```
```ts theme={"system"}
import { loadAgentaOS } from '@agentaos/checkout';
const agentaos = await loadAgentaOS(); // null on the server
agentaos?.checkout.open({ link, target: '#checkout', onEvent });
```
`loadAgentaOS()` adds the script once, however many times it's called (the first call's `origin` wins). While developing against a local AgentaOS, pass `loadAgentaOS({ origin: 'http://localhost:3000' })`. If the script can't load, the promise rejects with a plain sentence and a later call tries again. `` reports it to `onEvent` as `checkout.error` `not_loaded`.
## React
```tsx theme={"system"}
import { AgentaOSCheckout, useAgentaOSCheckout } from '@agentaos/checkout/react';
// Inline: switching `link` (e.g. a Monthly/Yearly toggle) updates the open checkout.
// Overlay, from a button: it lives as long as this component (unmounting closes it)
const { open } = useAgentaOSCheckout();
```
## Options
The product's buyer link.
Instead of `link`: the `sessionId` of a checkout your server created with [`checkouts.create`](/sdk/pay-checkouts). Use it for logged-in users, with your own `metadata`. Create the checkout when the buyer clicks Buy, not on every page view: checkout creation is [rate-limited](/api-reference/introduction#rate-limits) to 60 per minute from your server's IP address, shared by all your buyers.
Selector or element for inline. Leave it out for the overlay.
Prefill the buyer's email. Sent to the checkout directly, never in a URL.
Prefill the buyer's country, ISO code. Sent to the checkout directly, never in a URL.
Where the whole page goes after payment, with `sessionId` added (https only). Defaults to the checkout's success URL: the one given to `checkouts.create`, else the product's. `false` keeps the page where it is.
Receives the [events](#events) below.
`open()` returns `{ update({ link | session }), close() }`. Prefill and `successUrl` are read when the checkout opens; `update()` switches only the product or checkout.
## Events
| `name` | `data` |
| - | - |
| `checkout.loaded` | `{ product: { name, type, billingInterval, trialDays }, currency, testMode }` |
| `checkout.completed` | `{ sessionId, kind: 'payment' \| 'trial' \| 'bank_transfer', email, amountMinor, currency, successUrl }` |
| `checkout.closed` | `{}`: the overlay closed, by the buyer, by `close()`, or because the React component that opened it unmounted |
| `checkout.error` | `{ code: 'not_loaded' \| 'not_found' \| 'unavailable', message }` |
`amountMinor` is what was charged today in minor units (0 for a free trial), or what the buyer was asked to send for a bank transfer. A bank transfer never moves the page: the buyer needs the bank details on screen.
**An event is never proof of payment.** Unlock access from the [webhook](/payments/webhooks) or `checkouts.retrieve(sessionId)`, and handle `checkout.completed` once per `sessionId`: a reload of a finished checkout sends it again.
`not_loaded` after 10 seconds usually means the page's site is not on the business's approved sites.
## Payment methods in a frame
Cards with 3D Secure work everywhere. On EUR sales the checkout also offers bank transfer. Apple Pay and Google Pay appear on devices that support them.
## Next steps
Create a checkout on your server for a logged-in user.
Unlock access when the payment is confirmed.
# Invoices
Source: https://docs.agentaos.ai/payments/invoices
Every sale gets a VAT-correct invoice automatically. Create and send invoices by hand, download the PDF, a statement, or a CSV for your accountant.
Every sale gets an invoice. You do not create it or send it; AgentaOS issues it when the payment clears. For a card or bank sale, AgentaOS is the merchant of record, so the invoice names AgentaOS (Aristokrates OÜ, Estonia) as the seller, "Merchant of Record for" your business. The invoice carries the buyer's details and the tax breakdown for the buyer's country.
You can also create an invoice by hand and email it to a customer. The customer pays it on the same hosted checkout as a product.
## The Invoices page
In the sidebar, click **Invoices**. The page has four tabs, each with a count:
* **All**: every invoice, newest first.
* **Open**: invoices sent and waiting for payment. An open invoice past its due date shows **Overdue**.
* **Paid**: paid invoices for a period you pick, with **Revenue**, **VAT**, and **Invoices** totals at the top. A paid invoice with a chargeback shows **Disputed** or **Charged back**.
* **Void**: voided invoices, kept for your records.
The **How invoicing works** link at the top opens this page.
## When an invoice is issued
A sale from a product's buyer link gets its invoice when the payment clears. The invoice goes straight to **Paid**.
An invoice you create by hand, or a checkout your server creates, gets its invoice when you create it. That invoice sits in **Open** until the customer pays, then moves to **Paid**.
When the payment clears, AgentaOS also emails the buyer a receipt. See [Receipts](/payments/receipts).
## Create and send an invoice
On the Invoices page, click **Create invoice**.
Enter the customer's name, email, and country. Add their VAT number if they are a business. The country sets the VAT.
Enter an item description, a quantity, and a price. Click **Add line item** for each extra line. Add **Notes** for the customer if you want.
Under **Invoice settings**, choose the **Currency** and the **Payment terms**: **Net 7**, **Net 14**, **Net 30**, or **Due on receipt**.
Click **Review & send**. Check the email under **Delivery**, then click **Send to** followed by the customer's name. To share the link yourself instead, click **Create without sending**.
The builder saves your draft as you type. After you create the invoice, the next screen has **Copy payment link** and **Download invoice**. The customer pays on the hosted checkout, by card or, for EUR invoices, by bank transfer. You can also start an invoice from a customer's row on the [Customers](/payments/customers) page.
## Download a PDF, a statement, or a CSV
* **Invoice PDF**: the seller and buyer details, the line items, and the tax breakdown (subtotal, tax, total).
* **Statement**: on the **Paid** tab, click **Download statement**. The PDF covers the period you picked, with the opening and closing figures, every transaction, and a VAT summary by rate.
* **CSV**: on the **Paid** tab, click **Export CSV**. The file has 23 columns for accounting software, including the invoice number, the tax name, rate, and amount, a `total_eur` column, and the buyer's name, company, country, and VAT number.
## Void an invoice
Voiding marks the invoice as void in your records. It does not refund the buyer or touch the payment. Use it to correct a bookkeeping mistake. Only an owner or an admin can void an invoice.
On the **Paid** tab, click the void icon in the invoice's row, then click **Confirm void**. To cancel an open invoice that the customer will not pay, void it through the API (below).
You cannot undo a void. The invoice stays in the **Void** tab.
## From code
Every invoice on the Invoices page is also available through the API. The full reference is in [Invoices (SDK)](/sdk/pay-invoices) and [agenta invoices](/cli/invoices). Amounts on an invoice are in currency units: `19.99` means €19.99.
```typescript theme={"system"}
import { writeFileSync } from 'node:fs';
import { AgentaOS } from '@agentaos/pay';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
// List, newest first. Filter by date range and status.
const page = await agentaos.invoices.list({ from: '2026-03-01', to: '2026-03-31', limit: 50 });
const invoice = await agentaos.invoices.retrieve('invoice-uuid');
writeFileSync('invoice.pdf', await agentaos.invoices.downloadPdf('invoice-uuid'));
writeFileSync(
'march-statement.pdf',
await agentaos.invoices.downloadStatement({ from: '2026-03-01', to: '2026-03-31' }),
);
writeFileSync(
'invoices.csv',
await agentaos.invoices.exportCsv({ from: '2026-03-01', to: '2026-03-31' }),
);
await agentaos.invoices.void('invoice-uuid');
```
```bash theme={"system"}
agenta invoices list --limit 50
```
```bash theme={"system"}
curl "https://api.agentaos.ai/api/v1/gateway/invoices?from=2026-03-01&to=2026-03-31&status=paid" \
-H "x-api-key: sk_live_..."
curl https://api.agentaos.ai/api/v1/gateway/invoices/invoice-uuid/pdf \
-H "x-api-key: sk_live_..." -o invoice.pdf
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
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
curl -X POST https://api.agentaos.ai/api/v1/gateway/invoices/invoice-uuid/void \
-H "x-api-key: sk_live_..."
```
The `status` filter takes `all`, `issued`, `paid`, or `voided`. An invoice's `status` is `issued` (the **Open** tab), `paid`, or `voided`. A list call returns `{ items, total, hasMore }`.
## Next steps
The receipt a buyer gets for every paid invoice.
The buyer behind each invoice.
How VAT is calculated for the buyer's country.
The documents that come with each payout.
# Products and payment links
Source: https://docs.agentaos.ai/payments/payment-links
Create a product in the dashboard, share its buyer link, and get paid. One-time or subscription, with VAT handled for you.
A product is one thing you sell at one price. It is a one-time payment or a subscription billed monthly or yearly. Every product has a buyer link, a public URL in the shape `https://app.agentaos.ai/pay/…`. You share that link, and each buyer who opens it gets their own checkout. One link can be paid any number of times.
AgentaOS is the merchant of record on every sale. Buyers pay by card, and on EUR sales also by bank transfer. The checkout asks for the buyer's country, adds the correct VAT, and issues the invoice. You do not register for VAT in the buyer's country. See [How merchant of record works](/mor/how-it-works).
The API calls a product a payment link. Both names mean the same object, and the product's ID is the `linkId` in the API.
## Create a product in the dashboard
In the sidebar, click **Products**. Then click **New product**.
Under **Details**, enter the **Product name** and a **Description**. Add a **Product image** if you want one. The buyer sees all three on the checkout.
Under **Pricing**, enter the price and pick **EUR** or **USD**. Choose **One-time** or **Subscription**. For a subscription, pick **Monthly** or **Yearly**.
Under **After payment**, click **+ Redirect buyers after payment**. Enter a **Success URL** and a **Cancel URL**.
Click **Create product**. The confirmation screen shows the product as live, with **Copy link** to copy its buyer link.
The **Summary** panel on the right of the form recaps the product as you type. Click **Preview** to see the product the way a buyer sees it.
In test mode, the confirmation screen shows **Try a test payment**. Use card `4242 4242 4242 4242`, any future date, and any CVC. Nothing is charged. See [Test mode and testing](/getting-started/test-mode).
### Subscriptions and trials
When you choose **Subscription**, two more fields appear. **Free trial (days)** delays the first full charge by that number of days, up to 730. **Trial price** sets what the whole trial costs; leave it empty for a free trial. See [Subscriptions](/payments/subscriptions) for what happens after the buyer subscribes.
In live mode, **Subscription** stays locked until your business is verified. See [Go live](/guides/go-live).
### VAT on your prices
The Products page shows how VAT applies to your prices: **VAT added at checkout** or **Prices include VAT**. Click **Change** next to it to set the default for your account under **Settings → Business**. The **Prices include VAT** checkbox on the product form changes it for that one product.
## Share the buyer link
Every product row on the Products page has a copy button that copies the buyer link. Send that link by email or in a chat, or put it behind a Buy button on your site.
The link carries no information about who the buyer is. When your app must know which of your users paid, create a checkout from your server instead. See [Checkouts](/payments/checkouts#create-a-checkout-from-your-server).
To show the checkout inside your own page, open the product and use **Embed on your site**. See [Embedded checkout](/payments/embedded-checkout).
## Custom checkout fields
A product can ask the buyer extra questions before they pay, such as an attendee name or a company. The checkout shows these fields under the line **Asked by** followed by your business name. Each field is required or optional.
You add custom fields through the API with `checkoutFields`. The product form in the dashboard does not have them.
```typescript theme={"system"}
const link = await agentaos.paymentLinks.create({
amount: 15,
currency: 'EUR',
name: 'Event ticket',
checkoutFields: [
{ key: 'attendeeName', label: 'Attendee name', type: 'text', required: true },
{ key: 'company', label: 'Company', type: 'text', required: false },
],
});
```
Each field has a `key`, a `label`, a `type` (`text`, `email`, `tel`, or `select`), and `required`. It can also have a `placeholder`, and `options` for `select`.
## From code
You can create and manage products with the SDK, the CLI, or the REST API. The full parameter and response reference is in [Payment links (SDK)](/sdk/pay-payment-links) and [agenta products](/cli/products).
### Create a product
```typescript theme={"system"}
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',
name: 'Pro plan',
successUrl: 'https://myshop.com/success',
cancelUrl: 'https://myshop.com/cancel',
});
console.log(link.checkoutUrl);
// https://app.agentaos.ai/pay/7rr6S9ml4BMp829wV5WeAA
```
```bash theme={"system"}
agenta products create --name "Pro plan" --amount 49.99 --currency EUR \
--success-url https://myshop.com/success
```
```bash theme={"system"}
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", "name": "Pro plan" }'
```
`amount` is in currency units: `49.99` means €49.99, not cents. See the [money model](/api-reference/introduction#money-model). The CLI creates test-mode products; create live products in the dashboard.
### One-time vs. subscription
A product is `type: 'one_time'` by default. Every payment is independent, and the link can be paid any number of times. Set `type: 'subscription'` to make a recurring plan. A subscription needs a `billingInterval`.
```typescript theme={"system"}
const plan = await agentaos.paymentLinks.create({
amount: 29.99,
currency: 'EUR',
name: 'Pro plan, monthly',
type: 'subscription',
billingInterval: 'month', // 'month' | 'year'
trialPeriodDays: 14, // optional, 1 to 730
});
```
Creating a subscription product does not create a subscription. A subscription starts only when a buyer pays the product at the checkout. See [Subscriptions](/payments/subscriptions).
### Retrieve, list, and cancel
```typescript theme={"system"}
const link = await agentaos.paymentLinks.retrieve('link-uuid');
const page = await agentaos.paymentLinks.list({ limit: 20, offset: 0 });
console.log(page.total, page.hasMore);
await agentaos.paymentLinks.cancel('link-uuid');
```
```bash theme={"system"}
agenta products list --limit 20
```
```bash theme={"system"}
curl https://api.agentaos.ai/api/v1/gateway/payment-links/link-uuid \
-H "x-api-key: sk_live_..."
curl "https://api.agentaos.ai/api/v1/gateway/payment-links?limit=20&offset=0" \
-H "x-api-key: sk_live_..."
curl -X DELETE https://api.agentaos.ai/api/v1/gateway/payment-links/link-uuid \
-H "x-api-key: sk_live_..."
```
A list call returns `{ items, total, hasMore }`. Canceling a product stops new checkouts on its link. Checkouts already in progress and subscriptions already running from it continue.
## Next steps
What the buyer sees, and how to start a checkout from your server.
How a subscription product becomes a running subscription.
Codes a buyer types at checkout.
Get notified when a buyer pays.
# Receipts
Source: https://docs.agentaos.ai/payments/receipts
Every paid invoice gets a receipt, emailed to the buyer automatically. Download the PDF, or send the email again.
When a payment clears, AgentaOS emails the buyer a receipt at the email they gave at checkout. You do not send it or build it. The receipt belongs to the paid invoice, and you can send it again if the buyer loses it. AgentaOS also emails you when you make a sale.
A receipt exists only for a paid invoice. An open or voided invoice has no receipt.
## In the dashboard
In the sidebar, click **Invoices**, then open a paid invoice from the **Paid** tab.
* **Download receipt** saves the receipt PDF.
* **Resend invoice** emails the receipt to the buyer again. The button is grayed out when the invoice has no buyer email.
## Send a receipt again
Resending goes to the buyer's email on the invoice, the same address as the first receipt. Only an owner or an admin can resend a receipt.
```typescript theme={"system"}
const result = await agentaos.invoices.sendReceipt('invoice-uuid');
console.log(result.sentTo); // the address it went to
```
```bash theme={"system"}
agenta invoices send-receipt invoice-uuid
```
```bash theme={"system"}
curl -X POST https://api.agentaos.ai/api/v1/gateway/invoices/invoice-uuid/send-receipt \
-H "x-api-key: sk_live_..."
```
The call returns `{ ok: true, sentTo }`. It fails when the invoice is not paid, or when the invoice has no buyer email.
## Download the receipt PDF
```typescript theme={"system"}
import { writeFileSync } from 'node:fs';
const pdf = await agentaos.invoices.getReceipt('invoice-uuid');
writeFileSync('receipt.pdf', pdf);
```
```bash theme={"system"}
agenta invoices receipt invoice-uuid -o receipt.pdf
```
```bash theme={"system"}
curl https://api.agentaos.ai/api/v1/gateway/invoices/invoice-uuid/receipt \
-H "x-api-key: sk_live_..." \
-o receipt.pdf
```
An invoice from before receipts existed on AgentaOS has no receipt document. For that invoice, the call returns the invoice PDF, which is also a proof of payment.
## Next steps
The invoice behind every receipt.
Where the buyer's email comes from.
# Subscriptions
Source: https://docs.agentaos.ai/payments/subscriptions
Sell a monthly or yearly plan. The buyer pays on the checkout, AgentaOS bills every cycle as merchant of record, and you manage plan changes and cancellations from the dashboard or from code.
A subscription starts from a product with a monthly or yearly price. The buyer pays for it on the hosted checkout or on the checkout embedded in your site, and their card is charged again every cycle. AgentaOS is the merchant of record: it adds VAT where the buyer owes it, takes the payment, and issues the invoice for every cycle.
You create the plan once. Each buyer who pays for it starts their own subscription, and you see each one under **Subscriptions** in the dashboard. For a step-by-step first sale, see [Sell a subscription](/guides/sell-a-subscription).
## Create a plan
In the dashboard, go to **Products** and click **New product**. Under **Pricing**, click **Subscription**, then pick **Monthly** or **Yearly**. The price you type is the plan price before VAT, unless you tick **Prices include VAT**. Below the price, the form shows what buyers pay. For a €29 plan it reads "EU consumers pay €35.96 (€29.00 + €6.96 VAT), you receive €29.00 before fees. Everyone else pays €29.00."
Click **Create product**. The plan gets a link that you send to buyers, and a link ID that your code uses. Make one product for each price: a monthly Pro plan and a yearly Pro plan are two products.
## Subscription statuses
A subscription moves through these statuses. The dashboard shows the status on each subscription, and the API returns it as `status`.
| Status | When |
| - | - |
| `incomplete` | The buyer started the checkout but did not finish paying or saving a card. |
| `trialing` | The buyer started a free trial. Their card is saved and nothing is charged. |
| `active` | The first payment went through. The card is charged again at the start of each cycle. |
| `past_due` | A renewal charge failed and the charge is retried. |
| `canceled` | The subscription ended. There are no more charges. |
The dashboard shows the next charge date under **Renews**. Each paid cycle produces an invoice. The [SDK reference](/sdk/pay-subscriptions#statuses) lists every status, including the rare ones.
## Free trials
A trial belongs to the plan. Type the number of days in **Free trial (days)**, from 1 to 730. At checkout the buyer sees €0.00 due today and the price that follows the trial.
The €35.96 is the €29.00 plan price plus 24% VAT, the Estonian rate. The checkout charges it to every EU consumer while AgentaOS sells under the EU distance-selling threshold. See [How tax works](/mor/tax#the-rate-on-eu-sales-today).
When the buyer clicks **Start 14-day free trial**, their card is saved and the subscription starts as `trialing`. The first charge happens when the trial ends. The buyer and you each get an email when the trial starts.
To charge for the trial instead of giving it free, type an amount in **Trial price**. That amount covers the whole trial, and the buyer pays it at checkout.
The `checkout.session.completed` webhook for a free trial has `amount` `"0"`, `trial: true`, and `trial_end`. Treat it as "trial started", not as money received. See [Webhook events](/webhooks/events).
## Upgrade and downgrade
To move a subscriber to another plan, open the subscription and click **Change plan**. Pick the new plan. The dialog shows what the change costs before you confirm it.
* **Upgrade** (the new plan costs more): the buyer pays the prorated difference today on their saved card, and the new plan applies at once. During a free trial nothing has been paid yet, so the amount due today is €0.00.
* **Downgrade** (the new plan costs the same or less): nothing is charged today. The buyer keeps the current plan until the current period ends, and then the new plan starts.
A scheduled downgrade shows on the subscription as **Scheduled change** until it takes effect.
To undo a scheduled downgrade, click **Change plan** and pick the current plan. The dashboard labels it "current plan", and choosing it cancels the scheduled change without a charge.
A plan change keeps the currency and the billing interval. You can move a monthly EUR plan only to another monthly EUR plan. The subscription must be `trialing` or `active`.
## Cancel
Click **Cancel subscription** on the subscription page and confirm. The subscription stays active until the end of the current period, and then it ends. The buyer keeps what they paid for, and nothing is refunded.
Until the period ends, **Resume subscription** undoes the cancellation. A subscription always runs to the end of the period it is in; it cannot be ended earlier.
## Credits
A credit takes money off a subscriber's next invoice, for example to make up for two days of downtime. It is not a refund: no money goes back to the card, and receipts already sent stay as they are. The credit comes out of what you earn on that invoice.
To give one, open the subscription and click **Give credit** under **Credit**. Type the **Amount** and a **Reason**, then click **Confirm credit**. The form says of the reason: "The subscriber never sees this. It is what you and your team read later."
The panel then shows what the next invoice will charge.
* A credit can cover the whole next invoice, and no more.
* The subscription must be `active` or `trialing`, not scheduled to cancel, and without a scheduled plan change.
* While a subscriber has credit left, a plan change waits until their next invoice.
* The unspent credit belongs to the buyer, and it is shared by every subscription they have with you.
* Only an owner or admin can give or see credits. An API key cannot.
The buyer pays less on their next charge. When a credit covers a whole month, their receipt says the credit covered it and nothing was charged to the card.
## Discount codes
A buyer can type a discount code at checkout. On a subscription, the discount applies to the first invoice only, and every renewal charges the full price. The subscription records which code the buyer used, so you can list the subscribers each code brought in. See [Discount codes](/payments/discount-codes).
## Emails and receipts for the buyer
* A receipt email for every paid cycle, with the receipt attached as a PDF. AgentaOS issues it as the merchant of record, and the subscription page lists each paid cycle under **Billing history**. See [Invoices](/payments/invoices) and [Receipts](/payments/receipts).
* An email when a free trial starts.
## From code
For a signed-in user who upgrades inside your app, see the runnable demo on [Paywall for logged-in users](/sdk/paywall-for-logged-in-users).
The same actions are in the SDK (`@agentaos/pay`) and the CLI (`agenta`). The SDK needs an API key: `const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!)`. Code uses the subscription `id` that `subscriptions list` returns.
```bash theme={"system"}
# Create a monthly plan with a 14-day free trial
agenta products create --name "Pro" -a 29 -c EUR --subscription --interval month --trial-days 14
# List subscriptions
agenta subscriptions list --limit 20
# Quote a plan change, then apply it
agenta subscriptions change-plan --to --dry-run
agenta subscriptions change-plan --to
# Cancel at the end of the period
agenta subscriptions cancel
# Give a €10 credit, then list the credits given
agenta subscriptions credit -a 10 --reason "Two days of downtime"
agenta subscriptions credits
```
```typescript theme={"system"}
// Create a monthly plan with a 14-day free trial
const plan = await agentaos.paymentLinks.create({
name: 'Pro',
amount: 29,
currency: 'EUR',
type: 'subscription',
billingInterval: 'month',
trialPeriodDays: 14,
});
// plan.id is the link ID; plan.checkoutUrl is the link for buyers
// List subscriptions
const page = await agentaos.subscriptions.list({ limit: 20 });
// Quote a plan change, then apply it with the quote's prorationDate
const quote = await agentaos.subscriptions.previewPlanChange(subscriptionId, proPlusLinkId);
const change = await agentaos.subscriptions.changePlan(subscriptionId, {
targetLinkId: proPlusLinkId,
prorationDate: quote.prorationDate,
});
// Cancel at the end of the period
const cancel = await agentaos.subscriptions.cancel(subscriptionId);
// Give a €10 credit (minor units), then list the credits given
const credit = await agentaos.subscriptions.credit(subscriptionId, {
amountMinor: 1000,
reason: 'Two days of downtime',
idempotencyKey: randomUUID(), // from node:crypto; reuse the same key on a retry
});
const credits = await agentaos.subscriptions.credits(subscriptionId);
```
The quote has `direction` (`upgrade`, `downgrade`, or `revert`), `dueTodayMinor`, `nextInvoiceMinor`, and `nextInvoiceAt`. Amounts that end in `Minor` are in minor units: `4900` is €49.00. Show the quote to the person who approves the change before you apply it, because an upgrade charges the card. Credits need an owner or admin who is signed in, through `agenta login` or the dashboard session: an API key is refused. Every field is in the reference: [SDK subscriptions](/sdk/pay-subscriptions) and [CLI subscriptions](/cli/subscriptions).
## Next steps
Create a plan, take the first subscriber, change their plan, and cancel.
Take money off the first invoice, or track who sent a buyer.
Show the checkout on your own page.
React to trials, renewals, failed payments, and plan changes.
# Webhooks
Source: https://docs.agentaos.ai/payments/webhooks
Get notified when a payment completes. Register a URL, verify the HMAC-SHA256 signature, and never trust an unverified payload.
AgentaOS sends a webhook to your server when something happens in your account: a checkout is paid, a subscription renews or changes, a dispute opens, and more. [Events](/webhooks/events) lists every event and its payload. Webhooks tell your backend about a payment without polling. Build on them, not on 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
In [app.agentaos.ai](https://app.agentaos.ai), open **Settings → Developers → Webhooks**.
Enter the HTTPS URL you want events sent to, for example `https://myshop.com/webhooks`, and click **Save**. AgentaOS does not deliver to `localhost` or a private address: for local development, use a public HTTPS tunnel.
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 stays the same when you change the URL. If you suspect it has been exposed, click **Rotate secret** to issue a new one. Rotating invalidates the old secret immediately, so deploy the new value first if you cannot afford failed deliveries.
You can also pass a `webhookUrl` when you create a [checkout](/payments/checkouts) or [payment link](/payments/payment-links). That payment's `checkout.session.completed` then goes to that URL instead of your account's URL, signed with the same account secret. Every other event goes to the account's 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={"system"}
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) {
res.status(400).send('Invalid signature');
return;
}
res.status(500).send('Webhook processing failed');
return;
}
// Signature is valid: safe to act on event.data now.
switch (event.type) {
case 'checkout.session.completed':
fulfillOrder(event.data.sessionId, event.data.amount); // your own code
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);
});
app.listen(3000);
```
`webhooks.verify()` does four things, in order. It parses the `t=...,v1=...` header, and rejects a timestamp older than 5 minutes or in the future (replay protection). It recomputes the HMAC-SHA256 digest and compares it in constant time. Then it parses and returns a typed `WebhookEvent`. Any failure throws `WebhookVerificationError` instead of returning a falsy value, so your code cannot skip the check by accident.
### Manual verification (no SDK)
The algorithm is HMAC-SHA256 over `{timestamp}.{raw_body}`, so any language with an HMAC and a constant-time compare can check it. The samples below apply the SDK's time rule: they reject a timestamp older than the tolerance, and a timestamp in the future by any amount. Keep your server's clock synced (NTP), or a clock that runs behind rejects every event.
```python Python theme={"system"}
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 and future signatures (replay protection)
age = int(time.time()) - timestamp
if age < 0 or age > 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={"system"}
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={"system"}
$toleranceSec) {
return false; // expired or in the future
}
$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 timestamp `t` is part of the signed content, so a captured request cannot be replayed later: verification rejects anything older than 5 minutes by default, or dated in the future.
## Delivery and retries
```mermaid theme={"system"}
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
```
When your endpoint does not answer `2xx` in time, AgentaOS tries again, up to 3 attempts in all, and then marks the event `failed`. You can send a failed event again from **Settings → Developers → Webhooks**. The exact timeouts, waits and refused addresses are in [Delivery and retries](/webhooks/events#delivery-and-retries).
Return `200` as soon as you have verified the signature and stored the event (a job, a database row). Do the actual work, such as fulfillment emails and external API calls, after you respond. A slow handler is the most common reason a delivery fails and repeats.
## Best practices
The customer can close the browser before the `successUrl` redirect fires. Treat the webhook, not the redirect, as the proof that a payment happened.
Retries mean the same event can arrive more than once. Record a key per event and make your handler safe to run twice. The key for each event type is in [Deduplication](/webhooks/events#deduplication).
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 a sale becomes money you can pay out, where the payout goes, when it runs, what it costs, and the documents you get for each payout.
Your card and bank sales collect in one AgentaOS balance per currency, EUR or USD. Payouts run twice a month, on the 1st and the 15th, and send the available part of that balance to your payout account: a bank account, or a stablecoin wallet if AgentaOS turned that on for you. Each payout comes with a payout statement and a self-billed invoice for your books.
Before your first payout, you need three things:
* An approved business review. See [Verify your business](/mor/verification) and [Go live](/guides/go-live).
* A bank account in the currency of your balance. See [Add a bank account](/payouts/payout-accounts).
* The identity check, done once by an owner or admin in live mode. It starts from **Home** or **Balances** when you have money to pay out. See [The identity check](/mor/verification#the-identity-check).
## Your balance
In the dashboard, open **Balances**. The **Overview** tab shows your **Balance** for each currency, split into rows that add up to the **Total**:
| Row | What it holds |
| - | - |
| **Available now** | Cleared money. The next payout sends it. |
| **On the way** | Sales that are still clearing, with the date each amount becomes available. |
| **Reserve** | "Safety hold on new accounts". The row shows the date the reserve frees up. |
| **To your bank** | A payout in progress. It leaves your balance when it lands. |
| **Paused (disputed)** | Money from a sale that a buyer disputes, held until the dispute ends. |
A new sale clears for 14 days before it moves from **On the way** to **Available now**.
## Where payouts go
Payouts are same-currency. A EUR balance pays to a EUR account and a USD balance pays to a USD account, so no currency conversion happens inside a payout.
Paid through our banking partner. For each currency, AgentaOS uses your newest bank account that is ready for payouts.
Public preview. EUR pays out in EURC and USD pays out in USDC, to a wallet you choose per currency.
To see which countries bank payouts reach, go to [Supported countries](/mor/supported-countries).
## Schedule
Payouts run in two windows each month: the 1st and the 15th. On the **Payouts** tab, the **Payout schedule** section has two options:
* **Automatic**: "Paid automatically every window (1st & 15th)." Set a **Minimum payout**. Below that amount, AgentaOS holds your balance for the next window.
* **You ask**: "Queued for the next window when you request it." Click **Request payout**. The next window pays what is available at that time, including sales that clear before then.
A new account starts on **You ask**. The smallest payout is €50 or \$50. When your balance is below that amount, it waits for a later window.
With a verified account and a bank account, your first payout goes out in the first window after all three are true: your first sales have cleared (14 days after each sale), your **Available now** amount is at least €50 or \$50, and you have clicked **Request payout**. Money in **Reserve** is not paid out yet: the **Reserve** row shows the amount and, once it is set, the date it frees up.
## Fees
Each payout carries a **Payout fee** of 1% of the amount paid out, with a minimum of €1 or \$1. The route adds its own cost:
* **Bank transfer fee**: what our banking partner charges for the transfer, passed through at cost. It depends on the country and currency. See the payout fee table on [Supported countries](/mor/supported-countries).
* **Wallet corridor fee**: for a stablecoin payout. See [Stablecoin payouts](/payouts/stablecoin-payouts#fees).
On **You ask**, the **Payouts** tab shows what you would receive today and the fees before you click **Request payout**. The payout statement lists each fee after the payout.
## Documents for each payout
Every payout gets two documents on the **Payouts** tab:
* **Payout statement**: "What was paid this run: the covered sales, our commission, the payout fee, the bank transfer fee, and what reached your bank." Use it to match the payout to your bank statement.
* **Self-billed invoice**: your supply to AgentaOS, one line per sale that the payout covers, with the fees deducted beneath. The total is what reached your account. It shows no VAT line: it records the value of your supply to AgentaOS (Aristokrates OÜ, Estonia).
Give both documents to your accountant. See [Payout statements and self-billed invoices](/payouts/statements) for each line and how to use the documents in your books.
## FAQ
EUR and USD. Each currency pays out to an account in the same currency.
Check four things. Your account must be verified. You must have a bank account in that currency, or a wallet that you chose for that currency. Your available balance must be at least the minimum payout. On **You ask**, you must request the payout.
Yes, per currency. For example, EUR can pay out to your bank and USD to a wallet. See [Stablecoin payouts](/payouts/stablecoin-payouts).
Yes. The money from a disputed sale moves to **Paused (disputed)** and is not paid out until the dispute ends.
## Next steps
Save the bank account AgentaOS pays you to.
Where bank payouts reach, and the payout fee table.
Verify your account to take live payments and receive payouts.
How a payment becomes money in your balance.
# Add a bank account
Source: https://docs.agentaos.ai/payouts/payout-accounts
Save the bank account AgentaOS pays your EUR or USD balance to, and read the name check that follows.
AgentaOS sends your balance to your bank account. You add one bank account per currency, EUR or USD: sales in EUR pay out to a EUR account, and sales in USD to a USD account. See [Where payouts go](/payouts/overview#where-payouts-go). A bank account belongs to your business, not to test or live mode: the account you add in test mode is the one live payouts use.
You do not need a bank account to take live payments. You need one before your first payout. You can add it before your verification is done, so your first payout is ready to go when it is.
To be paid to a stablecoin wallet instead, see [Stablecoin payouts](/payouts/stablecoin-payouts).
## Add a bank account
In the dashboard sidebar, click **Balances**. Then click the **Payout accounts** tab.
Click **Add bank account**. The **Add bank account** panel opens.
* **Pay me in**: choose **EUR** or **USD**. This is the currency of the balance that pays out to this account.
* **Account type**: choose **Inside Europe** or **Outside Europe**.
* **Account holder type**: choose **Person** or **Business**.
For a EUR account inside Europe, the form asks for:
* **Full name of the account holder**, or **Name of the business / organisation** when the holder is a business: the name exactly as your bank holds it, including a legal suffix such as Ltd or GmbH.
* **Bank code (BIC/SWIFT)**: optional.
* **IBAN**.
Our banking partner decides the fields for each currency and account type, so a USD account or an account outside Europe asks for different details.
Click **Save account**. The panel shows **Account saved** and the result of the name check.
The **Bank accounts** section says: "The holder name should match your verified legal name." Use the name from your [account review](/mor/account-review).
## The name check
When you save the account, our banking partner asks your bank whether the holder name matches the account. The account then shows one of these labels:
| Label | What it means | What to do |
| - | - | - |
| **Ready for payouts** | The name matches, or your bank's country has no name check. | Nothing. |
| **Ready, check the name** | "Only part of the name matched the bank". Payouts still go to this account. | Confirm the name is correct. |
| **Not usable yet** | "The name doesn't match the bank's records". AgentaOS cannot pay out to this account. | Deactivate the account. Add it again with the name exactly as your bank holds it. |
When only part of the name matches, the panel shows **Double-check the name**. When the name does not match, the panel shows **Name doesn't match**. If the name still does not match after you add the account again, ask your bank for the exact name on the account.
When your bank's country has no name check, the panel shows **Nothing to do here**. The account is ready for payouts, but make sure that the name is correct.
## Which account gets paid
For each currency, a payout goes to your newest bank account that is ready for payouts. At the top of the tab, **Next payout goes to** shows, per currency, the account that the next payout uses.
To change the account, add the new one. To stop using an account, click the trash icon on its row, then click **Deactivate**.
## Next steps
The schedule, the fees, and the documents for each payout.
Where bank payouts reach, and the payout fee table.
# Stablecoin payouts
Source: https://docs.agentaos.ai/payouts/stablecoin-payouts
Be paid in USDC or EURC to a wallet instead of your bank account, per currency.
Public preview Open to every account; not feature-complete, and a change can still break an integration. See [Product release phases](/getting-started/release-phases).
A stablecoin payout sends your balance to a wallet instead of your bank account. You choose it per currency: your EUR balance pays out in EURC and your USD balance pays out in USDC. Payouts keep the same schedule as bank payouts, the 1st and the 15th of each month. See [Payouts](/payouts/overview).
## Who can be paid in stablecoins
Stablecoin payouts are switched on per account, after your account has paid you once by bank. Open **Balances**, then the **Payout accounts** tab. The **Stablecoin payouts** card lists three conditions and ticks each one when you meet it:
1. **Business verified and identity check passed**, the same as for any payout. See [Verify your business](/mor/verification).
2. **Bank account ready for payouts**: a bank account that shows **Ready for payouts** (the name check passed). See [Add a bank account](/payouts/payout-accounts).
3. **First bank payout received**: at least one bank payout to that account.
Under the list, the card counts the conditions: "0 of 3 done. Ask us when all three are ticked." The **Go live** page shows the same count on its **Stablecoin payouts** row.
When all three are ticked, click **Ask us**, or write to [contact@agentaos.ai](mailto:contact@agentaos.ai). AgentaOS switches stablecoin payouts on for your account, usually the same day. The card then shows **On** and the button **Add wallet**. Until then, the API answers "Wallet payouts are not open for this account."
If your country has no local bank route, so that a bank payout can only travel by Swift (the countries marked on [Supported countries](/mor/supported-countries#supported-payout-countries)), the card has one condition: **Business verified and identity check passed**. Click **Start the identity check** on the card, in live mode. AgentaOS can switch stablecoin payouts on without the bank payout in that case.
## Networks
| Pay me in | You receive | Networks |
| - | - | - |
| EUR | EURC | Base, Ethereum, Solana |
| USD | USDC | Base, Ethereum, Polygon, Solana |
## Add a wallet
In the dashboard sidebar, click **Balances**. Then click the **Payout accounts** tab. The **Stablecoin payouts** section is below your bank accounts and shows **On**.
Click **Add wallet**. The **Add wallet** panel opens.
* **Pay me in**: choose **EUR** or **USD**.
* **Network**: choose the network your wallet receives on. **Base** is selected first.
* **Wallet address**: paste the address. "Payouts to a wallet cannot be recalled. Check the address before you save it."
* **Name**: optional, for example "Treasury".
* **Who holds this wallet**: choose **My own wallet, I hold the keys** or **An exchange account that holds it for me**.
Click **Save wallet**.
AgentaOS checks the address before it saves it. It refuses an address that is not valid on the network you chose, the burn address, and the contract address of a USDC or EURC token. Anything sent to those addresses cannot be recovered.
AgentaOS sends your answer to **Who holds this wallet** with each payout, as part of the information that crypto transfers must carry about the sender and the receiver.
## Pay a currency to the wallet
Saving a wallet does not move your payouts to it. Your bank account keeps receiving them until you choose the wallet:
1. On the wallet's row, click **Use for EUR payouts** or **Use for USD payouts**.
2. The row then shows "Pays your EUR payouts." (or USD).
If you have no bank account in that currency and have not chosen a wallet, that currency is not paid out.
To return to your bank account, click **Switch back to bank** on the wallet's row. If you remove the wallet that pays a currency, that currency goes back to your bank account.
## The 24-hour wait
AgentaOS does not pay a new wallet until 24 hours after you save it. Until then, its row shows **Payable from** and the date. After that, it shows **Ready for payouts**.
If a payout window falls inside those 24 hours and the wallet pays that currency, the payout waits for the next window. It does not go to your bank account instead.
You cannot edit a wallet's address, network or currency. To use a different address, add a new wallet. The new wallet gets its own 24-hour wait.
## Fees
A stablecoin payout carries two fees, both listed on the payout statement:
* **Payout fee**: the same as a bank payout. See [Payouts](/payouts/overview#fees).
* **Wallet corridor fee**: 1% of the amount paid out, plus €2 or \$2, plus the cost of the bank transfer that funds the stablecoin payout. The network fee is included.
On **You ask**, the **Payouts** tab shows what you would receive today and the fees before you request the payout.
## Next steps
Your balance, the schedule, and the documents for each payout.
Save a bank account for the currencies you do not pay to a wallet.
# Payout statements and self-billed invoices
Source: https://docs.agentaos.ai/payouts/statements
The two documents you get with each payout: what the payout statement shows, why AgentaOS issues a self-billed invoice in your name, and how to use both in your books.
Each payout comes with two PDF documents. The **payout statement** shows what the payout paid and what it cost. The **self-billed invoice** is the invoice for the products you supplied to AgentaOS, which AgentaOS issues in your name. Give both to your accountant.
## Find your documents
1. In the dashboard sidebar, click **Balances**.
2. Click the **Payouts** tab.
3. Scroll to **Payout runs**. Each row is one payout, with its date, its status (**Paid**, **In progress** or **Failed**), its amount, its **Fees**, and what was sent.
4. Click **Statement** to download the payout statement. Click **Self-bill** to download the self-billed invoice.
Each document gets its number the first time you download it. Until then, the row shows **Not yet issued**. Payout statement numbers start with `PS-` and self-billed invoice numbers start with `SBI-`.
## The payout statement
The statement opens with the payout date, the date it was paid, and the **Bank reference**: the text that appears on your bank statement for this payout. For a stablecoin payout, it shows **Paid to** with the network and the end of the wallet address instead.
Three figures follow:
* **Sales included (net)**: what your sales added to this payout. A payout of released reserve shows **Reserve released**. For a platform, a payout of fees from the businesses you manage shows **Platform fees collected**.
* **Payout**: the amount taken from your balance.
* **Sent to your bank**: what the payout sent after fees. A stablecoin payout says **Sent to your wallet**. A payout still on its way says **You receive (est.)**.
### Lines
One line for each sale that the payout covers, with the columns **Date**, **Description**, **Gross**, **Fee**, **Net** and **Paid out**. The description is the sale's receipt reference and the product. **Fee** is our commission on that sale.
Some lines are not sales:
* **Reserve release**: money held back on earlier sales, now paid out.
* **Platform fee from** a business you manage: your share of that business's sale.
* **Retained for** a platform: the share of this sale that went to the platform that manages your business, under the share that platform set.
These two lines appear only when a platform is involved. A business that no platform manages never sees them.
### Totals
* **Returned · Refunded sale** or **Returned · Charged back sale**: part of the payout paid back money owed on an earlier sale that was refunded or charged back. Each line names the sale. When there are returned lines, **Lines total** shows the sum of the sale lines above them.
* **Payout fee**: our fee on the payout. See [Payouts](/payouts/overview#fees).
* **Bank transfer fee**: the cost of the bank transfer, passed through at cost. It shows **Pending** until the transfer cost is known. A stablecoin payout shows **Wallet corridor fee** instead.
* **Sent to your bank**: the amount after both fees. A stablecoin payout also shows **Delivered**: the amount in USDC or EURC.
Notes at the end explain anything unusual, for example a transfer that came back. When a payout does not go through, the statement says so and the money goes back to your balance to be paid out again.
## The self-billed invoice
AgentaOS is the merchant of record: it buys your product and resells it to your buyer. The self-billed invoice documents your side of that: your sale of the products to AgentaOS. AgentaOS issues it in your name, so you do not have to write an invoice for every payout.
The invoice states what the money is:
> "Consideration for the Products resold by Aristokrates OÜ as merchant of record under the Master Services Terms. The supplier retains all intellectual property; no copyright is transferred."
Aristokrates OÜ is the company that operates AgentaOS. It is the legal seller on every sale, so its name is on the invoice.
The payout is the purchase price of the products that AgentaOS resold. The invoice never calls it a royalty or a licence fee.
The invoice shows:
* **Supplier (you)**: your legal name, your address, and your VAT number if you have one.
* **Buyer**: the AgentaOS legal entity, with its registration number and VAT number.
* One line per sale that the payout covers, at the amount the payout paid for it. Released reserve shows as **Reserve release**.
* **Subtotal**, then the deductions:
* **Payout and transfer fees**: the payout fee and the bank transfer fee as one figure. A stablecoin payout shows **Payout fee** and **Wallet corridor fee** as two lines.
* **Platform share retained for** a platform, when a platform manages your business.
* **Total**: what reached your account.
The invoice is "accepted unless you notify us of a good-faith objection within five business days".
A payout gets no self-billed invoice when it contains none of your own sales:
* A payout that is only platform fees from businesses you manage.
* A payout that did not reach you.
* A payout that paid nothing out.
For a platform, the **Platform fees collected** section of the payout statement is the record of your fee income. Platform fees ride in the same payout as your own sales, one payout per currency, and the 1% payout fee applies to the whole payout, fees included. See [Platform fees](/connect/fees).
## Use them in your books
* Match each payout on your bank statement to its payout statement with the **Bank reference**.
* Record the self-billed invoice as your invoice to AgentaOS for the payout. Its total is the amount that reached your account.
* Use the payout statement for the detail behind the total: each sale, our commission, the fees, and anything returned.
AgentaOS charges and pays the VAT on the sales to your buyers; that VAT is never yours. The self-billed invoice carries no VAT line: it records the value of your supply to AgentaOS (Aristokrates OÜ, Estonia), a company in Estonia. Your accountant books it as a sale to a customer in Estonia under your own country's rules.
## Next steps
Your balance, the schedule, and the fees.
Why AgentaOS is the seller to your buyers.
# Receive webhooks in Express
Source: https://docs.agentaos.ai/sdk/handle-webhooks
Register a webhook URL, verify the signature, and fulfill an order when checkout.session.completed fires. A complete, runnable Express example.
By the end of this page you have an Express endpoint that verifies AgentaOS's signature and marks an order paid when a checkout completes. It fulfills each order once, even if the same event arrives twice.
## Before you start
* Node.js 20.6 or later, and `@agentaos/pay` and Express installed (`npm install @agentaos/pay express`). The samples run on Express 4 and 5.
* A URL AgentaOS can reach over HTTPS. AgentaOS does not deliver to `localhost` or a private address, so for local development run a tunnel service that gives `http://localhost:3000` a public HTTPS address.
* 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.
In [app.agentaos.ai](https://app.agentaos.ai), open **Settings → Developers → Webhooks**, enter the HTTPS URL you want events sent to, for example `https://myshop.com/webhooks`, and click **Save**. 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={"system"}
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 a timestamp older than 5 minutes or in the future, recomputes the HMAC-SHA256 digest in constant time, and returns a typed `WebhookEvent`. If any check fails, it throws `WebhookVerificationError`. Keep your server's clock synced (NTP): a clock that runs behind rejects every event.
```typescript theme={"system"}
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) {
res.status(400).send('Invalid signature');
return;
}
res.status(500).send('Webhook processing failed');
return;
}
// Signature is valid, safe to act on event.data now.
}
```
Key your fulfillment logic on `event.data.sessionId`, and check whether you've already processed it before doing anything. Retries mean the same event can arrive more than once, so your handler must be safe to run twice.
```typescript theme={"system"}
// 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, metadata } = event.data;
if (fulfilledSessions.has(sessionId)) {
break; // already handled this payment, skip
}
fulfilledSessions.add(sessionId);
console.log(`Order ${sessionId} paid: ${amount} ${currency}`);
// metadata.customerId is the id you passed on checkouts.create.
// For a subscription, metadata.subscriptionId is also set.
break;
}
}
```
`metadata` holds what you set on `checkouts.create`, so `metadata.customerId` is your own user id. `verify()` camelCases every key, including the keys inside `metadata`: use camelCase keys in metadata, because a key like `user_id` comes back as `userId`. The event's `customerId` field is different: it is AgentaOS's own id for the buyer, not yours. A subscription renewal fires this event without your `metadata`. See [Event order for subscriptions](/webhooks/events#event-order-for-subscriptions) and [Sell a subscription](/guides/sell-a-subscription).
`event.data.amount` is a string, such as `"49.99"`. See the [money model](/api-reference/introduction#money-model).
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={"system"}
res.sendStatus(200);
```
## Full example
```typescript webhook-server.ts theme={"system"}
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) {
res.status(400).send('Invalid signature');
return;
}
res.status(500).send('Webhook processing failed');
return;
}
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 HMAC-SHA256 over `{timestamp}.{raw_body}`, and any language can check it. See [manual verification in Python, Go, and PHP](/payments/webhooks#manual-verification-no-sdk).
## Test it end to end
Save the example as `webhook-server.ts`, put `AGENTAOS_API_KEY` and `AGENTAOS_WEBHOOK_SECRET` in a `.env` file, and run `npx tsx --env-file=.env webhook-server.ts`. 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. The event list in **Settings → Developers → Webhooks** shows each delivery's status and response code.
* 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.
# Businesses
Source: https://docs.agentaos.ai/sdk/pay-businesses
Manage your clients' businesses with agentaos.businesses, and act for any of them with the business option.
Private preview Working end to end for the accounts that use it; details can still change. Tell us at [contact@agentaos.ai](mailto:contact@agentaos.ai) before you rely on it, and we support you directly. See [Product release phases](/getting-started/release-phases).
`agentaos.businesses` is for [platforms](/connect/overview): the businesses you manage, whether a client's business or another app of your own company.
Every method acts on your own account, the platform. Call `businesses.*` on your platform client. A client made with `{ business }` acts as that business, and a business manages no businesses of its own. To do anything **as** one of the businesses, see [Acting for a business](#acting-for-a-business).
## `businesses.list()`
```typescript theme={"system"}
const businesses = await agentaos.businesses.list();
for (const b of businesses) console.log(b.name, b.status);
```
Returns `Business[]`: every business you manage. Not paginated.
## `businesses.retrieve(id)`
```typescript theme={"system"}
const biz = await agentaos.businesses.retrieve('5b1f0c2e-8a4d-4c7b-9e21-3f6a7d8c9b10');
```
The business id.
Returns one `Business`, the same shape as a row of `list()`.
## `businesses.create(params)`
```typescript theme={"system"}
const { business, inviteToken, inviteUrl } = await agentaos.businesses.create({
name: 'ClientCo',
country: 'DE',
clientEmail: 'founder@example.com',
sendInvitationEmail: false,
});
```
The name buyers see. A brand, not an email. Up to 100 characters.
Where the business is registered, ISO 3166-1 alpha-2 in upper case, e.g. `'DE'`.
Invite your client as the business's admin. Leave it out to run the business yourself. Only an owner of your platform may invite.
`true` for another app of your own company: verified and priced with you, with no share and no identity check of its own.
`false`: we send no email. You send `inviteUrl` to your client yourself (white-label).
The business starts in test mode and goes live after its own verification. Store `business.id` next to your own record of the client.
The new business.The invitation token, or `null` without `clientEmail`.The link your client opens to accept, or `null` without `clientEmail`. Ready to send as it is. The invitation expires after 7 days.
## `businesses.resendInvitation(id, params?)`
```typescript theme={"system"}
const { inviteUrl } = await agentaos.businesses.resendInvitation(biz.id, { sendInvitationEmail: false });
```
Sends the invitation again, to the person last invited. It is a new link: the old one stops working. The new invitation expires after 7 days.
The business id.`false`: no email, you send the link yourself.The new invitation token.The new link to send.
Once your client has joined, the invitation is theirs to manage: this returns `409 client_joined`.
## `businesses.revokeInvitation(id)`
```typescript theme={"system"}
await agentaos.businesses.revokeInvitation(biz.id);
```
The business id.
Withdraws the open invitation. Its link stops working. Returns nothing. `409 client_joined` once your client has joined.
## `businesses.createVerificationLink(id)`
```typescript theme={"system"}
const { url, status } = await agentaos.businesses.createVerificationLink(biz.id);
if (url) sendToClient(url);
```
The identity check link for your client. You send it; only your client can complete it.
The business id.The link to send. `null` when the client is already verified: nothing to send.Where the identity check stands. These are the check's own states, not a business status: `Not Started`, `In Progress`, `Awaiting User`, `In Review`, `Approved`, `Declined`, `Resubmitted`, `Expired`, `Kyc Expired` or `Abandoned`. `Approved` means the check is done.
Needs a live key (`sk_live_...`). A test key gets `403 live_key_required`. The CLI signed in with `agenta login`, and the **ID check link** button in the dashboard, work in test mode too. For another app of your own company it returns `409 identity_from_company`: its identity check is your company's.
Calling it again while a check is open gives back the same check.
## Business fields
Business UUID. Use it as `{ business }` and in `AgentaOS-Account`.The name buyers see.ISO 3166-1 alpha-2.
Where the business stands. `live` means verified. `changes_needed` means we asked the business for a change. See [Business status](#business-status).
Your own id: the platform that manages it.ISO 8601.
Your share of its new sales: basis points of the price before VAT (`1000` = 10%) plus a fixed amount in minor units. Set in the dashboard. See [Platform fees](/connect/fees).
`true` for another app of your own company.
Your share of its live sales this month, per currency. `feesDisplay` is ready to show, e.g. `"€12.50"`.
Who an open invitation is waiting on, or `null`.The identity check is done: its own, or your company's for another app of your own company.
```json theme={"system"}
{
"id": "5b1f0c2e-8a4d-4c7b-9e21-3f6a7d8c9b10",
"name": "ClientCo",
"country": "DE",
"status": "in_review",
"platformOrgId": "0e6d2c1a-3b4f-4a5e-8c7d-9f1a2b3c4d5e",
"createdAt": "2026-09-28T10:00:00.000Z",
"platformFee": { "bps": 1000, "fixedMinor": 50 },
"sameLegalEntity": false,
"feesThisMonth": [{ "currency": "EUR", "feesMinor": 1250, "feesDisplay": "€12.50" }],
"invitedEmail": "founder@example.com",
"identityVerified": false
}
```
### Business status
The dashboard, the `status` field and the [`account.updated`](/connect/webhooks) event name the same states with different words:
| Dashboard | `status` | `account.updated` `verification` |
| - | - | - |
| **Test only** | `test_only` | `unverified` |
| **In review** | `in_review` | `in_review` |
| **Changes needed** | `changes_needed` | `in_review`, with `changes_requested: true` (`changesRequested` in the SDK) |
| **On hold** | `on_hold` | `on_hold` |
| **Rejected** | `rejected` | `rejected` |
| **Live** | `live` | `verified` |
A business is live when it is verified. A bank account is not part of the status: the business needs one before its first payout, not before its first sale. **Test only** (`test_only`) means that no verification was submitted.
## Acting for a business
Any call on any resource can run as a business you manage.
**For a whole client**, pass `business` when you create it. Every request it sends acts as that business:
```typescript theme={"system"}
const clientco = new AgentaOS(process.env.AGENTAOS_API_KEY!, { business: biz.id });
await clientco.paymentLinks.list();
```
**For one call**, pass `{ business }` as the last argument of any method. It overrides the client's `business`:
```typescript theme={"system"}
await agentaos.checkouts.create({ amount: 49, currency: 'EUR' }, { business: biz.id });
await agentaos.accountReview.submit(details, { business: biz.id });
await agentaos.invoices.list({ limit: 20 }, { business: biz.id });
```
Either way the SDK sends the id in the `AgentaOS-Account` header. Leave it out to act as yourself. A business you do not manage returns `403 You cannot act as this business.`, the same answer for an id that does not exist.
Filing a client's verification with `accountReview.submit(…, { business })` is recorded as filed by the platform. Only your client can complete the identity check: send them the link from `createVerificationLink`.
### File a client's verification
This is the same verification any account submits, sent as the business.
We record it as filed by the platform, and our reviewers see that. File only what your client confirmed to you.
```typescript theme={"system"}
await agentaos.accountReview.submit(
{
entityType: 'business',
legalName: 'ClientCo GmbH',
registrationNumber: 'HRB 123456',
taxCountry: 'DE',
address: { street: 'Hauptstrasse 1', city: 'Berlin', postal: '10115', country: 'DE' },
productUrl: 'https://clientco.com',
productDescription: 'Scheduling software for clinics',
checklist: {
prohibited_ok: true,
checklist_ack: true,
cooldown_ack: true,
privacy_ok: true,
tos_ok: true,
},
},
{ business: biz.id },
);
```
See [Account review](/mor/account-review) for what we check.
## One webhook for every business
You need no webhook per client. Your own endpoint receives every event of every business you manage, signed with your own secret. Each event carries `business`, the id of the business it happened in.
```typescript theme={"system"}
const event = agentaos.webhooks.verify(
req.body, // raw body
req.headers['x-agentaos-signature'] as string,
process.env.AGENTAOS_WEBHOOK_SECRET!, // your own secret, for every client
);
switch (event.type) {
case 'checkout.session.completed':
await fulfil(event.business, event.data.sessionId); // event.business = the client's id
break;
case 'account.updated': {
const client = await agentaos.businesses.retrieve(event.business);
await saveVerification(client.id, client.status, client.identityVerified);
break;
}
}
```
`account.updated` fires when the client's verification changes. Set your endpoint in **Settings → Developers → Webhooks**. See [Platform webhooks](/connect/webhooks).
## Next steps
End to end.
The same from the terminal.
`business` on every event, and `account.updated`.
The raw HTTP endpoints.
# 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: each visitor to the link gets their own checkout.
A standalone (linkless) checkout is always a **one-time** payment: `checkouts.create()` has no `type` field. 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={"system"}
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={"system"}
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.
The payment link's `id`, a UUID. On the product page this is **Copy link ID**. It is not the token in the link's `checkoutUrl`. The session inherits the link's amount, currency, and description. 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 this checkout's `checkout.session.completed` event, instead of your account's webhook URL. AgentaOS signs it with your account's signing secret. **HTTPS only**, max 2048 characters. See [Webhooks](/sdk/pay-webhooks).
Seconds until the session expires. Range `300` to `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, returned on the session and on the `checkout.session.completed` event. Max 8KB serialized. Use camelCase keys: `webhooks.verify()` camelCases them, so `user_id` comes back as `userId`. A subscription's renewal events do not carry it: see [Event order for subscriptions](/webhooks/events#event-order-for-subscriptions).
### Pre-populate buyer info
Skip the checkout form's fields by pre-filling what you already know:
```typescript theme={"system"}
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 appear 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.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={"system"}
{
"id": "a1b2c3d4-...",
"paymentLinkId": null,
"orgId": "b7e2a1c4-...",
"sessionId": "mZrESFyR7RC9RPsJfZCVkg",
"checkoutUrl": "https://app.agentaos.ai/checkout/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={"system"}
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={"system"}
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={"system"}
await agentaos.checkouts.cancel('mZrESFyR7RC9RPsJfZCVkg');
// → { success: true }
```
Canceling prevents the customer from paying through that session. A payment already in flight when you cancel (card authorized, on-chain broadcast) can still complete: cancel changes the status and does not stop a running transaction.
## Next steps
Reusable templates. Each visitor gets their own checkout.
Get notified server-side when 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={"system"}
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={"system"}
{
"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={"system"}
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={"system"}
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={"system"}
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={"system"}
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={"system"}
const invoice = await agentaos.invoices.retrieve('uuid');
```
Returns the full `Invoice` shape above for a single record.
## `invoices.void(id)`
```typescript theme={"system"}
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={"system"}
const pdf = await agentaos.invoices.downloadPdf('uuid'); // Promise
fs.writeFileSync('invoice.pdf', pdf);
```
```typescript downloadStatement(params) theme={"system"}
// 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={"system"}
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={"system"}
// 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={"system"}
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={"system"}
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={"system"}
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: **Developers → API keys**. See [Test mode and testing](/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={"system"}
// 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={"system"}
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={"system"}
interface PaginatedList {
items: T[];
total: number; // total matching records, not just this page
hasMore: boolean; // true if offset + items.length < total
}
```
```typescript theme={"system"}
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={"system"}
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={"system"}
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.**
Free-trial length in days, 1 to 730. Subscription links only. The buyer's card is saved with nothing due today, the subscription starts as `trialing`, and the first charge happens when the trial ends. See [Free trials](/sdk/pay-subscriptions#free-trials).
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={"system"}
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={"system"}
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={"system"}
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 the 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.Free-trial length in days; `null` when there is no trial.Shareable payment URL.Times this link has been paid.
```json theme={"system"}
{
"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={"system"}
const link = await agentaos.paymentLinks.retrieve('7rr6S9ml-...');
console.log(link.paymentCount); // how many times it's been paid
```
## `paymentLinks.list(params?)`
```typescript theme={"system"}
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={"system"}
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, invoices, free trials, plan changes, cancel, credits. No create, by design.
`agentaos.subscriptions` reads and manages subscriptions; it has no `create()`. A subscription starts when a buyer pays a [payment link](/sdk/pay-payment-links) created with `type: 'subscription'`. From then on AgentaOS, as merchant of record, bills the buyer every `billingInterval` by card or bank.
```typescript theme={"system"}
const product = await agentaos.paymentLinks.create({
amount: 29.99,
currency: 'EUR',
description: 'Pro',
type: 'subscription',
billingInterval: 'month',
});
// Logged-in user: create a checkout from the product's linkId
const checkout = await agentaos.checkouts.create({
linkId: product.id,
buyerEmail: user.email,
buyerCountry: user.country,
metadata: { customerId: user.id },
});
```
To copy a plan's link ID in the dashboard, open **Building this into your site?** on the plan's page and click **Copy ID**. When the buyer finishes, `checkout.session.completed` carries your `customerId` and the new `subscriptionId` in `data.metadata`. Store the `subscriptionId` on your user. Renewals do not carry your `customerId`, so match them by that `subscriptionId`. See [Receive webhooks in Express](/sdk/handle-webhooks) to verify the event.
See [Sell a subscription](/guides/sell-a-subscription).
## `subscriptions.list(params?)`
Mirrors the dashboard's Subscriptions list, scoped to the environment (test or live) your API key belongs to.
```typescript theme={"system"}
const page = await agentaos.subscriptions.list({
limit: 20, // default 20, max 100
offset: 0,
});
console.log(page.total, page.hasMore);
```
Max 100.Only the subscribers who redeemed this code. The code as the buyer types it, not case-sensitive.
Returns `PaginatedList`.
### Read one subscription
There is no `retrieve(id)`. Store the subscription's `id` when the first `checkout.session.completed` arrives: it is `metadata.subscriptionId` on that event. To read the subscription later, page through `list()` and find it:
```typescript theme={"system"}
import type { Subscription } from '@agentaos/pay';
async function findSubscription(subscriptionId: string): Promise {
for (let offset = 0; ; offset += 100) {
const page = await agentaos.subscriptions.list({ limit: 100, offset });
const found = page.items.find((s) => s.id === subscriptionId);
if (found || !page.hasMore) return found;
}
}
```
[Paywall for logged-in users](/sdk/paywall-for-logged-in-users) shows the full pattern: store the id on your user, then read the subscription before a paid feature.
### Subscription fields
The subscription's id, a UUID. The same value as `metadata.subscriptionId` on the checkout event and `id` on the [subscription events](/webhooks/events#subscription-events).The product name or description.See statuses below.
Per-cycle amount in integer minor units: `1999` means €19.99. See the [money model](/api-reference/introduction#money-model).
ISO 8601. `null` before the first billing cycle has booked.The underlying subscription ID from the card processor.The plan's payment link `id`, a UUID: the value `checkouts.create({ linkId })` takes and **Copy link ID** on the product page copies. It is not the token in the checkout URL, which `checkout.session.completed` sends as `linkId`.
Null unless a downgrade is scheduled. Then `{ linkId, planName, unitAmountMinor, effectiveAt }`: the plan the subscription switches to at `effectiveAt` (the period end). `planName`, `unitAmountMinor` and `linkId` above stay what the buyer paid for until then, so gate access on them, not on the pending change.
`{ code, kind }` when the subscriber redeemed a discount code, else `null`. `kind` is `discount` or `tracking` (a code that takes nothing off). What the code took off is on the first invoice.
```json theme={"system"}
{
"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...",
"linkId": "4cbb8f52-7cf2-4480-8c66-34f794cd23d0"
}
```
### Statuses
`SubscriptionStatus` is the card processor's status for the subscription. AgentaOS reads it on a schedule, so a change shows here after the next check. It has eight values. Webhook events never carry `incomplete` or `incomplete_expired`, because a subscription that never activated sends no event.
| 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.invoices(id)`
Each billing cycle's invoice, newest first.
```typescript theme={"system"}
const invoices = await agentaos.subscriptions.invoices(sub.id);
```
Currency units.
## Free trials
A trial is a property of the product, not of the subscription: create the plan with `trialPeriodDays` (1 to 730). At checkout the buyer's card is saved with nothing due today, the subscription is created as `trialing`, and the first charge happens when the trial ends, after which it is `active`.
```typescript theme={"system"}
const product = await agentaos.paymentLinks.create({
amount: 29,
currency: 'EUR',
description: 'Pro, monthly',
type: 'subscription',
billingInterval: 'month',
trialPeriodDays: 14,
});
```
What you observe:
* `checkout.session.completed` fires when the card is saved, with `amount: "0"`. Treat it as "trial started", not as money received. The wire body also has `trial: true` and `trial_end` (ISO 8601), which `verify()` returns as `trial` and `trialEnd`. The SDK type does not declare them yet: read them through a [wider type](/sdk/pay-webhooks#fields-the-type-does-not-declare-yet), or read `event.data.metadata.trial`, which is also `true`.
* `subscriptions.list()` shows `status: 'trialing'` and the plan's `unitAmountMinor`; `currentPeriodEnd` is the trial end while trialing.
* When the trial ends and the first charge succeeds, `checkout.session.completed` and `subscription.renewed` fire. See [Event order for subscriptions](/webhooks/events#event-order-for-subscriptions).
* Canceling during the trial stops it before any charge.
## `subscriptions.previewPlanChange(id, targetLinkId)`
Quote moving a subscription to another plan. Read-only: nothing is charged or scheduled. The target must be a different subscription product in the **same currency and billing interval**, and the subscription must be `active` or `trialing`; anything else is a `400`.
```typescript theme={"system"}
const quote = await agentaos.subscriptions.previewPlanChange(sub.id, PRO_PLUS_LINK_ID);
// quote.direction 'upgrade' | 'downgrade' | 'revert'
// quote.dueTodayMinor charged now (0 for a downgrade)
// quote.effectiveAt upgrade: now; downgrade: the current period end
// quote.prorationDate pass it to changePlan
```
`upgrade` when the new plan costs more per cycle, `downgrade` when it costs the same or less, `revert` when the target is the current plan while a downgrade is pending.Integer minor units charged now. Always `0` for a downgrade.ISO 8601. Upgrade: now. Downgrade: the current period end.The next regular cycle invoice on the new plan, integer minor units.ISO 8601.Unix seconds. Echo it in `changePlan` so the charge matches this quote.
## `subscriptions.changePlan(id, { targetLinkId, prorationDate })`
Apply a quoted plan change. An **upgrade** charges the prorated difference on the saved card **now**; a **downgrade** charges nothing today and switches at the current period end. Idempotent on (subscription, target plan, `prorationDate`).
```typescript theme={"system"}
const result = await agentaos.subscriptions.changePlan(sub.id, {
targetLinkId: PRO_PLUS_LINK_ID,
prorationDate: quote.prorationDate,
});
switch (result.direction) {
case 'upgrade':
console.log(result.status, result.unitAmountMinor); // charged now, e.g. 'active' 4900
break;
case 'downgrade':
console.log(result.effectiveAt, result.unitAmountMinor); // when the cheaper plan starts
break;
case 'revert':
console.log(result.unitAmountMinor); // the pending downgrade is canceled, the plan stays
break;
}
```
Show the quote to the merchant (or the subscriber) before calling this: the upgrade charge is real money. The CLI does both steps in one command: `agenta subscriptions change-plan --to ` (add `--dry-run` for the quote only).
**Canceling a scheduled downgrade.** Change the plan to the current plan again. The preview and the result come back with `direction: 'revert'`, nothing is charged, and `pendingPlanChange` is null afterwards. Targeting the plan that is already pending returns `400` ("already scheduled").
**Webhooks.** Every plan change fires `subscription.updated` on your account's webhook URL: when a downgrade is scheduled (`data.pendingPlan` is set), when it is reverted (`pendingPlan` back to null), when an upgrade is applied, and when the scheduled plan takes over at the period end. See [Events](/webhooks/events).
## `subscriptions.cancel(id)`
The cancellation always takes effect at the end of the current period: there is no immediate cancel. The subscriber keeps access until `currentPeriodEnd`. No refund. Calling it again is a no-op.
```typescript theme={"system"}
await agentaos.subscriptions.cancel(sub.id);
```
### 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={"system"}
{
"status": "active",
"currentPeriodEnd": "2026-09-06T00:00:00.000Z",
"cancelAtPeriodEnd": true,
"effectiveCancelDate": "2026-09-06"
}
```
## `subscriptions.credit(id, { amountMinor, reason, idempotencyKey })`
Put credit on the subscriber's account. It comes off their next invoice and lowers what the card is charged. It is not a refund: no money goes back to the card. The client must be signed in as an owner or admin; an API key is refused with `403`.
```typescript theme={"system"}
import { randomUUID } from 'node:crypto';
const credit = await agentaos.subscriptions.credit(sub.id, {
amountMinor: 1000, // €10.00 in the plan's currency
reason: 'Two days of downtime',
idempotencyKey: randomUUID(),
});
credit.nextInvoiceDueAfterCreditMinor; // what the next invoice will charge
```
Positive integer minor units. There is no currency field: the subscription's currency is used. At most the whole next invoice.Why you give it. The subscriber never sees it.One key per credit you mean to give. Send the same key again when you retry after a timeout; a new key gives a second credit.The ledger entry id.All unspent credit this buyer has with you now.What the next invoice would have charged before this credit.What it charges after it.ISO 8601.The id of the person who gave it.ISO 8601.
The subscription must be `active` or `trialing`, not scheduled to cancel, and without a pending plan change; otherwise the call returns `400`. While the buyer has credit left, `previewPlanChange` and `changePlan` return `400` until the next invoice.
## `subscriptions.credits(id)`
The credits given on this subscription, newest first, and what is still unspent. Owner or admin only, like `credit`.
```typescript theme={"system"}
const { items, balanceMinor } = await agentaos.subscriptions.credits(sub.id);
```
`{ id, amountMinor, reason, createdBy, createdAt }` for each credit.Unspent credit on the buyer, shared by every subscription they have with you.
## Next steps
Create a product, share the link or check out a logged-in user.
Create the `type: 'subscription'` link that starts 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.
`webhooks.verify()` runs in your process and makes no network call. It checks the payload's HMAC-SHA256 signature against your webhook secret, rejects a stale or malformed signature, and returns the parsed event with camelCase keys. Set the destination URL and copy the signing secret in **Settings → Developers → Webhooks**, or set `webhookUrl` on a [checkout](/sdk/pay-checkouts) or link.
## `webhooks.verify(payload, signature, secret, toleranceSec?)`
```typescript theme={"system"}
import express from 'express';
import { AgentaOS, WebhookVerificationError } from '@agentaos/pay';
const agentaos = new AgentaOS(process.env.AGENTAOS_API_KEY!);
const app = express();
// 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) {
res.status(400).send('Invalid signature');
return;
}
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);
});
app.listen(3000);
```
The samples on this page run on Express 4 and 5.
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 throws `WebhookVerificationError`. A timestamp in the future throws too, by any amount: if your server's clock runs behind AgentaOS's, every event fails, so keep the clock synced (NTP).
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`. `verify()` never returns a partial or unverified event.
## `WebhookEvent`
A discriminated union on `type`. `switch`/narrow on it to get typed `data`.
The id of the business the event happened in: your own, or one of the [businesses you manage](/connect/webhooks). Present on every event.
The wire JSON also carries an `id` for each event, but the `WebhookEvent` type does not declare it yet. Deduplicate on the keys in [Deduplication](/webhooks/events#deduplication).
Amounts follow the [money model](/api-reference/introduction#money-model): `amount` in `data` is a string, and `amountMinor` is an integer in minor units.
`verify()` camelCases every key, including the keys inside `metadata`. Use camelCase keys in metadata: a key like `user_id` comes back as `userId`.
### `checkout.session.completed`
Fires when a checkout is paid or a free trial starts. The full field list, with the wire names, is in [Events](/webhooks/events).
The payment link's public token from its `checkoutUrl`, not its `id`. `null` for a checkout created without a link, although the type says `string`.e.g. `"49.99"`.On-chain settlement hash. `null` for a card or bank sale, although the type says `string`.Payer wallet address. `null` for a card or bank sale, although the type says `string`.`card`, `bank_transfer`, or a CAIP-2 network ID for a stablecoin payment.`true` in live mode, `false` in test mode.The `metadata` you set on the checkout or payment link, with camelCase keys. `metadata.customerId` is your own id if you set one. On a renewal, `metadata` is AgentaOS's own: see [Event order for subscriptions](/webhooks/events#event-order-for-subscriptions).
#### Fields the type does not declare yet
`verify()` also returns `rail`, `vendorReference`, `customerId`, `buyerEmail`, `buyerName` and `overpaidByCents` on every checkout event, and `trial` and `trialEnd` when a free trial starts. `customerId` is AgentaOS's id for the buyer, not your user id. `CheckoutCompletedData` does not declare these fields yet, so read them through a wider type:
```typescript theme={"system"}
import type { CheckoutCompletedData } from '@agentaos/pay';
type CheckoutCompletedWire = CheckoutCompletedData & {
customerId: string | null;
buyerEmail: string | null;
trial?: true;
trialEnd?: string | null;
};
if (event.type === 'checkout.session.completed') {
const data = event.data as CheckoutCompletedWire;
if (data.trial) {
console.log(`Trial started, first charge at ${data.trialEnd}`);
}
}
```
`metadata.trial` is also `true` on a trial start, and needs no cast.
### `send.completed`
Fires when an outbound send (a payout or transfer you initiated) is broadcast successfully.
`true` in live mode, `false` in test mode.
### `send.failed`
Same shape as `send.completed`, but `txHash` is always `null` and no broadcast succeeded.
`true` in live mode, `false` in test mode.
### `subscription.*`
All five `subscription.*` events carry the same `SubscriptionData`, delivered to your account's webhook URL. For when each one fires, and in what order, see [Event order for subscriptions](/webhooks/events#event-order-for-subscriptions).
The subscription's id, a UUID: the `id` that `subscriptions.list()` returns.The status after the change, one of the [subscription statuses](/sdk/pay-subscriptions#statuses). A subscription that never activated sends no event, so `incomplete` and `incomplete_expired` do not appear.The plan name, from the backing payment link.Per-cycle price in integer minor units. `1000` means `€10.00`.ISO 8601 timestamp, or `null` before the first cycle is set.`true` once a cancellation is scheduled for period end.The current plan's payment link `id`, a UUID. Not the token in the checkout URL that `checkout.session.completed` sends as `linkId`.
Null unless a downgrade is scheduled. Then `{ linkId, planName, amountMinor, effectiveAt }`: the plan the subscription switches to at `effectiveAt`. `subscription.updated` fires when it is scheduled, reverted, and when it takes over.
`true` in live mode, `false` in test mode.
### `dispute.created` / `dispute.closed`
A buyer's bank opened a chargeback on a sale, or closed it in your favor. A lost dispute sends no event. Both carry the same `DisputeData`.
The dispute's status as the card processor reports it, e.g. `needs_response`, or `won` on `dispute.closed`.The sale the dispute is about.Integer minor units. `1240` means `€12.40`.`true` in live mode, `false` in test mode.
### `account.updated`
A business's verification changed: its identity check landed, or we approved, held, resumed or rejected it, or asked it for changes. For a platform, `business` names the client it happened to. See [Platform webhooks](/connect/webhooks).
Where the business's review stands, in the words the dashboard uses.We have asked the business something and are waiting on it.The identity check is done: its own, or its company's for another app of the same company.Always `true`: verification is about live money.
### `webhook.test`
The sample event the dashboard's **Send test event** button fires at your endpoint.
`true` in live mode, `false` in test mode.
The customer can close the browser before the `successUrl` redirect lands. Fulfill orders from the webhook, which your server receives directly, not from the redirect.
Record a key per event, such as `event.data.sessionId` or `event.data.transactionId`, and skip an event you have already handled. The key for each event type is in [Deduplication](/webhooks/events#deduplication).
If your endpoint is down, poll [`checkouts.retrieve()`](/sdk/pay-checkouts) for `status === 'completed'` as a backstop. Do not build your main flow on polling: it is slower, and every call counts against your [rate limit](/api-reference/introduction#rate-limits).
## Next steps
Set `webhookUrl` to receive `checkout.session.completed`.
`WebhookVerificationError` and the rest of the error hierarchy.
# Paywall for logged-in users
Source: https://docs.agentaos.ai/sdk/paywall-for-logged-in-users
A runnable demo app that puts a Pro plan behind a login: checkout with the user's ID, a success page and a webhook that unlock Pro, an access check, the user's invoices with PDFs, and cancel and upgrade.
The demo on this page is a Notes app with a Pro plan. A signed-in user clicks **Upgrade to Pro**, pays on the checkout, and comes back with Pro unlocked. The home page then shows the plan and its next charge, a **Manage plan** section to cancel or upgrade, and **My invoices** with the invoice PDF and the receipt for each payment. Every call goes to the AgentaOS API through the `@agentaos/pay` SDK, from the app's server.
## The demo app
The app is in `examples/paywall-demo` in the [AgentaOS repository](https://github.com/AgentaOS/agentaos/tree/main/examples/paywall-demo). It uses Node.js, Express, and plain HTML. The login is fake (you pick a user) and the database is a JSON file, so every other line is the integration itself.
| File | What it does |
| - | - |
| `src/checkout.ts` | Creates the checkout (`POST /upgrade`) and confirms it (`GET /success`) |
| `src/webhooks.ts` | Verifies and handles events (`POST /webhooks`) |
| `src/grant.ts` | Unlocks Pro once per checkout |
| `src/access.ts` | Reads the subscription and guards the Pro page |
| `src/invoices.ts` | Lists the user's invoices and sends the PDFs |
| `src/plan.ts` | Cancels, quotes an upgrade, and applies it |
## Run it
You need Node.js 20 or later, pnpm, and an AgentaOS account in test mode.
Clone the [AgentaOS repository](https://github.com/AgentaOS/agentaos), then install and build the SDK from its root:
```bash theme={"system"}
pnpm install
pnpm -F @agentaos/pay build
```
In the dashboard, create a monthly subscription plan. The steps and screens are in [Sell a subscription](/guides/sell-a-subscription#create-the-plan). On the plan's page, open **Building this into your site?** and click **Copy ID**. For the upgrade button, create a second plan in the same currency and with the same interval, and copy its ID too.
```bash theme={"system"}
cd examples/paywall-demo
cp .env.example .env
```
In `.env`, set `AGENTAOS_API_KEY` to a test key: in the dashboard, open **Developers → API keys** in the sidebar and click **Generate Test Key**. Set `PRO_LINK_ID` and `PRO_PLUS_LINK_ID` to the two plan IDs. Set `AGENTAOS_WEBHOOK_SECRET` to the signing secret from **Settings → Developers → Webhooks**, **Reveal signing secret**.
The checkout sends the buyer back only to an `https` address, and `https://localhost` is refused. The demo therefore runs on `https://127.0.0.1:4567` with a self-signed certificate:
```bash theme={"system"}
pnpm cert
```
```bash theme={"system"}
pnpm dev
```
Open `https://127.0.0.1:4567` and accept the browser's certificate warning once. Sign in as Ada. The plan says **Free**.
Click **Upgrade to Pro**. The checkout opens with Ada's email already filled in. Pick a country and click **Continue to payment**. Pay with the test card `4242 4242 4242 4242`, any future expiry date, and any CVC.
After payment, the checkout sends you to `/success?sessionId=...`. The page reads the checkout from the API. While the checkout is still `open`, it says "Still processing" with a **Refresh** button. Once the checkout is `completed`, it says "You are on Pro".
Back on the home page, the plan shows the status and the date of the first charge.
## How it works
### The checkout carries your user ID
When the user clicks **Upgrade to Pro**, the server creates a checkout from the plan's link ID. It puts the user's own ID in `metadata`, so the success page and the webhook can tell who paid. Do not send signed-in users the plan's shared link: that link carries no `metadata`.
```typescript src/checkout.ts theme={"system"}
export async function upgrade(_req: Request, res: Response): Promise {
const user: User = res.locals.user;
// A second click reuses the open checkout instead of creating another one.
if (user.pendingSessionId) {
const pending = await agentaos.checkouts.retrieve(user.pendingSessionId);
if (pending.status === 'open') {
res.redirect(303, pending.checkoutUrl);
return;
}
}
const checkout = await agentaos.checkouts.create({
linkId: config.proLinkId,
buyerEmail: user.email,
metadata: { customerId: user.id },
successUrl: `${config.appUrl}/success`,
cancelUrl: `${config.appUrl}/`,
});
updateUser(user.id, { pendingSessionId: checkout.sessionId });
res.redirect(303, checkout.checkoutUrl);
}
```
The SDK sends an idempotency key with every create call and reuses it when it retries after a network error or a server error, so one call makes one checkout. The success URL must start with `https://`. The checkout adds `?sessionId=` to it when it sends the buyer back.
### The success page asks the API
Anyone can open the success URL, so the page never unlocks from the redirect alone. It reads the checkout with `checkouts.retrieve`, checks that the checkout belongs to the signed-in user, and unlocks Pro only when the status is `completed`.
```typescript src/checkout.ts theme={"system"}
const checkout = await agentaos.checkouts.retrieve(sessionId);
if (checkout.metadata.customerId !== user.id) {
res.status(404).send('Not found');
return;
}
if (checkout.status === 'completed') {
const { subscriptionId } = checkout.metadata;
grantPro({
userId: user.id,
subscriptionId: typeof subscriptionId === 'string' ? subscriptionId : null,
sessionId,
});
}
res.send(successPage(user, checkout.status));
```
`metadata.subscriptionId` is the ID of the new subscription. `grantPro` stores it on the user, because it is the only link between later events and the user. In the demo run, the checkout for the paid plan was `completed` at the first load of the success page. The checkout for the plan with a free trial was `open` at first and became `completed` after a few refreshes.
### The webhook unlocks Pro and matches renewals
The webhook unlocks the user even when they close the tab before the success page loads. It verifies the signature over the raw body, so the route is mounted with `express.raw()` before any body parser. See [Receive webhooks in Express](/sdk/handle-webhooks).
```typescript src/webhooks.ts theme={"system"}
switch (event.type) {
case 'checkout.session.completed': {
const { sessionId, metadata } = event.data;
const subscriptionId =
typeof metadata.subscriptionId === 'string' ? metadata.subscriptionId : null;
if (typeof metadata.customerId === 'string') {
// First payment, or the start of a free trial.
grantPro({ userId: metadata.customerId, subscriptionId, sessionId });
} else if (subscriptionId) {
// A renewal carries no customerId: match it by the stored subscription ID.
recordRenewal({ subscriptionId, sessionId });
}
break;
}
case 'subscription.updated':
case 'subscription.canceled': {
const user = findUserBySubscription(event.data.id);
if (user) updateUser(user.id, { subscriptionStatus: event.data.status });
break;
}
}
res.sendStatus(200);
```
A renewal's `checkout.session.completed` has its own `sessionId` and does not carry your `customerId`, so the stored subscription ID is the only way to find the user. `webhooks.verify()` returns every key in camelCase, including the keys inside `metadata`. The route answers `400` to a bad signature and `200` to every verified event.
AgentaOS does not deliver webhooks to a private address such as `127.0.0.1`, so the demo on your machine receives no events. The success page unlocks Pro without them. To test the route locally, run `pnpm webhook:test`: it signs sample events with `AGENTAOS_WEBHOOK_SECRET` and posts them to `/webhooks`.
```text theme={"system"}
bad signature -> 400
renewal -> 200
same renewal again -> 200
subscription.updated -> 200
```
To receive real events, expose the app through a public HTTPS tunnel, enter its `/webhooks` URL as the **Webhook URL** in **Settings → Developers → Webhooks**, and click **Send test event**.
### Each payment unlocks once
The same event can arrive more than once, and the success page and the webhook both report the first payment. `grantPro` and `recordRenewal` record each `sessionId` and do nothing for a session they have already seen. In the demo run, the webhook replay of Grace's first payment logged `already handled`. In your database, a unique constraint on the session ID does the same when two requests arrive at the same moment.
```typescript src/grant.ts theme={"system"}
export function grantPro(input: {
userId: string;
subscriptionId: string | null;
sessionId: string;
}): void {
const granted = onceForSession(input.sessionId, () =>
updateUser(input.userId, { subscriptionId: input.subscriptionId, pendingSessionId: null }),
);
```
### Free trials
With a free trial, the buyer pays nothing at checkout and the subscription starts as `trialing`. The webhook still receives `checkout.session.completed`, with `amount` `"0"` and `metadata.trial` set to `true`: treat it as a trial start, not as money received. The checkout that `checkouts.retrieve` returns has no `trial` key in its `metadata`, so read the trial state from the subscription's `status`. The first charge happens when the trial ends. See [Free trials](/payments/subscriptions#free-trials).
### The access check reads the subscription
Before the Pro page, `requirePro` reads the user's subscription by the stored ID and allows `active` or `trialing`. `subscriptions.list()` does not return your `metadata`, so the stored ID is the lookup key.
```typescript src/access.ts theme={"system"}
export async function findSubscription(id: string | null): Promise {
if (!id) return null;
for (let offset = 0; ; offset += 100) {
const page = await agentaos.subscriptions.list({ limit: 100, offset });
const found = page.items.find((s) => s.id === id);
if (found) return found;
if (!page.hasMore) return null;
}
}
export function isPro(subscription: Subscription | null): boolean {
return subscription?.status === 'active' || subscription?.status === 'trialing';
}
```
The home page reads the same subscription for its plan card:
* `currentPeriodEnd`: the end of the current period and the date of the next charge.
* `cancelAtPeriodEnd`: `true` after the user cancels. Access ends at `effectiveCancelDate`.
* `planName` and `unitAmountMinor`: the plan and the charge per period in integer minor units, tax included. For the €29.00 plan, the demo run returned `3596` (€35.96).
* `pendingPlanChange`: set when a downgrade waits for the period end.
### The user's invoices, with PDF and receipt
`subscriptions.invoices(id)` returns the invoice of each billing cycle of one subscription, newest first. Its `amount` is in currency units, not minor units: `60.76` for Grace's first payment. The invoice list has no filter by buyer, so the subscription ID is the key here too.
`invoices.downloadPdf(id)` and `invoices.getReceipt(id)` return the PDF as a `Buffer`, not a URL. The demo's server checks that the invoice belongs to the user's subscription, then sends the bytes. An invoice ID from another user gets a `404`.
```typescript src/invoices.ts theme={"system"}
export async function userInvoices(user: User): Promise {
if (!user.subscriptionId) return [];
return agentaos.subscriptions.invoices(user.subscriptionId);
}
export async function downloadInvoice(req: Request, res: Response): Promise {
const user: User = res.locals.user;
const id = String(req.params.id);
const kind = req.path.endsWith('/receipt') ? 'receipt' : 'invoice';
const invoice = (await userInvoices(user)).find((i) => i.id === id);
if (!invoice) {
res.status(404).send('Not found');
return;
}
const pdf =
kind === 'receipt'
? await agentaos.invoices.getReceipt(id)
: await agentaos.invoices.downloadPdf(id);
res.type('application/pdf').attachment(`${kind}-${invoice.invoiceNumber}.pdf`).send(pdf);
}
```
A subscription in its free trial has no invoice yet. The first one comes with the first charge, when the trial ends. In test mode the PDF is marked "TEST MODE: not a tax invoice" and its number starts with `TEST-`.
### Cancel and upgrade
**Cancel plan** calls `subscriptions.cancel`. The subscription ends at the end of the current period, nothing is refunded, and the user keeps Pro until then. The server rejects an immediate cancellation.
```typescript src/plan.ts theme={"system"}
export async function cancel(_req: Request, res: Response): Promise {
await agentaos.subscriptions.cancel(subscriptionOf(res));
res.redirect(303, '/');
}
```
**Upgrade to Pro Plus** quotes the change with `previewPlanChange` and shows the amounts. **Confirm upgrade** applies it with the quote's `prorationDate`, so the charge equals the quote.
```typescript src/plan.ts theme={"system"}
const quote = await agentaos.subscriptions.previewPlanChange(
subscriptionOf(res),
config.proPlusLinkId,
);
res.send(quotePage(res.locals.user, quote));
```
```typescript src/plan.ts theme={"system"}
await agentaos.subscriptions.changePlan(subscriptionOf(res), {
targetLinkId: config.proPlusLinkId,
// The quote's prorationDate makes the charge equal the amount the user saw.
prorationDate: Number(req.body.prorationDate),
});
```
The quote's `direction` is `upgrade`, `downgrade`, or `revert`. An upgrade charges the prorated difference now and applies at once. A downgrade charges nothing and starts at the end of the period. A revert cancels a pending downgrade. During a free trial, the upgrade quote is €0.00 due today. See [Upgrade and downgrade](/payments/subscriptions#upgrade-and-downgrade).
## Take it to production
* **Login**: replace the demo's cookie login in `src/session.ts` with your own authentication.
* **Database**: replace the JSON file in `src/store.ts` with tables. Put a unique constraint on the processed session ID.
* **Webhook**: register `https://yourapp.com/webhooks` in the dashboard and set its signing secret in `AGENTAOS_WEBHOOK_SECRET`.
* **Live key**: use your live key (`sk_live_...`) on the live server. Keep it out of the browser and out of git.
* **Test and live**: every event carries `livemode`. Ignore events with `livemode: false` on the live server.
* **Address**: set `APP_URL` to your public `https` address, and remove `TLS_CERT` and `TLS_KEY` when a proxy handles TLS in front of the app.
To keep the user on your site, you can embed the checkout instead of redirecting: create the checkout on your server as above and pass its `sessionId` to the [embedded checkout](/payments/embedded-checkout).
## Next steps
Register the endpoint and verify every event.
Every event and its fields.
Statuses, trials, plan changes, and cancellation.
Verify your business and take real payments.
# 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 12 webhook event types, for checkouts, outbound sends, subscriptions, disputes, account verification, and the dashboard's test event. This page is the full list. Every event shares the same envelope: an `id`, a `type`, a `business`, 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 you trust any of this.
Amounts follow the [money model](/api-reference/introduction#money-model): `amount` is a string in currency units, and `amount_minor` is an integer in the smallest currency unit.
The field names below are the literal snake\_case keys AgentaOS `POST`s to your `webhookUrl`: parse these in any language. The SDK's `webhooks.verify()` (Node.js) returns the same fields camelCased: `link_id` becomes `linkId`, `session_id` becomes `sessionId`, `trial_end` becomes `trialEnd`, and so on.
## When each event fires
Each event type below lists every trigger in the platform. [Which action fires which event](#which-action-fires-which-event) gives the same facts in the other direction, per action.
| Event | Fires when | What to do |
| - | - | - |
| `checkout.session.completed` | A buyer pays a checkout by card, bank transfer or stablecoin. A free trial starts: `amount` is `"0"` and `trial` is `true`. A trial with a trial price starts: `amount` is the amount charged. A later subscription charge succeeds: a renewal, the first charge after a trial, or an upgrade's prorated charge. A subscriber credit pays a renewal in full: `amount` is `"0"`. | Fulfill the order or unlock access. |
| `subscription.created` | The subscription becomes `active` or `trialing` for the first time: the buyer pays the first period, or saves a card for a trial. | Store the subscription `id` with your user. |
| `subscription.renewed` | A charge after the first one succeeds: a renewal, the first charge after a trial, or an upgrade's prorated charge. A renewal that a subscriber credit pays also fires it. | Extend access to `current_period_end`. |
| `subscription.payment_failed` | The status becomes `past_due` or `unpaid`: a renewal charge fails, including the first charge after a trial. | Ask the subscriber to update their card. |
| `subscription.updated` | A cancel is scheduled. A scheduled cancel is resumed. An upgrade is applied. A downgrade is scheduled. A scheduled downgrade is reverted. A scheduled downgrade takes over at the period end. | Store the payload as the subscription's latest state. |
| `subscription.canceled` | The status becomes `canceled`: at the period end after a scheduled cancel, or when the card processor ends the subscription for another reason. | Remove access. |
| `dispute.created` | AgentaOS finds a new dispute on one of your sales. | Mark the order as disputed and reply to our dispute email with your evidence; AgentaOS answers the buyer's bank. |
| `dispute.closed` | A dispute closes in your favor. A lost dispute sends no event. | Clear the disputed mark on the order. |
| `send.completed` | An outbound stablecoin send that you started is confirmed. | Mark the payout as paid. |
| `send.failed` | An outbound stablecoin send that you started fails: the broadcast fails, for example on a network error or an insufficient balance, or the transaction reverts on-chain. | Retry the send. |
| `account.updated` | A business's verification changes, in live mode only. Its identity check is verified or declined. Our reviewers approve, reject, hold or resume the business, or ask for a change. Our reviewers verify or withdraw its identity by hand. The event goes to the business and to the platform that manages it. | Show the business its next step from `verification`. |
| `webhook.test` | You click **Send test event** in **Settings → Developers → Webhooks**. | Check that your endpoint verifies it. |
## Event envelope
The event ID. A paid checkout's id is `evt_` plus the sale's transaction id, a send's id is `evt_` plus `data.transaction_id`, and every other event gets a random id such as `evt_4e1b9a0c2d3f5a67`. An event keeps its `id` across delivery attempts and a retry from the dashboard. The SDK's `WebhookEvent` type does not declare `id` yet, so deduplicate on the keys in [Deduplication](#deduplication).
One of `checkout.session.completed`, `send.completed`, `send.failed`, `subscription.created`, `subscription.renewed`, `subscription.payment_failed`, `subscription.updated`, `subscription.canceled`, `dispute.created`, `dispute.closed`, `account.updated`, or `webhook.test`. Switch on this to decide how to parse `data`.
The id of the business the event happened in. On your own events it is your own id. If you are a [platform](/connect/overview), your webhook also receives the events of every business you manage, and there `business` is your client's id. See [Platform webhooks](/connect/webhooks).
Event-specific payload. See each event below.
## Which action fires which event
Events describe money and account state, not every API call. An action that changes nothing about a payment, a subscription or a business's verification sends no event. A "check" below is AgentaOS reading the current state from the card processor. While the buyer's checkout page is open, AgentaOS checks within seconds. A background check runs every 5 minutes for subscriptions and every 10 minutes for disputes.
### Your API calls
| Action | Event | When |
| - | - | - |
| [Create a payment link](/api-reference/payment-links/create) | None | |
| [Update a payment link](/api-reference/payment-links/update) | None | |
| [Cancel a payment link](/api-reference/payment-links/cancel) | None | |
| [Create a checkout](/api-reference/checkouts/create) | None | The buyer's payment sends `checkout.session.completed` later. |
| [Cancel a checkout](/api-reference/checkouts/cancel) | None | |
| [Cancel a subscription](/api-reference/subscriptions/cancel) | `subscription.updated` | Right away, when the call schedules the cancel. A repeat call sends nothing. `subscription.canceled` follows when the period ends. |
| [Resume a subscription](/api-reference/subscriptions/resume) | `subscription.updated` | Right away. |
| [Change the plan](/api-reference/subscriptions/plan-change) | `subscription.updated` | Right away, for an upgrade, a scheduled downgrade, and the undo of a scheduled downgrade. |
| [Preview a plan change](/api-reference/subscriptions/plan-change-preview) | None | The preview changes nothing. |
| [Give a subscriber credit](/api-reference/subscriptions/credits-create) | None | A renewal that the credit pays sends the usual renewal events. |
| [Create a discount code](/api-reference/discount-codes/create) | None | |
| [Stop a discount code](/api-reference/discount-codes/archive) | None | |
| [Void an invoice](/api-reference/invoices/void) | None | |
| [Re-send the receipt email](/api-reference/invoices/send-receipt) | None | |
| [Create a business](/api-reference/businesses/create) | None | `account.updated` fires later, when the business's verification changes. |
| [Resend the invitation](/api-reference/businesses/resend-invitation) | None | |
| [Revoke the invitation](/api-reference/businesses/revoke-invitation) | None | |
| [Create a verification link](/api-reference/businesses/verification-link) | None | `account.updated` fires when your client completes the identity check. |
| **Send test event** in **Settings → Developers → Webhooks** | `webhook.test` | Right away. |
### Actions outside your API calls
| Action | Event | When |
| - | - | - |
| A buyer pays a checkout, by card, bank transfer or stablecoin | `checkout.session.completed` | When AgentaOS confirms the payment. |
| A buyer subscribes and pays the first period | `subscription.created` and `checkout.session.completed` | On the next check. |
| A buyer starts a free trial | `subscription.created` and `checkout.session.completed` with `amount: "0"` | On the next check after the card is saved. |
| A renewal charge succeeds, including the first charge after a trial | `checkout.session.completed` and `subscription.renewed` | On the next check after the charge. |
| An upgrade's prorated charge succeeds | `checkout.session.completed` and `subscription.renewed` | On the next check after the charge. |
| A renewal charge fails, including the first charge after a trial | `subscription.payment_failed` | On the next check. |
| A scheduled cancel takes effect at the period end | `subscription.canceled` | On the next check after the period ends. |
| A scheduled downgrade takes over at the period end | `subscription.updated` | On the next check after the period ends. |
| A buyer's bank opens a chargeback | `dispute.created` | On the next dispute check. |
| A chargeback closes in your favor | `dispute.closed` | On the next dispute check. |
| A chargeback closes against you | None | |
| A business's identity check is verified or declined | `account.updated` | When AgentaOS reads the result. |
| Our reviewers approve, reject, hold or resume a business, or ask for a change | `account.updated` | Right away. |
| A stablecoin send you started confirms on-chain | `send.completed` | When the transaction confirms. |
| A stablecoin send you started fails | `send.failed` | When the send fails. |
For the order of the subscription events, see [Event order for subscriptions](#event-order-for-subscriptions).
## Which URL receives an event
A `checkout.session.completed` event goes to one URL: the checkout's own `webhookUrl` if it has one, else its payment link's, else your account's webhook URL. The first one found is the only one that receives it. Every other event goes to your account's webhook URL. If no URL applies, AgentaOS sends nothing and shows no error.
## Delivery and retries
AgentaOS sends each event as a `POST` with `Content-Type: application/json` and an `X-AgentaOS-Signature` header. A delivery follows these rules:
* Each attempt times out after 10 seconds. A response outside `2xx`, a timeout, or a network error counts as a failed attempt.
* AgentaOS makes up to 3 attempts. It waits 1 second before the second attempt and 2 seconds before the third.
* The 3 attempts carry the same signature, with the same `t`.
* AgentaOS does not follow redirects. A `3xx` response is a failed attempt.
* AgentaOS refuses a URL that resolves to a private or internal address, such as `localhost`, `10.0.0.0/8` or `192.168.0.0/16`, and marks the event failed without an attempt. For local development, expose your server through a public HTTPS tunnel.
After the third failed attempt, AgentaOS marks the event `failed` in **Settings → Developers → Webhooks** and sends it no more by itself. An owner or admin can send a failed event again with the retry button on its row (labeled "Retry webhook"). The retry sends the same body, signed again with a new `t`.
Events are delivered independently of each other. Two events about the same subscription can arrive in either order.
## Deduplication
The same event can arrive more than once: after a retry, or when your server stored the event but answered too slowly. Record a key per event and skip one you have already handled.
| Event | Key |
| - | - |
| `checkout.session.completed` | `data.session_id` |
| `send.completed`, `send.failed` | `data.transaction_id` |
| `subscription.created`, `subscription.renewed`, `subscription.payment_failed`, `subscription.canceled` | The event `type`, `data.id` and `data.current_period_end` |
| `subscription.updated` | None. Two updates in the same period, such as a cancel scheduled and then resumed, share the same `type`, `data.id` and `data.current_period_end`. Apply the event as the subscription's latest state instead of skipping it. |
| `dispute.created`, `dispute.closed` | The event `type` and `data.dispute_id` |
## Event order for subscriptions
AgentaOS checks each subscription with the card processor on a schedule and sends the events for what changed. The events below are what your endpoint receives. Unlock access on `checkout.session.completed`: it carries the `metadata` of the checkout, with your own ids.
| What happens | Events |
| - | - |
| A buyer subscribes and pays the first period | `subscription.created` and `checkout.session.completed`, in either order |
| A buyer starts a free trial | `subscription.created` with `status: "trialing"`, and `checkout.session.completed` with `amount: "0"` and `trial: true` |
| A buyer starts a trial with a trial price | `subscription.created`, and `checkout.session.completed` with the amount charged for the trial. There is no `trial` field. |
| The trial ends and the first full charge succeeds | `checkout.session.completed` and `subscription.renewed` |
| A later period is paid | `checkout.session.completed` and `subscription.renewed` |
| A renewal charge fails | `subscription.payment_failed` |
| A cancel is scheduled or resumed, or a plan changes | `subscription.updated` |
| The subscription ends | `subscription.canceled` |
The `checkout.session.completed` for a renewal, or for the first charge after a trial, has its own `session_id`. Its `metadata` is AgentaOS's own, not the metadata you set on the checkout, so your `metadata.customerId` is not on it. Match it to your user through `metadata.subscriptionId`, the subscription's `id`, which is also on the first checkout event. A renewal's `metadata` also has `subscriptionCycle: true`.
***
## `checkout.session.completed`
**Fires when:**
* A buyer pays a checkout by card, bank transfer or stablecoin.
* A free trial starts: `amount` is `"0"` and `trial` is `true`.
* A trial with a trial price starts: `amount` is the amount charged.
* A later subscription charge succeeds: a renewal, the first charge after a trial, or an upgrade's prorated charge.
* A subscriber credit pays a renewal in full: `amount` is `"0"`.
This is the event to listen for to fulfill an order. For the order of subscription events, see [Event order for subscriptions](#event-order-for-subscriptions).
The payment link's public token: the last part of its `checkoutUrl` (`https://app.agentaos.ai/pay/`). It is not the link's `id`, which `checkouts.create({ linkId })` and the subscription events use. `null` for a checkout created without a link.
The checkout's public session ID. Key your fulfillment and deduplication on it.
Amount paid, in currency units, as a string, e.g. `"49.99"`. `"0"` for a free trial start.
Settlement currency, e.g. `"EUR"`.
How the buyer paid: `card`, `sepa` (a bank transfer), or `crypto`.
On-chain transaction hash for a stablecoin payment. `null` for a card or bank sale: use `vendor_reference` there.
The payment's reference at the card processor or the bank. `null` for a stablecoin payment.
The paying wallet address for a stablecoin payment. `null` for a card or bank sale.
`human` or `agent`: whether a person or an autonomous agent made the payment.
AgentaOS's own id for the buyer, the `id` the [Customers API](/sdk/pay-customers) returns. It is not your user id: put your own id in `metadata`, for example `metadata.customerId`.
The buyer's email, when the checkout collected one.
The buyer's name, when the checkout collected one.
`card` for a card payment, `bank_transfer` for a bank transfer, or a CAIP-2 network ID such as `"eip155:8453"` (Base) for a stablecoin payment.
`true` in live mode, `false` in test mode.
Set when a bank-transfer buyer sent more than the amount due (beyond a 1-cent tolerance). `null` otherwise.
Present only when the checkout started a free trial, and then always `true`. `metadata.trial` is also `true` on that event.
Present only with `trial`: the ISO 8601 time the trial ends and the first charge is due.
The key-value data you attached when you created the checkout or payment link. On a renewal it is AgentaOS's own: see [Event order for subscriptions](#event-order-for-subscriptions).
The SDK's `CheckoutCompletedData` type differs from the wire body in two ways. It types `txHash`, `payer` and `linkId` as `string`, but they are `null` in the cases above, so check for `null`. It does not declare `rail`, `vendorReference`, `customerId`, `buyerEmail`, `buyerName`, `overpaidByCents`, `trial` or `trialEnd` yet, although `verify()` returns them: read them with a cast, as shown in [Payment SDK: Webhooks](/sdk/pay-webhooks#fields-the-type-does-not-declare-yet).
```json Example theme={"system"}
{
"id": "evt_8c7d6e5f-4a3b-2c1d-0e9f-8a7b6c5d4e3f",
"type": "checkout.session.completed",
"business": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"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",
"customer_id": "3c9d2e1f-7a6b-4c5d-8e9f-0a1b2c3d4e5f",
"buyer_email": "customer@example.com",
"buyer_name": "Alex Rivera",
"network": "card",
"livemode": true,
"overpaid_by_cents": null,
"metadata": { "orderId": "order-123" }
}
}
```
## `send.completed`
**Fires when:** an outbound stablecoin send that you started is confirmed.
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` in live mode, `false` in test mode.
Optional description you attached to the send. `null` if none was set.
```json Example theme={"system"}
{
"id": "evt_9d8e7f6a-5b4c-3d2e-1f0a-9b8c7d6e5f4a",
"type": "send.completed",
"business": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"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",
"livemode": true,
"description": "Payout to contractor"
}
}
```
## `send.failed`
**Fires when:** an outbound stablecoin send that you started fails: the broadcast fails, for example on a network error or an insufficient balance, or the transaction reverts on-chain.
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` in live mode, `false` in test mode.
Optional description you attached to the send. `null` if none was set.
```json Example theme={"system"}
{
"id": "evt_8fae0d2b-7d54-4b3e-9a41-2b3f9c1e0aa2",
"type": "send.failed",
"business": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"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",
"livemode": true,
"description": null
}
}
```
`send.failed` means the transaction never landed on-chain. A payment dispute or a card decline does not fire this event: those affect your balance directly. See [Payouts](/payouts/overview).
## Subscription events
**Fires when:**
* **`subscription.created`**: the subscription becomes `active` or `trialing` for the first time: the buyer pays the first period, or saves a card for a trial.
* **`subscription.renewed`**: a charge after the first one succeeds: a renewal, the first charge after a trial, or an upgrade's prorated charge. A renewal that a subscriber credit pays also fires it. The first period does not fire it.
* **`subscription.payment_failed`**: the status becomes `past_due` or `unpaid`: a renewal charge fails, including the first charge after a trial.
* **`subscription.updated`**: a cancel is scheduled or resumed (`cancel_at_period_end`), an upgrade is applied (new `plan_name`, `link_id` and `amount_minor`), a downgrade is scheduled (`pending_plan` set), a scheduled downgrade is reverted (`pending_plan` null again), or a scheduled downgrade takes over at the period end.
* **`subscription.canceled`**: the status becomes `canceled`: at the period end after a scheduled cancel, or when the card processor ends the subscription for another reason. No more charges follow.
Subscription lifecycle events, delivered to your account's webhook URL. All five share the same `data` shape. For the order they arrive in, see [Event order for subscriptions](#event-order-for-subscriptions).
The subscription's id, a UUID. The same value as `metadata.subscriptionId` on the checkout event and `id` in the [subscriptions API](/sdk/pay-subscriptions).The status after the change, one of the eight [subscription statuses](/sdk/pay-subscriptions#statuses). A subscription that never activated sends no event, so `incomplete` and `incomplete_expired` do not appear on events.Plan name, from the backing payment link.e.g. `"EUR"`.Per-cycle price in integer minor units. `1000` = `€10.00`.ISO 8601, or `null` before the first cycle.`true` if cancellation is scheduled for period end.The current plan's payment link `id`, a UUID: the `linkId` you pass to `checkouts.create`. It is not the token in the checkout URL that `checkout.session.completed` sends as `link_id`.
Null unless a downgrade is scheduled. Then `{ link_id, plan_name, amount_minor, effective_at }`: the plan the subscription switches to at `effective_at` (the period end). `plan_name`, `link_id` and `amount_minor` above stay what the buyer paid for until then.
Null unless the subscriber redeemed a discount code. Then `{ code, kind }`, where `kind` is `discount` or `tracking` (a code that changes no price). The amount the code took off is on the subscription's first invoice. The SDK's TypeScript types do not list this field yet.
`true` in live mode, `false` in test mode.
```json Example theme={"system"}
{
"id": "evt_3f2a1b0c9d8e7f6a",
"type": "subscription.renewed",
"business": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"data": {
"id": "b7e2a1c4-5d6f-4a8b-9c0d-1e2f3a4b5c6d",
"status": "active",
"plan_name": "Pro Monthly",
"link_id": "4cbb8f52-7cf2-4480-8c66-34f794cd23d0",
"currency": "EUR",
"amount_minor": 1000,
"current_period_end": "2026-09-19T12:00:00.000Z",
"cancel_at_period_end": false,
"pending_plan": null,
"discount": null,
"customer_email": "customer@example.com",
"customer_name": "Alex Rivera",
"livemode": true
}
}
```
## Dispute events
**Fires when:**
* **`dispute.created`**: AgentaOS finds a new dispute on one of your sales.
* **`dispute.closed`**: a dispute closes in your favor (`status` is `won`). A lost dispute sends no event.
A dispute starts when a buyer's bank opens a chargeback on a sale. Both events share the same `data` shape.
The dispute's id at the card processor.The dispute's status as the card processor reports it, e.g. `needs_response` or `won`.The sale the dispute is about.Disputed amount in integer minor units. `4999` = `€49.99`.e.g. `"EUR"`.`true` in live mode, `false` in test mode.
```json Example theme={"system"}
{
"id": "evt_1a2b3c4d5e6f7a8b",
"type": "dispute.created",
"business": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"data": {
"dispute_id": "dp_1P...",
"status": "needs_response",
"transaction_id": "8c7d6e5f-4a3b-2c1d-0e9f-8a7b6c5d4e3f",
"amount_minor": 4999,
"currency": "EUR",
"livemode": true
}
}
```
## `account.updated`
**Fires when:** a business's verification changes, in live mode only.
* Its identity check is verified or declined.
* Our reviewers approve, reject, hold or resume the business, or ask for a change.
* Our reviewers verify or withdraw its identity by hand.
The event goes to the business and to the platform that manages it.
The body is the state after the change.
`unverified`, `in_review`, `verified`, `on_hold`, or `rejected`.We asked the business for a change and are waiting on it.The identity check is done. For another app of your own company, this is your company's check.Always `true`: verification is about live money. The event still reaches your endpoint while you build in test mode, because an account has one webhook endpoint.
```json Example theme={"system"}
{
"id": "evt_4e1b9a0c2d3f5a67",
"type": "account.updated",
"business": "5b1f0c2e-8a4d-4c7b-9e21-3f6a7d8c9b10",
"data": {
"verification": "verified",
"changes_requested": false,
"identity_verified": true,
"livemode": true
}
}
```
## `webhook.test`
**Fires when:** you click **Send test event** in **Settings → Developers → Webhooks**.
Use it to check that your endpoint receives and verifies events. It goes to your account's webhook URL, signed like every other event.
Always `"Test event from AgentaOS"`.`true` if you sent it from live mode, `false` from test mode.
```json Example theme={"system"}
{
"id": "evt_test_5c2e9a1f0b7d3e48",
"type": "webhook.test",
"business": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"data": {
"message": "Test event from AgentaOS",
"livemode": false
}
}
```
## 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`.
One endpoint for all the businesses you manage.