Skip to content
NanoFlags
Start free

Reference

Management API

A versioned REST API for CI pipelines, scripts and infrastructure tooling: flags, drafts, segments, reviewed changes, publishing, revisions and the audit log, with the same rules as the dashboard. Version 1.0.0; the machine-readable description is /api/v1/openapi.json (OpenAPI 3.1).

Overview

text
https://edge.nanoflags.io/api/v1
  • Authenticate with an API token: Authorization: Bearer nf_api_…. SDK, CI and admin keys are not accepted here (they keep working on the edge API).
  • JSON in and out. Objects are addressed by key where they have one (/projects/commerce/flags/checkout-v2); ids work too.
  • Lists answer { data, nextCursor }; everything else answers the object.
  • A request runs as the person who created the token, with that person's current role, narrowed by the token's scopes.

API tokens

Tokens belong to an organization and can be limited to one project. Each has explicit scopes and an expiry (at most 366 days). Only a SHA-256 hash is stored: the token is shown once, when it is created. Every use is recorded in the audit log with the token's id (api_token.used; reads at most once a minute per operation), and every change made with it carries detail.via = "api_token" and detail.api_token_id.

  • Who: organization owners and admins create, list and revoke tokens. Creating one is an admin write, so organizations that require two-step verification need a verified session for it. Using a token needs no interactive step.
  • Acts as: the token's creator. If that person is later made a viewer, the token can only read; if they leave the organization, it stops working (401).
  • Limits: 50 active tokens per organization. Revocation is immediate.
Creating a token today
A token screen in the dashboard settings is on its way. Until then, an owner or admin creates tokens with the backend function the screen will use, from a signed-in session:
create a token (Node, signed in as an owner or admin)
import { createClient } from "@supabase/supabase-js";

// The dashboard's own backend; the publishable key is public by design.
const supabase = createClient("https://dofbwbbexgmdiejfapzr.supabase.co", "sb_publishable_eR5wR1bUwDar8aG229JqxQ_RuEHxvpp");
await supabase.auth.signInWithPassword({ email: process.env.NANOFLAGS_EMAIL, password: process.env.NANOFLAGS_PASSWORD });

const { data, error } = await supabase.rpc("create_api_token", {
  p_org: "<organization id>",            // Settings → Organization
  p_name: "GitHub Actions (commerce)",
  p_scopes: ["flags:read", "changes:*", "publish"],
  p_expires_at: new Date(Date.now() + 90 * 864e5).toISOString(),   // ≤ 366 days
  p_project: "<project id>",              // optional: limit the token to one project
});
if (error) throw error;                   // e.g. FORBIDDEN (not an admin), MFA_REQUIRED, LIMIT_REACHED
console.log(data.token);                  // nf_api_… — shown once; store it as a CI secret
token functions
create_api_token(p_org uuid, p_name text, p_scopes text[], p_expires_at timestamptz, p_project uuid default null)
  → { id, name, prefix, orgId, projectId, projectKey, scopes, expiresAt, createdAt, createdBy, createdByName, lastUsedAt, revokedAt, status, token }
list_api_tokens(p_org uuid)   → [ { …same fields, without token } ]   (owners and admins)
revoke_api_token(p_id uuid)   → { …same fields, status: "revoked" }    (owners and admins; idempotent)

Check a token with GET /token. Treat tokens like passwords: keep them in your CI secret store, give each pipeline its own, and prefer project tokens with the fewest scopes.

Scopes

ScopeAllows
none neededReading the token, projects and environments.
flags:readFlags, flag configurations, drafts, revisions and diffs, scenarios and run results.
flags:writeCreate, edit, archive flags; edit drafts; restore a revision into a draft. Implies flags:read.
segments:read, segments:writeRead / create, edit, delete segments. segments:* grants both.
changes:read, changes:write, changes:approveRead / create and edit changes and submit them / approve or reject. changes:* grants all three.
publishRead publish requirements and publish an environment's draft.
audit:readThe audit log (the token's project only, for project tokens).
keys:readSDK key metadata (name, kind, prefix, last use). Never key material.

A missing scope answers 403 INSUFFICIENT_SCOPE; a role that does not allow the action answers 403 FORBIDDEN.

Same rules as the dashboard

Every endpoint calls the same database functions and row-level security as the dashboard. Nothing is checked only in the API.

  • Roles: editors edit flags, drafts, segments and changes and publish; approving needs admin or owner; SDK keys are listed for admins only.
  • Independent approval: the author of a change, and anyone who edited that environment's draft or project-wide content since the change's base revision, cannot approve it (403 SELF_APPROVAL). With a token, “anyone” is the token's creator. Approvals are bound to the draft checksum and go stale when the draft changes.
  • Publishing policy: where an environment requires approval, publishing needs an approved change (422 CHANGE_REQUIRED, APPROVAL_REQUIRED); where it requires scenario checks, every saved scenario must pass on the current draft in a run by CI or by someone other than the author and the draft editors (422 SCENARIOS_REQUIRED). Publishing also needs the revision you expect to replace (409 DESTINATION_CHANGED).
  • Protected environments: their policy cannot be relaxed or removed through this API at all.
  • Plans: features your plan does not include answer 402 PLAN_REQUIRED; data you already have stays readable.

Errors

json
{ "error": { "code": "APPROVAL_REQUIRED", "message": "An independent approval of the current draft is required." } }
StatuscodeWhen
400INVALID_REQUEST, INVALID_JSON, INVALID_CURSORThe body, a parameter or a header does not meet the endpoint's rules.
401UNAUTHORIZEDMissing, malformed, unknown, revoked or expired token, or its creator left the organization.
402PLAN_REQUIRED, PLAN_LIMITThe organization's plan does not include this.
403FORBIDDEN, INSUFFICIENT_SCOPE, SELF_APPROVAL, OWNER_REQUIREDRole, scope or approval independence.
404NOT_FOUNDUnknown path, or the object does not exist in the token's organization or project.
405METHOD_NOT_ALLOWEDWrong method; see Allow.
409CONFLICT, DESTINATION_CHANGED, NO_CHANGES, LIMIT_REACHEDDuplicate key, a concurrent publish, nothing to publish, a limit reached.
413PAYLOAD_TOO_LARGEBody above 256 KB.
422INVALID_VALUE, INVALID_STATE, CHANGE_REQUIRED, APPROVAL_REQUIRED, SCENARIOS_REQUIRED, IDEMPOTENCY_MISMATCHRefused by a rule: value validation, the change's state, the publishing policy.
429RATE_LIMITEDToo many requests; wait for Retry-After seconds.
503ORIGIN_UNAVAILABLENanoFlags could not be reached; Retry-After is set. Retry writes with the same Idempotency-Key.

Pagination, idempotency, rate limits, caching

Cursor pagination

List endpoints take limit (1–100, default 50) and answer nextCursor; pass it back as cursor until it is null. Cursors are opaque and belong to one list.

Idempotency-Key

Send Idempotency-Key (up to 255 visible ASCII characters) on any POST. Retrying the same request with the same key within 24 hours returns the first answer with Idempotency-Replayed: true and changes nothing; the same key with a different request answers 422 IDEMPOTENCY_MISMATCH. The key is stored in the same database transaction as the change, so a failed request is not stored and can simply be retried.

Rate limits

600 requests per 60 seconds per token at each Cloudflare location, announced as RateLimit-Policy: "token";q=600;w=60. Above it: 429 with Retry-After.

ETags

GET answers carry a strong ETag of the body; send it as If-None-Match to get 304 Not Modified. Answers are private (Cache-Control: private, no-cache). Every answer has an X-Request-Id; quote it when you contact support.

Example: publish a reviewed change from CI

  1. A developer opens a change (POST /changes) with the flags in scope and submits it.
  2. Another admin approves it in the dashboard or with their own token.
  3. The release job checks the requirements and publishes exactly the approved draft:
release job
# .github/workflows/release.yml (excerpt) — NANOFLAGS_API_TOKEN has flags:read, changes:*, publish
API=https://edge.nanoflags.io/api/v1/projects/commerce
H=(-H "Authorization: Bearer $NANOFLAGS_API_TOKEN" -H "Content-Type: application/json")

# 1. Is the reviewed change ready? (approvals by someone else, scenarios passing)
REQ=$(curl -sf "${H[@]}" "$API/environments/production/publish-requirements?change=$CHANGE_ID")
echo "$REQ" | jq -e .canPublish > /dev/null || { echo "$REQ" | jq '{approval, scenarios}'; exit 1; }

# 2. Publish exactly what was reviewed; the key makes a retried job safe.
REV=$(echo "$REQ" | jq .publishedRevision)
curl -sf "${H[@]}" -H "Idempotency-Key: release-$GITHUB_RUN_ID" -X POST "$API/environments/production/publish" \
  -d "{\"expectedRevision\": $REV, \"change\": \"$CHANGE_ID\", \"note\": \"$GITHUB_SHA\"}"

The new revision reaches connected SDKs within about a second, like a dashboard publish.

Token

The token making the request.

The calling token

get/api/v1/tokenany token

Metadata of the token used for this request: scopes, project scope, expiry.

Answer 200: Token · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

example
curl https://edge.nanoflags.io/api/v1/token -H "Authorization: Bearer $NANOFLAGS_API_TOKEN"

Projects

Projects of the token's organization.

List projects

get/api/v1/projectsany token

Projects of the token's organization (only its project for a project token).

ParameterInTypeDescription
cursorquerystringOpaque cursor from nextCursor of the previous page.
limitqueryintegerPage size, 1–100 (default 50). Default 50.

Answer 200: ProjectList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Get a project

get/api/v1/projects/{project}any token
ParameterInTypeDescription
projectpath, requiredstringProject key or id.

Answer 200: Project · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Environments

Environments and their publishing policy.

List environments

get/api/v1/projects/{project}/environmentsany token

Ordered as in the dashboard. Not paginated (nextCursor is always null).

ParameterInTypeDescription
projectpath, requiredstringProject key or id.

Answer 200: EnvironmentList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Get an environment

get/api/v1/projects/{project}/environments/{environment}any token

Includes the publishing policy (protection, approvals, scenario checks) and the published revision.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentpath, requiredstringEnvironment key or id.

Answer 200: Environment · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Flags

Flag definitions (shared by every environment of a project).

List flags

get/api/v1/projects/{project}/flagsscope flags:read
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
archivedquerystringfalse (default), true or all. One of false, true, all.
cursorquerystringOpaque cursor from nextCursor of the previous page.
limitqueryintegerPage size, 1–100 (default 50). Default 50.

Answer 200: FlagList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

example
curl "https://edge.nanoflags.io/api/v1/projects/commerce/flags?limit=50" -H "Authorization: Bearer $NANOFLAGS_API_TOKEN"
# → { "data": [ { "key": "checkout-v2", … } ], "nextCursor": "djE6ZmxhZ3MubGlzdDpjaGVja291dC12Mg" }

Create a flag

post/api/v1/projects/{project}/flagsscope flags:write

Every environment's draft starts with initialValue; nothing is published. Editor role.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.

Request body: FlagCreate.

Answer 201: Flag · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

example
curl -X POST https://edge.nanoflags.io/api/v1/projects/commerce/flags \
  -H "Authorization: Bearer $NANOFLAGS_API_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-checkout-v3" \
  -d '{"key":"checkout-v3","type":"boolean","initialValue":false,"description":"New checkout flow"}'

Get a flag

get/api/v1/projects/{project}/flags/{flag}scope flags:read
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
flagpath, requiredstringFlag key.

Answer 200: Flag · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Update flag details

patch/api/v1/projects/{project}/flags/{flag}scope flags:write

Key and type are immutable. Editor role.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
flagpath, requiredstringFlag key.

Request body: FlagUpdate.

Answer 200: Flag · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

Archive a flag

post/api/v1/projects/{project}/flags/{flag}/archivescope flags:write

Removes the flag from every draft; it leaves published revisions on the next publish. Editor role.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
flagpath, requiredstringFlag key.

Answer 200: Flag · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

Restore an archived flag

post/api/v1/projects/{project}/flags/{flag}/unarchivescope flags:write
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
flagpath, requiredstringFlag key.

Answer 200: Flag · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

Flag configurations

A flag's draft configuration per environment.

Get a flag's configuration in an environment

get/api/v1/projects/{project}/flags/{flag}/environments/{environment}scope flags:read

The draft and, separately, what is published.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
flagpath, requiredstringFlag key.
environmentpath, requiredstringEnvironment key or id.

Answer 200: FlagConfig · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Edit a flag's draft in an environment

patch/api/v1/projects/{project}/flags/{flag}/environments/{environment}scope flags:write

Changes the draft only. Publishing is a separate step with the environment's policy. Editor role; the edit is recorded for independent approval (you cannot approve a change whose draft you edited).

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
flagpath, requiredstringFlag key.
environmentpath, requiredstringEnvironment key or id.

Request body: ConfigUpdate.

Answer 200: FlagConfig · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

example
curl -X PATCH https://edge.nanoflags.io/api/v1/projects/commerce/flags/checkout-v3/environments/staging \
  -H "Authorization: Bearer $NANOFLAGS_API_TOKEN" -H "Content-Type: application/json" \
  -d '{"enabled":true,"rules":[{"id":"beta","match":"all","conditions":[{"attribute":"","op":"segment","value":"beta-testers"}],"serve":{"type":"fixed","value":true}}]}'

Segments

Reusable audiences, part of every environment's draft.

List segments

get/api/v1/projects/{project}/segmentsscope segments:read
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
cursorquerystringOpaque cursor from nextCursor of the previous page.
limitqueryintegerPage size, 1–100 (default 50). Default 50.

Answer 200: SegmentList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Create a segment

post/api/v1/projects/{project}/segmentsscope segments:write

Segments are project-wide: part of every environment's draft. Editor role.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.

Request body: SegmentCreate.

Answer 201: Segment · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

example
curl -X POST https://edge.nanoflags.io/api/v1/projects/commerce/segments \
  -H "Authorization: Bearer $NANOFLAGS_API_TOKEN" -H "Content-Type: application/json" \
  -d '{"key":"beta-testers","name":"Beta testers","conditions":[{"attribute":"plan","op":"in","value":["pro","scale"]}]}'

Get a segment

get/api/v1/projects/{project}/segments/{segment}scope segments:read
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
segmentpath, requiredstringSegment key.

Answer 200: Segment · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Update a segment

patch/api/v1/projects/{project}/segments/{segment}scope segments:write
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
segmentpath, requiredstringSegment key.

Request body: SegmentUpdate.

Answer 200: Segment · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

Delete a segment

delete/api/v1/projects/{project}/segments/{segment}scope segments:write

Rules that still reference the segment stop matching it (an unknown segment matches nobody), as in the dashboard. Check GET …/draft before publishing.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
segmentpath, requiredstringSegment key.

Answer 200: Deleted · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

Changes

Reviewed changes and independent approvals.

List changes

get/api/v1/projects/{project}/changesscope changes:read

Newest first.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentquerystringEnvironment key or id.
statusquerystringFilter by status. One of draft, testing, awaiting_approval, approved, published, closed.
cursorquerystringOpaque cursor from nextCursor of the previous page.
limitqueryintegerPage size, 1–100 (default 50). Default 50.

Answer 200: ChangeList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Create a change

post/api/v1/projects/{project}/changesscope changes:write

The token's creator becomes the author. baseRevision is the environment's current revision. Editor role.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.

Request body: ChangeCreate.

Answer 201: Change · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

example
curl -X POST https://edge.nanoflags.io/api/v1/projects/commerce/changes \
  -H "Authorization: Bearer $NANOFLAGS_API_TOKEN" -H "Content-Type: application/json" \
  -d '{"environment":"production","title":"Launch checkout v3","codeRef":"https://github.com/acme/shop/pull/412","flags":["checkout-v3"]}'

Get a change with its flags and approvals

get/api/v1/projects/{project}/changes/{change}scope changes:read
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
changepath, requiredstring (uuid)Change id.

Answer 200: Change · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Edit a change or move it to draft/testing/closed

patch/api/v1/projects/{project}/changes/{change}scope changes:write

Finished changes (published, closed) refuse every edit with 422 INVALID_STATE.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
changepath, requiredstring (uuid)Change id.

Request body: ChangeUpdate.

Answer 200: Change · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

Add a flag to a change

post/api/v1/projects/{project}/changes/{change}/flagsscope changes:write
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
changepath, requiredstring (uuid)Change id.

Request body: ChangeFlag.

Answer 200: Change · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

Remove a flag from a change

delete/api/v1/projects/{project}/changes/{change}/flags/{flag}scope changes:write
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
changepath, requiredstring (uuid)Change id.
flagpath, requiredstringFlag key.

Answer 200: Change · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

Request approval

post/api/v1/projects/{project}/changes/{change}/submitscope changes:write

Moves the change to awaiting_approval.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
changepath, requiredstring (uuid)Change id.

Answer 200: Change · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

example
curl -X POST https://edge.nanoflags.io/api/v1/projects/commerce/changes/$CHANGE_ID/submit -H "Authorization: Bearer $NANOFLAGS_API_TOKEN"

Approve or reject a change

post/api/v1/projects/{project}/changes/{change}/approvalsscope changes:approve

Admin or owner. Independence rules apply to the token's creator: the author, and anyone who edited the environment's draft (or project-wide content) since the change's base revision, get 403 SELF_APPROVAL. An approval is bound to the current draft checksum and goes stale when the draft changes.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
changepath, requiredstring (uuid)Change id.

Request body: Decision.

Answer 200: Change · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

example
curl -X POST https://edge.nanoflags.io/api/v1/projects/commerce/changes/$CHANGE_ID/approvals \
  -H "Authorization: Bearer $NANOFLAGS_API_TOKEN" -H "Content-Type: application/json" \
  -d '{"decision":"approved","comment":"Scenarios pass, rollout plan reviewed"}'

Publishing

Publishing an environment's draft as a new revision.

What publishing the current draft needs

get/api/v1/projects/{project}/environments/{environment}/publish-requirementsscope publish
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentpath, requiredstringEnvironment key or id.
changequerystring (uuid)Change id to evaluate approvals for.

Answer 200: PublishRequirements · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

example
curl "https://edge.nanoflags.io/api/v1/projects/commerce/environments/production/publish-requirements?change=$CHANGE_ID" -H "Authorization: Bearer $NANOFLAGS_API_TOKEN"

Publish the environment's draft

post/api/v1/projects/{project}/environments/{environment}/publishscope publish

Publishes the draft as revision N+1 exactly like the dashboard: refused with 409 DESTINATION_CHANGED (another publish happened), 409 NO_CHANGES, 422 CHANGE_REQUIRED / APPROVAL_REQUIRED / SCENARIOS_REQUIRED when the environment's policy is not met, 422 INVALID_VALUE for an invalid draft. Editor role. Distribution to SDKs starts immediately.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentpath, requiredstringEnvironment key or id.

Request body: Publish.

Answer 200: PublishResult · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

example
REV=$(curl -s https://edge.nanoflags.io/api/v1/projects/commerce/environments/production -H "Authorization: Bearer $NANOFLAGS_API_TOKEN" | jq .publishedRevision)
curl -X POST https://edge.nanoflags.io/api/v1/projects/commerce/environments/production/publish \
  -H "Authorization: Bearer $NANOFLAGS_API_TOKEN" -H "Content-Type: application/json" \
  -H "Idempotency-Key: publish-$GITHUB_RUN_ID" \
  -d "{\"expectedRevision\":$REV,\"change\":\"$CHANGE_ID\",\"note\":\"Release 2026.11\"}"
# → { "environment": "production", "revision": 42, "checksum": "…", "committedAt": 1762927598541 }

Scheduled publishing

Publishing a pinned draft at a future time, under the same rules as publishing now (Team plan and above).

List scheduled publishes

get/api/v1/projects/{project}/environments/{environment}/schedulesscope publish

Upcoming and past schedules of the environment, latest time first.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentpath, requiredstringEnvironment key or id.
statusquerystringFilter by status. One of scheduled, published, failed, canceled.
cursorquerystringOpaque cursor from nextCursor of the previous page.
limitqueryintegerPage size, 1–100 (default 50). Default 50.

Answer 200: ScheduleList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Schedule a publish of the current draft

post/api/v1/projects/{project}/environments/{environment}/schedulesscope publish

Pins the current draft (its checksum) and the destination revision, and publishes exactly that at runAt, as the token's creator, through the same path as POST …/publish: role, approvals (valid on the pinned draft at that time), scenario checks, protected-environment policy and audit all apply when it runs. If the draft or the destination changed, or approvals are not valid then, nothing is published and the schedule fails (errorCode), with a schedule.failed webhook and e-mail; success sends schedule.executed. One pending schedule per environment (409 CONFLICT). Team plan and above (402 PLAN_REQUIRED). Editor role.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentpath, requiredstringEnvironment key or id.

Request body: ScheduleCreate.

Answer 201: Schedule · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

Cancel a scheduled publish

delete/api/v1/projects/{project}/environments/{environment}/schedules/{schedule}scope publish

Only pending schedules can be canceled (422 INVALID_STATE otherwise). Any editor of the project may cancel. Canceling is allowed on every plan.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentpath, requiredstringEnvironment key or id.
schedulepath, requiredstring (uuid)Schedule id.

Answer 200: Schedule · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

Revisions

Immutable published revisions, diffs and restore-to-draft.

The environment's draft as a bundle

get/api/v1/projects/{project}/environments/{environment}/draftscope flags:read
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentpath, requiredstringEnvironment key or id.

Answer 200: Draft · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

List published revisions

get/api/v1/projects/{project}/environments/{environment}/revisionsscope flags:read

Newest first, without bundles.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentpath, requiredstringEnvironment key or id.
cursorquerystringOpaque cursor from nextCursor of the previous page.
limitqueryintegerPage size, 1–100 (default 50). Default 50.

Answer 200: RevisionList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Get a revision with its bundle

get/api/v1/projects/{project}/environments/{environment}/revisions/{number}scope flags:read
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentpath, requiredstringEnvironment key or id.
numberpath, requiredintegerRevision number.

Answer 200: RevisionDetail · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Field-level diff from a revision to the draft or another revision

get/api/v1/projects/{project}/environments/{environment}/revisions/{number}/diffscope flags:read
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentpath, requiredstringEnvironment key or id.
numberpath, requiredintegerRevision number.
toquerystringdraft (default), published, or a revision number.

Answer 200: RevisionDiff · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

example
curl "https://edge.nanoflags.io/api/v1/projects/commerce/environments/production/revisions/41/diff?to=draft" -H "Authorization: Bearer $NANOFLAGS_API_TOKEN"

Load a revision into the draft

post/api/v1/projects/{project}/environments/{environment}/revisions/{number}/restorescope flags:write

Then publish normally (a new revision). Shared segments are restored too. Editor role.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentpath, requiredstringEnvironment key or id.
numberpath, requiredintegerRevision number.

Answer 200: RestoreResult · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.

example
curl -X POST https://edge.nanoflags.io/api/v1/projects/commerce/environments/production/revisions/41/restore -H "Authorization: Bearer $NANOFLAGS_API_TOKEN"

Scenarios

Saved scenarios and their run results.

List saved scenarios

get/api/v1/projects/{project}/scenariosscope flags:read
ParameterInTypeDescription
projectpath, requiredstringProject key or id.
flagquerystringFlag key.
cursorquerystringOpaque cursor from nextCursor of the previous page.
limitqueryintegerPage size, 1–100 (default 50). Default 50.

Answer 200: ScenarioList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

List scenario run results

get/api/v1/projects/{project}/scenario-runsscope flags:read

Newest first. A run counts for publishing only while its configChecksum equals the draft checksum.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentquerystringEnvironment key or id.
scenarioquerystring (uuid)Scenario id.
cursorquerystringOpaque cursor from nextCursor of the previous page.
limitqueryintegerPage size, 1–100 (default 50). Default 50.

Answer 200: ScenarioRunList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Audit log

Who did what, when; includes every API token use.

List audit log entries

get/api/v1/audit-logscope audit:read

Newest first. Organization-wide for org tokens, the token's project for project tokens.

ParameterInTypeDescription
projectquerystringProject key or id.
actionquerystringExact action, e.g. revision.published.
sincequerystring (date-time)Only entries at or after this time.
cursorquerystringOpaque cursor from nextCursor of the previous page.
limitqueryintegerPage size, 1–100 (default 50). Default 50.

Answer 200: AuditEntryList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

example
curl "https://edge.nanoflags.io/api/v1/audit-log?project=commerce&action=revision.published&limit=20" -H "Authorization: Bearer $NANOFLAGS_API_TOKEN"

SDK keys

Key metadata, never key material.

List SDK key metadata

get/api/v1/projects/{project}/keysscope keys:read

Admin role. Never returns key material or hashes.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentquerystringEnvironment key or id.
revokedquerystringtrue to include revoked keys. One of true, false.

Answer 200: SdkKeyList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Usage insights

Sampled evaluation usage per flag and project (scope insights:read).

Usage of one flag in an environment

get/api/v1/projects/{project}/flags/{flag}/insightsscope insights:read

Sampled evaluation events over a window (series, totals), the served values and deciding rules, the services and SDKs that evaluate the flag, and the revisions published in the window. Counts are sampled events, not evaluations; weighted estimates evaluations only where the SDK reported its sample rate. Windows longer than the plan's usage history are capped.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
flagpath, requiredstringFlag key.
environmentquerystringEnvironment key or id (default: the project's default environment).
windowquerystring1h, 24h (default), 7d or 30d. One of 1h, 24h, 7d, 30d.

Answer 200: FlagInsights · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Usage overview of a project in an environment

get/api/v1/projects/{project}/insightsscope insights:read

Every live flag with its sampled events in the last days and when it was last seen (flags with no sampled evaluation are cleanup candidates), active services and SDK versions, and revisions per week across environments.

ParameterInTypeDescription
projectpath, requiredstringProject key or id.
environmentquerystringEnvironment key or id (default: the project's default environment).
daysqueryinteger1–400 (default 7), capped by the plan's usage history.

Answer 200: ProjectInsights · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.

Schemas

Times are RFC 3339 strings. Fields marked required are always present; null is used where a value is absent.

Error

FieldTypeRequiredDescription
errorobjectyes—
error.codestringyesStable machine-readable code, e.g. FORBIDDEN, PLAN_REQUIRED, APPROVAL_REQUIRED.
error.messagestringyesHuman readable explanation.

Token

FieldTypeRequiredDescription
idstring (uuid)yes—
namestringyes—
prefixstringyesFirst 14 characters of the token, safe to display.
orgIdstring (uuid)yes—
projectIdstring | null (uuid)yes—
projectKeystring | nullyes—
scopesstring[]yes—
expiresAtstring (date-time)yes—
createdAtstring (date-time)yes—
createdBystring (uuid)yes—
createdByNamestring | nullyes—
lastUsedAtstring | null (date-time)yes—
revokedAtstring | null (date-time)yes—
statusstringyes One of active, expired, revoked.

Project

FieldTypeRequiredDescription
idstring (uuid)yes—
keystringyes—
namestringyes—
timezonestringyes—
defaultEnvironmentstringyes—
createdAtstring (date-time)yes—

Environment

FieldTypeRequiredDescription
idstring (uuid)yes—
keystringyes—
namestringyes—
sortintegeryes—
isProtectedbooleanyes—
requireApprovalbooleanyes—
requiredApproversintegeryes—
requireScenariosbooleanyes—
publishedRevisionintegeryes0 when nothing was published.
publishedChecksumstring | nullyes—
publishedAtstring | null (date-time)yes—
createdAtstring (date-time)yes—

Flag

FieldTypeRequiredDescription
idstring (uuid)yes—
keystringyes—
namestringyes—
descriptionstringyes—
typestringyes One of boolean, string, number, json.
purposestringyes One of release, experiment, operational, permission.
permanentbooleanyes—
clientExposedbooleanyes—
initialValueanyyesAny JSON value.
jsonSchemaobject | nullyes—
ownerIdstring | null (uuid)yes—
reviewDatestring | null (date)yes—
archivedAtstring | null (date-time)yes—
createdAtstring (date-time)yes—

Rule

FieldTypeRequiredDescription
idstringyes—
descriptionstringno—
matchstringno One of all, any.
conditionsobject[]yes—
serveobjectyes{ type: 'fixed', value } or { type: 'rollout', percentage, value, otherwise, bucketBy? }.
serve.typestringyes One of fixed, rollout.

FlagConfig

FieldTypeRequiredDescription
flagstringyes—
environmentstringyes—
environmentIdstring (uuid)yes—
draftobjectyes—
draft.enabledbooleanyes—
draft.rulesRule[]yes—
draft.defaultanyyesAny JSON value.
draft.offanyyesAny JSON value.
draft.updatedAtstring (date-time)yes—
draft.updatedBystring | null (uuid)yes—
publishedRevisionintegeryes—
publishedobject | nullyesThe flag as published in publishedRevision ({ type, enabled, rules, default, off, clientExposed }); null if not in it.

Segment

FieldTypeRequiredDescription
idstring (uuid)yes—
keystringyes—
namestringyes—
descriptionstringyes—
matchstringyes One of all, any.
conditionsobject[]yes—
createdAtstring (date-time)yes—
updatedAtstring (date-time)yes—

Deleted

FieldTypeRequiredDescription
deletedbooleanyes Always true.
keystringyes—

Approval

FieldTypeRequiredDescription
approverIdstring (uuid)yes—
decisionstringyes One of approved, rejected.
commentstringyes—
draftChecksumstringyes—
createdAtstring (date-time)yes—

Change

FieldTypeRequiredDescription
idstring (uuid)yes—
environmentstringyes—
environmentIdstring (uuid)yes—
titlestringyes—
reasonstringyes—
codeRefstringyes—
statusstringyes One of draft, testing, awaiting_approval, approved, published, closed.
authorIdstring | null (uuid)yes—
baseRevisionintegeryes—
publishedRevisioninteger | nullyes—
createdAtstring (date-time)yes—
updatedAtstring (date-time)yes—
flagsstring[]yes—
approvalsApproval[]yes—
approvedChecksumstringnoOnly on the approval answer: the draft checksum the decision is bound to.

PublishRequirements

FieldTypeRequiredDescription
environmentstringyes—
changeIdstring | null (uuid)yes—
checksumstringyesChecksum of the current draft.
hasChangesbooleanyes—
publishedRevisionintegeryes—
changeRequiredbooleanyes—
approvalobjectyes—
approval.requiredbooleanyes—
approval.neededintegeryes—
approval.validintegeryes—
approval.staleintegeryes—
approval.metbooleanyes—
scenariosobjectyes—
scenarios.requiredbooleanyes—
scenarios.totalintegeryes—
scenarios.passedintegeryes—
scenarios.failedintegeryes—
scenarios.notRunintegeryes—
scenarios.selfOnlyintegeryesScenarios whose only passing runs are by the change author or a draft editor (they do not count).
scenarios.metbooleanyes—
canPublishbooleanyesAll of: has changes, no change required (or one given), approvals met, scenarios met.

Schedule

FieldTypeRequiredDescription
idstring (uuid)yes—
environmentstringyes—
environmentIdstring (uuid)yes—
changeIdstring | null (uuid)yes—
runAtstring (date-time)yesWhen the publish runs (UTC). It runs within about 10 seconds after this time.
timezonestringyesIANA time zone the schedule was entered in (display only).
expectedRevisionintegeryesThe destination revision pinned at scheduling time.
checksumstringyesThe draft checksum pinned at scheduling time. Only exactly this draft is ever published.
notestringyes—
statusstringyes One of scheduled, published, failed, canceled.
createdBystring (uuid)yes—
createdByNamestring | nullyes—
viastringyes One of dashboard, api_token.
createdAtstring (date-time)yes—
canceledBystring | null (uuid)yes—
canceledAtstring | null (date-time)yes—
executedAtstring | null (date-time)yes—
publishedRevisioninteger | nullyes—
errorCodestring | nullyesWhy it failed, e.g. DRAFT_CHANGED, DESTINATION_CHANGED, APPROVAL_REQUIRED, SCENARIOS_REQUIRED, FORBIDDEN.
errorMessagestring | nullyes—
readinessobjectnoOnly on the create answer: whether approvals and scenario checks are already met for the pinned draft. They are checked again when the publish runs.
readiness.approvalMetbooleanyes—
readiness.scenariosMetbooleanyes—

PublishResult

FieldTypeRequiredDescription
environmentstringyes—
environmentIdstring (uuid)yes—
revisionintegeryes—
checksumstringyes—
committedAtintegeryesCommit time, epoch milliseconds.

Revision

FieldTypeRequiredDescription
numberintegeryes—
checksumstringyes—
changeIdstring | null (uuid)yes—
restoredFrominteger | nullyes—
notestringyes—
publishedBystring | null (uuid)yes—
publishedAtstring (date-time)yes—
distributedAtstring | null (date-time)yes—

RevisionDetail

All fields of Revision, plus:

FieldTypeRequiredDescription
bundleobjectyesThe immutable engine bundle: { schema, project, environment, revision, flags, segments }.

DiffRow

FieldTypeRequiredDescription
flagKeystringyesFlag key, or segment:<key>.
fieldstringyes—
beforestringyes—
afterstringyes—

RevisionDiff

FieldTypeRequiredDescription
environmentstringyes—
fromintegeryes—
tointeger | stringyesRevision number or draft.
changesDiffRow[]yes—

RestoreResult

FieldTypeRequiredDescription
environmentstringyes—
restoredFromintegeryes—
flagsintegeryes—
segmentsintegeryes—
segmentsCreatedintegeryes—
segmentKeysstring[]yes—
notInRevisionstring[]yes—

Draft

FieldTypeRequiredDescription
environmentstringyes—
checksumstringyes—
publishedRevisionintegeryes—
publishedChecksumstring | nullyes—
dirtybooleanyes—
bundleobjectyes—

Scenario

FieldTypeRequiredDescription
idstring (uuid)yes—
namestringyes—
flagstringyes—
contextobjectyes—
expectedanyyesAny JSON value.
includeInCibooleanyes—
versionintegeryes—
createdAtstring (date-time)yes—
updatedAtstring | null (date-time)yes—

ScenarioRun

FieldTypeRequiredDescription
idstring (uuid)yes—
scenarioIdstring (uuid)yes—
environmentstringyes—
sourcestringyes One of draft, revision.
revisioninteger | nullyes—
configChecksumstringyes—
actualanyyesAny JSON value.
passedbooleanyes—
viastringyes—
runBystring | null (uuid)yes—
commitShastring | nullyes—
ciRunUrlstring | nullyes—
scenarioVersioninteger | nullyes—
createdAtstring (date-time)yes—

AuditEntry

FieldTypeRequiredDescription
idintegeryes—
createdAtstring (date-time)yes—
actorIdstring | null (uuid)yes—
actionstringyes—
objectTypestringyes—
objectIdstring | nullyes—
objectLabelstringyes—
summarystringyes—
projectIdstring | null (uuid)yes—
environmentIdstring | null (uuid)yes—
detailobjectyesEntries written through an API token carry via: "api_token" and api_token_id.

SdkKey

FieldTypeRequiredDescription
idstring (uuid)yes—
namestringyes—
kindstringyes One of client, server, ci, admin.
prefixstringyes—
environmentstringyes—
environmentIdstring (uuid)yes—
createdAtstring (date-time)yes—
createdBystring | null (uuid)yes—
lastUsedAtstring | null (date-time)yes—
revokedAtstring | null (date-time)yes—

FlagCreate

FieldTypeRequiredDescription
keystringyesImmutable. ^[a-z0-9][a-z0-9_-]{0,62}$
typestringno One of boolean, string, number, json.
initialValueanynoEvery environment's draft starts with this value (default false for boolean flags).
namestringno1–120 characters; defaults to the key.
descriptionstringnoAt most 2000 characters.
purposestringno One of release, experiment, operational, permission.
permanentbooleanno—
clientExposedbooleannoServed to browser (nf_pub_) keys.
jsonSchemaobject | nullnoJSON Schema the values of a json flag must satisfy.
reviewDatestring | null (date)no—
ownerIdstring | null (uuid)no—

FlagUpdate

FieldTypeRequiredDescription
namestringno1–120 characters; defaults to the key.
descriptionstringnoAt most 2000 characters.
purposestringno One of release, experiment, operational, permission.
permanentbooleanno—
clientExposedbooleannoServed to browser (nf_pub_) keys.
jsonSchemaobject | nullnoJSON Schema the values of a json flag must satisfy.
reviewDatestring | null (date)no—
ownerIdstring | null (uuid)no—

ConfigUpdate

FieldTypeRequiredDescription
enabledbooleanno—
rulesRule[]no—
defaultanynoValue served when no rule matches.
offanynoValue served while the flag is disabled.

SegmentCreate

FieldTypeRequiredDescription
keystringyes^[a-z0-9][a-z0-9_-]{0,62}$
namestringno—
descriptionstringno—
matchstringno One of all, any.
conditionsobject[]no—

SegmentUpdate

FieldTypeRequiredDescription
namestringno—
descriptionstringno—
matchstringno One of all, any.
conditionsobject[]no—

ChangeCreate

FieldTypeRequiredDescription
environmentstringyesEnvironment key or id.
titlestringyes1–140 characters.
reasonstringnoAt most 2000 characters.
codeRefstringnoAt most 200 characters, e.g. a PR URL.
flagsstring[]noFlag keys in scope (≤ 200).

ChangeUpdate

FieldTypeRequiredDescription
titlestringno—
reasonstringno—
codeRefstringno—
statusstringnoManual moves only; approval and publishing set the other states. One of draft, testing, closed.

ChangeFlag

FieldTypeRequiredDescription
flagstringyesFlag key.

Decision

FieldTypeRequiredDescription
decisionstringyes One of approved, rejected.
commentstringno—

Publish

FieldTypeRequiredDescription
expectedRevisionintegeryesThe environment's current published revision. A different value → 409 DESTINATION_CHANGED.
changestring (uuid)noThe reviewed change to publish (required where approvals are).
notestringnoAt most 500 characters.

ScheduleCreate

FieldTypeRequiredDescription
runAtstring (date-time)yesWhen to publish: 1 minute to 366 days from now, with an offset or Z (e.g. 2026-11-27T00:00:00+03:00).
expectedRevisionintegeryesThe environment's current published revision. A different value → 409 DESTINATION_CHANGED, now and again when the publish runs.
checksumstringnoOptional: the draft checksum you reviewed (GET …/draft). A different current draft → 409 DRAFT_CHANGED. Whether given or not, the schedule pins the current draft's checksum.
changestring (uuid)noThe reviewed change to publish (required where approvals are).
notestringnoAt most 500 characters; becomes the revision note.
timezonestringnoIANA time zone, for display (default UTC).

FlagInsights

FieldTypeRequiredDescription
environmentstringyes—
environmentIdstring (uuid)yes—
flagstringyes—
windowstringyes One of 1h, 24h, 7d, 30d.
fromstring (date-time)yes—
tostring (date-time)yes—
bucketSecondsintegeryesWidth of a series bucket: 300 for 1h, 3600 for 24h, 21600 for 7d, 86400 for 30d.
retentionDaysintegeryesDays of usage history the organization's plan keeps; longer windows are capped (capped: true).
cappedbooleanyes—
totalsobject + objectyes—
seriesobject + object[]yes—
variationsobject[]yesTop 20 served values by sampled events.
rulesobject[]yes—
servicesobject[]yes—
publishesobject[]yesRevisions published in the window (chart markers).
seenobjectyesFirst and last sampled evaluation ever recorded for this flag in this environment (kept beyond retention).
seen.firstSeenAtstring | null (date-time)yes—
seen.lastSeenAtstring | null (date-time)yes—

ProjectInsights

FieldTypeRequiredDescription
environmentstringyes—
environmentIdstring (uuid)yes—
projectIdstring (uuid)yes—
daysintegeryesDays covered (requested days capped by retention).
requestedDaysintegeryes—
retentionDaysintegeryes—
fromstring (date-time)yes—
tostring (date-time)yes—
totalsobject + objectyes—
flagsobject + object[]yesEvery live flag, most sampled first.
servicesobject[]yes—
sdksobject[]yes—
revisionsPerWeekobject[]yes12 ISO weeks, every environment of the project.