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

# REST API

> The full HTTP surface behind app.abtestly.com. Base URL, auth, rate limits, and every endpoint you can hit from your own code.

The dashboard is a thin React app over a Cloudflare Worker. Everything
you do in the UI, you can do from your own code by hitting the same
endpoints. This page is the authoritative list, generated from the
worker source.

<Warning>
  There is no long-lived API key mechanism today. All calls to
  `/api/*` require a Clerk session JWT in the `Authorization` header.
  The workflow is: obtain a Clerk session, pull its JWT with
  [Clerk's `session.getToken()`](https://clerk.com/docs/references/javascript/session#get-token),
  and forward it as `Authorization: Bearer <jwt>`. If you need service
  account credentials for automation, tell us at
  `enterprise@abtestly.com` and we will scope the ask.
</Warning>

## Base URL

```
https://api.abtestly.com
```

Every authenticated endpoint below is mounted under `/api/*`. Public
endpoints (snippet delivery, event ingestion) are documented in the
[beacon protocol](/developer/beacon-protocol) reference.

## Authentication

```http theme={null}
Authorization: Bearer <clerk-session-jwt>
```

* Verified on every request against Clerk's JWKS.
* Missing or malformed → `401 unauthorized`.
* Expired → `401 token_expired`. Refresh via the Clerk client and
  retry.
* Valid JWT for a user with no access to the requested resource →
  `403 forbidden`.

Cross-site requests are allowed from `https://app.abtestly.com` only.
Server-to-server calls do not need CORS.

## Rate limits

* **100 requests per minute per user** across all `/api/*` endpoints.
* Exceeding the limit returns `429 rate_limited` with a
  `Retry-After` header in seconds.
* Public endpoints (`/s`, `/e`, `/g`, `/m`, `/err`) have their own
  limits documented on [Rate limits](/developer/rate-limits).

## Errors

All errors return JSON with a stable machine-readable code and a
human-readable message.

```json theme={null}
{ "error": "not_found", "message": "Experiment not found" }
```

Common codes: `unauthorized`, `forbidden`, `not_found`,
`validation_failed`, `conflict`, `rate_limited`, `internal_error`.

## Endpoint reference

Every path below is under `https://api.abtestly.com`. Path parameters
are marked `:name`. All `POST`, `PATCH`, `PUT`, and `DELETE` calls
except where noted expect and return JSON.

### Account and identity

| Method | Path           | Purpose                                  |
| ------ | -------------- | ---------------------------------------- |
| `GET`  | `/api/me`      | Current user, active org, memberships    |
| `POST` | `/api/onboard` | Complete first-time setup for a new user |

### Organizations and invitations

| Method   | Path                                       | Purpose                             |
| -------- | ------------------------------------------ | ----------------------------------- |
| `GET`    | `/api/organizations`                       | Organizations the caller belongs to |
| `GET`    | `/api/organizations/:id`                   | One organization                    |
| `PATCH`  | `/api/organizations/:id`                   | Rename, change owner                |
| `GET`    | `/api/organizations/:id/members`           | Roster                              |
| `PATCH`  | `/api/organizations/:id/members/:memberId` | Change role or per-site permissions |
| `DELETE` | `/api/organizations/:id/members/:memberId` | Remove member                       |
| `GET`    | `/api/invitations`                         | Pending invites                     |
| `POST`   | `/api/invitations`                         | Send invite                         |
| `POST`   | `/api/invitations/:id/resend`              | Resend                              |
| `POST`   | `/api/invitations/:id/accept`              | Accept                              |
| `DELETE` | `/api/invitations/:id`                     | Revoke                              |

### Sites

| Method   | Path                                      | Purpose                                    |
| -------- | ----------------------------------------- | ------------------------------------------ |
| `GET`    | `/api/sites`                              | Sites the caller can see                   |
| `POST`   | `/api/sites`                              | Create a site                              |
| `GET`    | `/api/sites/usage`                        | MTU and quota snapshot for the current org |
| `GET`    | `/api/sites/:siteId`                      | One site                                   |
| `PATCH`  | `/api/sites/:siteId`                      | Update settings                            |
| `DELETE` | `/api/sites/:siteId`                      | Delete site (irreversible)                 |
| `GET`    | `/api/sites/:siteId/install-status`       | Has the snippet ever fired                 |
| `GET`    | `/api/sites/:siteId/my-access`            | Caller's permission grid for this site     |
| `GET`    | `/api/sites/:siteId/collection-health`    | Health digest                              |
| `GET`    | `/api/sites/:siteId/blocked-ips/activity` | Recent blocked-IP hits                     |
| `GET`    | `/api/sites/:siteId/publish-status`       | Publish outbox state                       |
| `POST`   | `/api/sites/:siteId/publish`              | Republish configuration                    |

### Experiments

| Method   | Path                                     | Purpose                                             |
| -------- | ---------------------------------------- | --------------------------------------------------- |
| `GET`    | `/api/experiments`                       | List; supports pagination, filters, sort            |
| `POST`   | `/api/experiments`                       | Create draft                                        |
| `GET`    | `/api/experiments/:id`                   | Full experiment payload                             |
| `PATCH`  | `/api/experiments/:id`                   | Save edits (draft or running edit-lock rules apply) |
| `DELETE` | `/api/experiments/:id`                   | Delete draft                                        |
| `GET`    | `/api/experiments/tags`                  | Distinct tag list for the org                       |
| `GET`    | `/api/experiments/digest`                | Health-digest bundle for the list view              |
| `GET`    | `/api/experiments/:id/collection-health` | Digest for one experiment                           |
| `GET`    | `/api/experiments/:id/lock-status`       | Is another editor holding the lock                  |
| `GET`    | `/api/experiments/:id/edit-lock`         | Acquire the editor lock                             |
| `POST`   | `/api/experiments/:id/clone`             | Clone into a fresh draft with a new salt and key    |
| `POST`   | `/api/experiments/:id/reset`             | Wipe results, keep configuration                    |
| `POST`   | `/api/experiments/:id/status`            | Start, pause, stop, schedule                        |
| `POST`   | `/api/experiments/:id/restore`           | Restore a deleted experiment                        |
| `GET`    | `/api/experiments/:id/results`           | Computed results (verdict, per-variant, segments)   |

### Result exports

| Method | Path                                                  | Purpose                         |
| ------ | ----------------------------------------------------- | ------------------------------- |
| `POST` | `/api/experiments/:id/events/exports`                 | Start an async raw-event export |
| `GET`  | `/api/experiments/:id/events/exports/:jobId`          | Poll export status              |
| `GET`  | `/api/experiments/:id/events/exports/:jobId/download` | Download the CSV once ready     |

### Goals

| Method   | Path                                     | Purpose                   |
| -------- | ---------------------------------------- | ------------------------- |
| `GET`    | `/api/goals/:siteId/goals`               | Goal library for one site |
| `POST`   | `/api/goals/:siteId/goals`               | Create a goal             |
| `PATCH`  | `/api/goals/:siteId/goals/:goalId`       | Update                    |
| `DELETE` | `/api/goals/:siteId/goals/:goalId`       | Delete                    |
| `POST`   | `/api/goals/:siteId/goals/:goalId/clone` | Clone                     |

### Audiences

| Method   | Path                                   | Purpose         |
| -------- | -------------------------------------- | --------------- |
| `GET`    | `/api/audiences/:siteId/audiences`     | Saved audiences |
| `POST`   | `/api/audiences/:siteId/audiences`     | Create          |
| `GET`    | `/api/audiences/:siteId/audiences/:id` | One             |
| `PATCH`  | `/api/audiences/:siteId/audiences/:id` | Update          |
| `DELETE` | `/api/audiences/:siteId/audiences/:id` | Delete          |

### Saved locations

| Method   | Path                                   | Purpose            |
| -------- | -------------------------------------- | ------------------ |
| `GET`    | `/api/locations/:siteId/locations`     | Saved URL rulesets |
| `POST`   | `/api/locations/:siteId/locations`     | Create             |
| `GET`    | `/api/locations/:siteId/locations/:id` | One                |
| `PATCH`  | `/api/locations/:siteId/locations/:id` | Update             |
| `DELETE` | `/api/locations/:siteId/locations/:id` | Delete             |

### Mutual exclusion groups

| Method   | Path                                                       | Purpose                                 |
| -------- | ---------------------------------------------------------- | --------------------------------------- |
| `GET`    | `/api/sites/:siteId/exclusion-groups`                      | Groups on this site                     |
| `GET`    | `/api/sites/:siteId/exclusion-groups/eligible-experiments` | Experiments that can join a group       |
| `GET`    | `/api/sites/:siteId/exclusion-groups/:id`                  | One group                               |
| `POST`   | `/api/sites/:siteId/exclusion-groups`                      | Create                                  |
| `PUT`    | `/api/sites/:siteId/exclusion-groups/:id/members`          | Set membership (rebuilds slot map)      |
| `POST`   | `/api/sites/:siteId/exclusion-groups/:id/activate`         | Bump generation, arm the split          |
| `POST`   | `/api/sites/:siteId/exclusion-groups/:id/reset`            | Reshuffle without touching membership   |
| `PUT`    | `/api/sites/:siteId/exclusion-groups/:id/active-members`   | Change which members are currently live |
| `POST`   | `/api/sites/:siteId/exclusion-groups/:id/archive`          | Archive                                 |
| `DELETE` | `/api/sites/:siteId/exclusion-groups/:id`                  | Delete                                  |

### Billing

| Method | Path                        | Purpose                                    |
| ------ | --------------------------- | ------------------------------------------ |
| `GET`  | `/api/billing/subscription` | Current plan, seats, MTU cap, next renewal |
| `POST` | `/api/billing/checkout`     | Start a Paddle checkout                    |
| `POST` | `/api/billing/portal`       | Open the Paddle customer portal            |

## Public endpoints

Not authenticated with Clerk. Used by the snippet on the visitor's
device.

| Path               | Purpose                                              |
| ------------------ | ---------------------------------------------------- |
| `/s`               | Snippet loader and runtime bundles                   |
| `/e`               | Exposure and conversion beacons                      |
| `/e/validate`      | Preview-mode dry-run of a beacon                     |
| `/g`               | Geo lookup for targeting                             |
| `/m`               | Snippet telemetry                                    |
| `/err`             | Runtime error reports (first-occurrence dedup)       |
| `/preview-config`  | Preview-mode config fetch                            |
| `/health`          | Liveness ping                                        |
| `/webhooks/paddle` | Paddle billing webhooks                              |
| `/a/*`             | Public customer assets on `usercontent.abtestly.com` |

The wire format for each of these is on the
[beacon protocol](/developer/beacon-protocol) page.

## Not yet published

* **OpenAPI spec.** We are drafting one. Until it ships, treat this
  page as the source of truth.
* **Long-lived API keys.** No self-serve keys today. If you have a
  server-side automation use case, tell us at
  `enterprise@abtestly.com`.
* **Webhooks out.** Paddle webhooks come in; we do not send outbound
  webhooks to your infrastructure yet.

If you are building against these and something on this page does
not match reality, email `support@abtestly.com` with the endpoint
and the response you got. That is the fastest way for us to fix the
doc.
