REST API
Everything the dashboard does goes through the REST API at https://flag.nightroll.app/api/v1. Paths below are
relative to it; {org} is your org’s slug (or id) and must be the org your key belongs to.
Authentication
Send an API key as a bearer token:
curl https://flag.nightroll.app/api/v1/orgs/$ORG/projects/default/flags \
-H "Authorization: Bearer nr_flag_..."
Make keys under Settings → API keys. A key is bound to one org, acts with your role there, and is limited by its scopes:
| Scope | Allows |
|---|---|
read |
reading projects, flags, segments, change requests, history and insights; evaluating |
write |
also changing flags and segments, and proposing change requests |
admin |
everything your role allows: projects, environments, SDK keys, org settings |
A key (or an MCP client connected with OAuth) acts as an agent: its changes to a critical environment become
change requests unless an admin turns that off in the dashboard’s Settings, and it can never review a change
request. GET /me shows who and which org a key is for.
The dashboard uses a session cookie; state-changing cookie requests must come from Flag’s own origin.
Errors
Errors are JSON with a stable code and a message written for people:
{"error": {"code": "conflict", "message": "Flag new-checkout already exists in default."}}
| Status | Code | When |
|---|---|---|
| 400 | invalid_request |
the request is invalid (the message says how) |
| 401 | unauthorized |
no or bad credentials |
| 403 | forbidden, insufficient_scope |
your role or the key’s scopes don’t allow it |
| 404 | not_found |
|
| 409 | conflict |
the state doesn’t allow it (a key in use, a request already decided…) |
| 503 | unavailable |
Dry runs
Every mutation accepts ?dryRun=true: it runs in a transaction that is rolled back and answers with what would have
happened, plus "dryRun": true. Use it to preview a change, or to validate one.
curl -X PATCH 'https://flag.nightroll.app/api/v1/orgs/acme/projects/default/flags/new-checkout?dryRun=true' \
-H "Authorization: Bearer $FLAG_TOKEN" -H 'content-type: application/json' \
-d '{"environmentKey": "production", "instructions": [{"kind": "turnFlagOn"}]}'
# {"outcome": "applied", "dryRun": true, "diff": {"production": [{"path": "/on", "from": false, "to": true}]}, "flag": {...}}
Organizations, projects and environments
Paths take the org’s id or slug, and project, environment and flag keys.
GET /me |
you, the key’s org and your role there |
GET /orgs/{org} |
the org, your role, its projects and environments |
GET|POST /orgs/{org}/projects, GET|PATCH|DELETE .../projects/{proj} |
projects (DELETE archives and revokes SDK keys) |
GET|POST .../projects/{proj}/environments, GET|PATCH|DELETE .../environments/{env} |
environments: critical, requireApproval, minApprovals, allowSelfApproval (owners and admins); deleting a critical one needs ?confirm=<key> |
GET|POST .../environments/{env}/keys, DELETE .../keys/{id} |
SDK keys; {"kind": "server"} returns the key once; expireOthersInHours rotates; ?expireInHours= revokes later |
GET .../projects/{proj}/compare?from=&to= |
which flags and segments differ between two environments |
POST .../environments/{env}/copy |
copy targeting ({"from", "flags"?, "includeSegments"?}) into {env}, through its approvals |
Flags
GET .../flags |
flags with a summary per environment; ?tag=, ?q=, ?archived=true, ?full=true |
POST .../flags |
create: {"key", "name"?, "template"?, "kind"?, "variations"?, "tags"?, "clientSide"?, ...} |
GET .../flags/{flag} |
the flag with its full targeting in every environment, dependents, pending change requests and schedules |
PATCH .../flags/{flag} |
change it with semantic patch instructions |
DELETE .../flags/{flag} |
archive (restore with restoreFlag) |
GET .../flags/{flag}/history?env= |
versions, newest first, with who changed what and the diff (omit env for flag-wide settings) |
POST .../flags/{flag}/restore |
{"revisionId"}: write an earlier version back as a new one |
POST .../flags/{flag}/evaluate |
{"environmentKey", "context"}: the result and every step that led to it |
GET .../flags/{flag}/insights?env=&hours= |
evaluations per hour and variation |
POST .../flags/{flag}/blast-radius |
{"environmentKey", "instructions"?}: traffic, dependents, segments, streams and a risk level |
GET|POST /orgs/{org}/templates, DELETE .../templates/{key} |
flag templates (built-in and your own) |
Semantic patch
A change is a list of instructions applied in order, all or nothing:
{
"environmentKey": "production",
"instructions": [
{"kind": "turnFlagOn"},
{"kind": "addRule", "description": "Acme staff",
"clauses": [{"contextKind": "user", "attribute": "email", "op": "endsWith", "values": ["@acme.com"]}],
"variation": 0},
{"kind": "updateFallthroughVariationOrRollout",
"rollout": {"variations": [{"variation": 0, "weight": 10000}, {"variation": 1, "weight": 90000}]}}
],
"comment": "Acme first, then 10%"
}
The answer has an outcome: applied, unchanged, or changeRequest when the environment requires approval (or an
agent writes to a critical environment); then changeRequest holds the request. It also has the updated flag and the
diff per scope ("flag" for flag-wide settings, the environment’s key for targeting). Owners and admins may add
?bypassApprovals=true to apply directly where approval is required; it is audited.
Targeting instructions (need environmentKey):
kind |
Fields |
|---|---|
turnFlagOn, turnFlagOff |
|
updateOffVariation |
variation (index, or null to serve the SDK’s default) |
updateFallthroughVariationOrRollout |
variation or rollout (the default rule) |
addTargets, removeTargets |
variation, values (keys), contextKind (default user) |
replaceTargets |
targets: [{"contextKind", "variation", "values"}] |
addRule |
clauses, variation or rollout, description?, ruleId?, beforeRuleId? |
removeRule |
ruleId |
updateRuleVariationOrRollout |
ruleId, variation or rollout |
updateRuleDescription |
ruleId, description |
addClauses |
ruleId, clauses |
removeClauses |
ruleId, clauseIndexes |
reorderRules |
ruleIds (every rule, in the new order) |
replaceRules |
rules: [{"id"?, "description"?, "clauses", "variation" or "rollout", "trackEvents"?}] |
addPrerequisite, updatePrerequisite |
key (another flag), variation it must serve |
removePrerequisite |
key |
updateTrackEvents |
value: send a full event per evaluation |
replaceConfig |
config: the whole environment configuration |
Flag-wide instructions (no environmentKey; in an environment that requires approval, send them separately):
kind |
Fields |
|---|---|
updateName, updateDescription |
value |
addTags, removeTags |
values |
addMaintainers, removeMaintainers |
values (user ids) |
makeFlagTemporary, makeFlagPermanent |
|
turnOnClientSideAvailability, turnOffClientSideAvailability |
|
addVariation |
value, name?, description? |
updateVariation |
index, and any of value, name, description |
updateDefaultVariations |
onVariation, offVariation (for new environments) |
updateJsonSchema |
schema (JSON flags; null removes it) |
archiveFlag, restoreFlag |
|
replaceMeta |
meta: every flag-wide field |
Clauses: {"contextKind": "user", "attribute": "email", "op": "endsWith", "values": ["@acme.com"], "negate": false}.
Operators: in, startsWith, endsWith, contains, matches (regular expression), lessThan, lessThanOrEqual,
greaterThan, greaterThanOrEqual (numbers), before, after (RFC 3339 or Unix milliseconds), semVerEqual,
semVerLessThan, semVerGreaterThan, and segmentMatch (values are segment keys; no attribute). A rule matches when
all its clauses do. Exact semantics: evaluation spec.
Rollouts: {"variations": [{"variation": 0, "weight": 25000}, {"variation": 1, "weight": 75000}], "bucketBy": "key", "contextKind": "user"}. Weights are thousandths of a percent and add up to 100000. A context always lands in
the same bucket for the same flag.
Segments
GET|POST .../projects/{proj}/environments/{env}/segments, GET|PATCH|DELETE .../segments/{seg}. Create with
{"key", "name"?, "included"?, "excluded"?, "includedContexts"?, "excludedContexts"?, "rules"?}; change with
{"instructions", "comment"?}:
kind |
Fields |
|---|---|
addIncludedTargets, removeIncludedTargets, addExcludedTargets, removeExcludedTargets |
values, contextKind (default user) |
addRule |
clauses, weight? (0 to 100000: only that share of matching contexts), bucketBy?, rolloutContextKind?, ruleId? |
removeRule |
ruleId |
replaceRules |
rules |
replaceBody |
body: {"included", "excluded", "includedContexts", "excludedContexts", "rules"} |
updateName, updateDescription |
value |
addTags, removeTags |
values |
Segments in environments that require approval change through change requests too. A segment in use can’t be deleted.
Change requests
GET .../projects/{proj}/change-requests?status=&env=, POST to propose ({"kind": "flag"|"segment", "targetKey", "environmentKey", "instructions", "comment"?, "runAt"?}), GET .../change-requests/{id} (with a preview of what it
would change now), and POST .../{id}/review ({"decision": "approve"|"decline"|"comment", "comment"?}),
.../{id}/apply, .../{id}/cancel. A request applies once it has the environment’s minApprovals approvals (at
runAt if it has one). People can’t approve their own requests unless the environment allows it; agents and tokens
can propose but never review.
Schedules and cleanup
GET|POST .../flags/{flag}/schedules, DELETE .../schedules/{id} |
targeting instructions to apply at runAt (Unix ms or RFC 3339); a timed change request where approval is required |
GET .../projects/{proj}/stale-flags |
temporary flags that look ready to remove, and why |
GET .../projects/{proj}/kill-switches?env= |
flags tagged kill-switch, on or off per environment |
Organization
GET|PATCH /orgs/{org}/settings |
agentProductionWrites: changeRequest (default) or direct (owners and admins) |
GET /orgs/{org}/export |
everything the org configured, as JSON (owners and admins) |
Members, invites, API keys, SSO, the audit log and usage are the platform’s, under Settings and its
JSON API at /api/orgs/{org}/...; your agent reaches them with the MCP tools listed on AI & MCP.
This page as Markdown: /docs/api.md · All docs for agents: /llms.txt