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.
Before you start
- The Development stage of
express-checkoutis 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.ymland.env.jqand Node 20 or newer are installed. - Sign in as
priyaRequester 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 /healthreturns200while the edge holds flags or its backend stream is open, and503otherwise. The edge can keep serving flags when the backend is down. See Edge resilience.
1 · Connect on start and on every reconnect
- Edge server to Backend
Look up the client’s team
which team does this client belong to? - Edge server to Backend
Open the stream
signs in with the client ID and API key - Backend to Edge server
Snapshot of every team feature
one message per feature - Backend to Edge server
Snapshot complete
the edge drops features that are gone
2 · Live change every saved change
- Admin UI to Backend
Save a change
for example criteria, stage, approval, delete - Backend
Commit to PostgreSQL
before anything is pushed - Backend to Edge server
Push the full feature
to every edge of the team - Edge server
Update the cache
replace it, clear cached results
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 type | Use it for | Extra rule |
|---|---|---|
| Backend | Servers and services that keep the key private. | No origin check. |
| Web | Browser 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:
<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
| Endpoint | Who the edge evaluates as | Credentials |
|---|---|---|
POST /evaluate | The 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/flags | The 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
- Click Clients under Connect to open
/clients. - Click Create New Client. The page Create Client opens at
/clients/create. - In Client Name, type
checkout-service. - In Client Type, choose Backend.
- In Environment, choose Development.
- Click Create Client. The page reloads on the edit page and shows API Key and SDK key, both masked.
- Click Create New Client again on
/clients. In Client Name, typejuniper-web. - 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. - 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.
- Open
checkout-serviceon/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. - Append both values to
.env. Use>>. A single>would overwriteFLUXGATE_ENCRYPTION_KEY. - Start the
edgeservice with theedgeprofile. - Check the health endpoint. It answers
200once the edge has connected to the backend.
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/health200The 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.
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{
"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.
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 84 classic
16 expressThe 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.
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"}}'; echoHTTP/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:
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"}}'HTTP/1.1 304 Not Modified
etag: 0e92f4814ad87c3587f1faee8851f76922a8a0fd40af993fb3475ffaf4a14b52The 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
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"}}'{"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:
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"}}'HTTP/1.1 200 OK
access-control-expose-headers: ETag, FluxGate-Config-Age, FluxGate-Config-Source
access-control-allow-origin: http://localhost:5173
vary: OriginHTTP/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:
npm install @openfeature/server-sdk @openfeature/ofrep-providerimport { 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();FLUXGATE_SDK_KEY=<checkout-service SDK key> node app.mjscheckout: expressSet 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
/evaluateanswers 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 anenvironment_idthat differs from that environment gets401.- A wrong SDK key does not make the OpenFeature SDK fail. It returns your default value,
classicin 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-analyticsshows 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.