Reference
Management API
On this page
Overview
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.
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 secretcreate_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
| Scope | Allows |
|---|---|
none needed | Reading the token, projects and environments. |
flags:read | Flags, flag configurations, drafts, revisions and diffs, scenarios and run results. |
flags:write | Create, edit, archive flags; edit drafts; restore a revision into a draft. Implies flags:read. |
segments:read, segments:write | Read / create, edit, delete segments. segments:* grants both. |
changes:read, changes:write, changes:approve | Read / create and edit changes and submit them / approve or reject. changes:* grants all three. |
publish | Read publish requirements and publish an environment's draft. |
audit:read | The audit log (the token's project only, for project tokens). |
keys:read | SDK 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
{ "error": { "code": "APPROVAL_REQUIRED", "message": "An independent approval of the current draft is required." } }| Status | code | When |
|---|---|---|
| 400 | INVALID_REQUEST, INVALID_JSON, INVALID_CURSOR | The body, a parameter or a header does not meet the endpoint's rules. |
| 401 | UNAUTHORIZED | Missing, malformed, unknown, revoked or expired token, or its creator left the organization. |
| 402 | PLAN_REQUIRED, PLAN_LIMIT | The organization's plan does not include this. |
| 403 | FORBIDDEN, INSUFFICIENT_SCOPE, SELF_APPROVAL, OWNER_REQUIRED | Role, scope or approval independence. |
| 404 | NOT_FOUND | Unknown path, or the object does not exist in the token's organization or project. |
| 405 | METHOD_NOT_ALLOWED | Wrong method; see Allow. |
| 409 | CONFLICT, DESTINATION_CHANGED, NO_CHANGES, LIMIT_REACHED | Duplicate key, a concurrent publish, nothing to publish, a limit reached. |
| 413 | PAYLOAD_TOO_LARGE | Body above 256 KB. |
| 422 | INVALID_VALUE, INVALID_STATE, CHANGE_REQUIRED, APPROVAL_REQUIRED, SCENARIOS_REQUIRED, IDEMPOTENCY_MISMATCH | Refused by a rule: value validation, the change's state, the publishing policy. |
| 429 | RATE_LIMITED | Too many requests; wait for Retry-After seconds. |
| 503 | ORIGIN_UNAVAILABLE | NanoFlags 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
- A developer opens a change (
POST /changes) with the flags in scope and submits it. - Another admin approves it in the dashboard or with their own token.
- The release job checks the requirements and publishes exactly the approved draft:
# .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
/api/v1/tokenany tokenMetadata 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.
curl https://edge.nanoflags.io/api/v1/token -H "Authorization: Bearer $NANOFLAGS_API_TOKEN"Projects
Projects of the token's organization.
List projects
/api/v1/projectsany tokenProjects of the token's organization (only its project for a project token).
| Parameter | In | Type | Description |
|---|---|---|---|
cursor | query | string | Opaque cursor from nextCursor of the previous page. |
limit | query | integer | Page 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
/api/v1/projects/{project}any token| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project 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
/api/v1/projects/{project}/environmentsany tokenOrdered as in the dashboard. Not paginated (nextCursor is always null).
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
Answer 200: EnvironmentList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.
Get an environment
/api/v1/projects/{project}/environments/{environment}any tokenIncludes the publishing policy (protection, approvals, scenario checks) and the published revision.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | path, required | string | Environment 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
/api/v1/projects/{project}/flagsscope flags:read| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
archived | query | string | false (default), true or all. One of false, true, all. |
cursor | query | string | Opaque cursor from nextCursor of the previous page. |
limit | query | integer | Page size, 1–100 (default 50). Default 50. |
Answer 200: FlagList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.
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
/api/v1/projects/{project}/flagsscope flags:writeEvery environment's draft starts with initialValue; nothing is published. Editor role.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
Request body: FlagCreate.
Answer 201: Flag · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
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
/api/v1/projects/{project}/flags/{flag}scope flags:read| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
flag | path, required | string | Flag key. |
Answer 200: Flag · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.
Update flag details
/api/v1/projects/{project}/flags/{flag}scope flags:writeKey and type are immutable. Editor role.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
flag | path, required | string | Flag key. |
Request body: FlagUpdate.
Answer 200: Flag · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
Archive a flag
/api/v1/projects/{project}/flags/{flag}/archivescope flags:writeRemoves the flag from every draft; it leaves published revisions on the next publish. Editor role.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
flag | path, required | string | Flag key. |
Answer 200: Flag · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
Restore an archived flag
/api/v1/projects/{project}/flags/{flag}/unarchivescope flags:write| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
flag | path, required | string | Flag 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
/api/v1/projects/{project}/flags/{flag}/environments/{environment}scope flags:readThe draft and, separately, what is published.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
flag | path, required | string | Flag key. |
environment | path, required | string | Environment 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
/api/v1/projects/{project}/flags/{flag}/environments/{environment}scope flags:writeChanges 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).
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
flag | path, required | string | Flag key. |
environment | path, required | string | Environment key or id. |
Request body: ConfigUpdate.
Answer 200: FlagConfig · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
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
/api/v1/projects/{project}/segmentsscope segments:read| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
cursor | query | string | Opaque cursor from nextCursor of the previous page. |
limit | query | integer | Page 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
/api/v1/projects/{project}/segmentsscope segments:writeSegments are project-wide: part of every environment's draft. Editor role.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
Request body: SegmentCreate.
Answer 201: Segment · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
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
/api/v1/projects/{project}/segments/{segment}scope segments:read| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
segment | path, required | string | Segment key. |
Answer 200: Segment · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.
Update a segment
/api/v1/projects/{project}/segments/{segment}scope segments:write| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
segment | path, required | string | Segment key. |
Request body: SegmentUpdate.
Answer 200: Segment · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
Delete a segment
/api/v1/projects/{project}/segments/{segment}scope segments:writeRules that still reference the segment stop matching it (an unknown segment matches nobody), as in the dashboard. Check GET …/draft before publishing.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
segment | path, required | string | Segment key. |
Answer 200: Deleted · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
Changes
Reviewed changes and independent approvals.
List changes
/api/v1/projects/{project}/changesscope changes:readNewest first.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | query | string | Environment key or id. |
status | query | string | Filter by status. One of draft, testing, awaiting_approval, approved, published, closed. |
cursor | query | string | Opaque cursor from nextCursor of the previous page. |
limit | query | integer | Page 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
/api/v1/projects/{project}/changesscope changes:writeThe token's creator becomes the author. baseRevision is the environment's current revision. Editor role.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
Request body: ChangeCreate.
Answer 201: Change · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
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
/api/v1/projects/{project}/changes/{change}scope changes:read| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
change | path, required | string (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
/api/v1/projects/{project}/changes/{change}scope changes:writeFinished changes (published, closed) refuse every edit with 422 INVALID_STATE.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
change | path, required | string (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
/api/v1/projects/{project}/changes/{change}/flagsscope changes:write| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
change | path, required | string (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
/api/v1/projects/{project}/changes/{change}/flags/{flag}scope changes:write| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
change | path, required | string (uuid) | Change id. |
flag | path, required | string | Flag key. |
Answer 200: Change · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
Request approval
/api/v1/projects/{project}/changes/{change}/submitscope changes:writeMoves the change to awaiting_approval.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
change | path, required | string (uuid) | Change id. |
Answer 200: Change · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
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
/api/v1/projects/{project}/changes/{change}/approvalsscope changes:approveAdmin 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.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
change | path, required | string (uuid) | Change id. |
Request body: Decision.
Answer 200: Change · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
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
/api/v1/projects/{project}/environments/{environment}/publish-requirementsscope publish| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | path, required | string | Environment key or id. |
change | query | string (uuid) | Change id to evaluate approvals for. |
Answer 200: PublishRequirements · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.
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
/api/v1/projects/{project}/environments/{environment}/publishscope publishPublishes 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.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | path, required | string | Environment key or id. |
Request body: Publish.
Answer 200: PublishResult · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
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
/api/v1/projects/{project}/environments/{environment}/schedulesscope publishUpcoming and past schedules of the environment, latest time first.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | path, required | string | Environment key or id. |
status | query | string | Filter by status. One of scheduled, published, failed, canceled. |
cursor | query | string | Opaque cursor from nextCursor of the previous page. |
limit | query | integer | Page 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
/api/v1/projects/{project}/environments/{environment}/schedulesscope publishPins 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.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | path, required | string | Environment 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
/api/v1/projects/{project}/environments/{environment}/schedules/{schedule}scope publishOnly pending schedules can be canceled (422 INVALID_STATE otherwise). Any editor of the project may cancel. Canceling is allowed on every plan.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | path, required | string | Environment key or id. |
schedule | path, required | string (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
/api/v1/projects/{project}/environments/{environment}/draftscope flags:read| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | path, required | string | Environment key or id. |
Answer 200: Draft · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.
List published revisions
/api/v1/projects/{project}/environments/{environment}/revisionsscope flags:readNewest first, without bundles.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | path, required | string | Environment key or id. |
cursor | query | string | Opaque cursor from nextCursor of the previous page. |
limit | query | integer | Page 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
/api/v1/projects/{project}/environments/{environment}/revisions/{number}scope flags:read| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | path, required | string | Environment key or id. |
number | path, required | integer | Revision 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
/api/v1/projects/{project}/environments/{environment}/revisions/{number}/diffscope flags:read| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | path, required | string | Environment key or id. |
number | path, required | integer | Revision number. |
to | query | string | draft (default), published, or a revision number. |
Answer 200: RevisionDiff · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.
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
/api/v1/projects/{project}/environments/{environment}/revisions/{number}/restorescope flags:writeThen publish normally (a new revision). Shared segments are restored too. Editor role.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | path, required | string | Environment key or id. |
number | path, required | integer | Revision number. |
Answer 200: RestoreResult · accepts Idempotency-Key · errors 400, 401, 402, 403, 404, 409, 422, 429, 503.
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
/api/v1/projects/{project}/scenariosscope flags:read| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
flag | query | string | Flag key. |
cursor | query | string | Opaque cursor from nextCursor of the previous page. |
limit | query | integer | Page 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
/api/v1/projects/{project}/scenario-runsscope flags:readNewest first. A run counts for publishing only while its configChecksum equals the draft checksum.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | query | string | Environment key or id. |
scenario | query | string (uuid) | Scenario id. |
cursor | query | string | Opaque cursor from nextCursor of the previous page. |
limit | query | integer | Page 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
/api/v1/audit-logscope audit:readNewest first. Organization-wide for org tokens, the token's project for project tokens.
| Parameter | In | Type | Description |
|---|---|---|---|
project | query | string | Project key or id. |
action | query | string | Exact action, e.g. revision.published. |
since | query | string (date-time) | Only entries at or after this time. |
cursor | query | string | Opaque cursor from nextCursor of the previous page. |
limit | query | integer | Page size, 1–100 (default 50). Default 50. |
Answer 200: AuditEntryList · accepts If-None-Match · errors 304, 400, 401, 403, 404, 429, 503.
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
/api/v1/projects/{project}/keysscope keys:readAdmin role. Never returns key material or hashes.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | query | string | Environment key or id. |
revoked | query | string | true 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
/api/v1/projects/{project}/flags/{flag}/insightsscope insights:readSampled 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.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
flag | path, required | string | Flag key. |
environment | query | string | Environment key or id (default: the project's default environment). |
window | query | string | 1h, 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
/api/v1/projects/{project}/insightsscope insights:readEvery 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.
| Parameter | In | Type | Description |
|---|---|---|---|
project | path, required | string | Project key or id. |
environment | query | string | Environment key or id (default: the project's default environment). |
days | query | integer | 1–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
| Field | Type | Required | Description |
|---|---|---|---|
error | object | yes | — |
error.code | string | yes | Stable machine-readable code, e.g. FORBIDDEN, PLAN_REQUIRED, APPROVAL_REQUIRED. |
error.message | string | yes | Human readable explanation. |
Token
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | — |
name | string | yes | — |
prefix | string | yes | First 14 characters of the token, safe to display. |
orgId | string (uuid) | yes | — |
projectId | string | null (uuid) | yes | — |
projectKey | string | null | yes | — |
scopes | string[] | yes | — |
expiresAt | string (date-time) | yes | — |
createdAt | string (date-time) | yes | — |
createdBy | string (uuid) | yes | — |
createdByName | string | null | yes | — |
lastUsedAt | string | null (date-time) | yes | — |
revokedAt | string | null (date-time) | yes | — |
status | string | yes | One of active, expired, revoked. |
Project
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | — |
key | string | yes | — |
name | string | yes | — |
timezone | string | yes | — |
defaultEnvironment | string | yes | — |
createdAt | string (date-time) | yes | — |
Environment
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | — |
key | string | yes | — |
name | string | yes | — |
sort | integer | yes | — |
isProtected | boolean | yes | — |
requireApproval | boolean | yes | — |
requiredApprovers | integer | yes | — |
requireScenarios | boolean | yes | — |
publishedRevision | integer | yes | 0 when nothing was published. |
publishedChecksum | string | null | yes | — |
publishedAt | string | null (date-time) | yes | — |
createdAt | string (date-time) | yes | — |
Flag
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | — |
key | string | yes | — |
name | string | yes | — |
description | string | yes | — |
type | string | yes | One of boolean, string, number, json. |
purpose | string | yes | One of release, experiment, operational, permission. |
permanent | boolean | yes | — |
clientExposed | boolean | yes | — |
initialValue | any | yes | Any JSON value. |
jsonSchema | object | null | yes | — |
ownerId | string | null (uuid) | yes | — |
reviewDate | string | null (date) | yes | — |
archivedAt | string | null (date-time) | yes | — |
createdAt | string (date-time) | yes | — |
Rule
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
description | string | no | — |
match | string | no | One of all, any. |
conditions | object[] | yes | — |
serve | object | yes | { type: 'fixed', value } or { type: 'rollout', percentage, value, otherwise, bucketBy? }. |
serve.type | string | yes | One of fixed, rollout. |
FlagConfig
| Field | Type | Required | Description |
|---|---|---|---|
flag | string | yes | — |
environment | string | yes | — |
environmentId | string (uuid) | yes | — |
draft | object | yes | — |
draft.enabled | boolean | yes | — |
draft.rules | Rule[] | yes | — |
draft.default | any | yes | Any JSON value. |
draft.off | any | yes | Any JSON value. |
draft.updatedAt | string (date-time) | yes | — |
draft.updatedBy | string | null (uuid) | yes | — |
publishedRevision | integer | yes | — |
published | object | null | yes | The flag as published in publishedRevision ({ type, enabled, rules, default, off, clientExposed }); null if not in it. |
Segment
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | — |
key | string | yes | — |
name | string | yes | — |
description | string | yes | — |
match | string | yes | One of all, any. |
conditions | object[] | yes | — |
createdAt | string (date-time) | yes | — |
updatedAt | string (date-time) | yes | — |
Deleted
| Field | Type | Required | Description |
|---|---|---|---|
deleted | boolean | yes | Always true. |
key | string | yes | — |
Approval
| Field | Type | Required | Description |
|---|---|---|---|
approverId | string (uuid) | yes | — |
decision | string | yes | One of approved, rejected. |
comment | string | yes | — |
draftChecksum | string | yes | — |
createdAt | string (date-time) | yes | — |
Change
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | — |
environment | string | yes | — |
environmentId | string (uuid) | yes | — |
title | string | yes | — |
reason | string | yes | — |
codeRef | string | yes | — |
status | string | yes | One of draft, testing, awaiting_approval, approved, published, closed. |
authorId | string | null (uuid) | yes | — |
baseRevision | integer | yes | — |
publishedRevision | integer | null | yes | — |
createdAt | string (date-time) | yes | — |
updatedAt | string (date-time) | yes | — |
flags | string[] | yes | — |
approvals | Approval[] | yes | — |
approvedChecksum | string | no | Only on the approval answer: the draft checksum the decision is bound to. |
PublishRequirements
| Field | Type | Required | Description |
|---|---|---|---|
environment | string | yes | — |
changeId | string | null (uuid) | yes | — |
checksum | string | yes | Checksum of the current draft. |
hasChanges | boolean | yes | — |
publishedRevision | integer | yes | — |
changeRequired | boolean | yes | — |
approval | object | yes | — |
approval.required | boolean | yes | — |
approval.needed | integer | yes | — |
approval.valid | integer | yes | — |
approval.stale | integer | yes | — |
approval.met | boolean | yes | — |
scenarios | object | yes | — |
scenarios.required | boolean | yes | — |
scenarios.total | integer | yes | — |
scenarios.passed | integer | yes | — |
scenarios.failed | integer | yes | — |
scenarios.notRun | integer | yes | — |
scenarios.selfOnly | integer | yes | Scenarios whose only passing runs are by the change author or a draft editor (they do not count). |
scenarios.met | boolean | yes | — |
canPublish | boolean | yes | All of: has changes, no change required (or one given), approvals met, scenarios met. |
Schedule
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | — |
environment | string | yes | — |
environmentId | string (uuid) | yes | — |
changeId | string | null (uuid) | yes | — |
runAt | string (date-time) | yes | When the publish runs (UTC). It runs within about 10 seconds after this time. |
timezone | string | yes | IANA time zone the schedule was entered in (display only). |
expectedRevision | integer | yes | The destination revision pinned at scheduling time. |
checksum | string | yes | The draft checksum pinned at scheduling time. Only exactly this draft is ever published. |
note | string | yes | — |
status | string | yes | One of scheduled, published, failed, canceled. |
createdBy | string (uuid) | yes | — |
createdByName | string | null | yes | — |
via | string | yes | One of dashboard, api_token. |
createdAt | string (date-time) | yes | — |
canceledBy | string | null (uuid) | yes | — |
canceledAt | string | null (date-time) | yes | — |
executedAt | string | null (date-time) | yes | — |
publishedRevision | integer | null | yes | — |
errorCode | string | null | yes | Why it failed, e.g. DRAFT_CHANGED, DESTINATION_CHANGED, APPROVAL_REQUIRED, SCENARIOS_REQUIRED, FORBIDDEN. |
errorMessage | string | null | yes | — |
readiness | object | no | Only 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.approvalMet | boolean | yes | — |
readiness.scenariosMet | boolean | yes | — |
PublishResult
| Field | Type | Required | Description |
|---|---|---|---|
environment | string | yes | — |
environmentId | string (uuid) | yes | — |
revision | integer | yes | — |
checksum | string | yes | — |
committedAt | integer | yes | Commit time, epoch milliseconds. |
Revision
| Field | Type | Required | Description |
|---|---|---|---|
number | integer | yes | — |
checksum | string | yes | — |
changeId | string | null (uuid) | yes | — |
restoredFrom | integer | null | yes | — |
note | string | yes | — |
publishedBy | string | null (uuid) | yes | — |
publishedAt | string (date-time) | yes | — |
distributedAt | string | null (date-time) | yes | — |
RevisionDetail
All fields of Revision, plus:
| Field | Type | Required | Description |
|---|---|---|---|
bundle | object | yes | The immutable engine bundle: { schema, project, environment, revision, flags, segments }. |
DiffRow
| Field | Type | Required | Description |
|---|---|---|---|
flagKey | string | yes | Flag key, or segment:<key>. |
field | string | yes | — |
before | string | yes | — |
after | string | yes | — |
RevisionDiff
| Field | Type | Required | Description |
|---|---|---|---|
environment | string | yes | — |
from | integer | yes | — |
to | integer | string | yes | Revision number or draft. |
changes | DiffRow[] | yes | — |
RestoreResult
| Field | Type | Required | Description |
|---|---|---|---|
environment | string | yes | — |
restoredFrom | integer | yes | — |
flags | integer | yes | — |
segments | integer | yes | — |
segmentsCreated | integer | yes | — |
segmentKeys | string[] | yes | — |
notInRevision | string[] | yes | — |
Draft
| Field | Type | Required | Description |
|---|---|---|---|
environment | string | yes | — |
checksum | string | yes | — |
publishedRevision | integer | yes | — |
publishedChecksum | string | null | yes | — |
dirty | boolean | yes | — |
bundle | object | yes | — |
Scenario
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | — |
name | string | yes | — |
flag | string | yes | — |
context | object | yes | — |
expected | any | yes | Any JSON value. |
includeInCi | boolean | yes | — |
version | integer | yes | — |
createdAt | string (date-time) | yes | — |
updatedAt | string | null (date-time) | yes | — |
ScenarioRun
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | — |
scenarioId | string (uuid) | yes | — |
environment | string | yes | — |
source | string | yes | One of draft, revision. |
revision | integer | null | yes | — |
configChecksum | string | yes | — |
actual | any | yes | Any JSON value. |
passed | boolean | yes | — |
via | string | yes | — |
runBy | string | null (uuid) | yes | — |
commitSha | string | null | yes | — |
ciRunUrl | string | null | yes | — |
scenarioVersion | integer | null | yes | — |
createdAt | string (date-time) | yes | — |
AuditEntry
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | yes | — |
createdAt | string (date-time) | yes | — |
actorId | string | null (uuid) | yes | — |
action | string | yes | — |
objectType | string | yes | — |
objectId | string | null | yes | — |
objectLabel | string | yes | — |
summary | string | yes | — |
projectId | string | null (uuid) | yes | — |
environmentId | string | null (uuid) | yes | — |
detail | object | yes | Entries written through an API token carry via: "api_token" and api_token_id. |
SdkKey
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | — |
name | string | yes | — |
kind | string | yes | One of client, server, ci, admin. |
prefix | string | yes | — |
environment | string | yes | — |
environmentId | string (uuid) | yes | — |
createdAt | string (date-time) | yes | — |
createdBy | string | null (uuid) | yes | — |
lastUsedAt | string | null (date-time) | yes | — |
revokedAt | string | null (date-time) | yes | — |
FlagCreate
| Field | Type | Required | Description |
|---|---|---|---|
key | string | yes | Immutable. ^[a-z0-9][a-z0-9_-]{0,62}$ |
type | string | no | One of boolean, string, number, json. |
initialValue | any | no | Every environment's draft starts with this value (default false for boolean flags). |
name | string | no | 1–120 characters; defaults to the key. |
description | string | no | At most 2000 characters. |
purpose | string | no | One of release, experiment, operational, permission. |
permanent | boolean | no | — |
clientExposed | boolean | no | Served to browser (nf_pub_) keys. |
jsonSchema | object | null | no | JSON Schema the values of a json flag must satisfy. |
reviewDate | string | null (date) | no | — |
ownerId | string | null (uuid) | no | — |
FlagUpdate
| Field | Type | Required | Description |
|---|---|---|---|
name | string | no | 1–120 characters; defaults to the key. |
description | string | no | At most 2000 characters. |
purpose | string | no | One of release, experiment, operational, permission. |
permanent | boolean | no | — |
clientExposed | boolean | no | Served to browser (nf_pub_) keys. |
jsonSchema | object | null | no | JSON Schema the values of a json flag must satisfy. |
reviewDate | string | null (date) | no | — |
ownerId | string | null (uuid) | no | — |
ConfigUpdate
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | no | — |
rules | Rule[] | no | — |
default | any | no | Value served when no rule matches. |
off | any | no | Value served while the flag is disabled. |
SegmentCreate
| Field | Type | Required | Description |
|---|---|---|---|
key | string | yes | ^[a-z0-9][a-z0-9_-]{0,62}$ |
name | string | no | — |
description | string | no | — |
match | string | no | One of all, any. |
conditions | object[] | no | — |
SegmentUpdate
| Field | Type | Required | Description |
|---|---|---|---|
name | string | no | — |
description | string | no | — |
match | string | no | One of all, any. |
conditions | object[] | no | — |
ChangeCreate
| Field | Type | Required | Description |
|---|---|---|---|
environment | string | yes | Environment key or id. |
title | string | yes | 1–140 characters. |
reason | string | no | At most 2000 characters. |
codeRef | string | no | At most 200 characters, e.g. a PR URL. |
flags | string[] | no | Flag keys in scope (≤ 200). |
ChangeUpdate
| Field | Type | Required | Description |
|---|---|---|---|
title | string | no | — |
reason | string | no | — |
codeRef | string | no | — |
status | string | no | Manual moves only; approval and publishing set the other states. One of draft, testing, closed. |
ChangeFlag
| Field | Type | Required | Description |
|---|---|---|---|
flag | string | yes | Flag key. |
Decision
| Field | Type | Required | Description |
|---|---|---|---|
decision | string | yes | One of approved, rejected. |
comment | string | no | — |
Publish
| Field | Type | Required | Description |
|---|---|---|---|
expectedRevision | integer | yes | The environment's current published revision. A different value → 409 DESTINATION_CHANGED. |
change | string (uuid) | no | The reviewed change to publish (required where approvals are). |
note | string | no | At most 500 characters. |
ScheduleCreate
| Field | Type | Required | Description |
|---|---|---|---|
runAt | string (date-time) | yes | When to publish: 1 minute to 366 days from now, with an offset or Z (e.g. 2026-11-27T00:00:00+03:00). |
expectedRevision | integer | yes | The environment's current published revision. A different value → 409 DESTINATION_CHANGED, now and again when the publish runs. |
checksum | string | no | Optional: 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. |
change | string (uuid) | no | The reviewed change to publish (required where approvals are). |
note | string | no | At most 500 characters; becomes the revision note. |
timezone | string | no | IANA time zone, for display (default UTC). |
FlagInsights
| Field | Type | Required | Description |
|---|---|---|---|
environment | string | yes | — |
environmentId | string (uuid) | yes | — |
flag | string | yes | — |
window | string | yes | One of 1h, 24h, 7d, 30d. |
from | string (date-time) | yes | — |
to | string (date-time) | yes | — |
bucketSeconds | integer | yes | Width of a series bucket: 300 for 1h, 3600 for 24h, 21600 for 7d, 86400 for 30d. |
retentionDays | integer | yes | Days of usage history the organization's plan keeps; longer windows are capped (capped: true). |
capped | boolean | yes | — |
totals | object + object | yes | — |
series | object + object[] | yes | — |
variations | object[] | yes | Top 20 served values by sampled events. |
rules | object[] | yes | — |
services | object[] | yes | — |
publishes | object[] | yes | Revisions published in the window (chart markers). |
seen | object | yes | First and last sampled evaluation ever recorded for this flag in this environment (kept beyond retention). |
seen.firstSeenAt | string | null (date-time) | yes | — |
seen.lastSeenAt | string | null (date-time) | yes | — |
ProjectInsights
| Field | Type | Required | Description |
|---|---|---|---|
environment | string | yes | — |
environmentId | string (uuid) | yes | — |
projectId | string (uuid) | yes | — |
days | integer | yes | Days covered (requested days capped by retention). |
requestedDays | integer | yes | — |
retentionDays | integer | yes | — |
from | string (date-time) | yes | — |
to | string (date-time) | yes | — |
totals | object + object | yes | — |
flags | object + object[] | yes | Every live flag, most sampled first. |
services | object[] | yes | — |
sdks | object[] | yes | — |
revisionsPerWeek | object[] | yes | 12 ISO weeks, every environment of the project. |