featrs

Integration Guide

How to integrate your application with the Featrs API. Each section covers the recommended integration pattern, documents behaviour you should design around, and links to the corresponding section of the API reference for endpoint-level detail.

Errors

All errors share one shape:

{ "traceId": "0198f2b1-…", "message": "Failed to create flag" }

traceId is unique per request — include it when reporting a problem so the team can find the exact server-side log line. Messages are intentionally generic; the HTTP status carries the semantics:

StatusMeaning
400Invalid input — malformed key syntax, out-of-range percentage, invalid state transition
401Missing, invalid, or expired credentials
403Wrong principal type or no organisation on the token
404Resource not found
409Conflict — e.g. creating a flag whose key already exists
502Upstream provider failure
500Unexpected server error

Notes

Authentication

Machine-to-machine authentication is a two-step process: create an API key once in the dashboard, then exchange it for a short-lived JWT at runtime.

# Exchange key for a token (1 hour TTL)
curl -X POST https://api.featrs.com/auth/token \
  -H "Content-Type: application/json" \
  -d '{"api_key_id": "<uuid>", "secret": "<secret>"}'
# → { "access_token": "eyJ…", "exp": 1711929600 }

# Use it
curl https://api.featrs.com/flags -H "Authorization: Bearer eyJ…"

exp is a Unix timestamp in seconds. There is no refresh endpoint — when the token nears expiry, perform the exchange again. Cache the token for its lifetime rather than exchanging per request; the secret check uses Argon2 and is deliberately expensive.

Notes

API keys

MethodPathNotes
POST/api-key Body {name, scopes: [string], expiration?: epoch-seconds}. Response includes secret — shown exactly once.
GET/api-keys List (id, name, created_at, expiration, last_used_at). Never returns secrets.
POST/api-key/{id}/revoke Immediate, permanent.

Notes

Feature flags

CRUD

MethodPathNotes
POST/flag {key, value, description?, activation_date_time?, expiration_date_time?, percentage?} → 200 with the flag; 409 if the key already exists
GET/flags All flags for your org
POST/flag/{flag_id} Toggle: {value: bool}
PUT/flag/{flag_id} Full replace of every field
POST/flag/{flag_id}/dates Set schedule window
DEL/flag/{flag_id} → 204

Keys are dot-separated hierarchies (platform.auth.signup). Validation rejects empty keys, leading or trailing dots, and consecutive dots (400); percentage must be between 0 and 100 (400 outside the range).

Notes

Evaluation

Three endpoints, all authenticated:

# 1. Simple boolean by key
curl https://api.featrs.com/flag/evaluate/checkout.new-flow \
  -H "Authorization: Bearer $TOKEN"
# → { "key": "checkout.new-flow", "enabled": true }

# 2. With user context (segment targeting)
curl -X POST https://api.featrs.com/flag/evaluate \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"key": "checkout.new-flow", "context": {"country": "ZA", "plan": "enterprise"}}'
# → { "key": "checkout.new-flow", "enabled": true, "segment_match": true }

# 3. Batch (e.g. app boot)
curl -X POST https://api.featrs.com/flags/evaluate \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"keys": ["checkout.new-flow", "platform.dark-mode"], "context": {"country": "ZA"}}'
# → [ { "key": "…", "enabled": true }, … ]

Evaluation order for a key:

  1. Circuit breaker — if the root of the hierarchy (checkout for checkout.new-flow) is tripped, the answer is false immediately.
  2. Exact flag lookup — if the key exists: check its schedule window, then its value and percentage. Parents are not consulted.
  3. Hierarchy fallback — only if the key does not exist, walk up (a.b.c → a.b → a) and use the first ancestor found. A missing root resolves to false.
  4. Segments (context endpoints only) — if the flag is on and a non-empty context was sent, the flag's attached segments are matched; enabled is true only if at least one matches (or none are attached).

Notes

Scheduling (time windows)

activation_date_time/expiration_date_time (RFC 3339, UTC) bound when a flag can be true. Outside the window the flag evaluates false; the stored value is untouched. Boundaries are exclusive: a flag whose activation time is exactly now is still inactive, and it expires at the instant of expiration_date_time.

Statistics

GET /flags/stats → per-key totals from the evaluation log:

[ { "flag_key": "checkout.new-flow", "total": 1523, "enabled_count": 1200,
    "last_24h": 245, "last_7d": 1102 } ]

Notes

Segments

Segments are named rule sets matched against the context you send at evaluation time.

MethodPathNotes
POST/segment {name, description?, rules} → 201
GET/segments List
PUT/segment/{id} Full replace
DEL/segment/{id} → 204, removed from all flags
POST/flag/{flag_id}/segments {segment_ids: [uuid]} — replaces the assignment set; [] clears
GET/flag/{flag_id}/segments Current assignments

Rule semantics — all rules in a segment must match (AND); a flag with multiple segments is enabled if any segment matches (OR):

{ "country": "ZA", "plan": ["enterprise", "scale-up"] }

Notes

Experiments

Lightweight A/B tests built on flags and segments.

MethodPathNotes
POST/experiment {name, description?, hypothesis?, prediction?, flag_ids: [uuid], segment_ids: [uuid], funnel_steps: [string], goal_event?} → 201
GET/experiments List
GET/experiment/{id} Detail incl. attached flags + segments
POST/experiment/{id}/status {"status": "running"|"paused"|"completed"} — transitions validated
POST/experiment/{id}/event Log one event (only while running) → 201
GET/experiment/{id}/metrics Per-variant conversion + Z-test winner verdict
GET/experiment/{id}/funnel Per-step drop-off
DEL/experiment/{id} Cascades events/links → 204

Integration loop: create the experiment (attaching variant segments and observed flags) → set status: running → log events from your app with event_name, a stable user_id, and the user's context → read /metrics for the verdict.

Notes

Organisation & users

MethodPathNotes
GET/me Caller's user id, email, active org. User tokens only — API-key tokens get 403.
GET/users Members of the org
POST/user/invite {email} — the user must already have a Featrs account; 404 otherwise
DEL/user/{user_id} Remove from org

Notes

Health

GET /health (no auth) → {"status": "Ok", "git_sha": "…"}. Use it for uptime checks; git_sha identifies the build that is live.

Rate limits & capacity

There is no formal rate limit today. For best performance and reliability: cache tokens for their full hour, batch flag evaluation at startup, cache evaluation results client-side for your session length, and back off on 5xx responses.