# How to Create and Manage Feature Flags Programmatically via the PostHog API

> Learn to programmatically create and manage feature flags using the PostHog API. Control boolean and multivariate flags with simple REST API calls and Bearer token authentication.

- Repository: [PostHog/posthog](https://github.com/PostHog/posthog)
- Tags: how-to-guide
- Published: 2026-04-25

---

**Use the PostHog REST API endpoints under `/api/projects/{project_id}/feature_flags/` with Bearer token authentication to create, update, and delete feature flags programmatically, supporting both boolean and multivariate configurations.**

The `PostHog/posthog` repository exposes a comprehensive REST API for feature flag management through the `FeatureFlagViewSet` class. Located in [`posthog/api/feature_flag.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/feature_flag.py), this viewset enables teams to automate feature rollouts, A/B testing, and targeting rules without manual UI interactions.

## API Endpoints and Authentication

### Available Endpoints

The `FeatureFlagViewSet` registered in [`posthog/api/__init__.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/__init__.py) (lines 84-90) exposes the following endpoints under each project scope:

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/projects/{project_id}/feature_flags/` | Create a new flag |
| GET | `/api/projects/{project_id}/feature_flags/` | List all flags |
| GET | `/api/projects/{project_id}/feature_flags/{flag_id}/` | Retrieve a specific flag |
| PATCH | `/api/projects/{project_id}/feature_flags/{flag_id}/` | Partial update |
| PUT | `/api/projects/{project_id}/feature_flags/{flag_id}/` | Full replacement |
| DELETE | `/api/projects/{project_id}/feature_flags/{flag_id}/` | Soft delete |

### Authentication Methods

All requests require authentication via the backends defined in [`posthog/auth.py`](https://github.com/PostHog/posthog/blob/main/posthog/auth.py). Include one of the following in the `Authorization` header:

- **Project API key**: `Bearer <PROJECT_API_KEY>`
- **Personal API key**: `Bearer <PERSONAL_API_KEY>`
- **Project Secret API key**

DRF mixins automatically apply the appropriate authentication classes to each endpoint.

## Request Schema and Validation

### Core Fields

Creation payloads are validated by `FeatureFlagCreateRequestSchemaSerializer` (lines 604-623 in [`posthog/api/feature_flag.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/feature_flag.py)), while updates use `FeatureFlagPartialUpdateRequestSchemaSerializer` (lines 625-644). Both serializers accept:

- `key`: Unique identifier matching regex `^[a-zA-Z0-9_-]+$`
- `name`: Human-readable description
- `filters`: Targeting configuration object
- `active`: Boolean flag status
- `tags`: Array of organization-wide tags
- `evaluation_contexts`: Runtime contexts (e.g., `"production"`, `"staging"`)

Responses are generated by `FeatureFlagSerializer` (lines 646-674), returning computed fields like `status`, `evaluation_contexts`, and linked resources.

### Filter Configuration Format

The `filters` object follows `FeatureFlagFiltersSchemaSerializer` from [`posthog/api/documentation.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/documentation.py):

```json
{
  "groups": [
    {
      "properties": [
        {
          "key": "email",
          "type": "person",
          "operator": "icontains",
          "value": "@example.com"
        }
      ],
      "rollout_percentage": 100
    }
  ]
}

```

Groups function as **OR** clauses; properties within groups are **AND**-combined. Supported operators include `icontains`, `gt`, `is_date_before`, and `flag_evaluates_to` (defined in `FEATURE_FLAG_SUPPORTED_OPERATORS`, lines 534-588).

## Creating Feature Flags

### Boolean Flags

Create a simple on/off flag with user targeting:

```bash
curl -X POST "https://app.posthog.com/api/projects/123/feature_flags/" \
     -H "Authorization: Bearer <PROJECT_API_KEY>" \
     -H "Content-Type: application/json" \
     -d '{
           "key": "new-feature",
           "name": "New Feature rollout",
           "filters": {
             "groups": [
               {
                 "properties": [
                   { "key": "email", "type": "person", "operator": "icontains", "value": "@example.com" }
                 ],
                 "rollout_percentage": 100
               }
             ]
           },
           "active": true,
           "tags": ["beta"]
         }'

```

### Multivariate Flags

For A/B testing, include a `multivariate` block with variants:

```bash
curl -X POST "https://app.posthog.com/api/projects/123/feature_flags/" \
     -H "Authorization: Bearer <PROJECT_API_KEY>" \
     -H "Content-Type: application/json" \
     -d '{
           "key": "ui-experiment",
           "name": "UI Experiment",
           "filters": {
             "multivariate": {
               "variants": [
                 { "key": "control", "rollout_percentage": 50 },
                 { "key": "variant_a", "rollout_percentage": 50 }
               ]
             },
             "groups": [
               {
                 "properties": [],
                 "rollout_percentage": 100
               }
             ]
           },
           "active": true
         }'

```

## Updating and Retrieving Flags

### Partial Updates

Use **PATCH** to modify specific fields without replacing the entire resource:

```bash
curl -X PATCH "https://app.posthog.com/api/projects/123/feature_flags/42/" \
     -H "Authorization: Bearer <PROJECT_API_KEY>" \
     -H "Content-Type: application/json" \
     -d '{
           "tags": ["beta", "released"]
         }'

```

### Listing and Fetching

Retrieve a specific flag using Python:

```python
import requests

API_KEY = "<PROJECT_API_KEY>"
PROJECT_ID = 123
FLAG_ID = 42

url = f"https://app.posthog.com/api/projects/{PROJECT_ID}/feature_flags/{FLAG_ID}/"
headers = {"Authorization": f"Bearer {API_KEY}"}

resp = requests.get(url, headers=headers)
resp.raise_for_status()
flag = resp.json()
print(flag["key"], flag["active"], flag["filters"])

```

List all flags with pagination:

```python
resp = requests.get(
    f"https://app.posthog.com/api/projects/{PROJECT_ID}/feature_flags/",
    headers={"Authorization": f"Bearer {API_KEY}"},
    params={"limit": 100}
)
flags = resp.json()["results"]
for f in flags:
    print(f["key"], f["active"])

```

## Limits and Validation Constraints

The API enforces several guardrails defined in [`posthog/api/feature_flag.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/feature_flag.py):

- **Team flag limit**: `check_flag_limits_for_team` (lines 63-78) enforces maximum flags per team via the Django setting `MAX_FEATURE_FLAGS_PER_TEAM` (configurable in [`posthog/settings/feature_flags.py`](https://github.com/PostHog/posthog/blob/main/posthog/settings/feature_flags.py)).
- **Key uniqueness**: Validated by `validate_key`; duplicate keys per project return 400 Bad Request.
- **Payload size**: `calculate_filter_size_bytes` (lines 49-61) limits the JSON size of filter configurations.

Violation of these constraints returns HTTP 400 with detailed error messages.

## Summary

- The **FeatureFlagViewSet** in [`posthog/api/feature_flag.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/feature_flag.py) handles all CRUD operations for feature flags under `/api/projects/{project_id}/feature_flags/`.
- **Authentication** requires Bearer tokens using Project API keys, Personal API keys, or Project Secret API keys.
- **Boolean flags** target users via `filters.groups` with property conditions and rollout percentages.
- **Multivariate flags** require a `multivariate.variants` array where rollout percentages must sum to 100%.
- **Team limits** are enforced by `check_flag_limits_for_team` and controlled by `MAX_FEATURE_FLAGS_PER_TEAM`.
- Use **PATCH** for partial updates and **PUT** for complete flag replacement.

## Frequently Asked Questions

### What authentication methods does the PostHog Feature Flags API support?

The API supports **Project API keys**, **Personal API keys**, and **Project Secret API keys**, all passed as Bearer tokens in the Authorization header. The authentication backends are implemented in [`posthog/auth.py`](https://github.com/PostHog/posthog/blob/main/posthog/auth.py) and integrated automatically through Django REST Framework mixins.

### How do I implement multivariate feature flags via the API?

Include a `multivariate` object within the `filters` payload containing a `variants` array. Each variant must specify a `key` and `rollout_percentage`, with all percentages summing to exactly 100. The request is validated by `FeatureFlagCreateRequestSchemaSerializer` in [`posthog/api/feature_flag.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/feature_flag.py).

### What are the limits on feature flag creation?

PostHog enforces a team-wide limit on the total number of feature flags, controlled by the `MAX_FEATURE_FLAGS_PER_TEAM` setting in [`posthog/settings/feature_flags.py`](https://github.com/PostHog/posthog/blob/main/posthog/settings/feature_flags.py). Additionally, the `calculate_filter_size_bytes` function limits the JSON payload size of filter configurations. Both constraints return HTTP 400 errors when exceeded.

### What is the difference between PATCH and PUT when updating flags?

**PATCH** requests use `FeatureFlagPartialUpdateRequestSchemaSerializer` and update only the provided fields, making them ideal for adding tags or toggling the `active` status. **PUT** requests replace the entire flag resource, requiring all required fields in the payload. Both methods target `/api/projects/{project_id}/feature_flags/{flag_id}/`.