Creates a feature flag in the resolved project.
const url = 'https://api.featureflip.io/api/v1/orgs/example/projects/example/flags';const options = { method: 'POST', headers: {Authorization: '<Authorization>', 'Content-Type': 'application/json'}, body: '{"key":"new-checkout-flow","name":"New Checkout Flow","type":"Boolean","description":"Gates the redesigned checkout experience.","tags":["checkout","q3-launch"],"initialVariations":[{"key":"true","name":"Enabled","value":"true","description":"Serve the new checkout."},{"key":"false","name":"Disabled","value":"false","description":"Serve the legacy checkout."}],"clientSideVisible":false}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.featureflip.io/api/v1/orgs/example/projects/example/flags \ --header 'Authorization: <Authorization>' \ --header 'Content-Type: application/json' \ --data '{ "key": "new-checkout-flow", "name": "New Checkout Flow", "type": "Boolean", "description": "Gates the redesigned checkout experience.", "tags": [ "checkout", "q3-launch" ], "initialVariations": [ { "key": "true", "name": "Enabled", "value": "true", "description": "Serve the new checkout." }, { "key": "false", "name": "Disabled", "value": "false", "description": "Serve the legacy checkout." } ], "clientSideVisible": false }'Requires at least Member. Type is passed through verbatim to Type (a bare string) — an invalid value surfaces as the handler’s own validation failure. Re-fetches via GetFeatureFlagQuery so the response reflects exactly what was persisted (e.g. server-assigned variation ids).
Authorizations
Section titled “ Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “ Path Parameters ”Request Body
Section titled “ Request Body ”Request body for POST /api/v1/orgs/{org}/projects/{project}/flags. Named distinctly
from the dashboard’s own CreateFeatureFlagRequest
— see PublicCreateEnvironmentRequest for the
schemaId-collision rationale (both controllers share one Swagger document). Type
is passed through verbatim to Type, which itself takes a
bare string (Boolean/String/Number/Json) rather than the domain enum — invalid values surface
as the handler’s own validation failure, not a controller-level 400. ExpiresAtUtc is
optional and advisory; a bare date (2026-12-01) means the end of that day in UTC, and a full
timestamp is stored exactly. It must be in the future and requires the flag-expiration feature
(400 EXPIRY_IN_PAST / FEATURE_NOT_ENABLED). OwnerEmail optionally names
the owner (an active member, matched case-insensitively; requires the flag-ownership feature —
400 OWNER_NOT_MEMBER / FEATURE_NOT_ENABLED). Omitted, the owner is the caller when
the caller is a member, so a service token creates an unowned flag. IsPermanent marks
the flag permanent (a kill switch, ops toggle or entitlement that is meant to stay; never reported for
cleanup). It requires the flag-permanence feature and can’t be combined with ExpiresAtUtc
(400 FEATURE_NOT_ENABLED / PERMANENT_FLAG_HAS_EXPIRY).
object
Allowed values: Boolean, String, Number, Json.
A variation supplied at flag-creation time. Maps 1:1 onto CreateVariationDto.
object
Example
{ "key": "new-checkout-flow", "name": "New Checkout Flow", "type": "Boolean", "description": "Gates the redesigned checkout experience.", "tags": [ "checkout", "q3-launch" ], "initialVariations": [ { "key": "true", "name": "Enabled", "value": "true", "description": "Serve the new checkout." }, { "key": "false", "name": "Disabled", "value": "false", "description": "Serve the legacy checkout." } ], "clientSideVisible": false}Request body for POST /api/v1/orgs/{org}/projects/{project}/flags. Named distinctly
from the dashboard’s own CreateFeatureFlagRequest
— see PublicCreateEnvironmentRequest for the
schemaId-collision rationale (both controllers share one Swagger document). Type
is passed through verbatim to Type, which itself takes a
bare string (Boolean/String/Number/Json) rather than the domain enum — invalid values surface
as the handler’s own validation failure, not a controller-level 400. ExpiresAtUtc is
optional and advisory; a bare date (2026-12-01) means the end of that day in UTC, and a full
timestamp is stored exactly. It must be in the future and requires the flag-expiration feature
(400 EXPIRY_IN_PAST / FEATURE_NOT_ENABLED). OwnerEmail optionally names
the owner (an active member, matched case-insensitively; requires the flag-ownership feature —
400 OWNER_NOT_MEMBER / FEATURE_NOT_ENABLED). Omitted, the owner is the caller when
the caller is a member, so a service token creates an unowned flag. IsPermanent marks
the flag permanent (a kill switch, ops toggle or entitlement that is meant to stay; never reported for
cleanup). It requires the flag-permanence feature and can’t be combined with ExpiresAtUtc
(400 FEATURE_NOT_ENABLED / PERMANENT_FLAG_HAS_EXPIRY).
object
Allowed values: Boolean, String, Number, Json.
A variation supplied at flag-creation time. Maps 1:1 onto CreateVariationDto.
object
Example
{ "key": "new-checkout-flow", "name": "New Checkout Flow", "type": "Boolean", "description": "Gates the redesigned checkout experience.", "tags": [ "checkout", "q3-launch" ], "initialVariations": [ { "key": "true", "name": "Enabled", "value": "true", "description": "Serve the new checkout." }, { "key": "false", "name": "Disabled", "value": "false", "description": "Serve the legacy checkout." } ], "clientSideVisible": false}Request body for POST /api/v1/orgs/{org}/projects/{project}/flags. Named distinctly
from the dashboard’s own CreateFeatureFlagRequest
— see PublicCreateEnvironmentRequest for the
schemaId-collision rationale (both controllers share one Swagger document). Type
is passed through verbatim to Type, which itself takes a
bare string (Boolean/String/Number/Json) rather than the domain enum — invalid values surface
as the handler’s own validation failure, not a controller-level 400. ExpiresAtUtc is
optional and advisory; a bare date (2026-12-01) means the end of that day in UTC, and a full
timestamp is stored exactly. It must be in the future and requires the flag-expiration feature
(400 EXPIRY_IN_PAST / FEATURE_NOT_ENABLED). OwnerEmail optionally names
the owner (an active member, matched case-insensitively; requires the flag-ownership feature —
400 OWNER_NOT_MEMBER / FEATURE_NOT_ENABLED). Omitted, the owner is the caller when
the caller is a member, so a service token creates an unowned flag. IsPermanent marks
the flag permanent (a kill switch, ops toggle or entitlement that is meant to stay; never reported for
cleanup). It requires the flag-permanence feature and can’t be combined with ExpiresAtUtc
(400 FEATURE_NOT_ENABLED / PERMANENT_FLAG_HAS_EXPIRY).
object
Allowed values: Boolean, String, Number, Json.
A variation supplied at flag-creation time. Maps 1:1 onto CreateVariationDto.
object
Example
{ "key": "new-checkout-flow", "name": "New Checkout Flow", "type": "Boolean", "description": "Gates the redesigned checkout experience.", "tags": [ "checkout", "q3-launch" ], "initialVariations": [ { "key": "true", "name": "Enabled", "value": "true", "description": "Serve the new checkout." }, { "key": "false", "name": "Disabled", "value": "false", "description": "Serve the legacy checkout." } ], "clientSideVisible": false}Responses
Section titled “ Responses ”Created
Public detail representation of a feature flag, returned by
GET .../flags/{flag} and the body of a successful create. Drops internal/dashboard-only
fields present on FeatureFlagDto — notably ProjectId (implicit in the
route) and Staleness (an internal analytics signal, not part of the public contract).
object
Allowed values: Boolean, String, Number, Json.
A single variation on a flag, as returned in Variations.
object
Capability hints: which operations the authenticated caller may perform on this resource, each with allowed + optional reason. Computed from the caller’s role and the resource’s state. Injected at runtime; safe to ignore. Present only on top-level resource responses — nested occurrences (e.g. a rule inside a targeting-config response) do not carry it.
object
One entry in a resource’s _actions block: may the caller perform it, and if not, why.
object
A flag’s owner as returned on flag reads. Null means the flag is unowned.
object
Capability hints: which operations the authenticated caller may perform on this resource, each with allowed + optional reason. Computed from the caller’s role and the resource’s state. Injected at runtime; safe to ignore. Present only on top-level resource responses — nested occurrences (e.g. a rule inside a targeting-config response) do not carry it.
object
One entry in a resource’s _actions block: may the caller perform it, and if not, why.
object
Example
{ "id": "0197b6a0-5f2a-7c3e-9b4d-1e2f3a4b5c6d", "key": "new-checkout-flow", "name": "New Checkout Flow", "description": "Gates the redesigned checkout experience.", "type": "Boolean", "isArchived": false, "clientSideVisible": false, "tags": [ "checkout", "q3-launch" ], "variations": [ { "id": "0197b6a0-6a1b-7d4f-8c2e-2f3a4b5c6d7e", "key": "true", "name": "Enabled", "value": "true", "description": "Serve the new checkout." }, { "id": "0197b6a0-6a1b-7d4f-8c2e-3a4b5c6d7e8f", "key": "false", "name": "Disabled", "value": "false", "description": "Serve the legacy checkout." } ], "createdAt": "2026-06-01T12:00:00Z", "updatedAt": "2026-06-15T08:30:00Z"}Headers
Section titled “ Headers ”The maximum number of requests permitted per rate-limit window for this caller.
The number of requests remaining in the current rate-limit window.
The UTC time at which the current rate-limit window resets, as a Unix timestamp in seconds.
Bad Request
The frozen public-API error contract. error codes are stable snake_case strings. Wire keys
are frozen snake_case too (error, message, docs_url, fields,
retry_after) — the global camelCase naming policy would otherwise emit
docsUrl/retryAfter, breaking the published spec. !:JsonPropertyName
always wins over the policy, so these are pinned explicitly rather than relying on the
property names already being lowercase for the single-word ones.
did_you_mean, next_actions and connection_id are ADDITIVE optional keys
(null → omitted via the global DefaultIgnoreCondition = WhenWritingNull), so
pre-existing error bodies are byte-for-byte unchanged when they’re absent.
connection_id is present only on the sso_required error: the organization
requires single sign-on, and this credential didn’t come through its identity provider.
connection_id identifies which SSO connection to sign in with.
object
object
object
Example
{ "error": "not_found", "message": "Flag 'new-checkout-flw' was not found in project 'checkout'.", "docs_url": "https://featureflip.io/docs/management-api/errors/not_found", "did_you_mean": [ "new-checkout-flow" ], "next_actions": [ { "method": "GET", "path": "/api/v1/orgs/acme/projects/checkout/flags" } ]}Headers
Section titled “ Headers ”The maximum number of requests permitted per rate-limit window for this caller.
The number of requests remaining in the current rate-limit window.
The UTC time at which the current rate-limit window resets, as a Unix timestamp in seconds.
Authentication is required, or the supplied API token is invalid or expired.
The frozen public-API error contract. error codes are stable snake_case strings. Wire keys
are frozen snake_case too (error, message, docs_url, fields,
retry_after) — the global camelCase naming policy would otherwise emit
docsUrl/retryAfter, breaking the published spec. !:JsonPropertyName
always wins over the policy, so these are pinned explicitly rather than relying on the
property names already being lowercase for the single-word ones.
did_you_mean, next_actions and connection_id are ADDITIVE optional keys
(null → omitted via the global DefaultIgnoreCondition = WhenWritingNull), so
pre-existing error bodies are byte-for-byte unchanged when they’re absent.
connection_id is present only on the sso_required error: the organization
requires single sign-on, and this credential didn’t come through its identity provider.
connection_id identifies which SSO connection to sign in with.
object
object
object
Example
{ "error": "not_found", "message": "Flag 'new-checkout-flw' was not found in project 'checkout'.", "docs_url": "https://featureflip.io/docs/management-api/errors/not_found", "did_you_mean": [ "new-checkout-flow" ], "next_actions": [ { "method": "GET", "path": "/api/v1/orgs/acme/projects/checkout/flags" } ]}Headers
Section titled “ Headers ”The maximum number of requests permitted per rate-limit window for this caller.
The number of requests remaining in the current rate-limit window.
The UTC time at which the current rate-limit window resets, as a Unix timestamp in seconds.
Forbidden
The frozen public-API error contract. error codes are stable snake_case strings. Wire keys
are frozen snake_case too (error, message, docs_url, fields,
retry_after) — the global camelCase naming policy would otherwise emit
docsUrl/retryAfter, breaking the published spec. !:JsonPropertyName
always wins over the policy, so these are pinned explicitly rather than relying on the
property names already being lowercase for the single-word ones.
did_you_mean, next_actions and connection_id are ADDITIVE optional keys
(null → omitted via the global DefaultIgnoreCondition = WhenWritingNull), so
pre-existing error bodies are byte-for-byte unchanged when they’re absent.
connection_id is present only on the sso_required error: the organization
requires single sign-on, and this credential didn’t come through its identity provider.
connection_id identifies which SSO connection to sign in with.
object
object
object
Example
{ "error": "not_found", "message": "Flag 'new-checkout-flw' was not found in project 'checkout'.", "docs_url": "https://featureflip.io/docs/management-api/errors/not_found", "did_you_mean": [ "new-checkout-flow" ], "next_actions": [ { "method": "GET", "path": "/api/v1/orgs/acme/projects/checkout/flags" } ]}Headers
Section titled “ Headers ”The maximum number of requests permitted per rate-limit window for this caller.
The number of requests remaining in the current rate-limit window.
The UTC time at which the current rate-limit window resets, as a Unix timestamp in seconds.
Not Found
The frozen public-API error contract. error codes are stable snake_case strings. Wire keys
are frozen snake_case too (error, message, docs_url, fields,
retry_after) — the global camelCase naming policy would otherwise emit
docsUrl/retryAfter, breaking the published spec. !:JsonPropertyName
always wins over the policy, so these are pinned explicitly rather than relying on the
property names already being lowercase for the single-word ones.
did_you_mean, next_actions and connection_id are ADDITIVE optional keys
(null → omitted via the global DefaultIgnoreCondition = WhenWritingNull), so
pre-existing error bodies are byte-for-byte unchanged when they’re absent.
connection_id is present only on the sso_required error: the organization
requires single sign-on, and this credential didn’t come through its identity provider.
connection_id identifies which SSO connection to sign in with.
object
object
object
Example
{ "error": "not_found", "message": "Flag 'new-checkout-flw' was not found in project 'checkout'.", "docs_url": "https://featureflip.io/docs/management-api/errors/not_found", "did_you_mean": [ "new-checkout-flow" ], "next_actions": [ { "method": "GET", "path": "/api/v1/orgs/acme/projects/checkout/flags" } ]}Headers
Section titled “ Headers ”The maximum number of requests permitted per rate-limit window for this caller.
The number of requests remaining in the current rate-limit window.
The UTC time at which the current rate-limit window resets, as a Unix timestamp in seconds.
Rate limit exceeded. Retry after the interval indicated by the Retry-After header.
The frozen public-API error contract. error codes are stable snake_case strings. Wire keys
are frozen snake_case too (error, message, docs_url, fields,
retry_after) — the global camelCase naming policy would otherwise emit
docsUrl/retryAfter, breaking the published spec. !:JsonPropertyName
always wins over the policy, so these are pinned explicitly rather than relying on the
property names already being lowercase for the single-word ones.
did_you_mean, next_actions and connection_id are ADDITIVE optional keys
(null → omitted via the global DefaultIgnoreCondition = WhenWritingNull), so
pre-existing error bodies are byte-for-byte unchanged when they’re absent.
connection_id is present only on the sso_required error: the organization
requires single sign-on, and this credential didn’t come through its identity provider.
connection_id identifies which SSO connection to sign in with.
object
object
object
Example
{ "error": "not_found", "message": "Flag 'new-checkout-flw' was not found in project 'checkout'.", "docs_url": "https://featureflip.io/docs/management-api/errors/not_found", "did_you_mean": [ "new-checkout-flow" ], "next_actions": [ { "method": "GET", "path": "/api/v1/orgs/acme/projects/checkout/flags" } ]}Headers
Section titled “ Headers ”Number of seconds to wait before retrying the request.
The maximum number of requests permitted per rate-limit window for this caller.
The number of requests remaining in the current rate-limit window.
The UTC time at which the current rate-limit window resets, as a Unix timestamp in seconds.