Skip to content

Creates an environment in the resolved project.

POST
/api/v1/orgs/{org}/projects/{project}/environments
curl --request POST \
--url https://api.featureflip.io/api/v1/orgs/example/projects/example/environments \
--header 'Authorization: <Authorization>' \
--header 'Content-Type: application/json' \
--data '{ "key": "production", "name": "Production", "color": "#16A34A", "sortOrder": 2 }'

Requires at least Member. An environment past the plan’s per-project environment cap is a 403 plan_limit_reached.

org
required
string
project
required
string

Request body for POST /api/v1/orgs/{org}/projects/{project}/environments. Named distinctly from the dashboard’s own CreateEnvironmentRequest — both controllers share one Swagger document (see Program.cs’s AddSwaggerGen``DocInclusionPredicate override), and Swashbuckle’s default schemaId is the bare type name, so an identical name in either namespace throws a schemaId collision at --export-openapi time.

object
key
required

The environment’s key.

string
name
required

The environment’s display name.

string
color

The environment’s display color, or null.

string
nullable
sortOrder

Position among the project’s environments.

integer format: int32
requireChangeReason

Require a change reason (X-Intent-Reason) on every change to flags in this environment. Business plan and above.

boolean
nullable
requireConfirmation

Ask dashboard users to type the environment key before a change lands. Dashboard only; not enforced by the API.

boolean
nullable

Example

{
"key": "production",
"name": "Production",
"color": "#16A34A",
"sortOrder": 2
}

Created

Media type application/json

Public representation of an environment, returned by GET /api/v1/orgs/{org}/projects/{project}/environments (list), GET .../environments/{env} (detail), and the body of a successful create/update. Drops internal fields (e.g. ProjectId is implicit in the route) — mirrors ProjectResponse’s minimal shape. Named PublicEnvironmentResponse rather than EnvironmentResponse (unlike the sibling ProjectResponse, which didn’t collide) because the dashboard already has its own EnvironmentResponse — see PublicCreateEnvironmentRequest for the schemaId-collision rationale.

object
id
required

The environment’s id.

string format: uuid
key
required

The environment’s key.

string
name
required

The environment’s display name.

string
color

The environment’s display color, or null.

string
nullable
sortOrder
required

Position among the project’s environments.

integer format: int32
requireChangeReason
required

Require a change reason (X-Intent-Reason) on every change to flags in this environment. Business plan and above.

boolean
requireConfirmation
required

Ask dashboard users to type the environment key before a change lands. Dashboard only; not enforced by the API.

boolean
_actions

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
key
additional properties

One entry in a resource’s _actions block: may the caller perform it, and if not, why.

object
allowed
boolean
reason
string
nullable

Example

{
"id": "0197b69f-2b3c-7d4e-9f5a-6b7c8d9e0f1a",
"key": "production",
"name": "Production",
"color": "#16A34A",
"sortOrder": 2,
"requireChangeReason": false,
"requireConfirmation": false
}
X-RateLimit-Limit
integer

The maximum number of requests permitted per rate-limit window for this caller.

X-RateLimit-Remaining
integer

The number of requests remaining in the current rate-limit window.

X-RateLimit-Reset
integer

The UTC time at which the current rate-limit window resets, as a Unix timestamp in seconds.

The request is malformed, or it carries an If-Match header, which this endpoint can’t evaluate. Send no If-Match, or *.

Media type application/json

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. plan_limit is present only on the plan_limit_reached error: which plan cap the request would have exceeded, so a client can branch on it without parsing message.

object
error
required
string
message
required
string
docs_url
required
string
fields
object
key
additional properties
Array<string>
retry_after
integer format: int32
nullable
did_you_mean
Array<string>
nullable
next_actions
Array<object>
nullable
object
method
required
string
path
required
string
connection_id
string format: uuid
nullable
plan_limit

The cap behind a plan_limit_reached error. resource is the capped collection (projects, environments, flags, webhooks, members); plan is the plan’s wire name, as GET /api/v1/orgs/{org} returns it; max is the cap. The environment and flag caps are per project.

object
resource
required
string
plan
required
string
max
required
integer format: int32

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"
}
]
}
X-RateLimit-Limit
integer

The maximum number of requests permitted per rate-limit window for this caller.

X-RateLimit-Remaining
integer

The number of requests remaining in the current rate-limit window.

X-RateLimit-Reset
integer

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.

Media type application/json

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. plan_limit is present only on the plan_limit_reached error: which plan cap the request would have exceeded, so a client can branch on it without parsing message.

object
error
required
string
message
required
string
docs_url
required
string
fields
object
key
additional properties
Array<string>
retry_after
integer format: int32
nullable
did_you_mean
Array<string>
nullable
next_actions
Array<object>
nullable
object
method
required
string
path
required
string
connection_id
string format: uuid
nullable
plan_limit

The cap behind a plan_limit_reached error. resource is the capped collection (projects, environments, flags, webhooks, members); plan is the plan’s wire name, as GET /api/v1/orgs/{org} returns it; max is the cap. The environment and flag caps are per project.

object
resource
required
string
plan
required
string
max
required
integer format: int32

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"
}
]
}
X-RateLimit-Limit
integer

The maximum number of requests permitted per rate-limit window for this caller.

X-RateLimit-Remaining
integer

The number of requests remaining in the current rate-limit window.

X-RateLimit-Reset
integer

The UTC time at which the current rate-limit window resets, as a Unix timestamp in seconds.

Forbidden

Media type application/json

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. plan_limit is present only on the plan_limit_reached error: which plan cap the request would have exceeded, so a client can branch on it without parsing message.

object
error
required
string
message
required
string
docs_url
required
string
fields
object
key
additional properties
Array<string>
retry_after
integer format: int32
nullable
did_you_mean
Array<string>
nullable
next_actions
Array<object>
nullable
object
method
required
string
path
required
string
connection_id
string format: uuid
nullable
plan_limit

The cap behind a plan_limit_reached error. resource is the capped collection (projects, environments, flags, webhooks, members); plan is the plan’s wire name, as GET /api/v1/orgs/{org} returns it; max is the cap. The environment and flag caps are per project.

object
resource
required
string
plan
required
string
max
required
integer format: int32

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"
}
]
}
X-RateLimit-Limit
integer

The maximum number of requests permitted per rate-limit window for this caller.

X-RateLimit-Remaining
integer

The number of requests remaining in the current rate-limit window.

X-RateLimit-Reset
integer

The UTC time at which the current rate-limit window resets, as a Unix timestamp in seconds.

Not Found

Media type application/json

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. plan_limit is present only on the plan_limit_reached error: which plan cap the request would have exceeded, so a client can branch on it without parsing message.

object
error
required
string
message
required
string
docs_url
required
string
fields
object
key
additional properties
Array<string>
retry_after
integer format: int32
nullable
did_you_mean
Array<string>
nullable
next_actions
Array<object>
nullable
object
method
required
string
path
required
string
connection_id
string format: uuid
nullable
plan_limit

The cap behind a plan_limit_reached error. resource is the capped collection (projects, environments, flags, webhooks, members); plan is the plan’s wire name, as GET /api/v1/orgs/{org} returns it; max is the cap. The environment and flag caps are per project.

object
resource
required
string
plan
required
string
max
required
integer format: int32

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"
}
]
}
X-RateLimit-Limit
integer

The maximum number of requests permitted per rate-limit window for this caller.

X-RateLimit-Remaining
integer

The number of requests remaining in the current rate-limit window.

X-RateLimit-Reset
integer

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.

Media type application/json

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. plan_limit is present only on the plan_limit_reached error: which plan cap the request would have exceeded, so a client can branch on it without parsing message.

object
error
required
string
message
required
string
docs_url
required
string
fields
object
key
additional properties
Array<string>
retry_after
integer format: int32
nullable
did_you_mean
Array<string>
nullable
next_actions
Array<object>
nullable
object
method
required
string
path
required
string
connection_id
string format: uuid
nullable
plan_limit

The cap behind a plan_limit_reached error. resource is the capped collection (projects, environments, flags, webhooks, members); plan is the plan’s wire name, as GET /api/v1/orgs/{org} returns it; max is the cap. The environment and flag caps are per project.

object
resource
required
string
plan
required
string
max
required
integer format: int32

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"
}
]
}
Retry-After
integer

Number of seconds to wait before retrying the request.

X-RateLimit-Limit
integer

The maximum number of requests permitted per rate-limit window for this caller.

X-RateLimit-Remaining
integer

The number of requests remaining in the current rate-limit window.

X-RateLimit-Reset
integer

The UTC time at which the current rate-limit window resets, as a Unix timestamp in seconds.