Feature flags
Server-evaluated boolean and multivariate flags with deterministic bucketing, ordered targeting groups, JSON payloads, and one bootstrap call for flags, surveys, and recording config.
Feature flags gate code paths per user — ship dark, roll out gradually, target by person properties, and attach JSON payloads. Evaluation happens server-side: targeting rules and other people’s person properties never reach the client. The browser receives only the resulting values.
Create a flag
A flag has a key, a type — boolean or multivariate (named variants with percentage weights) — and optional targeting groups. Flags are created and managed from the Feature flags page in the app.

An inactive flag evaluates to false for everyone. That is the entire off switch — no deploy, no cache to chase.
Target and roll out
Bucketing is deterministic: a hash of the flag key and distinct_id maps each person to a fixed position in [0, 1). The same person always lands in the same bucket, across processes and deploys, and raising a rollout percentage only ever adds users — nobody who has the feature loses it because you went from 20% to 40%.
Targeting rules are ordered groups, each with property conditions and a rollout percentage. Groups evaluate in order and the first group whose conditions pass (and whose rollout hash passes) wins. A group-level variant override beats the multivariate distribution — “everyone on the enterprise plan gets test” is one group at the top. A flag with no groups ships to everyone (at 100%).
Condition evaluation is fail-closed: a missing person property fails every operator except the explicit absence checks, and an invalid regex never matches.
Use in code
The SDKs fetch flags through one bootstrap call and read them locally. In the browser, flags load automatically on init:
import { init, azfive } from '@azfive/capture';
init('azfive_pub_…', { host: 'https://analytics.example.com' });
azfive.onFlags(() => {
if (azfive.isFlagEnabled('new-onboarding')) {
// show the new flow
}
const variant = azfive.getFlag('exp-checkout'); // 'control' | 'test' | false
});
On a server, use a secret events:write key and pass the distinct_id per call:
from azfive_capture import Client
azfive = Client("azfive_…", host="https://analytics.example.com")
variant = azfive.get_flag("user-42", "exp-checkout") # 'control' | 'test' | False | None
if azfive.is_flag_enabled("user-42", "new-onboarding"):
...
The method names follow each language’s convention: getFlag / isFlagEnabled in TypeScript, Kotlin, and Swift; get_flag / is_flag_enabled in Python; GetFlag / IsFlagEnabled in Go.
Under the hood every SDK calls POST /api/v1/decide, which returns flags, flag payloads, active surveys, and session-recording config for one visitor in a single round trip. Browsers authenticate with a public azfive_pub_… token (origin-validated); server SDKs use their secret events:write key, which needs no Origin. The request is {distinct_id, project?, person_properties?} — client-supplied person_properties are merged over the stored person properties, useful for anonymous visitors or properties that haven’t reconciled yet.
Payloads
Any flag value — true or a variant key — can carry a JSON payload: copy, prices, model names, whatever configuration the variant needs. getFlagPayload (get_flag_payload, GetFlagPayload) returns it. Payload reads deliberately fire no exposure event — read the flag first, then its payload.
Exposure and experiments
Reading a flag with getFlag or isFlagEnabled emits an az.flag_called exposure event, deduped so hot code paths don’t flood ingest. getFlagPayload never emits one. Exposure events are what tie flags to experiments: a person enters an experiment at their first exposure to one of its variants, so exposure only fires where a decision was actually used.
Reference
Decide
| Endpoint | POST /api/v1/decide |
| Auth | Public azfive_pub_… token (browser, origin-validated) or secret events:write key (server) |
| Request | {distinct_id, project?, person_properties?} — overrides merge over stored properties |
| Response | {flags, flagPayloads, surveys, sessionRecording} |
| Metering | Every call is metered; over quota → 429 with {"code": "quota_exceeded"} |
Evaluation
| Rule | Behavior |
|---|---|
| Bucketing | Hash of flag key + distinct_id → fixed position in [0, 1); monotonic under rollout increases |
| Groups | Evaluated in order; first passing group wins |
| Variant override | Group-level variant beats the multivariate distribution |
| No groups | Ships to everyone |
| Inactive flag | false for everyone |
Caches
| Cache | Duration |
|---|---|
| Server flag definitions | 30 s, invalidated on every flag write |
| SDK decide response | 60 s per distinct_id |
POST /api/v1/decide is the only programmatic surface; creating, targeting, and rolling out flags happens in the app, where deleting a flag is blocked while an experiment references it.