FlagBeta

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