Docs

Concepts, API, caching, and SDKs. Sign up to first feature in under five minutes.

Caching and syncs

Feature data is cached at several layers. The client app stays fast, and refreshes skip origin when nothing changed.

The database is the source of truth. Caches only accelerate reads.

Why this matters

Customer apps often keep a tab open for hours. Without caching, each refresh would hit origin for a full payload even when no feature changed.

  • Most refreshes come from memory or the edge.
  • 304 and edge hits skip full origin reads when features are unchanged.
  • Toggles propagate over live push. Worst-case delay follows short cache TTLs.

Cache layers

Loading diagram…
LayerWhereTypical TTLWhat it stores
SDK memoryClient app processUntil next refreshResolved features + ETag
Edge CDNNear end usersUp to 60sFull 200 responses per API key
API serverFeatureToggle backendUp to 30sResolved features per environment

The SDK handles this automatically.

Origin requests vs cache hits

A full origin response is a GET /v1/features or GET /v1/features/:key that returns 200 with body.

Not a full origin read:

ResponseWhy
304 Not ModifiedFeatures unchanged, no new data
Edge cache hitRequest never reached origin
Dashboard actionsCreating or toggling features in the UI

With caching and SSE, apps that refresh often while features are stable mostly see 304 or edge hits instead of repeated full payloads.

Steady state: nothing changed

Loading diagram…

After a feature is toggled

Loading diagram…

How fast does the client app see the change?

PathTypical latency
SSE connectedSeconds
SSE disconnectedNext tab-focus refresh, poll tick (stream: "off"), or init()
Worst case (caches only)Up to ~60s + refresh trigger

SDK refresh triggers

Loading diagram…
TriggerWhen
SSE features-changedPrimary path for a dashboard toggle (stream: "auto" or "notify")
Tab focusUser returns to the tab (when no poll timer is active)
Poll timerBackground interval when stream: "off" and pollInterval > 0
init()SDK startup
refresh()Manual refetch

Default production apps use stream: "auto" and run no background poll timer. Updates come from SSE, tab focus, and manual refresh().

With stream: "off", the SDK may run a background poll (default 30s). Set pollInterval: 0 to disable polling and use tab focus instead. Repeat polls send If-None-Match; unchanged features return 304 (not a full origin read).

Conditional requests (ETag / 304)

Only GET /v1/features supports conditional GET:

  1. First request → 200 + ETag header (environment version).
  2. Later requests → If-None-Match: "<version>".
  3. Unchanged → 304 (empty body).
  4. Changed → 200 + new payload + new ETag.

304 responses are never cached at the edge (Cache-Control: no-store).

Single-feature reads (GET /v1/features/:key) always return full data.

What is not cached

ItemReason
GET /v1/features/streamLong-lived SSE connection
304 at the edgeStale conditional response risk
GET /v1/features/:keyNo conditional GET

Practical tips

  1. Use the SDK. It manages ETag, SSE, and refresh timing.
  2. Keep SSE connected in production. It is the fastest toggle-to-app path.
  3. Keep stream: "auto" in production. SSE is faster than polling, so avoid running a tight custom poll loop alongside it.
  4. Use stream: "off" + pollInterval only when SSE is unavailable: tests, restricted runtimes, or proxies that block long-lived streams.
  5. Evaluate sensitive features on the client server. Browser cache can be sub-minute stale without SSE, and authorization decisions need server-side checks.

FAQ

The client refreshes 100 times and nothing changed. How many full origin payloads is that?

Usually zero. The client gets 304 or edge cache hits instead.

Does toggling in the dashboard count as an API read?

No. Dashboard writes are separate from the feature API.

Force a fresh read?

Call refresh(). Changed features → 200. Unchanged → 304.