Hanzo AI

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

CapabilityWhat it doesOperations
/v1/flagsStores definitions and evaluates them for one identity, in-process over your own org and project store8
/v1/experimentRegisters an experiment with its assignment flag, buckets subjects, and reports lift, z and p against the control arm7
/v1/eventTakes the per-subject outcome rows that POST /v1/experiment/{id}/analyze folds into per-variant samples12

Nouns

LaunchDarklyHanzo
ProjectYour org and project, from the validated key
EnvironmentNo such field. A definition is scoped to one org and project, so a second environment is a second project
Feature flagDefinition — /v1/flags/defs/{key}
VariationA variant on the definition, with a weight
Targeting ruleRules on the definition
Percentage rolloutWeights. The subject hash decides, not chance
client.variation(key, user, default)POST /v1/flags/decide
User / contextdistinct_id plus person_properties on the decide call
Audit logGET /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?