API Reference

Everything you need to evaluate flags and manage your feature flag configuration programmatically.

Language

MCP Server — AI-native flag control

What is MCP? The Model Context Protocol lets AI assistants like Claude Desktop call external tools directly. The Flagr MCP server exposes your feature flags as tools — so you can toggle, inspect, and roll back flags in plain English, without opening a dashboard.

Install

go install github.com/flagr-dev/flagr-mcp@latest

Configure Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "flagr": {
      "command": "/Users/you/go/bin/flagr-mcp",
      "env": {
        "FLAGR_API_KEY": "sk_org_..."
      }
    }
  }
}

Restart Claude Desktop — a hammer icon will appear confirming the tools are loaded.

Available tools

ToolDescription
list_projectsList all projects in your organization
list_environmentsList environments for a project
list_flagsList all feature flags in a project
get_flag_stateGet a flag's current state and tenant list
set_flag_stateSet a flag to enabled, disabled, or partially_enabled
get_flag_historyAudit log for a flag — newest first
list_tenantsList tenant IDs in the partial rollout list
add_tenantAdd a tenant to the partial rollout list
remove_tenantRemove a tenant from the partial rollout list

Example prompts

  • ›“Disable the payments-v2 flag in production immediately.”
  • ›“Which flags are currently partially enabled in staging?”
  • ›“Add tenant acme-corp to the dark-mode rollout.”
  • ›“Show me the change history for new-checkout — we had an incident last night.”

Authentication

Two key types are used depending on which API you're calling:

  • sdk_live_... — SDK key. Read-only access to flag states for a specific environment. Obtained when creating an environment.
  • sk_org_... — Org API key. Full management access scoped to your organization. Created from the API Keys page.
pip install requests sseclient-py
BASE_URL = "https://api.flagr.dev"

# SDK key — for evaluation endpoints (read-only)
SDK_KEY = "sdk_live_..."

# Org API key — for management endpoints (create/update/delete)
ORG_KEY = "sk_org_..."

sdk_headers  = {"Authorization": f"Bearer {SDK_KEY}"}
org_headers  = {"Authorization": f"Bearer {ORG_KEY}", "Content-Type": "application/json"}

Evaluate Flags

Returns the current state of one or more flags. All evaluation is done server-side against the Redis cache — never hits the database.

POST/evaluate

Omit flag-keysto return all flags for the environment. Flags that don't exist are omitted from the response (not an error).

import requests

response = requests.post(
    f"{BASE_URL}/evaluate",
    headers=sdk_headers,
    json={"flag-keys": ["new-feature", "dark-mode", "beta-rollout"]},
)
flags = response.json()
# {
#   "new-feature":   {"status": "enabled"},
#   "dark-mode":     {"status": "disabled"},
#   "beta-rollout":  {"status": "partially_enabled", "enabled-list": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890", "b2c3d4e5-f6a7-8901-bcde-f01234567891"]}
# }

# Check a boolean flag
if flags.get("new-feature", {}).get("status") == "enabled":
    print("New feature is on")

# Check partial rollout
beta = flags.get("beta-rollout", {})
if beta.get("status") == "partially_enabled":
    if "a1b2c3d4-e5f6-7890-abcd-ef1234567890" in beta.get("enabled-list", []):
        print("User is in the beta")

# Omit flag-keys to fetch all flags in the environment
all_flags = requests.post(
    f"{BASE_URL}/evaluate",
    headers=sdk_headers,
    json={},
).json()

SSE Stream

Opens a persistent Server-Sent Events connection. Receives a full snapshot on connect, then real-time updates whenever a flag state changes.

GET/evaluate/stream

Events: init (full snapshot) and flag_update (single flag change).

import json
import requests
from sseclient import SSEClient

# Local cache updated in real time
flags: dict = {}

response = requests.get(
    f"{BASE_URL}/evaluate/stream",
    headers={**sdk_headers, "Accept": "text/event-stream"},
    stream=True,
)

for event in SSEClient(response).events():
    if event.event == "init":
        # Full snapshot of all flags — seed the local cache
        flags = json.loads(event.data)
        print("Loaded", len(flags), "flags")

    elif event.event == "flag_update":
        # {"flag_key": "new-feature", "state": "enabled", "enabled_list": null}
        update = json.loads(event.data)
        key = update["flag_key"]
        flags[key] = {
            "state": update["state"],
            "enabled_list": update.get("enabled_list"),
        }
        print(f"Flag updated: {key} → {update['state']}")

Projects

Projects group environments and flags. Requires an org API key.

GET/api/v1/projects
POST/api/v1/projects
GET/api/v1/projects/{projectId}
DELETE/api/v1/projects/{projectId}
import requests

# List projects
projects = requests.get(f"{BASE_URL}/api/v1/projects", headers=org_headers).json()

# Create a project
project = requests.post(
    f"{BASE_URL}/api/v1/projects",
    headers=org_headers,
    json={"name": "My App", "slug": "my-app"},
).json()
# {"id": "...", "org_id": "...", "name": "My App", "slug": "my-app", ...}

project_id = project["id"]

# Get a project by ID
project = requests.get(f"{BASE_URL}/api/v1/projects/{project_id}", headers=org_headers).json()

# Delete a project
requests.delete(f"{BASE_URL}/api/v1/projects/{project_id}", headers=org_headers)

Environments

Each environment has its own SDK key and independent flag states. The sdk_key is only returned at creation — store it immediately.

POST/api/v1/projects/{projectId}/environments
GET/api/v1/projects/{projectId}/environments
DELETE/api/v1/projects/{projectId}/environments/{envId}
# Create an environment — returns sdk_key once (save it!)
env = requests.post(
    f"{BASE_URL}/api/v1/projects/{project_id}/environments",
    headers=org_headers,
    json={"name": "Production", "slug": "production"},
).json()
# {"id": "...", "sdk_key": "sdk_live_...", ...}
# sdk_key is only returned at creation — store it securely

sdk_key = env["sdk_key"]
env_id  = env["id"]

# List environments (sdk_key is not included in list responses)
envs = requests.get(
    f"{BASE_URL}/api/v1/projects/{project_id}/environments",
    headers=org_headers,
).json()

# Delete an environment
requests.delete(
    f"{BASE_URL}/api/v1/projects/{project_id}/environments/{env_id}",
    headers=org_headers,
)

Flags

Flags are created per project and automatically added to every environment in a disabled state. The flag key is immutable after creation.

POST/api/v1/projects/{projectId}/flags
GET/api/v1/projects/{projectId}/flags
DELETE/api/v1/projects/{projectId}/flags/{flagId}
# Create a flag — automatically added to all environments as "disabled"
flag = requests.post(
    f"{BASE_URL}/api/v1/projects/{project_id}/flags",
    headers=org_headers,
    json={"name": "New Feature", "key": "new-feature"},
).json()
# Flag key must match ^[a-z0-9-]+$ and is immutable after creation

flag_id = flag["id"]

# List flags
flags = requests.get(
    f"{BASE_URL}/api/v1/projects/{project_id}/flags",
    headers=org_headers,
).json()

# Delete a flag
requests.delete(
    f"{BASE_URL}/api/v1/projects/{project_id}/flags/{flag_id}",
    headers=org_headers,
)

Flag States

Controls the state of a flag in a specific environment. Changes are written to Postgres and immediately propagated to Redis and all connected SSE clients.

PATCH/api/v1/projects/{projectId}/flags/{flagId}/environments/{envId}
GET/api/v1/projects/{projectId}/flags/{flagId}/environments/{envId}

State values: enabled disabled partially_enabled

# Enable a flag in an environment
requests.patch(
    f"{BASE_URL}/api/v1/projects/{project_id}/flags/{flag_id}/environments/{env_id}",
    headers=org_headers,
    json={"state": "enabled"},
)

# Disable a flag
requests.patch(
    f"{BASE_URL}/api/v1/projects/{project_id}/flags/{flag_id}/environments/{env_id}",
    headers=org_headers,
    json={"state": "disabled"},
)

# Partially enable — only listed IDs evaluate to true
requests.patch(
    f"{BASE_URL}/api/v1/projects/{project_id}/flags/{flag_id}/environments/{env_id}",
    headers=org_headers,
    json={
        "state": "partially_enabled",
        "enabled_list": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890", "b2c3d4e5-f6a7-8901-bcde-f01234567891", "user_3"],
    },
)

# Get current state
state = requests.get(
    f"{BASE_URL}/api/v1/projects/{project_id}/flags/{flag_id}/environments/{env_id}",
    headers=org_headers,
).json()
# {"state": "partially_enabled", "enabled_list": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890", "b2c3d4e5-f6a7-8901-bcde-f01234567891", "user_3"], ...}

API Keys

Long-lived secrets for programmatic management API access. The full key value is only returned once — at creation.

POST/api/v1/api-keys
GET/api/v1/api-keys
DELETE/api/v1/api-keys/{keyId}
# Create an API key — full key is only returned once (save it!)
key = requests.post(
    f"{BASE_URL}/api/v1/api-keys",
    headers=org_headers,
    json={"name": "CI Pipeline"},
).json()
# {"id": "...", "key": "sk_org_...", "key_prefix": "sk_org_a1b2...", ...}

token = key["key"]  # use this as your Bearer token — never returned again

# List keys (only key_prefix is shown, not the full key)
keys = requests.get(f"{BASE_URL}/api/v1/api-keys", headers=org_headers).json()

# Delete / revoke a key
requests.delete(f"{BASE_URL}/api/v1/api-keys/{key['id']}", headers=org_headers)

# Using an org API key to call the management API
api_key_headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json",
}
projects = requests.get(f"{BASE_URL}/api/v1/projects", headers=api_key_headers).json()