Conventions
These conventions apply to every Management API endpoint.
Versioning
Section titled “Versioning”All endpoints live under /api/v1. Breaking changes ship under a new version prefix; additive changes (new fields, new endpoints) may appear within v1.
Content type
Section titled “Content type”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.
Resource addressing
Section titled “Resource addressing”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.
Pagination
Section titled “Pagination”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.
Errors
Section titled “Errors”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."] }}| Field | Type | Always present | Description |
|---|---|---|---|
error | string | Yes | Stable machine-readable code (see Errors) |
message | string | Yes | Human-readable explanation |
docs_url | string | Yes | Link to the docs page for this error code |
fields | object | No | Per-field validation messages, present on validation_failed |
retry_after | integer | No | Seconds to wait, present on rate_limited |
did_you_mean | array | No | Suggested corrections for an unknown key/slug |
next_actions | array | No | Suggested follow-up requests ({ "method", "path" }) |
See Errors for every code and how to resolve it.
Rate limiting
Section titled “Rate limiting”Every response carries rate-limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the window |
X-RateLimit-Reset | Unix 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.
Idempotency
Section titled “Idempotency”POST requests that create a resource accept an Idempotency-Key header:
Idempotency-Key: 6f9619ff-8b86-d011-b42d-00cf4fc964ffSend 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.
Hypermedia actions (_actions)
Section titled “Hypermedia actions (_actions)”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.