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_xxxxxxxxxxxx

The 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:
    • event present → record a conversion event
    • keys present → evaluate multiple flags
    • flag + user present → evaluate a single flag
    • flag present, no user → 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. Returns 404 if 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.