# Feature Flags API

A [feature flag](/product/audience-data-and-segmentation/feature-flags/) 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 on a website, use the [Web SDK](/developer/pushwoosh-sdk/web-push-notifications/feature-flags/).

<Aside type="tip" title="Beta feature">
Feature flags are in beta and available only on plans that include them. On other plans, `Create` and `Restore` fail with `FEATURE_FLAGS_NOT_ALLOWED`.
</Aside>

## Base URL

```
https://rpc-api.svc-nue.pushwoosh.com
```

All endpoints are served over HTTPS. Requests and responses use `application/json`.

## Authentication

Every request must include an `Authorization` header with your [Server API token](/developer/api-reference/api-access-token/#server-api-token):

```
Authorization: Api YOUR_API_TOKEN
```

Reading flags needs access to the application. Creating and changing them needs the right to manage it.

## Conventions

* **Field naming:** request bodies accept `lowerCamelCase` or `snake_case`. Responses use `snake_case`, as in the [Feature flag object](#feature-flag-object).
* **Percentages** are in basis points: `percent_bp` from `0` to `10000`, where `2000` is 20%.
* **Versions:** every flag carries a `version`. `Update`, `SetRulePercent`, and `Reshuffle` take the version you last read and fail with `VERSION_CONFLICT` when the flag changed since, so concurrent edits never overwrite each other. Read the flag again and retry.
* **Statuses:** `active`, `killed`, or `archived`. `Kill`, `Unkill`, `Archive`, and `Restore` change only the status and take no version.

## Endpoints

Each 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` |

Every endpoint except `List` returns `{ "flag": { ... } }`, the [Feature flag object](#feature-flag-object) after the operation.

## List

`GET` `/api/applications/{application_code}/feature_flags`

The request takes these parameters:

| Parameter | In | Type | Description |
| :---- | :---- | :---- | :---- |
| `application_code` | path | string | The [application code](/developer/api-reference/api-identifiers/#application-code). |
| `status` | query | string | Optional. `active`, `killed`, or `archived`. Omit to list all flags. |

Returns `{ "flags": [ ... ] }`, an array of [Feature flag objects](#feature-flag-object).

## Get

`GET` `/api/applications/{application_code}/feature_flags/{key}`

Returns one flag with its properties, rules, and current `version`.

## Create

Creates 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](#property-object) | 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](#rule-object) | No | Ordered rules, up to 10. The first rule whose segment matches the device decides. |

##### Request example

```json
{
  "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

Replaces 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](#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

Changes 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`

```json
{ "percentBp": 5000, "version": 3 }
```

## Kill and Unkill

`POST` `.../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

`POST` `.../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

Places 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`

```json
{ "confirm": true, "version": 3 }
```

`confirm` must be `true`. Otherwise the request fails with `CONFIRM_REQUIRED`.

## Objects

### Feature flag object

Every 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](#property-object) | The flag's properties. |
| `defaults` | object | The value of every property for devices the flag is off for. |
| `rules` | array of [Rule objects](#rule-object) | 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. |

### Property object

A 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

A 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](/product/audience-data-and-segmentation/segmentation/) 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

A 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:

```json
{
  "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:

```json
{
  "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. |
| `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. |