Streaming flag changes

Instead of polling for changes, open a Server-Sent Events stream and let Flagonic tell you the moment a flag in your project is created, updated, toggled, or deleted. OFREP does not define streaming yet, so this endpoint is a Flagonic extension — your evaluation calls stay pure OFREP.

The endpoint

With an SDK key (fgn_sdk_…), open a long-lived GET request. The stream is scoped like every SDK-key call: you hear about changes to your key's project, nothing else.

curl -N https://flags.example.com/api/v1/stream \
  -H "Authorization: Bearer $FLAGONIC_SDK_KEY"

# retry: 3000
#
# event: change
# data: {"version":"3a5b9c…"}

What you receive

event: changeThe project's flag configuration changed. data is a JSON object with a single version field.
versionAn opaque identifier for the current flag configuration. Equal versions mean nothing changed; you never need to parse it.
retry: 3000Standard SSE reconnect hint, sent once on connect.
: pingHeartbeat comment every 25 seconds so idle connections survive proxies and load balancers. Ignore it.

One change event arrives immediately on connect carrying the current version. Remember the last version you acted on: if the connect event matches it, you missed nothing while disconnected and can skip the refetch.

Reacting to a change

The event deliberately carries no flag data — your evaluation results depend on your own context, so the stream just tells you when to re-evaluate. On each new version, re-run bulk evaluation:

POST /ofrep/v1/evaluate/flags

A minimal Go consumer (any language with an SSE or line-reading HTTP client works the same way):

req, _ := http.NewRequest("GET", base+"/api/v1/stream", nil)
req.Header.Set("Authorization", "Bearer "+sdkKey)
resp, err := http.DefaultClient.Do(req)
if err != nil { /* back off and reconnect */ }
defer resp.Body.Close()

sc := bufio.NewScanner(resp.Body)
var last string
for sc.Scan() {
    data, ok := strings.CutPrefix(sc.Text(), "data: ")
    if !ok {
        continue // event name, heartbeat, or blank separator
    }
    var ev struct{ Version string }
    if json.Unmarshal([]byte(data), &ev) == nil && ev.Version != last {
        last = ev.Version
        refreshFlags() // re-run bulk evaluation
    }
}
Browsers: EventSource cannot send headers. The stream authenticates with a bearer token, which the browser EventSource API cannot attach — and an SDK key does not belong in frontend code anyway. Consume the stream from your backend (or an edge proxy) and fan changes out to browsers yourself.

Reliability notes

  • Reconnect with backoff. Honor the retry hint, then compare the version in the connect event against the last one you processed to decide whether to refetch.
  • Bursts coalesce. Several rapid changes may arrive as a single event with the final version — you always converge on the latest configuration.
  • Every instance hears every change. With the Postgres backend, a change made through any Flagonic instance reaches streams held by all instances, so streaming works behind a load balancer.
  • Polling still works. Bulk evaluation keeps its ETag / If-None-Match support; streaming is an optimization, not a replacement. If the stream drops, fall back to polling until it is re-established.