SDKs & API
The f69 edge API is what your application calls at request time. It is the only HTTP surface external code is expected to use - workspace and flag management happens in the dashboard.
Authentication
Section titled “Authentication”Edge requests authenticate with a service-account bearer token of the
shape xxxxxxxx@f69:<uuid>.<secret>. Mint one from the dashboard; the
plaintext token is shown once at creation and stored as an Argon2
hash, so if you lose it, mint a new one.
Every token is bound to exactly one environment for its lifetime, and has one of two roles:
reader- may evaluate flags.writer- may evaluate flags and identify entities.
Send the token as a standard bearer header:
Authorization: Bearer xxxxxxxx@f69:<uuid>.<secret>POST /v1/identify
Section titled “POST /v1/identify”Upserts an entity row keyed (environment, type, external_id). The
environment is implicit (it comes from the token); do not send it in the
body.
POST /v1/identifyAuthorization: Bearer xxxxxxxx@f69:<uuid>.<secret>Content-Type: application/json
{ "type": "user", "external_id": "u-alice", "attributes": { "country": "NG", "plan": "pro" }}Constraints:
type: 1 - 128 characters.external_id: 1 - 512 characters.attributes: a JSON object; fully replaces the stored attributes on upsert.
Identify requires a writer token. Reader tokens are evaluation-only.
POST /v1/evaluate
Section titled “POST /v1/evaluate”Evaluates one or more flags for a previously-identified entity.
POST /v1/evaluateAuthorization: Bearer xxxxxxxx@f69:<uuid>.<secret>Content-Type: application/json
{ "id": "u-alice", "type": "user", "keys": ["new-checkout", "promo-banner"]}Body:
id: the sameexternal_idyou passed to identify.type: the same entity kind.keys(optional): omit or set tonullto evaluate every flag in the project, sorted by feature key. If supplied, must be a non-empty array of feature keys (duplicates dropped, evaluated in first-seen order).keys: []returns400; an unknown key returns404.
Response is a JSON array:
[ { "key": "new-checkout", "value": true, "reason": "TARGETING_MATCH", "version": "live" }, { "key": "promo-banner", "value": false, "reason": "DEFAULT", "version": "live" }]reason is one of TARGETING_MATCH, SPLIT, DEFAULT, FALLBACK,
ERROR. version is the manifest version string echoed back to the
caller.
Evaluation reads attributes from the stored entity row, not from the request - so identify whenever an entity’s traits change, not on every evaluate.
Status codes worth knowing
Section titled “Status codes worth knowing”| Status | Meaning |
|---|---|
400 | Empty keys array or malformed body |
401 | Token missing, malformed, or token_reissue_required |
403 | Reader token attempting identify |
404 | Entity never identified in the token’s environment, or unknown flag key |
Client integration
Section titled “Client integration”There is no official SDK package yet - call the JSON endpoints directly or wrap them in a thin internal client. The same Rust evaluator that powers edge is published as a library if you need fully offline evaluation; that is the path SDKs will use as they land.
A minimal Node example
Section titled “A minimal Node example”const F69_TOKEN = process.env.F69_TOKEN!;const F69_EDGE = process.env.F69_EDGE_URL!;
export async function evaluate( id: string, type: string, keys?: string[],): Promise<Record<string, boolean>> { const res = await fetch(`${F69_EDGE}/v1/evaluate`, { method: 'POST', headers: { authorization: `Bearer ${F69_TOKEN}`, 'content-type': 'application/json', }, body: JSON.stringify({ id, type, keys }), }); if (!res.ok) throw new Error(`f69 evaluate ${res.status}`); const results = (await res.json()) as Array<{ key: string; value: boolean; }>; return Object.fromEntries(results.map((r) => [r.key, r.value]));}Always treat a non-200 from f69 as fail-closed: route past the flag to its known-safe default rather than throwing into the request path.