Public API: Environments

A feature flag is a setting in your code that can be turned on, off, or changed without a new deploy. An environment is where one version of your flags runs, such as production or staging. Each environment has its own SDK key.

A datafile is the small JSON file the snippet or SDK downloads that lists every live flag for one environment. This API lets a service token manage a project's environments. You can list them, read one, create new ones, rename or recolour them, archive them, and publish a fresh datafile.

Where Environments actually lives

Environments is its own link in the project sidebar, next to Settings, not inside it. It only shows up for feature-flag projects. A web-experiments project has no Environments item and no environment rows at all: listing or creating one there is refused with 400. The single-environment endpoints below (get, update, delete, publish) return 404 instead, because there is nothing to find.

Every environment endpoint lives under a project and authenticates with a service token. Your token already names its organization, so the path carries only {projectId} (and {envId} for a single environment), never {orgId}. A project that is not in your token's org returns 404, so a wrong id and a project you cannot see look the same.

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

Scopes

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

OperationScope
List / get environmentsprojects:read
Create / update / delete / publishprojects:write

List environments

GET /api/v1/projects/{projectId}/environments: every active environment (archived ones are hidden), in sidebar order. Cursor-paginated (?limit= up to 100, default 20; ?cursor=, the opaque value from page.nextCursor). Requires projects:read.

curl "https://app.avsb.cloud/api/v1/projects/<projectId>/environments?limit=20" \  -H "Authorization: Bearer avsb_svc_..."
Shell2 lines
Response
{  "data": [    {      "id": "<envId>",      "projectId": "<projectId>",      "name": "Production",      "key": "production",      "sdkKey": "sdk_production_cm3x9k2l40001qrs7t8u9v0w",      "color": "#6b7280",      "sortOrder": 1,      "archivedAt": null,      "lastSdkSeenAt": null,      "lastSdkType": null,      "lastSdkVersion": null,      "createdAt": "2026-06-18T00:00:00.000Z",      "updatedAt": "2026-06-18T00:00:00.000Z"    }  ],  "page": { "nextCursor": null, "hasMore": false }}
JSON20 lines

Pass ?cursor=<page.nextCursor> to fetch the next page. An out-of-range limit, or a cursor this API never produced, is refused rather than clamped. See Conventions for that error shape.

Get an environment

GET /api/v1/projects/{projectId}/environments/{envId}: one environment by id, in the flat { data: <environment> } envelope shown under List environments above. Requires projects:read.

curl https://app.avsb.cloud/api/v1/projects/<projectId>/environments/<envId> \  -H "Authorization: Bearer avsb_svc_..."
Shell2 lines
404: environment not found
{  "error": {    "code": "not_found",    "message": "Environment not found",    "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#not-found-errors",    "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77"  }}
JSON8 lines

An id from another org, or one that never existed, gets this same answer. Existence is never leaked across an org boundary.

Create an environment

POST /api/v1/projects/{projectId}/environments: name and key are the only required fields. Requires projects:write. key must start with a lowercase letter and hold only lowercase letters, digits, and underscores, up to 50 characters; it is fixed forever once set. Responds 201 with the created environment, including a freshly generated sdkKey.

curl -X POST https://app.avsb.cloud/api/v1/projects/<projectId>/environments \  -H "Authorization: Bearer avsb_svc_..." \  -H "Content-Type: application/json" \  -d '{ "name": "Staging", "key": "staging" }'
Shell4 lines
Response
{  "data": {    "id": "<envId>",    "projectId": "<projectId>",    "name": "Staging",    "key": "staging",    "sdkKey": "sdk_staging_cm4y0m3n50002wxyz5678ijkl",    "color": "#6b7280",    "sortOrder": 2,    "archivedAt": null,    "lastSdkSeenAt": null,    "lastSdkType": null,    "lastSdkVersion": null,    "createdAt": "2026-06-18T00:00:00.000Z",    "updatedAt": "2026-06-18T00:00:00.000Z"  }}
JSON17 lines

The new environment goes after every environment the project already has. Its datafile is published before the call returns, so an SDK given the new sdkKey can connect and report in straight away. Every flag in the project starts off in the new environment until you run it there.

color is optional and defaults to #6b7280. Send it to pick your own, alongside any other field you need; the shape is otherwise identical to the request above:

cURL: setting a custom color
curl -X POST https://app.avsb.cloud/api/v1/projects/<projectId>/environments \  -H "Authorization: Bearer avsb_svc_..." \  -H "Content-Type: application/json" \  -d '{ "name": "Staging EU", "key": "staging_eu", "color": "#3b82f6" }'
Shell4 lines

A duplicate key within the project is refused:

409: key already in use
{  "error": {    "code": "validation_failed",    "message": "An environment with this key already exists in this project",    "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#validation-errors",    "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77"  }}
JSON8 lines

Requires projects:write, and only works on a feature-flag project: see the callout near the top of this page for what happens otherwise.

Update an environment

PATCH /api/v1/projects/{projectId}/environments/{envId}: only name and color are mutable; key is fixed at creation. Send only the fields you are changing. Requires projects:write. Mutations support X-Avsb-If-Match (or If-Match) for optimistic concurrency (see Conventions).

curl -X PATCH https://app.avsb.cloud/api/v1/projects/<projectId>/environments/<envId> \  -H "Authorization: Bearer avsb_svc_..." \  -H "Content-Type: application/json" \  -d '{ "name": "Staging EU", "color": "#3b82f6" }'
Shell4 lines
Response
{  "data": {    "id": "<envId>",    "projectId": "<projectId>",    "name": "Staging EU",    "key": "staging",    "sdkKey": "sdk_staging_cm4y0m3n50002wxyz5678ijkl",    "color": "#3b82f6",    "sortOrder": 2,    "archivedAt": null,    "lastSdkSeenAt": null,    "lastSdkType": null,    "lastSdkVersion": null,    "createdAt": "2026-06-18T00:00:00.000Z",    "updatedAt": "2026-06-19T08:30:00.000Z"  }}
JSON17 lines

An id from another org, or one that never existed, returns the same 404 shown under Get an environment above.

Delete an environment

DELETE /api/v1/projects/{projectId}/environments/{envId}: archives the environment (a soft delete) and returns just its id. Requires projects:write.

curl -X DELETE https://app.avsb.cloud/api/v1/projects/<projectId>/environments/<envId> \  -H "Authorization: Bearer avsb_svc_..."
Shell2 lines
Response
{ "data": { "id": "<envId>" } }
JSON1 line

A project must keep at least two active environments, so deleting one that would drop the project below two is refused:

422: minimum active environments
{  "error": {    "code": "validation_failed",    "message": "Cannot delete: project must have at least two environments",    "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#validation-errors",    "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77"  }}
JSON8 lines

An environment that is already archived returns 410 with the same validation_failed code. That check comes first, so repeating a delete always gets the 410, however many environments are left.

Production and Development, the two environments every feature-flag project starts with, cannot be deleted, because the SDK keys in your code point at them. Deleting either returns 422 with details.reason set to environment_protected. Only environments you added can be deleted.

422: built-in environment
{  "error": {    "code": "validation_failed",    "message": "Production is one of the two environments every flag project starts with, so it cannot be deleted. Only environments you added can be.",    "details": { "reason": "environment_protected" },    "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#validation-errors",    "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77"  }}
JSON9 lines

Publish an environment

POST /api/v1/projects/{projectId}/environments/{envId}/publish: regenerate that environment's datafile and clear its pending-changes marker. A webhook is an automatic HTTP request A vs B sends to your own server when something happens. Publishing here fires one, flag.published, once the new datafile is live. Requires projects:write. The call changes state rather than creating something, so it returns 200, not 201.

curl -X POST https://app.avsb.cloud/api/v1/projects/<projectId>/environments/<envId>/publish \  -H "Authorization: Bearer avsb_svc_..."
Shell2 lines
Response
{ "data": { "published": true, "environmentId": "<envId>" } }
JSON1 line

An id from another org, or one that never existed, returns the same 404 shown under Get an environment above.

Next steps

Was this helpful?