Skip to content

Declaratively replaces the resolved flag's entire targeting rule set in the resolved environment.

PUT
/api/v1/orgs/{org}/projects/{project}/flags/{flag}/environments/{env}/targeting
curl --request PUT \
--url https://api.featureflip.io/api/v1/orgs/example/projects/example/flags/example/environments/example/targeting \
--header 'Authorization: <Authorization>' \
--header 'Content-Type: application/json' \
--data '{ "rules": [ { "description": "North American users get the new flow at 25%.", "variationId": "0197b6a0-6a1b-7d4f-8c2e-2f3a4b5c6d7e", "rolloutPercentage": 25, "bucketBy": "user_id", "conditionGroups": [ { "operator": "And", "conditions": [ { "attribute": "country", "operator": "In", "values": [ "US", "CA" ], "negate": false } ] } ] } ] }'

The ordered Rules list is applied in array order (= evaluation order); an empty or omitted list clears all rules. Segment-scoped rules are supported. The replace is atomic. A rule identical to an existing one keeps that rule’s id (moving it to its new position); only new or changed rules get new ids. Requires at least Member.

org
required
string
project
required
string
flag
required
string
env
required
string
If-Match
string

Optional. The ETag from a GET of this resource. The write applies only if the resource still has that ETag, meaning nothing in its body has changed since; otherwise 412 precondition_failed. * or no header applies the write unconditionally.

X-Intent-Reason
string

Why you are making this change, recorded in the audit log. Percent-encoded UTF-8 (encode it the way encodeURIComponent does); at most 2048 characters after decoding. Required when the target environment — or, for project-wide changes, any environment in the project — requires a change reason; without one the request is refused with 422 change_reason_required.

Request body for PUT .../targeting — a declarative full replace of the environment’s ordered targeting rule set. Rules are applied in array order (index = priority, so the array order IS the evaluation order). Rules is nullable so that an OMITTED rules field (a body of {}) binds successfully instead of failing ASP.NET Core’s implicit required-property check on non-nullable reference types — both an omitted rules field and an explicit empty list ({ "rules": [] }) clear all targeting rules for the environment. Each element is a PublicCreateTargetingRuleRequest (identical to the POST .../rules body), so UserSegmentId is supported — segment-scoped rules can be submitted through this endpoint. The replace is atomic (all-or-nothing): the whole rule set is swapped in one transaction, or nothing changes.

object
rules
Array<object>
nullable

Request body for POST .../rules. The Application-side CreateTargetingRuleDto/CreateConditionGroupDto/CreateConditionDto all take their Operator as a bare string, re-parsed CASE-SENSITIVELY inside CreateTargetingRuleCommandHandler — FlagTargetingController’s mapping helpers validate every operator against the enum’s member-name allowlist AND canonicalize the casing (e.g. "in" → "In") before building the command DTO, so a valid case-insensitive match never trips the handler’s strict re-parse.

object
description
string
nullable
variationId
string format: uuid
rolloutPercentage
integer format: int32
nullable
userSegmentId
string format: uuid
nullable
conditionGroups
required
Array<object>

Request-side condition group, symmetric with PublicConditionGroupResponse.

object
operator
required

Allowed values: And, Or.

string
Allowed values: And Or
conditions
required
Array<object>

Request-side condition, symmetric with PublicConditionResponse.

object
attribute
required
string
operator
required

Allowed values: Equals, NotEquals, Contains, NotContains, GreaterThan, LessThan, GreaterThanOrEqual, LessThanOrEqual, In, NotIn, MatchesRegex, StartsWith, EndsWith, Before, After, SemverEquals, SemverGreaterThan, SemverGreaterThanOrEqual, SemverLessThan, SemverLessThanOrEqual.

string
Allowed values: Equals NotEquals Contains NotContains GreaterThan LessThan GreaterThanOrEqual LessThanOrEqual In NotIn MatchesRegex StartsWith EndsWith Before After SemverEquals SemverGreaterThan SemverGreaterThanOrEqual SemverLessThan SemverLessThanOrEqual
values
required
Array<string>
negate
boolean
bucketBy

Context attribute the rollout hashes on (default user_id; userId is accepted as an alias). A context without it skips this rule, even at 100%. Changing it reshuffles who is in the rollout. A fixed rule (no rolloutPercentage) never hashes and always stores user_id. When omitted on PUT .../targeting, a rollout keeps the value of the existing rollout it edits: the existing rule with the same targeting, told apart from others with that targeting by its variation, then its description, then its position. Any other rule gets user_id. The request is refused with 400 until it sends bucketBy for a rollout whose edited rule it can’t tell when the answer changes its attribute: the rollout shares only its targeting with several rules that bucket differently, or it matches no existing rule while the request removes a rollout bucketed by another attribute. On create, omitting it gives user_id.

string
nullable

Example

{
"description": "North American users get the new flow at 25%.",
"variationId": "0197b6a0-6a1b-7d4f-8c2e-2f3a4b5c6d7e",
"rolloutPercentage": 25,
"bucketBy": "user_id",
"conditionGroups": [
{
"operator": "And",
"conditions": [
{
"attribute": "country",
"operator": "In",
"values": [
"US",
"CA"
],
"negate": false
}
]
}
]
}

No Content

ETag
string

Strong ETag of the resource’s whole body as sent to this caller: it changes whenever anything in the body does. Send it back as If-Match on a PUT.

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.

Bad Request

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.

The resource changed since the If-Match ETag.

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.

A change reason is required: this change affects an environment that requires one (error code change_reason_required). Retry with the X-Intent-Reason 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"
}
]
}
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.