LaunchDarkly
LaunchDarkly decides which users see which behaviour. Here that is /v1/flags — eight operations, one deterministic decision, and no SDK that has to be kept warm.
LaunchDarkly holds flag definitions and evaluates them per user. /v1/flags
(8 operations) does the same: PUT /v1/flags/defs/{key} stores a definition,
POST /v1/flags/decide answers for a subject.
The difference is where evaluation happens. LaunchDarkly's SDKs stream rules into your process and decide locally. Here the decision is a call, and it is deterministic: the same subject and key always land in the same bucket, because the bucket is a hash of them rather than a roll.
Start here
Three calls: mint a key, store one definition, ask what it says for a subject.
# 1. mint a key — sk- belongs on a server, pk- is safe in a browser
curl -sS -X POST https://api.hanzo.ai/v1/account/keys \
-H "Authorization: Bearer $HANZO_SESSION" \
-H 'Content-Type: application/json' \
-d '{"type":"secret"}'
# 2. store one definition — the key in the URL wins over any "key" in the body
curl -sS -X PUT https://api.hanzo.ai/v1/flags/defs/new-editor \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"active": true, "filters": {"groups": [{"rollout_percentage": 25}]}}'
# 3. ask what it says for one identity
curl -sS -X POST https://api.hanzo.ai/v1/flags/decide \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"distinct_id": "u1", "person_properties": {"plan": "pro"}}'Step 3 answers {"featureFlags": {"new-editor": true}, "featureFlagPayloads": {}, "errorsWhileComputingFlags": false} — every flag for that identity in one
call, not one flag per call. Run it twice and u1 gets the same answer, which
is the property a streaming SDK was buying you.
Core capabilities
| Capability | What it does | Operations |
|---|---|---|
/v1/flags | Stores definitions and evaluates them for one identity, in-process over your own org and project store | 8 |
/v1/experiment | Registers an experiment with its assignment flag, buckets subjects, and reports lift, z and p against the control arm | 7 |
/v1/event | Takes the per-subject outcome rows that POST /v1/experiment/{id}/analyze folds into per-variant samples | 12 |
Nouns
| LaunchDarkly | Hanzo |
|---|---|
| Project | Your org and project, from the validated key |
| Environment | No such field. A definition is scoped to one org and project, so a second environment is a second project |
| Feature flag | Definition — /v1/flags/defs/{key} |
| Variation | A variant on the definition, with a weight |
| Targeting rule | Rules on the definition |
| Percentage rollout | Weights. The subject hash decides, not chance |
client.variation(key, user, default) | POST /v1/flags/decide |
| User / context | distinct_id plus person_properties on the decide call |
| Audit log | GET /v1/flags/activity |
| Experiment | /v1/experiment, which composes flags for assignment |
The call
LaunchDarkly, in-process:
const show = await client.variation('new-checkout', { key: 'user-42' }, false)Hanzo — one call, every flag for that identity, and you read out the one you came for:
curl -sS -X POST https://api.hanzo.ai/v1/flags/decide \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"distinct_id": "user-42",
"person_properties": {"plan": "pro"}
}'Storing the definition:
curl -sS -X PUT https://api.hanzo.ai/v1/flags/defs/new-checkout \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"active": true,
"filters": {"groups": [{"rollout_percentage": 10}]}
}'The body is the definition document itself, kept byte-for-byte, so edit one by
reading it back with GET /v1/flags/defs/{key} and writing the whole thing —
not by rebuilding it from the parts you recognise.
Because the assignment is a hash of (key, subject), a subject keeps its variant
across processes, restarts and languages without any shared state — and an
experiment reading the same flag sees the same split.
What does not carry
No streaming SDK, and no local evaluation. LaunchDarkly's whole latency story
is that your process already holds the rules. decide is a network call. Cache
it yourself if the hot path cannot afford one; the decision is deterministic, so
a cache cannot disagree with the server about a subject it has already seen.
No flag prerequisites. LaunchDarkly lets one flag gate another. Definitions here are flat. Compose them in your own code, where the reader can see it.
No approval queue inside the flag service. A definition is written by whoever
holds the key; change control is your deploy pipeline and GET /v1/flags/activity.
A change that should happen later is a job — /v1/tasks is a durable engine and
outlives any one deploy — rather than a scheduled-change feature bolted onto the
flag.
No /sdk/goals or client-side experimentation bundle. Measurement is
/v1/experiment, which reads outcomes from analytics rather than from a
browser SDK.
Removing a flag is a delete. DELETE /v1/flags/defs/{key} removes the
definition. There is no archive state that keeps serving a last-known value, so
retire the call site first and the definition second.
How is this guide?
Grafana
Grafana renders dashboards and alerts over Mimir, Loki and Tempo. Here that is /v1/o11y — 381 operations over one store, so a panel that crosses metrics, logs and traces is one query instead of three datasources.
OpenAI
OpenAI serves models and the platform grown around them. Here that is /v1/ai (272 operations) for the models, their files and their routing, and /v1/agents (37) for the assistant that runs, with the OpenAI-shaped inference routes at the addresses your client already calls.