Feature Flags API

Manage project feature flags, evaluate them for an identity, and fetch definitions for server-side local evaluation.

The Feature Flags API has two credential scopes:

  • /api/* management endpoints use an account credential: a Firebase ID token or the superadmin API key.
  • /flags/* runtime endpoints use a project token for remote evaluation or a project flag secret for local-evaluation definitions.

The project token is suitable for client applications. A flag secret exposes targeting definitions and must remain server-side.

Flag model

FieldDescription
keyImmutable runtime key. Lowercase, starts with a letter or digit, and may contain - or _. Maximum 200 characters.
typeOne of release, experiment, operational, kill_switch, permission. Descriptive only - it never changes evaluation. Defaults to release.
activeWhether the flag can enable for any identity.
archived_atEpoch ms, or null. A non-null value means the flag is archived: it is absent from POST /flags/evaluate (like a deleted flag) and excluded from GET /api/flags and GET /flags/local-evaluation by default.
variantsUp to 10 variants. Non-empty variant rollout percentages must sum to 100.
rulesUp to 10 ordered rules. The first matching rule wins. In the dashboard, each rule is shown as a "Strategy" (Standard, Specific users, Gradual rollout, or Cohort) classified from its shape - the wire shape is unchanged.
rules[].cohortIdCohort in the same project.
rules[].distinctIdsExact override list ("Specific users" in the dashboard), up to 50 IDs, each up to 255 characters. A non-empty list requires rolloutPct: 100.
rules[].rolloutPctPercentage of matching identities that enter the rule.
rules[].variantOptional variant key to serve when the rule matches.

With no variants, a matching rule returns a simple enabled result. With variants, a matching rule returns the selected variant and its payload. With no rules, evaluation is disabled with reason no_match.

Authentication

Send an account credential on /api/*:

bash
curl https://cohorly-service.velloalabs.com/api/flags?projectId=1 \
-H "Authorization: Bearer $API_KEY"

Send a project token on /flags/evaluate:

bash
curl -X POST https://cohorly-service.velloalabs.com/flags/evaluate \
-H "Content-Type: application/json" \
-H "X-Cohorly-Token: YOUR_PROJECT_TOKEN" \
-d '{ "distinct_id": "user-123" }'

The token can also be supplied as the request body's token field. The X-Cohorly-Token header applies to the whole request.

Manage flags

List flags

GET/api/flags

List the flags accessible to the authenticated account.

NameTypeRequiredDefaultDescription
projectIdintegerOptional-

Project to list. For a Firebase user token, omission resolves to that organization's oldest project. For a superadmin key, omission uses the legacy default project when one exists and otherwise returns 400; pass it explicitly for predictable behavior.

archivedstringOptional-

Pass `1` to include archived flags alongside active ones. Omitted (or any other value) excludes archived flags - the default, and what /flags/local-evaluation always uses.

Create a flag

POST/api/flags

Create a flag in a project.

NameTypeRequiredDefaultDescription
projectIdintegerOptional-

Project that owns the flag. For a Firebase user token, omission resolves to that organization's oldest project. For a superadmin key, omission uses the legacy default project when one exists and otherwise returns 400; pass it explicitly for predictable behavior.

keystringRequired-

Lowercase runtime key matching ^[a-z0-9][a-z0-9_-]{0,199}$.

namestringRequired-

Human-readable name.

descriptionstring | nullOptional-

Optional description.

activebooleanOptionaltrue

Whether evaluations can enable the flag.

typestringOptionalrelease

One of release, experiment, operational, kill_switch, permission. Descriptive only.

variantsFlagVariant[]Optional-

Optional list of up to 10 variants. Percentages must total 100 when non-empty.

rulesFlagRule[]Optional-

Optional ordered list of up to 10 rules. An omitted list is empty and matches nobody.

bash
curl -X POST https://cohorly-service.velloalabs.com/api/flags \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": 1,
"key": "checkout-redesign",
"name": "Checkout redesign",
"description": "Gradual rollout of the new checkout",
"active": true,
"variants": [
{ "key": "control", "rolloutPct": 50 },
{ "key": "new", "rolloutPct": 50, "payload": { "layout": "compact" } }
],
"rules": [
{ "distinctIds": ["user-123"], "rolloutPct": 100, "variant": "new" },
{ "cohortId": 42, "rolloutPct": 25 }
]
}'

The response is the complete created flag. The key is unique within the project and cannot be changed later.

Read, update, and delete a flag

GET/api/flags/{id}

Get one flag by numeric id.

NameTypeRequiredDefaultDescription
idintegerRequired-

Flag id from the list or create response.

PUT/api/flags/{id}

Partially update a flag. The key is immutable.

The request can include name, description, active, type, variants, and rules. If variants change, existing rules are checked against the new variant keys. Send rules: [] to make an active flag explicitly disabled by configuration. Every PUT writes a flag_events row (see Activity below); when active flips, the recorded action is activated/deactivated instead of updated.

bash
curl -X PUT https://cohorly-service.velloalabs.com/api/flags/4 \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"active": true,
"rules": [{ "rolloutPct": 50, "variant": "new" }]
}'
DELETE/api/flags/{id}

Delete a flag.

Deletion removes the flag definition. Remove or retire the corresponding application code separately.

Cross-organization flag ids return 404, so the API does not disclose the existence of another organization's flags.

bash
curl "https://cohorly-service.velloalabs.com/api/flags?projectId=1" \
-H "Authorization: Bearer $API_KEY"
json
[
{
"id": 3,
"project_id": 1,
"created_by": 7,
"key": "new-checkout",
"name": "New checkout",
"description": null,
"active": true,
"variants": [],
"rules": [{ "rolloutPct": 100 }],
"created_at": 1753900800000,
"updated_at": 1753900800000
}
]
json
{
"id": 4,
"project_id": 1,
"created_by": 7,
"key": "checkout-redesign",
"name": "Checkout redesign",
"description": "Gradual rollout of the new checkout",
"active": true,
"variants": [
{ "key": "control", "rolloutPct": 50 },
{ "key": "new", "rolloutPct": 50, "payload": { "layout": "compact" } }
],
"rules": [
{ "distinctIds": ["user-123"], "rolloutPct": 100, "variant": "new" },
{ "cohortId": 42, "rolloutPct": 25 }
],
"created_at": 1753900800000,
"updated_at": 1753900800000
}
json
{ "ok": true }

Archive, revive, and delete

Archive is the reversible way to turn a flag off for good, for now; delete is the one-way action and is only offered once a flag is archived, so the API never exposes two destructive paths on a live flag.

POST/api/flags/{id}/archive

Archive a flag.

POST/api/flags/{id}/revive

Revive an archived flag.

Both are idempotent: archiving an already-archived flag (or reviving an already-active one) returns the flag unchanged and writes no extra event. DELETE /api/flags/{id} returns 409 { "error": "not archived" } unless the flag is already archived.

bash
curl -X POST https://cohorly-service.velloalabs.com/api/flags/4/archive \
-H "Authorization: Bearer $API_KEY"
json
{ "id": 4, "active": true, "archived_at": 1753900800000, "...": "..." }

Preview (Playground)

POST/api/flags/{id}/preview

Evaluate one flag for a distinct_id, using an account credential.

The dashboard's Playground tab calls this so a person testing a flag does not need a project token. It runs the exact same evaluator as POST /flags/evaluate, scoped to this one flag.

NameTypeRequiredDefaultDescription
distinctIdstringRequired-

Identity to evaluate.

bash
curl -X POST https://cohorly-service.velloalabs.com/api/flags/4/preview \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "distinctId": "qa-user-1" }'
json
{ "enabled": true, "variant": null, "payload": null, "reason": "rule:0" }

Activity

GET/api/flags/{id}/events

List a flag's audit trail, newest first.

NameTypeRequiredDefaultDescription
limitintegerOptional50

Maximum rows to return, up to 200.

bash
curl "https://cohorly-service.velloalabs.com/api/flags/4/events?limit=20" \
-H "Authorization: Bearer $API_KEY"

action is one of created, updated, activated, deactivated, archived, revived. activated/deactivated fire instead of updated when a PUT flips active. actor_user_id is null when the superadmin key or an MCP token made the change; actor_mcp_token_id names the MCP token in the second case. Deleting a flag deletes its event history with it (ON DELETE CASCADE).

json
[
{
"id": 91,
"project_id": 1,
"flag_id": 4,
"actor_user_id": 7,
"actor_mcp_token_id": null,
"action": "activated",
"before": { "active": false, "...": "..." },
"after": { "active": true, "...": "..." },
"created_at": 1753900800000
}
]

Remote evaluation

POST/flags/evaluate

Evaluate all or selected flags for one distinct_id.

NameTypeRequiredDefaultDescription
distinct_idstringRequired-

Identity for targeting and deterministic rollout bucketing.

flag_keysstring[]Optional-

Optional filter. When omitted, returns every flag in the project.

tokenstringOptional-

Project token alternative to the X-Cohorly-Token header.

bash
curl -X POST https://cohorly-service.velloalabs.com/flags/evaluate \
-H "Content-Type: application/json" \
-H "X-Cohorly-Token: YOUR_PROJECT_TOKEN" \
-d '{
"distinct_id": "user-123",
"flag_keys": ["checkout-redesign"]
}'

Remote evaluation uses the identity cluster's canonical id for cohort checks and rollout bucketing. An anonymous id linked to a user id therefore evaluates as the same identity. Exact overrides also match the raw submitted id.

json
{
"flags": {
"checkout-redesign": {
"enabled": true,
"variant": "new",
"payload": { "layout": "compact" },
"reason": "rule:0"
}
}
}

Local-evaluation definitions

GET/flags/local-evaluation

Fetch definitions that server SDKs can evaluate in-process.

Authenticate with the project flag secret, not the project token:

bash
curl https://cohorly-service.velloalabs.com/flags/local-evaluation \
-H "Authorization: Bearer $COHORLY_FLAG_SECRET"

The response contains only the fields required for local evaluation:

json
{
"flags": [
{
"key": "checkout-redesign",
"name": "Checkout redesign",
"active": true,
"variants": [
{ "key": "control", "rolloutPct": 50 },
{ "key": "new", "rolloutPct": 50, "payload": { "layout": "compact" } }
],
"rules": [{ "rolloutPct": 100, "variant": "new" }],
"localEvaluable": true
}
]
}

localEvaluable is false when a rule references a cohort. Cohort membership depends on server-side event and profile data, so SDKs must use remote evaluation for that flag. Local evaluation hashes the raw distinct_id, while remote evaluation hashes the canonical identity id. Pass the identified user id to local server evaluation when identity linking matters.

json
{ "flags": [] }

Flag secrets

The project flag secret is generated through the account API. Only the organization owner or superadmin can mint, rotate, or revoke it.

POST/api/projects/{id}/flag-secret

Mint or rotate a project's flag secret.

bash
curl -X POST https://cohorly-service.velloalabs.com/api/projects/1/flag-secret \
-H "Authorization: Bearer $API_KEY"

The response contains { "secret": "..." } once. A rotation invalidates the old value immediately. GET /api/projects reports only has_flag_secret, not the secret itself.

DELETE/api/projects/{id}/flag-secret

Revoke a project's flag secret without creating a replacement.

bash
curl -X DELETE https://cohorly-service.velloalabs.com/api/projects/1/flag-secret \
-H "Authorization: Bearer $API_KEY"

The response is { "ok": true }. Revocation is idempotent. New local definition requests return 401; running server SDKs retain their last successful definitions by design. Restart them or remove the flag secret from their configuration to force remote evaluation immediately.

Limits and errors

LimitValue
Flags per project100
Variants per flag10
Rules per flag10
Override IDs per rule50
Override ID length255 characters
Request body1 MB
Shared per-IP rate limit100 requests per 10 seconds
Flags endpoint rate limit1,000 requests per 10 seconds per project, plus the shared per-IP limit

Rate-limited responses are 429 and include a Retry-After header. SDKs should honor that header and retry with backoff. Invalid JSON, missing fields, invalid rollout totals, unknown variants, and foreign cohorts return 400. Creating a flag with a duplicate key returns 409 with key taken; creating one after the 100-flag project limit returns 409 with flag limit reached. Requests larger than 1 MB return 413 with { "status": 0, "error": "payload too large" }.

PreviousRate limits
NextAdmin API