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

# Businesses

> Manage your clients' businesses with agentaos.businesses, and act for any of them with the business option.

<Info>
  <span className="preview-pill">Private preview</span> 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).
</Info>

`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');
```

<ParamField path="id" type="string" required>The business id.</ParamField>

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,
});
```

<ParamField body="name" type="string" required>
  The name buyers see. A brand, not an email. Up to 100 characters.
</ParamField>

<ParamField body="country" type="string" required>
  Where the business is registered, ISO 3166-1 alpha-2 in upper case, e.g. `'DE'`.
</ParamField>

<ParamField body="clientEmail" type="string">
  Invite your client as the business's admin. Leave it out to run the business yourself. Only an owner of your platform may invite.
</ParamField>

<ParamField body="sameLegalEntity" type="boolean" default="false">
  `true` for another app of your own company: verified and priced with you, with no share and no identity check of its own.
</ParamField>

<ParamField body="sendInvitationEmail" type="boolean" default="true">
  `false`: we send no email. You send `inviteUrl` to your client yourself (white-label).
</ParamField>

The business starts in test mode and goes live after its own verification. Store `business.id` next to your own record of the client.

<ResponseField name="business" type="Business">The new business.</ResponseField>
<ResponseField name="inviteToken" type="string | null">The invitation token, or `null` without `clientEmail`.</ResponseField>
<ResponseField name="inviteUrl" type="string | null">The link your client opens to accept, or `null` without `clientEmail`. Ready to send as it is. The invitation expires after 7 days.</ResponseField>

## `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.

<ParamField path="id" type="string" required>The business id.</ParamField>
<ParamField body="sendInvitationEmail" type="boolean" default="true">`false`: no email, you send the link yourself.</ParamField>

<ResponseField name="inviteToken" type="string">The new invitation token.</ResponseField>
<ResponseField name="inviteUrl" type="string">The new link to send.</ResponseField>

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);
```

<ParamField path="id" type="string" required>The business id.</ParamField>

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.

<ParamField path="id" type="string" required>The business id.</ParamField>

<ResponseField name="url" type="string | null">The link to send. `null` when the client is already verified: nothing to send.</ResponseField>
<ResponseField name="status" type="string">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.</ResponseField>

<Warning>
  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.
</Warning>

Calling it again while a check is open gives back the same check.

## Business fields

<ResponseField name="id" type="string">Business UUID. Use it as `{ business }` and in `AgentaOS-Account`.</ResponseField>
<ResponseField name="name" type="string">The name buyers see.</ResponseField>
<ResponseField name="country" type="string | null">ISO 3166-1 alpha-2.</ResponseField>

<ResponseField name="status" type="'test_only' | 'in_review' | 'changes_needed' | 'on_hold' | 'rejected' | 'live'">
  Where the business stands. `live` means verified and with a payout account. `changes_needed` means we asked the business for a change. See [Business status](#business-status).
</ResponseField>

<ResponseField name="platformOrgId" type="string">Your own id: the platform that manages it.</ResponseField>
<ResponseField name="createdAt" type="string">ISO 8601.</ResponseField>

<ResponseField name="platformFee" type="{ bps: number; fixedMinor: number }">
  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).
</ResponseField>

<ResponseField name="sameLegalEntity" type="boolean">`true` for another app of your own company.</ResponseField>

<ResponseField name="feesThisMonth" type="Array<{ currency: string; feesMinor: number; feesDisplay: string }>">
  Your share of its live sales this month, per currency. `feesDisplay` is ready to show, e.g. `"€12.50"`.
</ResponseField>

<ResponseField name="invitedEmail" type="string | null">Who an open invitation is waiting on, or `null`.</ResponseField>
<ResponseField name="identityVerified" type="boolean">The identity check is done: its own, or your company's for another app of your own company.</ResponseField>

```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 and has a payout account saved. Until it has a payout account, a verified business shows **Test only** (`test_only`), and `verification` is already `verified`.

## 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.

<Tip>
  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`.
</Tip>

### File a client's verification

This is the same verification any account submits, sent as the business.

<Warning>
  We record it as filed by the platform, and our reviewers see that. File only what your client confirmed to you.
</Warning>

```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

<Columns cols={2}>
  <Card title="Build a platform" icon="route" href="/guides/build-a-platform">
    End to end.
  </Card>

  <Card title="Businesses (CLI)" icon="terminal" href="/cli/businesses">
    The same from the terminal.
  </Card>

  <Card title="Platform webhooks" icon="webhook" href="/connect/webhooks">
    `business` on every event, and `account.updated`.
  </Card>

  <Card title="API reference" icon="book" href="/api-reference/businesses/list">
    The raw HTTP endpoints.
  </Card>
</Columns>
