Public API: Flag attributes
Flag attributes are the named fields a feature-flag project can target on in its audience rules. An audience is a saved group of targeting conditions. Built-in attributes like browser, country, and deviceType are always there; you can also define your own, such as plan_tier or account_age_days.
All flag-attribute endpoints live under a project and authenticate with a Bearer token: a service token or a personal access token. The org comes from the token, so the path carries only {projectId} (and {attrId} for a single attribute), never {orgId}.
https://app.avsb.cloud/api/v1/projects/{projectId}/flag-attributesThe first time you list attributes for a feature-flag project, the built-in attributes are seeded automatically and returned.
Scopes
A scope is a named permission on your token. It decides exactly what that token is allowed to read or change.
| Operation | Scope |
|---|---|
| List / get attributes | flags:read |
| Create / update / delete | flags:write |
Every /api/v1 token is also rate-limited: a scoped token gets 600 reads and 120 writes per minute, and an admin:* token gets 600 requests per minute, reads and writes together. See Conventions for the response headers that show your remaining budget.
List flag attributes
GET /api/v1/projects/{projectId}/flag-attributes: built-in attributes first, then custom ones (oldest-created first), cursor-paginated (?limit=, up to 100, default 20; ?cursor=, the opaque value from page.nextCursor).
curl "https://app.avsb.cloud/api/v1/projects/<projectId>/flag-attributes?limit=20" \ -H "Authorization: Bearer avsb_svc_..."const projectId = 'cm1a2b3c4d5e6f7g8h9i0j1k2'const url = `https://app.avsb.cloud/api/v1/projects/${projectId}/flag-attributes?limit=20`const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.AVSB_SERVICE_TOKEN}` } })const { data, page } = await res.json()import os, requestsproject_id = "cm1a2b3c4d5e6f7g8h9i0j1k2"res = requests.get( f"https://app.avsb.cloud/api/v1/projects/{project_id}/flag-attributes", params={"limit": 20}, headers={"Authorization": f"Bearer {os.environ['AVSB_SERVICE_TOKEN']}"},)data, page = res.json()["data"], res.json()["page"]{ "data": [ { "id": "<attrId>", "projectId": "<projectId>", "key": "browser", "name": "Browser", "type": "STRING", "isBuiltIn": true, "description": "Browser name (e.g. Chrome, Firefox, Safari)", "suggestedValues": null, "createdAt": "2026-06-18T00:00:00.000Z", "updatedAt": "2026-06-18T00:00:00.000Z" } ], "page": { "nextCursor": null, "hasMore": false }}Get a flag attribute
GET /api/v1/projects/{projectId}/flag-attributes/{attrId}: one attribute by id.
curl https://app.avsb.cloud/api/v1/projects/<projectId>/flag-attributes/<attrId> \ -H "Authorization: Bearer avsb_svc_..."const projectId = 'cm1a2b3c4d5e6f7g8h9i0j1k2'const attrId = 'clx1a2b3c4d5e6f7g8h9i0j1'const url = `https://app.avsb.cloud/api/v1/projects/${projectId}/flag-attributes/${attrId}`const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.AVSB_SERVICE_TOKEN}` } })const { data } = await res.json()import os, requestsproject_id = "cm1a2b3c4d5e6f7g8h9i0j1k2"attr_id = "clx1a2b3c4d5e6f7g8h9i0j1"res = requests.get( f"https://app.avsb.cloud/api/v1/projects/{project_id}/flag-attributes/{attr_id}", headers={"Authorization": f"Bearer {os.environ['AVSB_SERVICE_TOKEN']}"},)data = res.json()["data"]{ "data": { "id": "<attrId>", "projectId": "<projectId>", "key": "plan_tier", "name": "Plan Tier", "type": "STRING", "isBuiltIn": false, "description": "The customer plan tier", "suggestedValues": ["free", "pro", "enterprise"], "createdAt": "2026-06-18T00:00:00.000Z", "updatedAt": "2026-06-18T00:00:00.000Z" }}An id that does not exist, or belongs to another org's project, returns 404 worded the same either way:
{ "error": { "code": "not_found", "message": "Attribute not found", "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#not-found-errors", "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77" }}This is the same error every other endpoint on this page returns for an id it cannot find, so the sections below do not repeat it.
Create a flag attribute
POST /api/v1/projects/{projectId}/flag-attributes: requires flags:write. The key must be a valid identifier (letters, digits, and underscores, not starting with a digit) and unique within the project. type is one of STRING, NUMBER, BOOLEAN, or JSON (defaults to STRING).
curl -X POST https://app.avsb.cloud/api/v1/projects/<projectId>/flag-attributes \ -H "Authorization: Bearer avsb_svc_..." \ -H "Content-Type: application/json" \ -d '{ "key": "plan_tier", "name": "Plan Tier", "type": "STRING", "description": "The customer plan tier", "suggestedValues": ["free", "pro", "enterprise"] }'const projectId = 'cm1a2b3c4d5e6f7g8h9i0j1k2'const res = await fetch(`https://app.avsb.cloud/api/v1/projects/${projectId}/flag-attributes`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.AVSB_SERVICE_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ key: 'plan_tier', name: 'Plan Tier', type: 'STRING', description: 'The customer plan tier', suggestedValues: ['free', 'pro', 'enterprise'], }),})const { data } = await res.json()import os, requestsproject_id = "cm1a2b3c4d5e6f7g8h9i0j1k2"res = requests.post( f"https://app.avsb.cloud/api/v1/projects/{project_id}/flag-attributes", headers={"Authorization": f"Bearer {os.environ['AVSB_SERVICE_TOKEN']}"}, json={ "key": "plan_tier", "name": "Plan Tier", "type": "STRING", "description": "The customer plan tier", "suggestedValues": ["free", "pro", "enterprise"], },)data = res.json()["data"]Returns 201 with the created attribute in the same single-object shape as Get a flag attribute.
A duplicate key returns 409 rather than overwriting the existing attribute:
{ "error": { "code": "validation_failed", "message": "An attribute with this key already exists in this project", "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#validation-errors", "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77" }}Update a flag attribute
PATCH /api/v1/projects/{projectId}/flag-attributes/{attrId}: requires flags:write. Send only the fields you want to change. Built-in attributes cannot have their name or type changed.
curl -X PATCH https://app.avsb.cloud/api/v1/projects/<projectId>/flag-attributes/<attrId> \ -H "Authorization: Bearer avsb_svc_..." \ -H "Content-Type: application/json" \ -d '{ "description": "Updated description", "suggestedValues": ["free", "pro"] }'const projectId = 'cm1a2b3c4d5e6f7g8h9i0j1k2'const attrId = 'clx1a2b3c4d5e6f7g8h9i0j1'const url = `https://app.avsb.cloud/api/v1/projects/${projectId}/flag-attributes/${attrId}`const res = await fetch(url, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.AVSB_SERVICE_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ description: 'Updated description', suggestedValues: ['free', 'pro'] }),})const { data } = await res.json()import os, requestsproject_id = "cm1a2b3c4d5e6f7g8h9i0j1k2"attr_id = "clx1a2b3c4d5e6f7g8h9i0j1"res = requests.patch( f"https://app.avsb.cloud/api/v1/projects/{project_id}/flag-attributes/{attr_id}", headers={"Authorization": f"Bearer {os.environ['AVSB_SERVICE_TOKEN']}"}, json={"description": "Updated description", "suggestedValues": ["free", "pro"]},)data = res.json()["data"]Returns the updated attribute in the single-object shape.
Trying to rename a built-in attribute, or change its type, is refused:
{ "error": { "code": "validation_failed", "message": "Built-in attributes cannot have their key, name, or type changed", "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#validation-errors", "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77" }}Delete a flag attribute
DELETE /api/v1/projects/{projectId}/flag-attributes/{attrId}: requires flags:write.
curl -X DELETE https://app.avsb.cloud/api/v1/projects/<projectId>/flag-attributes/<attrId> \ -H "Authorization: Bearer avsb_svc_..."const projectId = 'cm1a2b3c4d5e6f7g8h9i0j1k2'const attrId = 'clx1a2b3c4d5e6f7g8h9i0j1'const url = `https://app.avsb.cloud/api/v1/projects/${projectId}/flag-attributes/${attrId}`const res = await fetch(url, { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.AVSB_SERVICE_TOKEN}` } })const { data } = await res.json()import os, requestsproject_id = "cm1a2b3c4d5e6f7g8h9i0j1k2"attr_id = "clx1a2b3c4d5e6f7g8h9i0j1"res = requests.delete( f"https://app.avsb.cloud/api/v1/projects/{project_id}/flag-attributes/{attr_id}", headers={"Authorization": f"Bearer {os.environ['AVSB_SERVICE_TOKEN']}"},)data = res.json()["data"]{ "data": { "id": "<attrId>" } }Built-in attributes cannot be deleted:
{ "error": { "code": "forbidden", "message": "Built-in attributes cannot be deleted", "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#refusals-at-403", "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77" }}Nor can one still referenced by an audience, which also returns 409; the message says how many:
{ "error": { "code": "validation_failed", "message": "This attribute is used in 2 audience(s) and cannot be deleted", "docUrl": "https://docs.avsb.cloud/docs/developer-reference/public-api/conventions#validation-errors", "requestId": "req_9f2c41ab7e0b4d1e8c35a6f0d2b91e77" }}