# Cloud API

> Flipper Cloud API reference including features, gates, audits, and telemetry endpoints.

## Overview

The Flipper Cloud API is available at `https://www.flippercloud.io/adapter`. It includes all of the [open source Flipper API](/docs/api) endpoints plus Cloud-specific endpoints for audits and telemetry.

## Authentication

All requests require a token sent via the `Flipper-Cloud-Token` header:

```bash
curl -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  https://www.flippercloud.io/adapter/features
```

Tokens are scoped to a specific environment. You can create and manage tokens in your environment's settings.

The [Organization API](#organization) at `https://www.flippercloud.io/api/v1` uses organization API tokens instead. Environment tokens don't work there, and organization API tokens don't work with `/adapter`.

### Token access levels {#token-access-levels}

- **Read Only** tokens can evaluate features but cannot change them. Use Read Only for production applications and any client that only needs to evaluate flags.
- **Read/Write** tokens can evaluate and change features. Use Read/Write for personal, development, and test environments where the application or automation needs to change flags.

**Manage preview environments** is a separate permission from the access level. It lets a stable token create preview environments and delete the environments it created, but does not grant permission to change features. Enable it on a dedicated read-only preview automation token in a stable custom environment such as `previews`, rather than on a production application token.

## Features

### List All Features

`GET /adapter/features`

```bash
curl -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  https://www.flippercloud.io/adapter/features
```

```json
{
  "features": [
    {
      "key": "search",
      "state": "on",
      "gates": [
        {"key": "boolean", "name": "boolean", "value": true},
        {"key": "expression", "name": "expression", "value": null},
        {"key": "groups", "name": "group", "value": []},
        {"key": "actors", "name": "actor", "value": []},
        {"key": "percentage_of_actors", "name": "percentage_of_actors", "value": 0},
        {"key": "percentage_of_time", "name": "percentage_of_time", "value": 0}
      ]
    }
  ]
}
```

Responses include these headers:

- `flipper-sync-version`: a number that increases every time a feature in the environment changes, including changes inherited from production. It never decreases, so a response with a lower number is older.
- `poll-interval`: the recommended number of seconds between polls for this token.

### Get a Feature

`GET /adapter/features/{feature_name}`

- `feature_name` - The name of the feature to retrieve

```bash
curl -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  https://www.flippercloud.io/adapter/features/search
```

```json
{
  "key": "search",
  "state": "on",
  "gates": [
    {"key": "boolean", "name": "boolean", "value": true},
    {"key": "expression", "name": "expression", "value": null},
    {"key": "groups", "name": "group", "value": []},
    {"key": "actors", "name": "actor", "value": []},
    {"key": "percentage_of_actors", "name": "percentage_of_actors", "value": 0},
    {"key": "percentage_of_time", "name": "percentage_of_time", "value": 0}
  ]
}
```

### Create a Feature

`POST /adapter/features`

- `name` - The name of the feature to create

```bash
curl -X POST -d "name=new_dashboard" \
  -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  https://www.flippercloud.io/adapter/features
```

Returns an empty JSON response on success.

Feature names starting with `__flipper` are reserved for Flipper's internal use. Creating one, or enabling or disabling a gate on one, returns `422 Unprocessable Entity` and changes nothing.

### Delete a Feature

`DELETE /adapter/features/{feature_name}`

- `feature_name` - The name of the feature to delete

Features belong to the project, so deleting a feature removes it from every environment. This endpoint requires a writable Production environment token. Requests using other environment tokens return `403 Forbidden`; you can also delete the feature in Flipper Cloud.

```bash
curl -X DELETE -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  https://www.flippercloud.io/adapter/features/new_dashboard
```

Successful deletion returns a 204 No Content response.

### Clear a Feature

`DELETE /adapter/features/{feature_name}/clear`

- `feature_name` - The name of the feature to clear

```bash
curl -X DELETE -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  https://www.flippercloud.io/adapter/features/new_dashboard/clear
```

Removes all gate values and returns a 204 No Content response.

### Import Features

`POST /adapter/import`

Replaces the token's environment with the features in a Flipper export file, the same JSON that `Flipper.export` produces. This is what `flipper cloud push` uses. Features and gate values that aren't in the file are removed.

Features belong to the project, so removing one removes it from every environment. Only writable Production environment tokens can import a file that leaves out existing features. With any other token, the file must include every feature in the project. Otherwise the request returns `403 Forbidden` and changes nothing.

A file containing a feature whose name starts with `__flipper` returns `422 Unprocessable Entity` and changes nothing. Those names are reserved for Flipper's internal use.

```bash
curl -X POST -H "Content-Type: application/json" \
  -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  --data-binary @flipper.json \
  https://www.flippercloud.io/adapter/import
```

A successful import returns a 204 No Content response.

If the environment has an expression the client's flipper can't read, the import returns `409 Conflict` and changes nothing, because the client can't see the expression it would replace. That's a [Version (`SemVer`) expression](/docs/features/expressions#semver) for flipper 1.4.2 or older, or a [feature dependency (`FeatureEnabled`)](/docs/features/expressions#feature-dependencies) for flipper 1.3.6 or older. Upgrade flipper first.

## Gates

All gate endpoints return a 200 HTTP status and the updated feature object on success.

### Boolean

`POST /adapter/features/{feature_name}/boolean` - Enable for everyone

`DELETE /adapter/features/{feature_name}/boolean` - Disable

```bash
curl -X POST -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  https://www.flippercloud.io/adapter/features/new_dashboard/boolean
```

```json
{
  "key": "new_dashboard",
  "state": "on",
  "gates": [
    {"key": "boolean", "name": "boolean", "value": true},
    {"key": "expression", "name": "expression", "value": null},
    {"key": "groups", "name": "group", "value": []},
    {"key": "actors", "name": "actor", "value": []},
    {"key": "percentage_of_actors", "name": "percentage_of_actors", "value": 0},
    {"key": "percentage_of_time", "name": "percentage_of_time", "value": 0}
  ]
}
```

### Actors

`POST /adapter/features/{feature_name}/actors` - Enable for an actor

`DELETE /adapter/features/{feature_name}/actors` - Disable for an actor

- `flipper_id` - The identifier of the actor (e.g., `User;123`)

```bash
curl -X POST -d "flipper_id=User;123" \
  -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  https://www.flippercloud.io/adapter/features/new_dashboard/actors
```

```json
{
  "key": "new_dashboard",
  "state": "conditional",
  "gates": [
    {"key": "boolean", "name": "boolean", "value": false},
    {"key": "expression", "name": "expression", "value": null},
    {"key": "groups", "name": "group", "value": []},
    {"key": "actors", "name": "actor", "value": ["User;123"]},
    {"key": "percentage_of_actors", "name": "percentage_of_actors", "value": 0},
    {"key": "percentage_of_time", "name": "percentage_of_time", "value": 0}
  ]
}
```

### Groups

`POST /adapter/features/{feature_name}/groups` - Enable for a group

`DELETE /adapter/features/{feature_name}/groups` - Disable for a group

- `name` - The name of a registered group

```bash
curl -X POST -d "name=admins" \
  -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  https://www.flippercloud.io/adapter/features/new_dashboard/groups
```

```json
{
  "key": "new_dashboard",
  "state": "conditional",
  "gates": [
    {"key": "boolean", "name": "boolean", "value": false},
    {"key": "expression", "name": "expression", "value": null},
    {"key": "groups", "name": "group", "value": ["admins"]},
    {"key": "actors", "name": "actor", "value": []},
    {"key": "percentage_of_actors", "name": "percentage_of_actors", "value": 0},
    {"key": "percentage_of_time", "name": "percentage_of_time", "value": 0}
  ]
}
```

### Percentage of Actors

`POST /adapter/features/{feature_name}/percentage_of_actors` - Set percentage

`DELETE /adapter/features/{feature_name}/percentage_of_actors` - Clear percentage

- `percentage` - Integer between 0-100

```bash
curl -X POST -d "percentage=25" \
  -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  https://www.flippercloud.io/adapter/features/new_dashboard/percentage_of_actors
```

```json
{
  "key": "new_dashboard",
  "state": "conditional",
  "gates": [
    {"key": "boolean", "name": "boolean", "value": false},
    {"key": "expression", "name": "expression", "value": null},
    {"key": "groups", "name": "group", "value": []},
    {"key": "actors", "name": "actor", "value": []},
    {"key": "percentage_of_actors", "name": "percentage_of_actors", "value": 25},
    {"key": "percentage_of_time", "name": "percentage_of_time", "value": 0}
  ]
}
```

### Percentage of Time

`POST /adapter/features/{feature_name}/percentage_of_time` - Set percentage

`DELETE /adapter/features/{feature_name}/percentage_of_time` - Clear percentage

- `percentage` - Integer between 0-100

```bash
curl -X POST -d "percentage=50" \
  -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  https://www.flippercloud.io/adapter/features/new_dashboard/percentage_of_time
```

```json
{
  "key": "new_dashboard",
  "state": "conditional",
  "gates": [
    {"key": "boolean", "name": "boolean", "value": false},
    {"key": "expression", "name": "expression", "value": null},
    {"key": "groups", "name": "group", "value": []},
    {"key": "actors", "name": "actor", "value": []},
    {"key": "percentage_of_actors", "name": "percentage_of_actors", "value": 0},
    {"key": "percentage_of_time", "name": "percentage_of_time", "value": 50}
  ]
}
```

### Expression

`POST /adapter/features/{feature_name}/expression` - Set an expression

`DELETE /adapter/features/{feature_name}/expression` - Clear the expression

Send the expression as a JSON request body. Every `Any` and `All` group must
contain at least one condition. To remove every condition, use `DELETE` instead
of posting an empty group. Empty groups return HTTP 422 with the
`expression_invalid` error.

Flipper 1.4.2 and older can't evaluate a Version (`SemVer`) expression, and flipper 1.3.6 and older can't evaluate a feature dependency (`FeatureEnabled`). Requests from those clients read that feature's expression as `null`. Setting or clearing that expression from one of them returns `409 Conflict` and leaves it unchanged. Upgrade flipper to change it.

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  -d '{"All":[{"Equal":[{"Property":["plan"]},{"String":["pro"]}]}]}' \
  https://www.flippercloud.io/adapter/features/new_dashboard/expression
```

## Actors

### Check Features for an Actor

`GET /adapter/actors/{flipper_id}`

- `keys` - Comma-separated list of features to check (optional, returns all features if omitted)

```bash
curl -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  "https://www.flippercloud.io/adapter/actors/User;123?keys=new_dashboard,search"
```

```json
{
  "flipper_id": "User;123",
  "features": {
    "new_dashboard": {
      "enabled": true
    },
    "search": {
      "enabled": false
    }
  }
}
```

## Environments {#environments}

Create and delete custom environments for the project your token belongs to. These endpoints are intended for temporary preview environments, such as pull request or branch previews.

Only custom environments are affected. Production and personal environments are never created or deleted by these endpoints. Creating an environment counts against your plan's custom environment limit.

Use two tokens for preview environments:

1. Create a dedicated stable custom environment such as `previews`. In it, create a read-only token named something like `Preview Environments` with **Manage preview environments** enabled. Use it to create and delete preview environments. Avoid using a production application token for preview automation.
2. Use the token returned by the create endpoint inside the preview app itself.

The returned preview app token cannot create more environments. Keep it available until teardown because it can delete its own environment if the preview automation token has been rotated or disabled.

### Create an Environment

`POST /adapter/environments`

- `name` - The name of the environment to create (required). Names that generate reserved slugs like `new`, `edit`, or `admin` are rejected.
```bash
curl -X POST -d "name=pr-1234" \
  -H "Flipper-Cloud-Token: PREVIEW_ENVIRONMENT_MANAGER_TOKEN" \
  https://www.flippercloud.io/adapter/environments
```

```json
{
  "environment": {
    "id": 42,
    "slug": "pr-1234",
    "name": "pr-1234",
    "state": "other",
    "created_at": "2026-01-15T12:00:00Z"
  },
  "token": {
    "name": "API",
    "access": "read_and_write",
    "value": "Mb72kngHcVSLGefYptt2JvX5"
  }
}
```

The response includes a read-write token scoped to the new environment. Use its `value` as the `Flipper-Cloud-Token` for that preview app.

You can safely call create more than once with the same token and name. If the environment already exists and was created by that token, Flipper Cloud returns it instead of creating a duplicate. Existing matching environments return `200`; newly created environments return `201`. If another token or the web dashboard created the environment with that name, the request returns `409` with an `environment_name_taken` code. If your plan's custom environment limit has been reached, the request returns `402` with a `custom_environments_limit` code.

#### Create errors

| Status | Code | Meaning |
| --- | --- | --- |
| `402` | `custom_environments_limit` | The project has reached its plan's custom environment limit. |
| `403` | `preview_environment_management_token_required` | The token cannot manage preview environments. Preview app tokens never have this capability. |
| `403` | `organization_membership` | The token owner no longer has the organization access required for this project. |
| `403` | `project_membership` | The token owner no longer has access to this project. |
| `409` | `environment_name_taken` | The name belongs to an environment not created by this token. |
| `422` | `name_missing` | The request did not include a usable name. |
| `422` | `name_invalid` | The name cannot produce a valid, non-reserved slug. |
| `422` | `token_limit` | Flipper Cloud could not create a returned token because the environment reached its active token limit. |

### Delete an Environment

`DELETE /adapter/environments/{id}`

- `id` - The environment ID returned by create.

```bash
curl -X DELETE \
  -H "Flipper-Cloud-Token: PREVIEW_ENVIRONMENT_MANAGER_TOKEN" \
  "https://www.flippercloud.io/adapter/environments/42"
```

Deletes the custom environment, including its tokens, gates, and audits. Authenticate with the preview-environment management token that created it. The returned preview app token can also delete its own environment. Other tokens receive `403` with a `not_environment_creator` code.

Deleting an environment that is already gone, or is not a custom environment, returns `204`. Successful deletion also returns `204 No Content`. Repeated calls with the creating preview-environment management token are safe, and the ID remains the same if the environment is renamed in the dashboard.

#### Delete errors

| Status | Code | Meaning |
| --- | --- | --- |
| `403` | `not_environment_creator` | The token is neither the environment's creator token nor its returned preview token. |
| `403` | `organization_membership` | The token owner no longer has the organization access required for this project. |
| `403` | `project_membership` | The token owner no longer has access to this project. |
| `422` | `environment_id_required` | The path did not include a valid positive environment ID. |

## Audits {#audits}

### List Audits

`GET /adapter/audits`

Audit history is available on paid plans. Free-plan responses preserve the same
response shape, but return an empty `audits` array and a `limits` object with
`"limited": true` and `"max_events": 0`.

- `page` - Page number (default: 1)
- `per_page` - Records per page (default: 100, max: 250)
- `keys` - Comma-separated feature keys to filter by

`page`, `per_page`, and `keys` must be scalar, valid UTF-8 query parameters. Invalid numeric values, container-shaped values, conflicting Rack shapes, and malformed encodings return a JSON `400` or `422` response.

```bash
curl -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  "https://www.flippercloud.io/adapter/audits?per_page=10"
```

```json
{
  "audits": [
    {
      "action": "enable",
      "actor": {
        "type": "User",
        "id": "123"
      },
      "target": {
        "type": "Feature",
        "key": "new_dashboard"
      },
      "value": null,
      "created_at": "2026-01-15T12:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "per": 10,
    "previous": null,
    "next": 2
  },
  "limits": {
    "limited": false
  }
}
```

Audit actions include: `enable`, `disable`, `enable_actor`, `disable_actor`, `enable_group`, `disable_group`, `percentage_of_actors`, `percentage_of_time`, `clear`, `create`, `remove`, `mirror`, `rollback`, `enable_expression`, `disable_expression`.

## Telemetry {#telemetry}

### Submit Telemetry

`POST /adapter/telemetry`

Submit feature flag evaluation metrics. This is typically handled automatically by the Flipper client gem.

The endpoint accepts the content types supported by existing clients and gzip-compressed request bodies. Gzip requests are limited to 256 KiB compressed and 1 MiB after expansion. Malformed JSON, non-object JSON, invalid encodings, decompression errors, and size-limit violations return JSON `400` or `422` responses before telemetry is queued.

```bash
curl -X POST \
  -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "enabled_metrics": [
      {"key": "new_dashboard", "time": 1706108400, "result": true, "value": 150},
      {"key": "new_dashboard", "time": 1706108400, "result": false, "value": 50}
    ]
  }' \
  https://www.flippercloud.io/adapter/telemetry
```

Returns `202 Accepted` with an empty JSON body.

### Get Telemetry Summary

`GET /adapter/telemetry/summary`

- `days` - Number of days of history (clamped to your plan's retention limit)
- `keys` - Comma-separated feature keys to filter by

`days` and `keys` must be scalar, valid UTF-8 query parameters. Invalid numeric values, container-shaped values, conflicting Rack shapes, and malformed encodings return a JSON `400` or `422` response.

```bash
curl -H "Flipper-Cloud-Token: YOUR_TOKEN" \
  "https://www.flippercloud.io/adapter/telemetry/summary?days=7"
```

```json
{
  "days": 7,
  "resolution": "day",
  "features": [
    {
      "key": "new_dashboard",
      "description": "New dashboard experience for pro users",
      "longevity": "temporary",
      "tags": ["growth", "billing"],
      "creator": {
        "id": 42,
        "name": "Ada Lovelace",
        "email": "ada@example.com"
      },
      "owner": {
        "id": 57,
        "name": "Grace Hopper",
        "email": "grace@example.com"
      },
      "created_at": "2025-09-15T10:30:00Z",
      "state": "conditional",
      "gates_summary": {
        "boolean": false,
        "actors": 3,
        "groups": 2,
        "percentage_of_actors": 25,
        "percentage_of_time": 0,
        "expression": true
      },
      "summary": {
        "total": 10000,
        "enabled": 6000,
        "disabled": 4000,
        "enabled_rate": 0.6
      },
      "time_series": [
        {
          "timestamp": "2026-01-15T00:00:00Z",
          "total": 1500,
          "enabled": 900,
          "disabled": 600
        }
      ]
    }
  ]
}
```

Each feature includes configuration context alongside telemetry data:

- `description` — Feature description, or `null` if not set
- `longevity` — `"temporary"`, `"permanent"`, or `null` if not set
- `tags` — Array of tag strings (empty array if none)
- `creator` — User who created the feature, or `null` if unknown. Object with `id`, `name`, and `email`.
- `owner` — User assigned as the feature's owner, or `null` if unassigned. Object with `id`, `name`, and `email`.
- `created_at` — When the feature was created (ISO 8601)
- `state` — Current state: `"on"`, `"conditional"`, or `"off"`
- `gates_summary` — Summary of gate configuration (always present):
  - `boolean` — Whether the boolean gate is enabled
  - `actors` — Number of individually enabled actors
  - `groups` — Number of enabled groups
  - `percentage_of_actors` — Percentage of actors enabled (0-100)
  - `percentage_of_time` — Percentage of time enabled (0-100)
  - `expression` — Whether an expression gate is active

## Organization API {#organization}

The organization API manages your organization rather than a single environment. It lives at `https://www.flippercloud.io/api/v1` and authenticates with an organization API token in the same `Flipper-Cloud-Token` header.

Organization API tokens are available on plans with trusted domains. Organization admins create them in **Organization Settings → API Tokens**. A token stops working if the admin who created it is removed from the organization or is no longer an admin.

Treat an organization API token like a password. Keep it on a server or in your secrets manager, never in a repository or on developer machines. If one leaks, delete it: deleting a token also disables every token it handed out, and people get new ones when your tooling calls with a new organization token.

### Provision a Member

`POST /api/v1/organization/members`

- `email` - The person's email address (required). It must be on one of your organization's verified [trusted domains](/docs/cloud/permissions#trusted-domains).

Adds the person to your organization and returns their personal environment in every project, each with a read/write token. Use it from developer setup tooling so nobody has to copy tokens by hand.

```bash
curl -X POST -d "email=dev@example.com" \
  -H "Flipper-Cloud-Token: ORGANIZATION_API_TOKEN" \
  https://www.flippercloud.io/api/v1/organization/members
```

```json
{
  "member": {
    "email": "dev@example.com",
    "role": "member",
    "added": true
  },
  "environments": [
    {
      "project": {
        "id": 7,
        "slug": "storefront",
        "name": "Storefront"
      },
      "environment": {
        "id": 42,
        "slug": "dev-example-com",
        "name": "dev@example.com",
        "state": "personal",
        "created_at": "2026-11-06T12:00:00Z"
      },
      "token": {
        "name": "Provisioned via API",
        "access": "read_and_write",
        "value": "Mb72kngHcVSLGefYptt2JvX5"
      }
    }
  ]
}
```

The person doesn't need to have signed in before. If they haven't, Flipper Cloud creates their account, and it becomes theirs the first time they sign in with Google or GitHub using that email. New members join with the role set on the trusted domain and use a seat.

Each environment's token is one the API created for this purpose. It's named **Provisioned via API** and owned by the person. It never returns a token the person created themselves, even one with the same name. They can see it with their other tokens and disable it without breaking anything else. If they disable it or make it read-only, the next call creates a new one. Each organization API token gets its own tokens, so switching tooling to a new organization token hands out fresh ones.

You can safely call this more than once for the same email. Existing members return `200` with the same tokens, and `added` is `false`. Newly added members return `201`. Each call also adds existing members to any projects they're missing, so it picks up projects created since the last call.

Calls for one organization run one at a time. If another call is still running, you get `409` with an `organization_busy` code and a `Retry-After` header. Wait that many seconds and try again. Retrying is always safe. To provision many people at once, such as a backfill, make the calls one after another instead of in parallel.

#### Organization errors {#organization-errors}

| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `body_invalid` | The request body isn't valid JSON or form data. |
| `401` | `organization_token_invalid` | The `Flipper-Cloud-Token` header is missing or isn't an organization API token. |
| `402` | `no_available_seats` | The organization has no seats left for a new member. Existing members still succeed. |
| `403` | `google_sso_domains` | The organization's plan doesn't include trusted domains. |
| `403` | `organization_admin` | The admin who created the token is no longer an organization admin. |
| `409` | `organization_busy` | Another call for the organization is still running. Retry after the `Retry-After` header's seconds. |
| `422` | `email_missing` | The request didn't include an email. |
| `422` | `email_invalid` | The email isn't a valid address. |
| `422` | `email_domain_not_trusted` | The email isn't on one of the organization's verified trusted domains. |
| `422` | `token_limit` | A personal environment needs a new read/write token but already has the maximum number of active tokens. Disable one of its tokens and try again. |

## Error Responses

Errors return JSON with a code and message:

```json
{
  "code": 1,
  "message": "Feature not found",
  "more_info": "https://www.flippercloud.io/docs"
}
```

Common HTTP status codes:

- `401` - Invalid or missing token
- `402` - Feature requires a plan upgrade
- `403` - Token does not have write access, or the request deletes features with a non-production token
- `404` - Feature not found
- `422` - Invalid parameters, or a feature name starting with the reserved `__flipper` prefix

## Error Code Reference {#error-code-reference}

- `7` - The expression is invalid. Empty `Any` and `All` groups are not
  accepted; use `DELETE /adapter/features/{feature_name}/expression` to clear
  an expression.
