Skip to content

Chapter 3 · Connect your app

Clients and edge server

Your app evaluates express-checkout against the edge server with a client's SDK key, using curl, OFREP or the OpenFeature SDK.

4:08 videoBuilds on:Targeting rules

Before you start

  • The Development stage of express-checkout is deployed, with the two criteria from Targeting rules.
  • The demo stack is running. The edge server is not started yet.
  • You have a terminal in the install folder. It holds docker-compose.demo.yml and .env. jq and Node 20 or newer are installed.
  • Sign in as priya Requester and select the team Checkout. Creating a client needs no special role.

Why an edge server

Your app should not call the admin backend on every flag check. It asks the edge server, a small service that keeps a cached copy of your flags and answers from that copy.

  • The edge loads a snapshot of the flags for its team when it starts. It keeps the snapshot in memory.
  • The edge holds a gRPC stream open to the backend. When someone changes a flag, the backend pushes the change down the stream and the edge updates its cache.
  • GET /health returns 200 while the edge holds flags or its backend stream is open, and 503 otherwise. The edge can keep serving flags when the backend is down. See Edge resilience.

1 · Connect on start and on every reconnect

  1. Edge server to Backend

    Look up the client’s team

    which team does this client belong to?
  2. Edge server to Backend

    Open the stream

    signs in with the client ID and API key
  3. Backend to Edge server

    Snapshot of every team feature

    one message per feature
  4. Backend to Edge server

    Snapshot complete

    the edge drops features that are gone

2 · Live change every saved change

  1. Admin UI to Backend

    Save a change

    for example criteria, stage, approval, delete
  2. Backend

    Commit to PostgreSQL

    before anything is pushed
  3. Backend to Edge server

    Push the full feature

    to every edge of the team
  4. Edge server

    Update the cache

    replace it, clear cached results
The next evaluation on that edge uses the new rules, and OFREP responses carry a new ETag. The edge sends a heartbeat every 30 seconds and the backend answers it.

If the stream drops

The edge keeps answering from its last copy and stays ready while it holds flags. It reconnects after 1 second, doubling the wait up to 30 seconds, and loads a fresh snapshot. See Edge resilience.

If an edge falls behind

When an edge cannot keep up with the changes, the backend tells it so. The edge keeps its copy, reconnects and loads a full snapshot, which then removes the flags the backend no longer has.

The edge listens on port 8081 in the demo stack. The backend stays on 8080.

Clients and SDK keys

A client is how an app identifies itself to FluxGate. Each client belongs to one team and is bound to one environment. The environment decides which stage configuration the client sees, so a Development client never receives Production rules.

Client typeUse it forExtra rule
BackendServers and services that keep the key private.No origin check.
WebBrowser apps, where the request comes from a page on another origin.Needs Web Origins.

A client has an API key. The SDK key that your apps send is the client ID, a dot, and the API key:

SDK key format
<clientId>.<apiKey>

The edge splits the key at the first dot. Send it in Authorization: Bearer <SDK key> or in X-API-Key: <SDK key>. If both headers are present, Authorization wins.

Web origins and CORS

Browsers enforce cross-origin rules, so a Web client lists the origins that may call the edge for it. For a Web client, the edge serves a request only when its Origin header is on that list. The response then carries Access-Control-Allow-Origin for that origin and exposes the ETag, FluxGate-Config-Age and FluxGate-Config-Source headers. A request with a missing or unlisted origin gets 403. A Backend client skips the origin check.

A wrong SDK key returns 401 without CORS headers, so a browser shows an opaque network error instead of a readable 401.

Two ways to evaluate

EndpointWho the edge evaluates asCredentials
POST /evaluateThe client configured on the edge itself (EDGE_CLIENT_ID and EDGE_CLIENT_SECRET).None from the caller.
POST /ofrep/v1/evaluate/flags/{key} and POST /ofrep/v1/evaluate/flagsThe client in the caller's SDK key.Authorization: Bearer <SDK key>.

/evaluate takes no credentials, so anyone who can reach port 8081 can use it. Keep the edge on a private network, or put your own gateway in front of it.

The OFREP endpoints follow the OpenFeature Remote Evaluation Protocol, so any OpenFeature OFREP provider works against them. Full request and response details are in the Edge server API reference. For ready-made clients, see SDKs.

Create the clients

  1. Click Clients under Connect to open /clients.
  2. Click Create New Client. The page Create Client opens at /clients/create.
  3. In Client Name, type checkout-service.
  4. In Client Type, choose Backend.
  5. In Environment, choose Development.
  6. Click Create Client. The page reloads on the edit page and shows API Key and SDK key, both masked.
  7. Click Create New Client again on /clients. In Client Name, type juniper-web.
  8. Leave Client Type on Web. The Web Origins field is visible. Type http://localhost:5173, the address where the storefront runs. Put one origin on each line, or separate origins with commas.
  9. In Environment, choose Development, then click Create Client.

The edge server needs an identity of its own to reach the backend. Use checkout-service for it. A Web client works too, but then POST /evaluate demands an Origin header on every call.

Start the edge server

The edge reads its credentials from .env in the install folder: a client ID and an API key.

  1. Open checkout-service on /clients. Click the copy button next to API Key. Back on /clients, click the copy button in the Client ID column. The toast "Client ID copied to clipboard" appears.
  2. Append both values to .env. Use >>. A single > would overwrite FLUXGATE_ENCRYPTION_KEY.
  3. Start the edge service with the edge profile.
  4. Check the health endpoint. It answers 200 once the edge has connected to the backend.
Start the edge server
echo EDGE_CLIENT_ID=<checkout-service client id> >> .env
echo EDGE_CLIENT_SECRET=<checkout-service API key> >> .env
docker compose -f docker-compose.demo.yml --profile edge up -d edge
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8081/health
Output
200

The edge needs about a second to connect. If the first check prints 503, run it again. Compose reads .env on every run, so the edge keeps the credentials after a restart.

Find ready snippets

Open /developer/setup, the SDK Setup Wizard, and scroll to Ready Snippets. Edge OFREP (SDK key) is a curl call with your SDK key in the Authorization header. REST Evaluate calls the backend with a system client token. Automation and CI uses it.

Examples

Every example sends a bucketing key. /evaluate needs bucketingKey. OFREP uses targetingKey. Context attributes such as country and user_tier sit next to it at the top level.

One evaluation as the edge client

A Canadian Plus user asks for express-checkout. The first Development rule matches.

Evaluate as the edge client
curl -s -X POST http://localhost:8081/evaluate \
  -H 'Content-Type: application/json' \
  -d '{"flagKey":"express-checkout","context":{"bucketingKey":"user-1","country":"CA","user_tier":"plus"}}' | jq
Response
{
  "flagKey": "express-checkout",
  "value": "express",
  "variant": "express",
  "reason": "TARGETING_MATCH"
}

A hundred US free users

These users skip the Canadian rule and land in the weighted split, 20 percent express and 80 percent classic.

Count variants for 100 users
for i in $(seq 1 100); do
  curl -s -X POST http://localhost:8081/evaluate \
    -H 'Content-Type: application/json' \
    -d "{\"flagKey\":\"express-checkout\",\"context\":{\"bucketingKey\":\"user-$i\",\"country\":\"US\",\"user_tier\":\"free\"}}" | jq -r .variant
done | sort | uniq -c
Output
  84 classic
  16 express

The counts vary slightly with the bucketing keys you use. A split by hash gives close to 20 percent, not exactly 20 percent. A given bucketing key always gets the same answer. See Contexts for how the bucket is chosen.

OFREP bulk call and the ETag

The bulk call evaluates every flag of the client's team in one request. It uses the SDK key and returns an ETag header.

Bulk evaluation
export FLUXGATE_SDK_KEY=<checkout-service SDK key>
curl -si -X POST http://localhost:8081/ofrep/v1/evaluate/flags \
  -H "Authorization: Bearer $FLUXGATE_SDK_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"context":{"targetingKey":"user-1","country":"CA","user_tier":"plus"}}'; echo
Response
HTTP/1.1 200 OK
content-length: 252
content-type: application/json
etag: 0e92f4814ad87c3587f1faee8851f76922a8a0fd40af993fb3475ffaf4a14b52

{"flags":[{"key":"express-checkout","value":"express","reason":"TARGETING_MATCH","variant":"express"},{"key":"holiday-banner","value":false,"reason":"DISABLED"}],"metadata":{"version":"0e92f4814ad87c3587f1faee8851f76922a8a0fd40af993fb3475ffaf4a14b52"}}

holiday-banner is false with the reason DISABLED because it is not deployed to Development. The echo adds a newline, because the response body has none.

Send the ETag value back in If-None-Match with the same context and the same SDK key:

Repeat the call with the ETag
curl -si -X POST http://localhost:8081/ofrep/v1/evaluate/flags \
  -H "Authorization: Bearer $FLUXGATE_SDK_KEY" \
  -H 'If-None-Match: <etag value from the first response>' \
  -H 'Content-Type: application/json' \
  -d '{"context":{"targetingKey":"user-1","country":"CA","user_tier":"plus"}}'
Response
HTTP/1.1 304 Not Modified
etag: 0e92f4814ad87c3587f1faee8851f76922a8a0fd40af993fb3475ffaf4a14b52

The edge replies 304 with no body while the flag set, the client environment and the context are unchanged. A different context or a different client gets a fresh 200.

Single flag with OFREP

One flag
curl -s -X POST http://localhost:8081/ofrep/v1/evaluate/flags/express-checkout \
  -H "Authorization: Bearer $FLUXGATE_SDK_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"context":{"targetingKey":"user-1","country":"CA","user_tier":"plus"}}'
Response
{"key":"express-checkout","value":"express","reason":"TARGETING_MATCH","variant":"express"}

The Web client and its origin

With the juniper-web SDK key, the same call works from the listed origin and fails from any other. Send it with an Origin header:

One flag as juniper-web
curl -si -X POST http://localhost:8081/ofrep/v1/evaluate/flags/express-checkout \
  -H "Authorization: Bearer <juniper-web SDK key>" \
  -H 'Origin: http://localhost:5173' \
  -H 'Content-Type: application/json' \
  -d '{"context":{"targetingKey":"user-1","country":"CA","user_tier":"plus"}}'
Origin http://localhost:5173
HTTP/1.1 200 OK
access-control-expose-headers: ETag, FluxGate-Config-Age, FluxGate-Config-Source
access-control-allow-origin: http://localhost:5173
vary: Origin
Origin http://evil.example
HTTP/1.1 403 Forbidden
vary: Origin

{"errorCode":"FORBIDDEN","errorDetails":"Origin is not allowed for this client"}

Change the Origin header to http://evil.example, or drop it, to see the 403. CORS in the edge API reference has the preflight and the full rules.

OpenFeature SDK

Install the server SDK and the OFREP provider, then save the script as app.mjs:

Install
npm install @openfeature/server-sdk @openfeature/ofrep-provider
app.mjs
import { OpenFeature } from '@openfeature/server-sdk';
import { OFREPProvider } from '@openfeature/ofrep-provider';

await OpenFeature.setProviderAndWait(new OFREPProvider({
  baseUrl: 'http://localhost:8081',
  headers: [['Authorization', `Bearer ${process.env.FLUXGATE_SDK_KEY}`]],
}));

const client = OpenFeature.getClient();
const variant = await client.getStringValue('express-checkout', 'classic', {
  targetingKey: 'user-1',
  country: 'CA',
  user_tier: 'plus',
});
console.log(`checkout: ${variant}`);
await OpenFeature.close();
Run it
FLUXGATE_SDK_KEY=<checkout-service SDK key> node app.mjs
Output
checkout: express

Set baseUrl to the edge root, without a path. The script is an ES module, so keep the .mjs extension. Pass headers as a list of pairs. A plain object fails in version 0.2.5 of the provider. None of this code is FluxGate specific.

Good to know

  • /evaluate answers as the edge's own client, so it evaluates in that client's environment. The OFREP endpoints evaluate in the environment of the SDK key's client. A context with an environment_id that differs from that environment gets 401.
  • A wrong SDK key does not make the OpenFeature SDK fail. It returns your default value, classic in the script above. Check keys with curl first.
  • A disabled client gets 403. The edge caches a good key for up to 300 seconds, so a disabled client or a rotated API key can still pass for that long. A rejected key is cached for 30 seconds.
  • Send a bucketing key every time. Without one, a weighted rule cannot place the user.
  • The Integrations page at /developer/integrations, OpenFeature & OFREP, lists the Compatibility Matrix and example calls. Its token details describe the backend's own endpoints. Your apps use the SDK key against the edge.
  • Each evaluation is reported to the backend. After the loop above, Evaluation Analytics at /dashboard/evaluation-analytics shows Total Evaluations above 100 and Unique Users at 100.

Troubleshooting

The health check prints 503

The edge has not connected to the backend and holds no flags. Wait a second and try again. If it persists, check that .env holds the real client ID and API key, then run docker compose -f docker-compose.demo.yml --profile edge up -d edge again. The edge logs show an authentication error when the values are missing or wrong.

The Web client gets 403

The request has no Origin header, or the origin is not in Web Origins. Use the exact origin, with scheme and port, as the browser sends it. From curl, add -H 'Origin: http://localhost:5173'.

Next

Give a CI pipeline its own identity and gate a job on a flag in Automation and CI.