Public REST API
AvsB has a REST API. It can read and change almost everything you can already read and change by hand in the dashboard.
Use it to build your own tools, connect AvsB to another system, or manage your setup from a script instead of clicking through the UI.
Every endpoint here needs your organization's plan to include Integrations and API access. Without it, every call fails with 403 feature_disabled, reads included.
What you can manage
- Projects: the container for one website or app you run experiments and flags in.
- Experiments: A/B tests, and the variations inside them (control, plus each challenger version you're testing).
- Feature flags: a setting in your code you can turn on, off, or change without shipping new code.
- Audiences: a named group of visitors, picked out by rules such as location or device.
- Segments, metrics, and exclusion groups: the building blocks experiments and flags are made from. An exclusion group, for example, stops a visitor from landing in two conflicting experiments at once.
- Results: the numbers an experiment has produced so far.
See Resources for the full list, generated straight from the live API.
Base URL
Every endpoint is served from the same address as your AvsB dashboard:
https://app.avsb.cloud/api/v1/...Authentication
Every request needs a Bearer token in the Authorization header, and you have a choice of two. A service token (avsb_svc_...) belongs to your organization and is built for automation. Your own personal access token (avsb_pat_...) works just as well for a personal script. Both authenticate every route on this page.
curl https://app.avsb.cloud/api/v1/projects \ -H "Authorization: Bearer avsb_svc_abc1234_..."const res = await fetch('https://app.avsb.cloud/api/v1/projects', { headers: { Authorization: `Bearer ${process.env.AVSB_TOKEN}` },})const { data } = await res.json()import os, requestsres = requests.get( "https://app.avsb.cloud/api/v1/projects", headers={"Authorization": f"Bearer {os.environ['AVSB_TOKEN']}"},)data = res.json()["data"]Reach for a service token when the caller is not a person: CI, Terraform, or any job that must keep working after someone leaves. Reach for your personal access token for your own scripts and the CLI. See Authentication for how to create and scope a service token, or Personal Access Tokens if you already have one of your own.
Response envelope
A successful call returns { "data": ... }, your data inside a data field:
{ "data": { "id": "tok_abc", "name": "Terraform CI" } }A list also returns a page object, so you can ask for the next batch:
{ "data": [ ... ], "page": { "nextCursor": "eyJpZCI6ImFiYyIsInNvcnRWYWx1ZSI6IjIwMjYtMDUtMTciLCJzY2hlbWFWZXJzaW9uIjoxfQ", "hasMore": true }}A failed call returns an error field instead, never a bare string:
{ "error": { "code": "scope_missing", "message": "Token is missing required scope: experiments:write", "details": { "missingScope": "experiments:write" }, "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#refusals-at-403", "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77" }}Check code in your own code; it never changes meaning. docUrl links straight to the part of Conventions that explains that code. Keep requestId handy: quoting it in a support message is enough for us to find your exact request.
Rate limits
A scoped token gets 600 read requests and 120 write requests per minute. A token with the admin:* scope gets 600 requests per minute, reads and writes counted together. Every response carries headers that say where you stand, so you can slow down before you hit the limit:
X-RateLimit-Limit: requests allowed per minute for this token.X-RateLimit-Remaining: requests left in the current minute.X-RateLimit-Reset: a Unix timestamp for when the window resets.Retry-After: sent only on a429response, the seconds to wait before trying again.
Idempotent writes
Add an Idempotency-Key header to a write (POST, PUT, PATCH, DELETE) and it becomes safe to retry. Send the same key with the same body again within 24 hours, and AvsB hands back the first response instead of doing the work twice. Send the same key with a different body, and it refuses with 409 Conflict and the error code idempotency_conflict rather than guess which one you meant.
curl https://app.avsb.cloud/api/v1/projects \ -X POST \ -H "Authorization: Bearer avsb_svc_..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"name":"Checkout"}'const key = crypto.randomUUID()await fetch('https://app.avsb.cloud/api/v1/projects', { method: 'POST', headers: { Authorization: `Bearer ${process.env.AVSB_TOKEN}`, 'Content-Type': 'application/json', 'Idempotency-Key': key, }, body: JSON.stringify({ name: 'Checkout' }),})Optimistic concurrency via ETags
Read one of the core resources (a project, an experiment, a flag, and the like). The response carries an ETag header: a short fingerprint of that resource right now. Send it back as X-Avsb-If-Match on your next write to the same resource, and a stale fingerprint makes the write fail with 412 Precondition Failed instead of silently overwriting a change you never saw. The standard If-Match header is accepted too, and X-Avsb-If-Match is the one to use, because intermediaries such as proxies, caches and edge networks are allowed to act on the standard conditional header before the request reaches the API. Conventions lists which resources carry an ETag and the few writes that stay unconditional.
curl -i https://app.avsb.cloud/api/v1/projects/42# < ETag: W/"a1b2c3d4e5f60798"curl -X PATCH https://app.avsb.cloud/api/v1/projects/42 \ -H "Authorization: Bearer avsb_svc_..." \ -H "X-Avsb-If-Match: W/\"a1b2c3d4e5f60798\"" \ -d '{"name":"renamed"}'# 412 Precondition Failed if the resource changed since you read itNot every endpoint checks the conditional-write header yet, and commerce resources (catalog, datasets, recommendations, orders) don't send an ETag at all. See Conventions → ETag / If-Match for the exact list.
Versioning
Every endpoint lives under /api/v1, and v1 is the only version there is today, so there is nothing to pin. An optional AvsB-API-Version request header is accepted but ignored on v1: it does not change the response and is not echoed back. A future breaking change would ship at a new path, such as /api/v2, and leave /api/v1 working exactly as it does now. See Conventions → API versioning for the full detail.
Audit log
Every write made with an API token, service or personal, is recorded in your organization's audit log with source API. Each entry names what changed, which token made the change, and the client's IP address. Read it back with GET /api/v1/audit-logs (scope audit:read), or browse it in the dashboard.