HTTP API
What the SDK actually calls. Useful if you need a client Klor does not ship.
GET /v1/config
Returns the published snapshot for the environment the key belongs to.
curl https://edge.klor.dev/v1/config \
-H "Authorization: Bearer klor_pub_…" \
-H "If-None-Match: \"a1b2c3…\""A public key gets the payload with sensitive flags removed; a private key gets everything. Responses carry a strong ETag, send it back as If-None-Match and an unchanged snapshot answers 304 with no body.
{
"v": 1,
"seq": 42,
"environment": { "id": "env_…", "key": "prod" },
"publishedAt": "2026-09-14T09:12:04.000Z",
"flags": {
"checkout_v2": {
"type": "bool",
"enabled": true,
"default": false,
"rules": [
{
"id": "r_ios",
"conditions": [{ "attribute": "platform", "op": "eq", "values": ["ios"] }],
"rollout": { "percentage": 25, "bucketBy": "userId", "salt": "…" },
"value": true
}
]
}
},
"gates": { "ios": { "minSupportedVersion": "2.0.0", "latestVersion": "2.5.0" } },
"blockedBuilds": { "ios": ["2.3.1"] }
}Evaluation is yours to do. The payload is the ruleset, not an answer; that is what keeps user context on the device.
POST /v1/events
Optional usage counters. Klor uses them to tell you which flags are still being read, so dead config can be found and deleted.
curl -X POST https://edge.klor.dev/v1/events \
-H "Authorization: Bearer klor_pub_…" \
-H "Content-Type: application/json" \
-d '{"events":[{"flagKey":"checkout_v2","variant":"true","reason":"rule","count":12}]}'Counters only. No user or device identifier is accepted or stored, and sending nothing at all is a supported way to run.
The management API
Everything above is the read plane, served from edge.klor.dev and reachable with a public or private key. Changing configuration is a different surface, at klor.dev/api/v1, and needs a klor_adm_ management key created on your project’s API keys page.
A management key is scoped to one environment, is never published to the read plane, and is refused outright if the request carries an Origin header. It is for servers and CI, not for browsers.
curl https://klor.dev/api/v1/flags \
-H "Authorization: Bearer klor_adm_…"GET /api/v1/meidentityWhich project and environment this token points at. Worth calling first; most confusion is a token aimed at staging.
GET /api/v1/flagslistEvery flag in this environment with its configuration.
GET /api/v1/flags/:keyflagOne flag, addressed by key rather than by id.
PATCH /api/v1/flags/:keyflagChange enabled, defaultValue or rules. Values are checked against the flag’s declared type.
GET /api/v1/changesdiffWhat publishing would change. Empty means the environment is up to date.
POST /api/v1/publishsnapshotCompiles and publishes. Takes an optional note, returns the new seq.
POST /api/v1/rollbacksnapshotRepublishes an earlier seq under a new one. History moves forward, never backwards.
GET /api/v1/snapshotpayloadThe live payload. Add ?visibility=public for the one clients receive; bundle it in a build so a first cold start serves real values.
GET /api/v1/snapshotslistThe last fifty publishes, newest first.
curl -X PATCH https://klor.dev/api/v1/flags/checkout_v2 \
-H "Authorization: Bearer klor_adm_…" \
-H "Content-Type: application/json" \
-d '{"enabled":true,"defaultValue":false}'Editing does not change what your apps read. Publish is still a separate, deliberate act, which is what makes this safe to automate.
The most useful thing to automate first is the check, not the change: a job that fails when an environment has unpublished edits catches the one failure mode of a publish model, which is editing all day and never publishing.
# Fail the build if someone left edits unpublished.
changes=$(curl -sf https://klor.dev/api/v1/changes \
-H "Authorization: Bearer $KLOR_TOKEN" | jq '.changes | length')
if [ "$changes" -gt 0 ]; then
echo "prod has $changes unpublished change(s)" >&2
exit 1
fiErrors carry a machine-readable code alongside the message: missing_token, invalid_token, wrong_key_type, browser_request, not_found, invalid_value, snapshot_gone.
Responses
304Not ModifiedYour ETag matches the current snapshot. Keep using what you have.
401UnauthorizedMissing or unrecognised API key. Revoked keys stop working immediately.
404Not FoundThe environment exists but has never been published.