Integration guide
Your first Flagr call is one HTTP request: flag + entity → variant. Everything else on this page is the same idea at scale: many flags, many entities, or an impression after render.
Base URL: https://<flagr-host>/api/v1 (prepend FLAGR_WEB_PREFIX if you set one).
Invariants live in Behavioral contracts (eval vs exposure, segment evaluation, blank vs stream, recording gates, cache lag, eval-only). Concepts and bucketing: Overview. Deploy: Self-hosting. REST: API reference.
Eval vs exposure
POST /evaluation assigns a variant. POST /exposures records that the user saw it. UI experiments need both. Pure server-side branching usually needs only eval. Details: Eval vs exposure.
Endpoints you need
| Call | Method | When |
|---|---|---|
| Assign variant | POST /evaluation | Primary - servers, SDKs, rich entityContext |
| Assign variant (browser) | GET /evaluation?json=… | Same JSON as POST in one query param - GET evaluation |
| Assign many | POST /evaluation/batch | Many entities and/or flags (or tag filter) |
| Assign many (browser) | GET /evaluation/batch?json=… | Batch body in json= - same limits as POST |
| Log impression | POST /exposures | After the user sees the treatment |
| Liveness | GET /health | Probes |
Eval-only replicas (json_file / json_http) expose evaluation, health, and GET /api/v1/export/eval_cache/json only (the UI works as a read-only browser fed by that export; writes under /api/v1/flags return 403). See behavioral contracts: eval-only.
Request model
Three ideas on every eval request:
entityID- who you are evaluating (user, device, account). Stability is stickiness: same client-sent ID + unchanged flag → same variant. Omit it and the server injects a random id (non-sticky).entityType(optional) - labels the entity in logs and records souser-42anddevice-42stay distinct.entityContext(optional) - free-form JSON that constraints match. Dotted paths reach nested values (user.tier→{"user":{"tier":"pro"}}); rules: Overview: constraint property access.
With FLAGR_INJECTED_CONTEXT_ENABLED=true, Flagr merges server-side keys (@ts, @ts_hour, configured @http_* from request headers) into that map before constraints run. App code does not need to send those keys. See Built-in context injection.
Resolve the flag with flagID or flagKey. Either is enough.
Single evaluation
curl -sS -X POST 'http://localhost:18000/api/v1/evaluation' \
-H 'content-type: application/json' \
-d '{
"entityID": "user-42",
"entityType": "user",
"entityContext": {
"region": "us-west",
"age": 30,
"tier": "premium"
},
"flagID": 1
}'Nested context (constraints use dotted paths):
{
"entityID": "user-42",
"entityType": "user",
"entityContext": {
"user": { "name": "Alice", "age": 30 }
},
"flagKey": "my-feature"
}Response fields to use
| Field | Use |
|---|---|
variantKey | Branch in app code; empty ⇒ no assignment (EvalCache) |
variantID | Stable id for exposures and analytics |
variantAttachment | JSON config for this variant |
flagSnapshotID | Pass through on POST /exposures for warehouse joins |
evalContext | Echo of entity + match metadata |
enableDebug + evalDebugLog | Segment walk - Debug console |
Branch on variantKey. Empty means missing flag, disabled flag, or no assigned variant: normal outcomes, not transport failures.
Batch evaluation
One page, many flags? Use POST /api/v1/evaluation/batch. One request, one result per entity per selected flag.
By ID:
{
"entities": [
{
"entityID": "a",
"entityType": "user",
"entityContext": { "region": "us-west", "age": 30 }
}
],
"flagIDs": [1, 2]
}By tag (ANY = at least one listed tag; ALL = every listed tag):
{
"entities": [
{
"entityID": "a",
"entityType": "user",
"entityContext": { "region": "us-west" }
}
],
"flagTags": ["int_test"],
"flagTagsOperator": "ANY"
}Work cap: len(entities) * (len(flagIDs) + len(flagKeys) + tags estimate) against FLAGR_EVAL_BATCH_SIZE (0 = unlimited). Duplicate IDs/keys are deduped before the count.
CI gotcha: a flag you just created is not evaluable until EvalCache reloads. Poll with a real eval (this repo's waitForEvalReady) until you see a variant. See EvalCache freshness.
UI experiment loop
For rigid A/B tests, count people who saw the treatment, not assignment alone (behavioral contracts: eval vs exposure).
POST /evaluation- cachevariantKey,variantID,flagSnapshotID.- Render only when
variantKeyis non-empty. Low rollout on a matched segment can leave the key empty and does not fall through to later segments (segment evaluation). POST /exposureswhen the surface is visible (mount, viewport, or unload batch).
curl -sS -X POST 'http://localhost:18000/api/v1/exposures' \
-H 'content-type: application/json' \
-d '{
"exposures": [{
"flagID": 1,
"variantID": 2,
"variantKey": "treatment",
"entityID": "user-42",
"entityType": "user",
"flagSnapshotID": 42,
"entityContext": { "page": "/checkout" }
}]
}'Exposure validates against the cache; it does not re-run constraints. Pass the flagSnapshotID from eval so the warehouse can join impressions to the config that produced them. Full shape: Exposure logging. Downstream: Data recorders & A/B analysis.
Server-side only (no UI)
Timeouts, routing weights, feature paths: call POST /evaluation (or batch), read variantAttachment, branch. Skip POST /exposures unless you need a formal A/B denominator in a warehouse.
Client libraries
| Language | Package |
|---|---|
| Go | goflagr |
| JavaScript | jsflagr |
| Python | pyflagr |
| Ruby | rbflagr |
Where to go next
| Goal | Doc |
|---|---|
| Flag / segment concepts | Overview, Use cases |
| Browser GET eval | Use cases: GET evaluation |
| Time / header targeting | Built-in context injection |
| Run Flagr | Self-hosting |
| Env vars | Environment variables |
| GitOps flags | JSON flag source |
| Wrong variant | Debug console |
| Change Flagr itself | Contributing |
