Public API: Exclusion groups

An exclusion group makes a set of experiments mutually exclusive: a visitor entering one experiment in the group is held out of the others. Each member experiment gets a contiguous slice of the group's 0–100% traffic range, so the slices never overlap.

All exclusion-group endpoints live under a project and authenticate with a service token. The org is taken from the token, so the path carries only {projectId} (and {groupId} / {experimentId} for nested resources), not {orgId}.

Plain text
https://app.avsb.cloud/api/v1/projects/{projectId}/exclusion-groups
Plain text1 line

A group or experiment that isn't in the token's org and project returns 404; existence is hidden.

Scopes

A scope is a named permission on your token. It decides exactly what that token is allowed to read or change.

OperationScope
List / get groupsexperiments:read
Create / update / delete groupsexperiments:write
Attach / detach experimentsexperiments:write

List exclusion groups

GET /api/v1/projects/{projectId}/exclusion-groups: newest-updated first, cursor-paginated. Each group carries allocatedPercent (the sum of its members' traffic) and a member count.

curl "https://app.avsb.cloud/api/v1/projects/<projectId>/exclusion-groups?limit=20" \  -H "Authorization: Bearer avsb_svc_..."
Shell2 lines
Response
{  "data": [    {      "id": "<groupId>",      "shortId": 1,      "projectId": "<projectId>",      "name": "Checkout funnel",      "description": null,      "createdAt": "2026-06-18T00:00:00.000Z",      "updatedAt": "2026-06-18T00:00:00.000Z",      "_count": { "experiments": 2 },      "allocatedPercent": 0.5    }  ],  "page": { "nextCursor": null, "hasMore": false }}
JSON16 lines

Pass ?cursor=<page.nextCursor> to fetch the next page. limit defaults to 20, max 100. An out-of-range limit is refused rather than clamped: see Conventions for that error shape.

Get an exclusion group

GET /api/v1/projects/{projectId}/exclusion-groups/{groupId}: returns the group plus its membership rows (each with the assigned traffic slot), ordered by slot start.

curl https://app.avsb.cloud/api/v1/projects/<projectId>/exclusion-groups/<groupId> \  -H "Authorization: Bearer avsb_svc_..."
Shell2 lines
Response
{  "data": {    "id": "<groupId>",    "shortId": 1,    "projectId": "<projectId>",    "name": "Checkout funnel",    "description": null,    "createdAt": "2026-06-18T00:00:00.000Z",    "updatedAt": "2026-06-18T00:00:00.000Z",    "experiments": [      {        "id": "<membershipId>",        "exclusionGroupId": "<groupId>",        "experimentId": "<experimentId>",        "trafficPercent": 0.5,        "rangeStart": 0,        "rangeEnd": 4999,        "createdAt": "2026-06-18T00:00:00.000Z",        "experiment": { "id": "<experimentId>", "shortId": 5, "name": "Hero", "status": "DRAFT" }      }    ]  }}
JSON23 lines

A group id from another org or project, or one that never existed, returns 404 not_found.

Create an exclusion group

POST /api/v1/projects/{projectId}/exclusion-groups: name is the only required field. Requires experiments:write.

curl -X POST https://app.avsb.cloud/api/v1/projects/<projectId>/exclusion-groups \  -H "Authorization: Bearer avsb_svc_..." \  -H "Content-Type: application/json" \  -d '{ "name": "Checkout funnel" }'
Shell4 lines
Response
{  "data": {    "id": "<groupId>",    "shortId": 1,    "projectId": "<projectId>",    "name": "Checkout funnel",    "description": null,    "createdAt": "2026-06-18T00:00:00.000Z",    "updatedAt": "2026-06-18T00:00:00.000Z",    "_count": { "experiments": 0 },    "allocatedPercent": 0  }}
JSON13 lines

Add description (up to 2000 characters) for your own team's context. Leave it out and it comes back as null, not an empty string; the shape is otherwise identical to the request above:

cURL: adding a description
curl -X POST https://app.avsb.cloud/api/v1/projects/<projectId>/exclusion-groups \  -H "Authorization: Bearer avsb_svc_..." \  -H "Content-Type: application/json" \  -d '{ "name": "Checkout funnel", "description": "Mutually exclusive checkout tests" }'
Shell4 lines

Responds 201 either way. A new group has no members, so its allocatedPercent is always 0. A project id from another org returns 404 not_found.

Update an exclusion group

PATCH /api/v1/projects/{projectId}/exclusion-groups/{groupId}: rename or re-describe a group.

Only name and description are mutable. Mutations support X-Avsb-If-Match (or If-Match) for optimistic concurrency.

curl -X PATCH https://app.avsb.cloud/api/v1/projects/<projectId>/exclusion-groups/<groupId> \  -H "Authorization: Bearer avsb_svc_..." \  -H "Content-Type: application/json" \  -d '{ "name": "Checkout funnel v2" }'
Shell4 lines
Response
{  "data": {    "id": "<groupId>",    "shortId": 1,    "projectId": "<projectId>",    "name": "Checkout funnel v2",    "description": null,    "createdAt": "2026-06-18T00:00:00.000Z",    "updatedAt": "2026-06-19T08:30:00.000Z"  }}
JSON11 lines

A group id from another org or project, or one that never existed, returns the same 404 not_found shown under Get an exclusion group above.

Delete an exclusion group

DELETE /api/v1/projects/{projectId}/exclusion-groups/{groupId}: returns the removed id.

curl -X DELETE https://app.avsb.cloud/api/v1/projects/<projectId>/exclusion-groups/<groupId> \  -H "Authorization: Bearer avsb_svc_..."
Shell2 lines
Response
{ "data": { "id": "<groupId>", "removed": true } }
JSON1 line
Warning

A group must be empty before it can be deleted. Detach every experiment first, otherwise the call returns 400.

Attach an experiment

POST /api/v1/projects/{projectId}/exclusion-groups/{groupId}/experiments: assign an experiment a contiguous traffic slot inside the group. trafficPercent is a fraction in (0, 1].

curl -X POST https://app.avsb.cloud/api/v1/projects/<projectId>/exclusion-groups/<groupId>/experiments \  -H "Authorization: Bearer avsb_svc_..." \  -H "Content-Type: application/json" \  -d '{ "experimentId": "<experimentId>", "trafficPercent": 0.5 }'
Shell4 lines

Responds 201 with the new membership row, including the assigned rangeStart/rangeEnd.

Response
{  "data": {    "id": "<membershipId>",    "exclusionGroupId": "<groupId>",    "experimentId": "<experimentId>",    "trafficPercent": 0.5,    "rangeStart": 0,    "rangeEnd": 4999,    "createdAt": "2026-06-18T00:00:00.000Z",    "experiment": { "id": "<experimentId>", "shortId": 5, "name": "Hero", "status": "DRAFT" }  }}
JSON12 lines

The call returns 400 when:

  • the experiment is RUNNING (pause it first) or COMPLETED (a finished experiment can never be added to a group),
  • it is already in another group,
  • the total allocated traffic would exceed 100%, or
  • there is no contiguous slot large enough (remove an experiment to defragment).

Detach an experiment

DELETE /api/v1/projects/{projectId}/exclusion-groups/{groupId}/experiments/{experimentId}: remove an experiment from the group and free its slot.

curl -X DELETE https://app.avsb.cloud/api/v1/projects/<projectId>/exclusion-groups/<groupId>/experiments/<experimentId> \  -H "Authorization: Bearer avsb_svc_..."
Shell2 lines
Response
{ "data": { "experimentId": "<experimentId>", "removed": true } }
JSON1 line

A RUNNING experiment cannot be detached; pause it first. A membership row that does not exist, whether the group, the experiment, or the pairing between them is wrong, returns 404 not_found.

Next steps

Was this helpful?