API & SDK
The public v1 API is how your application evaluates flags and records experiment conversions. This page covers authentication, the endpoints, and how to integrate them.
For the complete endpoint reference — request and response schemas, error codes, and code examples in 9 languages — see the v1 API reference.
Base URL and authentication
The entire public API is a single route, /api/v1/flags. Every request must
include an API key as a Bearer token:
Authorization: Bearer rf_live_xxxxxxxxxxxxThe key's environment determines which environment is evaluated. You may also
pass an explicit environment in the request body — it must match the key.
One endpoint, inferred operations
There is no op parameter — /api/v1/flags works out what you want from the
request shape:
GET /api/v1/flags— list flag metadata. With?flag=KEY, the metadata for one flag.POST /api/v1/flags— the body decides:eventpresent → record a conversion eventkeyspresent → evaluate multiple flagsflag+userpresent → evaluate a single flagflagpresent, nouser→ one flag's metadata- empty body → list flag metadata
A body that sets more than one of event / keys / flag is rejected with
400 ambiguous_request.
Rate limits
Two limits apply, and both cover every request — evaluations, conversion
events and metadata reads alike (GET and POST):
- 10,000 requests per second per API key.
- 50 requests per second per client IP — front-line abuse protection.
Exceeding either returns HTTP 429 rate_limited. Limits refill continuously
rather than in fixed windows. Cache evaluation results briefly on your side
rather than calling eval on every render.
Separately, accounts on the free plan that exceed their monthly request cap get
HTTP 402 usage_exceeded on every request — evaluations and metadata reads
alike — until the plan is upgraded. See Billing.
Error format
Every error uses the same envelope:
{
"error": { "code": "unauthorized", "message": "Invalid or missing API key." }
}Common status codes: 400 bad request, 401 unauthorized, 402 usage cap
reached, 404 not found, 429 rate limited, 500 server error.
Evaluating a single flag
POST /api/v1/flags with a flag key and a user — the presence of both
selects single-flag evaluation.
curl https://api.restflags.com/api/v1/flags \
-H "Authorization: Bearer rf_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"flag": "new-checkout",
"environment": "production",
"user": { "id": "user-123", "plan": "pro", "country": "US" }
}'The user object must include an id. Any other attributes you add
(plan, country, email, …) are available to targeting rules.
Response:
{ "value": true, "variant": "on", "reason": "rule_match" }The reason explains the decision: rule_match, fallthrough,
fallthrough_off, flag_off or prerequisite_failed.
Evaluating many flags at once
POST /api/v1/flags with a keys array — evaluate several flags for one user
in a single request. Prefer this on page load instead of many single-flag calls.
A single request may evaluate up to 100 flags; more than that returns
400 too_many_keys.
curl https://api.restflags.com/api/v1/flags \
-H "Authorization: Bearer rf_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"keys": ["new-checkout", "dark-mode"],
"user": { "id": "user-123" }
}'Response — results keyed by flag key:
{
"results": {
"new-checkout": { "value": true, "variant": "on", "reason": "rule_match" },
"dark-mode": { "value": false, "variant": "off", "reason": "flag_off" }
}
}If a requested key does not exist, its entry is { "error": "not_found" }
instead of an eval result — the request as a whole still returns 200.
Listing flags
GET /api/v1/flags— list every flag's metadata (key, name, kind, variations, tags, archived).GET /api/v1/flags?flag={key}— one flag's metadata. Returns404if the key does not exist.
These return flag metadata only — never targeting rules. To get the value a
specific user should see, POST with a user (and a flag or keys).
Tracking conversions
POST /api/v1/flags with an event records a conversion event for
experiments. Call it when a user completes the goal you
are measuring.
curl https://api.restflags.com/api/v1/flags \
-H "Authorization: Bearer rf_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"event": "purchase",
"user": { "id": "user-123" },
"value": 4999
}'event— the metric name. It must match an experiment's primary metric.user.id— the same id you used when evaluating the flag, so the conversion is attributed to the right variant.value— optional numeric value (e.g. revenue in cents) summed into results.
Integration pattern
A typical client integration:
const RF_URL = "https://api.restflags.com/api/v1/flags";
const headers = {
Authorization: `Bearer ${process.env.RESTFLAGS_KEY}`,
"Content-Type": "application/json",
};
// 1. Evaluate the flags a page needs, in one call.
async function loadFlags(user) {
const res = await fetch(RF_URL, {
method: "POST",
headers,
body: JSON.stringify({ keys: ["new-checkout", "dark-mode"], user }),
});
return (await res.json()).results;
}
// 2. Record a conversion when the user completes the goal.
async function trackPurchase(user, amountCents) {
await fetch(RF_URL, {
method: "POST",
headers,
body: JSON.stringify({ event: "purchase", user, value: amountCents }),
});
}Good practice
- Evaluate flags as close to the user as you can, and cache results for a short window to stay well under the rate limits. For high-volume server-side traffic from a single IP, mind the per-IP limit and prefer bulk evaluation.
- Always send a stable
user.id— it keeps rollouts and experiment assignment consistent for that user. - Keep your API key in an environment variable or secrets manager, never in client-side source.