Public API: Integrations
Integrations forward experiment data to your analytics tools. A project can turn on up to nine providers: GOOGLE_ANALYTICS, GOOGLE_TAG_MANAGER, MIXPANEL, SEGMENT, ADOBE_ANALYTICS, FULLSTORY, CONTENTSQUARE, HEAP, and AMPLITUDE. Turning one on adds it to your project's datafile (the small JSON file your snippet downloads that lists your project's live experiments and flags). The snippet reads the datafile and forwards events to whichever of these tools is already loaded on the page.
Integrations are set per project and apply to every experiment in that project. Saving a change re-publishes the project's datafile so the new settings reach live visitors.
Every endpoint here authenticates with a Bearer token: a service token or a personal access token. The org comes from the token, so the path carries {projectId}, not {orgId}.
https://app.avsb.cloud/api/v1/projects/{projectId}/integrationsEvery endpoint here follows the shared conventions: the { data } envelope, an Idempotency-Key on the write below, and the standard error shape. Every /api/v1 token is rate-limited the same way everywhere: a scoped token gets 600 reads and 120 writes per minute, and an admin:* token gets 600 requests per minute, reads and writes together. Both endpoints also need a plan that includes Integrations and API access. Without it, every call answers 403 feature_disabled. See rate-limit headers and Refusals at 403.
These are the data-plane analytics integrations. Connecting a Slack workspace or a Shopify store is a different, dashboard-only flow and is not part of the public API.
Scopes
A scope is a named permission on your token. It decides exactly what that token is allowed to read or change.
| Operation | Scope |
|---|---|
| Read a project's integrations | integrations:read |
| Update a project's integrations | integrations:write |
The integration shape
Every integration shares the same four base fields. Google Analytics and Google Tag Manager each accept one extra field.
| Field | Type | Notes |
|---|---|---|
provider | one of the nine names above | picks the shape of the rest of the object |
enabled | boolean, required | turns the destination on or off. When enabled is true, a running experiment sends its exposure event (the moment a visitor is counted in the test). There is no separate flag just for exposures. |
forward | object, optional | { "goals": false, "purchases": false, "recs": false } by default. Set any of the three to true to also send goals, purchases, or recs events to this destination. |
names | object, optional | renames the event AvsB sends for exposure, goal, purchase, rec_impression, and rec_click. Leave a key out to keep the provider's default name. |
category | "analytics" or "marketing" | defaults to "analytics". Your consent settings read this field to decide whether a visitor's cookie choice allows this destination to fire. |
Google Analytics also accepts sendTo, a GA4 measurement id like G-XXXXXXX, to route events at one data stream. Google Tag Manager also accepts eventName, the key pushed to dataLayer (default avsb_experiment).
A bad name is rejected with 400, not silently dropped. Google Analytics blocks its own reserved event names (like purchase, screen_view, and session_start) and any name starting with firebase_, ga_, google_, or gtag. Mixpanel blocks names starting with $ or mp_, except its own default $experiment_started. Heap blocks click, change, pageview, and submit. Contentsquare accepts no name overrides and no forwarding beyond exposures: it only ever receives the experiment itself, and turning on forward for it is refused.
CUSTOM used to be a tenth provider. It was retired: the API rejects it with 400 and will not create a row for it.
Read a project's integrations
GET /api/v1/projects/{projectId}/integrations: every integration the project has, in one call. This is a settings list. It does not split across pages.
curl https://app.avsb.cloud/api/v1/projects/<projectId>/integrations \ -H "Authorization: Bearer avsb_svc_..."const projectId = '<projectId>'const res = await fetch(`https://app.avsb.cloud/api/v1/projects/${projectId}/integrations`, { headers: { Authorization: `Bearer ${process.env.AVSB_SERVICE_TOKEN}` },})const { data } = await res.json()import os, requestsproject_id = "<projectId>"res = requests.get( f"https://app.avsb.cloud/api/v1/projects/{project_id}/integrations", headers={"Authorization": f"Bearer {os.environ['AVSB_SERVICE_TOKEN']}"},)data = res.json()["data"]{ "data": [ { "id": "<integrationId>", "projectId": "<projectId>", "provider": "GOOGLE_ANALYTICS", "enabled": true, "forward": { "goals": true, "purchases": false, "recs": false }, "names": {}, "sendTo": "G-XXXXXXX", "category": "analytics", "createdAt": "2026-06-21T00:00:00.000Z", "updatedAt": "2026-06-21T00:00:00.000Z" } ]}An AMPLITUDE row carries one extra, derived field: "billingWarning": true when names.exposure was renamed away from Amplitude's billing-exempt default, $exposure.
{ "error": { "code": "not_found", "message": "Project not found", "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#not-found-errors", "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77" }}Update a project's integrations
PUT /api/v1/projects/{projectId}/integrations: create or update the providers you list. This is upsert by provider, not a full replace:
- A provider you send is created, or fully replaced if it already exists.
- A provider you leave out stays exactly as it was.
- This endpoint never deletes a provider. Set
enabled: falseto switch a destination off without losing its saved setup.
curl https://app.avsb.cloud/api/v1/projects/<projectId>/integrations \ -X PUT \ -H "Authorization: Bearer avsb_svc_..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "integrations": [ { "provider": "GOOGLE_ANALYTICS", "enabled": true, "sendTo": "G-XXXXXXX" }, { "provider": "MIXPANEL", "enabled": true, "forward": { "goals": true, "purchases": true, "recs": false }, "names": { "goal": "onboarding_completed" }, "category": "marketing" } ] }'const projectId = '<projectId>'const res = await fetch(`https://app.avsb.cloud/api/v1/projects/${projectId}/integrations`, { method: 'PUT', headers: { Authorization: `Bearer ${process.env.AVSB_SERVICE_TOKEN}`, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID(), }, body: JSON.stringify({ integrations: [ { provider: 'GOOGLE_ANALYTICS', enabled: true, sendTo: 'G-XXXXXXX' }, { provider: 'MIXPANEL', enabled: true, forward: { goals: true, purchases: true, recs: false }, names: { goal: 'onboarding_completed' }, category: 'marketing', }, ], }),})const { data, warning } = await res.json()import os, uuid, requestsproject_id = "<projectId>"res = requests.put( f"https://app.avsb.cloud/api/v1/projects/{project_id}/integrations", headers={ "Authorization": f"Bearer {os.environ['AVSB_SERVICE_TOKEN']}", "Idempotency-Key": str(uuid.uuid4()), }, json={ "integrations": [ {"provider": "GOOGLE_ANALYTICS", "enabled": True, "sendTo": "G-XXXXXXX"}, { "provider": "MIXPANEL", "enabled": True, "forward": {"goals": True, "purchases": True, "recs": False}, "names": {"goal": "onboarding_completed"}, "category": "marketing", }, ] },)data = res.json()["data"]Only provider and enabled are required; every other field falls back to its default. The response always returns your project's whole current set, including any provider left over from before this call, not just the ones you just sent:
{ "data": [ { "id": "<integrationId>", "projectId": "<projectId>", "provider": "GOOGLE_ANALYTICS", "enabled": true, "forward": { "goals": false, "purchases": false, "recs": false }, "names": {}, "sendTo": "G-XXXXXXX", "category": "analytics", "createdAt": "2026-06-21T00:00:00.000Z", "updatedAt": "2026-06-21T00:00:00.000Z" }, { "id": "<integrationId>", "projectId": "<projectId>", "provider": "MIXPANEL", "enabled": true, "forward": { "goals": true, "purchases": true, "recs": false }, "names": { "goal": "onboarding_completed" }, "category": "marketing", "createdAt": "2026-06-21T00:00:00.000Z", "updatedAt": "2026-06-21T00:00:00.000Z" } ]}{ "error": { "code": "validation_failed", "message": "This is a reserved Google Analytics event name; pick a different one", "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#validation-errors", "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77" }}Saving re-publishes the project's datafile so the change reaches live visitors. If that publish does not finish, the response still returns 200 with your saved integrations, plus a warning object naming datafile_publish_failed. Your settings are saved either way; only the live rollout might lag until the next successful publish. PUT is idempotent: replay the same body with the same Idempotency-Key and nothing changes on the second call.