Skip to main content

Evaluation endpoints

Server-side flag evaluation: pass the server a (flag namespace, environment, context) triple, get back resolved variants. Used by backend services that prefer not to embed the SDK, by browser bundles holding namespace-client tokens, and by ad-hoc tooling.

EndpointAuthStatus
POST /api/v1/tenants/{tenant}/namespaces/{namespace}/evaluateevaluate or evaluate.publicv0
POST .../evaluate/allevaluate or evaluate.publicv0
OPTIONS .../evaluate{,/all}unauthenticated (CORS preflight)v0
GET /api/v1/manifest/snapshotsnapshot.read.tenant / snapshot.read.globaldeferred

See conventions for the auth header, error envelope, and rate limits.

The evaluation algorithm itself — the four-step walk that turns a (flag, env, context) triple into a variant — is documented in resolution.


POST /api/v1/tenants/{tenant}/namespaces/{namespace}/evaluate

Evaluate a specified list of flags for an entity (user, device, organization, …) in a given environment. The server applies the rules from the current manifest version and returns the resolved variant for each requested flag.

Auth required:

  • Authenticated callers: namespace admin, tenant_admin for {tenant}, namespace-read, namespace-write, tenant-admin token, or superadmin. Permission: evaluate.
  • Public callers: namespace-client tokens bound to the (tenant, namespace, environment) triple, when the bound environment declares public_evaluate = true. Permission: evaluate.public. See access-control § Public evaluation.

Under a namespace-client token, the request body's environment field MUST be omitted OR MUST equal the token's bound environment, otherwise 403 Forbidden. The path's {tenant} MUST match the token's bound tenant.

CORS

Browser-originated requests (those carrying an Origin header) under namespace-client tokens are subject to the token's allowed_origins allowlist. OPTIONS preflights at this path are answered without authentication. See access-control § CORS.

Request body

{
"environment": "production",
"context": {
"entity_id": "user_01HV5XK2GFQT8N3JRDCP",
"attributes": {
"user.plan": "pro",
"user.country": "US",
"user.email_verified": true,
"user.account_days": 120,
"app.version": "2.3.1"
}
},
"flags": ["checkout-redesign", "homepage-banner-copy"]
}
FieldTypeRequiredDescription
environmentstringyesThe environment name. Must be declared in the flag namespace's namespace.toml.
context.entity_idstringyesStable, unique identifier for the entity. Segment bucket definitions may use this or another string attribute as their hash input. Must be consistent across calls for sticky assignment.
context.attributesobjectnoKey-value map of attributes for the entity. Keys are dot-separated attribute paths; values may be strings, numbers, or booleans. See evaluation-context.
flagsarray of stringsyesFlag keys to evaluate. Unknown keys produce a flag_not_found per-entry error; other flags still evaluate.
include_testingbooleannoPer-call opt-in for [flag.environments.<env>].testing = true envs. false (or omitted) → step 1 of resolution is skipped for testing-gated envs and the env's variant (step 2) is returned instead. true → the gated rules fire normally. See resolution § The rollout workflow.

Optional headers

HeaderDescriptionStatus
X-Exd-Dry-Run: trueEvaluate flags but do not record the evaluation event in telemetry.deferred
X-Exd-Manifest-Version: NEvaluate against a specific historical manifest version rather than the current one.deferred

Response: 200 OK

{
"results": {
"checkout-redesign": {
"value": true,
"variant_key": "on",
"rule_matched": {
"index": 0,
"description": "Always on for internal employees and beta program users"
},
"flag_version": 8
},
"homepage-banner-copy": {
"value": "Send money in seconds.",
"variant_key": "variant_a",
"rule_matched": {
"index": 0,
"description": "33/33/34 three-way split for all authenticated users"
},
"flag_version": 8
}
},
"manifest_version": 8,
"environment": "production",
"request_id": "01HV5XKAYAQT8N3JRDCP7MW04J"
}
Response fieldDescription
results.<flag>.valueThe resolved value. Type matches flag.type in the manifest.
results.<flag>.variant_keyThe key of the resolved variant within flag.variants.
results.<flag>.rule_matchedThe index and description of the rule that produced this result. null if the env's variant (or the _ catch-all's variant) was used. The description field is omitted when the rule does not declare one.
results.<flag>.flag_versionThe manifest version at which this flag's definition was read.
manifest_versionThe manifest version used for the entire evaluation batch.

Every response carries an X-Exd-Manifest-Version: <N> header with the version that served the evaluation.

Per-flag errors

When a requested flag does not exist in the current manifest, that entry is replaced by an error object. Success and error shapes are mutually exclusive — a result entry has either resolved fields OR an error, never both:

"results": {
"homepage-banner-copy": {
"error": {
"code": "flag_not_found",
"message": "flag 'homepage-banner-copy' is not declared in this namespace"
}
}
}

Flags that exist but have no [flag.environments.<env>] block for the requested environment use the same flag_not_found shape with a message that distinguishes the two cases.

Whole-request errors

Some failures abort the batch instead of producing per-entry errors:

ConditionHTTP statusError code
environment is not declared in namespace.toml400invalid_request
Context attribute's runtime type disagrees with the manifest's inferred type for that attribute (E034)400invalid_request (details carries attribute, expected, actual)
The flag namespace exists but no manifest has been uploaded yet404namespace_not_found
A context attribute value is not a string, number, or boolean400invalid_request

Example

curl -X POST https://exd.example.com/api/v1/tenants/acme/namespaces/payments/evaluate \
-H "Authorization: Bearer exd_read_4tRvBn9wKjMpXzQsUyAeCdFgHiJkLnOqRtWvYb" \
-H "Content-Type: application/json" \
-d '{
"environment": "production",
"context": {
"entity_id": "user_01HV5XK2GFQT8N3JRDCP",
"attributes": {
"user.plan": "pro",
"user.country": "US",
"user.email_verified": true,
"user.account_days": 120,
"app.version": "2.3.1"
}
},
"flags": ["checkout-redesign", "homepage-banner-copy"]
}'

POST /api/v1/tenants/{tenant}/namespaces/{namespace}/evaluate/all

Evaluate every flag in the flag namespace for the given entity and environment. Semantically identical to calling /evaluate with the full list of flag keys but more efficient — the server does not need to parse the flag list. Useful for client-side bootstrapping where the SDK needs to hydrate its local cache with all flag values on startup, and for browser bundles that hydrate a small flag set on page load under a namespace-client token.

Auth required: same as /evaluate, including namespace-client tokens bound to the (tenant, namespace, environment) triple when the bound environment has public_evaluate = true. CORS handling is identical to /evaluate.

Request body

Same as /evaluate but without the flags field. include_testing is accepted with identical semantics.

{
"environment": "production",
"context": {
"entity_id": "user_01HV5XK2GFQT8N3JRDCP",
"attributes": {
"user.plan": "pro",
"user.country": "US",
"user.email_verified": true
}
}
}

Optional headers

Same as /evaluate (X-Exd-Dry-Run, X-Exd-Manifest-Version).

Response: 200 OK

Same schema as /evaluate. The results map contains an entry for every flag defined in the flag namespace.

Example

curl -X POST https://exd.example.com/api/v1/tenants/acme/namespaces/payments/evaluate/all \
-H "Authorization: Bearer exd_read_4tRvBn9wKjMpXzQsUyAeCdFgHiJkLnOqRtWvYb" \
-H "Content-Type: application/json" \
-d '{
"environment": "production",
"context": {
"entity_id": "user_01HV5XK2GFQT8N3JRDCP",
"attributes": {
"user.plan": "pro",
"user.country": "US",
"user.email_verified": true
}
}
}'

OPTIONS /api/v1/tenants/{tenant}/namespaces/{namespace}/evaluate{,/all}

CORS preflight, answered without invoking authentication. The preflight response is identical regardless of whether a token is present, so a misconfigured browser does not leak whether a token is valid before the actual request.

For requests carrying an Origin header that matches the token's allowed_origins (on the subsequent POST), the server includes:

Access-Control-Allow-Origin: <Origin> (echoed exactly; never *)
Access-Control-Allow-Credentials: false
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Exd-Manifest-Version
Access-Control-Max-Age: 600
Vary: Origin

See access-control § CORS for the full policy.


GET /api/v1/manifest/snapshot

Deferred. Not implemented in v0.

Download a snapshot of current manifests as a single .tar.gz. Without filters, the archive contains every flag namespace in the installation. With tenant=<tenant-slug>, it contains every flag namespace in that tenant. Each namespace's directory is structured identically to an individual flag-namespace archive.

Use cases: audit agents scanning every flag namespace for stale flag or segment references; dashboard / catalog tools that need a complete read-only bootstrap; documentation generators; cross-namespace tooling.

Auth required: superadmin for a global snapshot. tenant_admin or tenant-admin token may request a tenant-limited snapshot with tenant=<tenant-slug>. Permissions: snapshot.read.global / snapshot.read.tenant.

Query parameters

ParameterDescription
tenantOptional tenant slug. When supplied, the snapshot is limited to flag namespaces in that tenant.

Response: 200 OK

Body is application/octet-stream. Response headers:

Content-Type: application/octet-stream
Content-Disposition: attachment; filename="exd-snapshot-20260425T120000Z.tar.gz"
X-Exd-Snapshot-At: 2026-04-25T12:00:00Z
X-Exd-Tenant-Slug: acme
X-Exd-Namespace-Count: 3
X-Request-Id: 01HV5XKBZAQT8N3JRDCP7MW04K

X-Exd-Tenant-Slug is present only for tenant-limited snapshots.

Archive structure:

payments/
namespace.toml
flags/
...
segments/
...
identity/
namespace.toml
...

Example

curl https://exd.example.com/api/v1/manifest/snapshot \
-H "Authorization: Bearer exd_admin_2mNpQrStUvWxYzAbCdEfGhJkLmNoPqRsTuVwXy" \
-o exd-snapshot.tar.gz

See also