API Reference
Everything you need to evaluate flags and manage your feature flag configuration programmatically.
Contents
MCP Server — AI-native flag control
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
| Tool | Description |
|---|---|
| list_projects | List all projects in your organization |
| list_environments | List environments for a project |
| list_flags | List all feature flags in a project |
| get_flag_state | Get a flag's current state and tenant list |
| set_flag_state | Set a flag to enabled, disabled, or partially_enabled |
| get_flag_history | Audit log for a flag — newest first |
| list_tenants | List tenant IDs in the partial rollout list |
| add_tenant | Add a tenant to the partial rollout list |
| remove_tenant | Remove 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-pyBASE_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.
/evaluateOmit 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.
/evaluate/streamEvents: 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.
/api/v1/projects/api/v1/projects/api/v1/projects/{projectId}/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.
/api/v1/projects/{projectId}/environments/api/v1/projects/{projectId}/environments/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.
/api/v1/projects/{projectId}/flags/api/v1/projects/{projectId}/flags/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.
/api/v1/projects/{projectId}/flags/{flagId}/environments/{envId}/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.
/api/v1/api-keys/api/v1/api-keys/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()