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.
| Endpoint | Auth | Status |
|---|---|---|
POST /api/v1/tenants/{tenant}/namespaces/{namespace}/evaluate | evaluate or evaluate.public | v0 |
POST .../evaluate/all | evaluate or evaluate.public | v0 |
OPTIONS .../evaluate{,/all} | unauthenticated (CORS preflight) | v0 |
GET /api/v1/manifest/snapshot | snapshot.read.tenant / snapshot.read.global | deferred |
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_adminfor{tenant},namespace-read,namespace-write,tenant-admintoken, orsuperadmin. Permission:evaluate. - Public callers:
namespace-clienttokens bound to the(tenant, namespace, environment)triple, when the bound environment declarespublic_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"]
}
| Field | Type | Required | Description |
|---|---|---|---|
environment | string | yes | The environment name. Must be declared in the flag namespace's namespace.toml. |
context.entity_id | string | yes | Stable, 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.attributes | object | no | Key-value map of attributes for the entity. Keys are dot-separated attribute paths; values may be strings, numbers, or booleans. See evaluation-context. |
flags | array of strings | yes | Flag keys to evaluate. Unknown keys produce a flag_not_found per-entry error; other flags still evaluate. |
include_testing | boolean | no | Per-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
| Header | Description | Status |
|---|---|---|
X-Exd-Dry-Run: true | Evaluate flags but do not record the evaluation event in telemetry. | deferred |
X-Exd-Manifest-Version: N | Evaluate 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 field | Description |
|---|---|
results.<flag>.value | The resolved value. Type matches flag.type in the manifest. |
results.<flag>.variant_key | The key of the resolved variant within flag.variants. |
results.<flag>.rule_matched | The 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_version | The manifest version at which this flag's definition was read. |
manifest_version | The 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:
| Condition | HTTP status | Error code |
|---|---|---|
environment is not declared in namespace.toml | 400 | invalid_request |
Context attribute's runtime type disagrees with the manifest's inferred type for that attribute (E034) | 400 | invalid_request (details carries attribute, expected, actual) |
| The flag namespace exists but no manifest has been uploaded yet | 404 | namespace_not_found |
| A context attribute value is not a string, number, or boolean | 400 | invalid_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
| Parameter | Description |
|---|---|
tenant | Optional 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
- resolution — the four-step algorithm the server walks to produce each result.
- predicates, buckets — what rules consult.
- evaluation-context —
context.attributesshape and type rules. - tokens §
namespace-client— the public-token contract. - access-control § Public evaluation — public-evaluate gating, untrusted attributes, CORS.
exd eval,exd explain— local single-shot evaluation against an in-memory manifest.