featrs

Featrs Integration Guide

How to integrate your application with the Featrs API, feature by feature. Every section ends with Integrator notes — behaviour observed in the implementation that you need to know about, including sharp edges the reference docs don't call out.

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 (401 bad/expired credentials, 403 wrong principal type or missing org, 404 not found, 400 invalid input, 502 upstream provider failure, 500 everything else).

Integrator notes

Authentication

Machine-to-machine auth is a two-step: 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 (seconds). There is no refresh endpoint — when the token nears expiry, do the exchange again. Cache the token for its lifetime; don't exchange per request (the secret check is Argon2 and deliberately expensive).

Integrator notes

API keys

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

Integrator notes

Feature flags

CRUD

MethodPathNotes
POST/flag {key, value, description?, activation_date_time?, expiration_date_time?, percentage?} → 200 with the flag
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/trailing dots, and ..; anything else — spaces, slashes, unicode — is accepted.

Integrator 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 in the circuit_breakers table, 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.ca.ba) and use the first ancestor found. A missing root resolves to false.
  4. Segments (context endpoints only) — if the flag is on and you sent a non-empty context, the flag's attached segments are matched; enabled is true only if at least one matches (or none are attached).

Integrator 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 } ]

Integrator 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"] }

Integrator notes

Experiments

Lightweight A/B tests built on flags + segments. Endpoint-level detail is in the API reference.

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 (attaching variant segments and observed flags) → status: running → log events from your app with event_name, a stable user_id, and the user's context → read /metrics for the verdict.

Integrator 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

Integrator notes

Health

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

Rate limits & capacity

There is no formal rate limit today, but the API is modestly provisioned. Be a good citizen: 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.