An **api_account is a user account: one of your customers**, with their own isolated index. It is distinct from you, the white-label organisation, which holds all of these user accounts and the provisioning token that manages them. Create one per customer, usually at signup.

`uid` is required: **your own stable customer id**. It is the idempotency key and the address for every later call, so you never need to store a Context Link id.

`site_url` is optional: include it if you know the customer's website; omit it for a push-only account you fill via [custom connections](/docs/white-label/upsert-post).

```bash
curl -X POST "https://context-link.ai/api/v1/api_accounts" \
  -H "Authorization: Bearer $PROVISIONING_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "uid": "tf-cust-8841",
    "label": "Acme Supply Co",
    "site_url": "https://acme.com",
    "webhook_url": "https://your-app.com/hooks/context-link"
  }'
```

`201 Created`. **Store the token now**:

```json
{
  "api_account": {
    "id": 4211,
    "uid": "tf-cust-8841",
    "label": "Acme Supply Co",
    "site_url": "https://acme.com",
    "connection_id": 9876,
    "sync_status": "syncing",
    "webhook_url": "https://your-app.com/hooks/context-link",
    "created_at": "2026-08-31T10:15:00Z"
  },
  "token": "6yTk2mWq8rVd4nXb...",
  "bundle_added": false
}
```

## Request fields

| Field | Type | Description |
|---|---|---|
| `uid` | string | Required. Your own stable customer id: `a-z`, `0-9`, hyphen, underscore or dot, 1 to 255 characters, unique **per organisation**. It is the idempotency key and the address for every later call. |
| `label` | string | Optional display name, 255 characters or fewer, defaulting to the uid. This is the "label" column on the fleet screen. |
| `site_url` | string | Optional http(s) URL. When present a website index is created and the first crawl starts immediately. We keep the **host**, plus a non-root path which becomes the crawl's starting point, and always crawl over `https` on the default port. A URL carrying an explicit port or embedded credentials is rejected rather than silently trimmed. |
| `webhook_url` | string | Optional http(s) URL. Overrides your organisation default for this customer only. |

## Response fields

| Field | Type | Description |
|---|---|---|
| `api_account` | object | The account that was provisioned or replayed. |
| `api_account.id` | integer | Context Link's own id for the account; you address it by `uid`, so you never need to store this. |
| `api_account.uid` | string | The customer id you provisioned the account with. |
| `api_account.label` | string | The account's display name, falling back to the uid when you sent none. |
| `api_account.site_url` | string | The website that will be crawled, `null` when the account was created without a `site_url`. |
| `api_account.connection_id` | integer | The website connection's id, `null` when the account has no website. |
| `api_account.sync_status` | string | The crawl state: `unsynced`, `syncing`, `synced`, `error_syncing`, or `no_site` for an account with no website. |
| `api_account.webhook_url` | string | The per-customer webhook override, `null` when the account falls back to your organisation default. |
| `api_account.created_at` | timestamp | When the account was first provisioned. |
| `token` | string | The account's query token, returned on creation and again on every replay. |
| `bundle_added` | boolean | Whether this call added a bundle of 10 slots to your subscription. |

Token-bearing responses are sent `Cache-Control: no-store`.

## Retries are safe

If the response gets lost, POST the same `uid` again. You get `200` with the existing account and **the same token**, and nothing is created twice.

A replay deliberately **ignores** a changed body, *including an invalid one*: re-POSTing with a different `site_url` or `label` will not mutate the existing account and will not `422`. So a retry needs no special casing, and needs to carry no more than the `uid`.

**Automatic growth**: if this was customer #11 on 1 bundle, a bundle is added automatically (+$50/mo, +10 slots) and the response carries `"bundle_added": true`. If the charge fails, the call returns `402` and **no** account is created. Bundles never shrink automatically; downgrade seats from billing if you want to.
