Feature Flags API
A feature flag is a remote switch with properties that the SDK reads for each device. This API manages the flags of an application: the same operations as Audience → Feature Flags in the Control Panel. To read flags in a website or a mobile app, use the Web SDK, the Android SDK, or the iOS SDK.
Base URL
Anchor link tohttps://rpc-api.svc-nue.pushwoosh.comAll endpoints are served over HTTPS. Requests and responses use application/json.
Authentication
Anchor link toEvery request must include an Authorization header with your Server API token:
Authorization: Api YOUR_API_TOKENReading flags needs access to the application. Creating and changing them needs the right to manage it.
Conventions
Anchor link to- Field naming: request bodies accept
lowerCamelCaseorsnake_case. Responses usesnake_case, as in the Feature flag object. - Percentages are in basis points:
percent_bpfrom0to10000, where2000is 20%. - Versions: every flag carries a
version.Update,SetRulePercent, andReshuffletake the version you last read and fail withVERSION_CONFLICTwhen the flag changed since, so concurrent edits never overwrite each other. Read the flag again and retry. - Statuses:
active,killed, orarchived.Kill,Unkill,Archive, andRestorechange only the status and take no version.
Endpoints
Anchor link toEach REST endpoint has an MCP tool with the same parameters:
| Method | Path | Description | MCP tool |
|---|---|---|---|
GET | /api/applications/{application_code}/feature_flags | List the application’s flags | list_feature_flags |
GET | /api/applications/{application_code}/feature_flags/{key} | Get one flag | get_feature_flag |
POST | /api/applications/{application_code}/feature_flags | Create a flag | create_feature_flag |
POST | /api/applications/{application_code}/feature_flags/{key} | Replace name, description, properties, and rules | update_feature_flag |
POST | /api/applications/{application_code}/feature_flags/{key}/rules/{rule_id}/percent | Change the rollout of one rule | set_feature_flag_rule_percent |
POST | /api/applications/{application_code}/feature_flags/{key}/kill | Turn the flag off for everyone | kill_feature_flag |
POST | /api/applications/{application_code}/feature_flags/{key}/unkill | Turn a killed flag back on | unkill_feature_flag |
POST | /api/applications/{application_code}/feature_flags/{key}/archive | Archive a flag | archive_feature_flag |
POST | /api/applications/{application_code}/feature_flags/{key}/restore | Restore an archived flag | restore_feature_flag |
POST | /api/applications/{application_code}/feature_flags/{key}/reshuffle | Pick a new sample of users | reshuffle_feature_flag |
GET | /api/applications/{application_code}/feature_flags/{key}/history | List the changes of a flag | list_feature_flag_history |
Every endpoint except List and History returns { "flag": { ... } }, the Feature flag object after the operation.
List
Anchor link toGET /api/applications/{application_code}/feature_flags
The request takes these parameters:
| Parameter | In | Type | Description |
|---|---|---|---|
application_code | path | string | The application code. |
status | query | string | Optional. active, killed, or archived. Omit to list all flags. |
Returns { "flags": [ ... ] }, an array of Feature flag objects.
GET /api/applications/{application_code}/feature_flags/{key}
Returns one flag with its properties, rules, and current version.
Create
Anchor link toCreates an active flag. The server generates the rule_id of every rule.
POST /api/applications/{application_code}/feature_flags
The request body has these fields:
| Parameter | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Matches ^[A-Za-z0-9_.-]{1,58}$. Unique in the application, archived flags included, compared case-insensitively. Can’t be changed later. |
name | string | No | Display name, up to 255 characters. |
description | string | No | Description. |
properties | array of Property objects | No | Up to 50 properties. |
defaults | object | Yes, if there are properties | The value of every property for devices the flag is off for, keyed by property key. Each value must match the property type. |
rules | array of Rule objects | No | Ordered rules, up to 10. The first rule whose segment matches the device decides. |
Request example
Anchor link to{ "key": "new_checkout", "name": "New checkout", "properties": [ { "key": "button_color", "type": "string" }, { "key": "show_apple_pay", "type": "boolean" } ], "defaults": { "button_color": "blue", "show_apple_pay": false }, "rules": [ { "name": "Beta testers", "filter_code": "XXXXX-XXXXX", "percent_bp": 10000, "overrides": { "button_color": "green", "show_apple_pay": true } }, { "name": "Everyone else", "filter_code": "", "percent_bp": 2000, "overrides": { "button_color": "orange" } } ]}Update
Anchor link toReplaces the name, description, properties, defaults, and rules of a flag. The status never changes here. Send all fields, not only the changed ones.
POST /api/applications/{application_code}/feature_flags/{key}
Takes the same body as Create without key, plus version from your last read. Keep the rule_id of every existing rule. A rule without rule_id is created as new. A property’s type can’t change after the property is created.
SetRulePercent
Anchor link toChanges the rollout of one rule. Users who already have the flag keep it when the percentage grows.
POST /api/applications/{application_code}/feature_flags/{key}/rules/{rule_id}/percent
{ "percentBp": 5000, "version": 3 }Kill and Unkill
Anchor link toPOST .../feature_flags/{key}/kill turns the flag off for every device, which then gets the default property values. Rules and properties are kept.
POST .../feature_flags/{key}/unkill turns a killed flag back on with its rules as they were.
Both take an empty body {}.
Archive and Restore
Anchor link toPOST .../feature_flags/{key}/archive stops sending the flag to devices. An archived flag doesn’t count toward the plan’s limit of flags per application, and its key stays reserved.
POST .../feature_flags/{key}/restore brings an archived flag back as killed, so it never turns on by itself. Call unkill to roll it out again.
Both take an empty body {}.
Reshuffle
Anchor link toPlaces every user in a new bucket, so the same percentages select a different sample of users. Some users who have the flag on lose it, and others get it.
POST /api/applications/{application_code}/feature_flags/{key}/reshuffle
{ "confirm": true, "version": 3 }confirm must be true. Otherwise the request fails with CONFIRM_REQUIRED.
History
Anchor link toReturns the changes of a flag, newest first: who made each change, when, and which fields changed. Like Get, it needs access to the application, and it returns 404 when the application or the flag doesn’t exist.
GET /api/applications/{application_code}/feature_flags/{key}/history
The request takes these parameters:
| Parameter | In | Type | Description |
|---|---|---|---|
application_code | path | string | The application code. |
key | path | string | The flag key. |
page | query | integer | Optional. Page number, starting from 0. |
per_page | query | integer | Optional. Entries per page, 20 by default, up to 100. |
Returns { "entries": [ ... ], "total": N }, where total is the number of entries across all pages. Each entry has these fields:
| Field | Type | Description |
|---|---|---|
version | integer | The flag version this change produced. |
action | string | create, update, set_percent, kill, unkill, archive, restore, or reshuffle. |
user_id | string | The ID of the Control Panel user who made the change. 0 for a change made with an API token or by a Pushwoosh service. |
user_email | string | That user’s email. Empty when the user was deleted or user_id is 0. |
by_api_token | boolean | true when the change was made with an API token. false for a change by a user or by a Pushwoosh service. |
api_token_name | string | The name the API token had at the time of the change. It stays after the token is deleted. Empty when the token has no name or by_api_token is false. |
created | string | Time of the change, RFC 3339. |
flag | Feature flag object | The flag after the change. |
changes | array | The fields that differ from the previous version. Empty for create and reshuffle, and for the oldest entry of a flag created before history was recorded, because there is no earlier version to compare it with. |
Each item of changes has field, before, and after. before and after hold the values encoded as JSON strings, so a string value comes in quotes. field takes these values:
field | What changed |
|---|---|
name, description, status | The flag field with this name. |
properties | The properties and their default values. |
rules[<rule_id>] | One rule. For an added rule, before is empty. For a removed rule, after is empty. |
rules.order | The order of the rules. |
Response example
Anchor link to{ "entries": [ { "version": 4, "action": "set_percent", "user_id": "12345", "user_email": "owner@example.com", "by_api_token": false, "api_token_name": "", "created": "2026-10-14T09:30:00Z", "flag": { "key": "new_checkout", "status": "active", "version": 4 }, "changes": [ { "field": "rules[r_1a2b3c4d]", "before": "{\"rule_id\":\"r_1a2b3c4d\",\"name\":\"Beta testers\",\"filter_code\":\"XXXXX-XXXXX\",\"percent_bp\":2000,\"overrides\":{\"button_color\":\"green\"}}", "after": "{\"rule_id\":\"r_1a2b3c4d\",\"name\":\"Beta testers\",\"filter_code\":\"XXXXX-XXXXX\",\"percent_bp\":5000,\"overrides\":{\"button_color\":\"green\"}}" } ] } ], "total": 4}The flag object in the example is shortened. History is recorded for changes from October 13, 2026. A flag with no recorded changes returns no entries.
Read flags for a device
Anchor link toThe SDKs read flags from a separate endpoint. You don’t call it when you use the Web SDK, the Android SDK, or the iOS SDK, because they pick the host and cache the answer themselves.
POST https://api.pushwoosh.com/api/v2/device-api/getFeatureFlags
The request body has these fields:
| Field | Type | Description |
|---|---|---|
application | string | The application code. |
hwid | string | The device’s HWID. |
etag | string | Optional. The etag of the last answer the device holds. |
The response has these fields:
| Field | Type | Description |
|---|---|---|
flags | object | Flags keyed by flag key. Each has enabled, rule_id, and properties. rule_id is default when no rule matched and empty for a killed flag. Archived flags aren’t included. |
etag | string | Send it with the next request. |
not_modified | boolean | true when the sent etag is current. flags is then omitted and the device keeps its copy. |
partial | boolean | true when the device’s segments weren’t matched in time. Don’t cache this answer. |
On a plan without feature flags, the response is empty.
Objects
Anchor link toFeature flag object
Anchor link toEvery endpoint returns the flag in this shape:
| Field | Type | Description |
|---|---|---|
application_code | string | The application the flag belongs to. |
key | string | The flag key. |
name | string | Display name. |
description | string | Description. |
status | string | active, killed, or archived. |
properties | array of Property objects | The flag’s properties. |
defaults | object | The value of every property for devices the flag is off for. |
rules | array of Rule objects | Ordered rules. |
version | integer | Send it back with Update, SetRulePercent, and Reshuffle. |
created | string | Creation time, RFC 3339. |
updated | string | Last change time, RFC 3339. |
last_modified_by_user_id | string | The Control Panel user who changed the flag last. 0 when the last change was made with an API token or by a Pushwoosh service. |
Property object
Anchor link toA property describes one value the SDK reads from the flag:
| Field | Type | Description |
|---|---|---|
key | string | Matches ^[A-Za-z0-9_]{1,64}$, unique within the flag. |
type | string | string, number, boolean, or json. Can’t change once the property exists. |
Rule object
Anchor link toA rule decides who gets the flag on and with which values:
| Field | Type | Description |
|---|---|---|
rule_id | string | Generated by the server, r_ followed by 8 hex digits, and stable across updates. Leave it empty for a new rule. |
name | string | Rule name. |
filter_code | string | The segment the device must match. Empty matches every device. The segment can hold only tag, location, application, and list conditions. |
percent_bp | integer | Share of matching users the flag is on for, 0–10000. A device that matches the segment stops at this rule even when it falls outside the percentage. |
overrides | object | Property values that replace the defaults for devices this rule turns on, keyed by property key. |
Errors
Anchor link toA rejected request returns one of these HTTP status codes:
| HTTP status | Meaning |
|---|---|
400 Bad Request | The request is invalid (field_violations) or the flag’s state or the plan doesn’t allow it (Type). See the body formats and reasons below. |
401 Unauthorized | The Authorization header is missing or the token is invalid. |
403 Forbidden | The token can’t manage the application, or the application is a system one. |
404 Not Found | The application or the flag doesn’t exist. |
409 Conflict | Another request is changing the same flag at this moment. Retry the request. |
500 Internal Server Error | Unexpected server failure. The body is {"message": "Sorry, an unexpected error occurred"}. |
An invalid request returns 400 with a list of field violations. Each violation names the field, the reason, and the arguments of the reason:
{ "code": 3, "message": "validation failed", "details": [ { "@type": "type.googleapis.com/pushwoosh.rpc.errors.v3.BadRequest", "field_violations": [ { "field": "key", "description": "a feature flag with this key already exists in the application", "reason": "KEY_TAKEN", "args": { "key": "new_checkout" } } ] } ]}A request that the flag’s state or the plan doesn’t allow returns 400 with one failed precondition. Its Type and Description fields are capitalized:
{ "code": 9, "message": "precondition failed", "details": [ { "@type": "type.googleapis.com/pushwoosh.rpc.errors.v3.FailedPrecondition", "Type": "VERSION_CONFLICT", "Description": "feature flag was changed: version 3 was sent, the current one is 4" } ]}The reason of a field violation and the Type of a failed precondition take these values:
| Reason | Cause |
|---|---|
KEY_INVALID | The key doesn’t match ^[A-Za-z0-9_.-]{1,58}$. |
KEY_TAKEN | Another flag of the application, archived ones included, has this key. |
TEXT_INVALID | The name or description is too long or has forbidden characters. |
PROPERTIES_LIMIT | More than 50 properties. |
PROPERTY_KEY_INVALID | A property key is invalid or repeated. |
PROPERTY_TYPE_INVALID | A property type isn’t string, number, boolean, or json. |
PROPERTY_TYPE_IMMUTABLE | The request changes the type of an existing property. |
VALUE_INVALID | A default or override is missing, doesn’t match the property type, or is nested deeper than 5 levels. |
PROPERTIES_TOO_LARGE | The property values of the flag exceed 10,000 bytes. |
RULES_LIMIT | More than 10 rules. |
RULE_NOT_FOUND | The rule_id isn’t one of the flag’s rules. |
PERCENT_INVALID | percent_bp is outside 0–10000. |
OVERRIDE_UNKNOWN_PROPERTY | An override names a property the flag doesn’t have. |
SEGMENT_NOT_FOUND | The rule segment doesn’t exist or belongs to another application. |
INVALID_SEGMENT_TYPE | The rule segment has a condition flags can’t evaluate: event, static, external, last update, or best time to send. |
FLAG_DEPENDENCY | The rule segment has a feature flag condition, and a flag can’t depend on another flag. Returned by Create, Update, and Restore, with the segment code in filter_code. |
CONFIRM_REQUIRED | Reshuffle was sent without confirm: true. |
TAG_CONFLICT | The account has a custom tag named PW_FF_ followed by the flag key. |
FEATURE_FLAGS_NOT_ALLOWED | The account’s plan doesn’t include feature flags. |
FLAGS_LIMIT | The application has as many non-archived flags as the plan allows. |
VERSION_CONFLICT | The flag changed since the version you sent. |
PAYLOAD_TOO_LARGE | The flags of the application would take more than 64 KB in one device’s answer. |
STATUS_CONFLICT | The status doesn’t allow the operation, for example killing an archived flag. |