Skip to content

Conventions

These conventions apply to every Management API endpoint.

All endpoints live under /api/v1. Breaking changes ship under a new version prefix; additive changes (new fields, new endpoints) may appear within v1.

Send request bodies as JSON with Content-Type: application/json. Requests with another content type are rejected with 415 unsupported_media_type. Responses are always JSON.

Organizations, projects, flags, environments, and segments are addressed by their key or slug — the same stable identifier you see in the dashboard:

GET /api/v1/orgs/{org}/projects/{project}/flags/{flag}

Nested resources without a user-facing key — targeting rules and variations — are addressed by their GUID id, returned when you create or list them.

List endpoints are cursor-paginated. Pass an opaque cursor query parameter to fetch the next page:

GET /api/v1/orgs/{org}/projects/{project}/flags?cursor=<next_cursor>

The response wraps items with the next cursor:

{
"items": [ /* … */ ],
"next_cursor": "eyJvIjoyNX0"
}

When next_cursor is null there are no more pages. Because cursors are offset-based, the final “next” page can legitimately come back empty — treat an empty page as the end.

Every error response uses one frozen JSON envelope:

{
"error": "validation_failed",
"message": "One or more fields are invalid.",
"docs_url": "https://featureflip.io/docs/management-api/errors/validation_failed",
"fields": {
"key": ["Key must be lowercase kebab-case."]
}
}
FieldTypeAlways presentDescription
errorstringYesStable machine-readable code (see Errors)
messagestringYesHuman-readable explanation
docs_urlstringYesLink to the docs page for this error code
fieldsobjectNoPer-field validation messages, present on validation_failed
retry_afterintegerNoSeconds to wait, present on rate_limited
did_you_meanarrayNoSuggested corrections for an unknown key/slug
next_actionsarrayNoSuggested follow-up requests ({ "method", "path" })

See Errors for every code and how to resolve it.

Every response carries rate-limit headers:

HeaderDescription
X-RateLimit-LimitRequests allowed in the current window
X-RateLimit-RemainingRequests remaining in the window
X-RateLimit-ResetUnix time (seconds) when the window resets

Exceeding the limit returns 429 rate_limited with a retry_after field and a Retry-After header. Back off for that many seconds before retrying.

POST requests that create a resource accept an Idempotency-Key header:

Idempotency-Key: 6f9619ff-8b86-d011-b42d-00cf4fc964ff

Send a unique key (e.g. a UUID) per logical create. If a request with the same token and key is retried, the original result is replayed instead of creating a duplicate. If a second request with the same key arrives while the first is still in flight, it returns 409 idempotency_key_in_progress — retry after the first completes.

Every resource response includes an _actions object describing which follow-up operations the current token may perform on that resource, and why not when it can’t:

"_actions": {
"update": { "allowed": true },
"delete": { "allowed": false, "reason": "requires Member role" }
}

Use _actions to drive UI affordances without hardcoding the permission model.