---
title: "Creates a feature flag in the resolved project."
method: POST
path: "/api/v1/orgs/{org}/projects/{project}/flags"
tags: ["Feature Flags"]
---

# Creates a feature flag in the resolved project.

`POST /api/v1/orgs/{org}/projects/{project}/flags`

## Request

- Base URL: `https://api.featureflip.io`
- URL: `https://api.featureflip.io/api/v1/orgs/{org}/projects/{project}/flags`
- Auth: API key in header `Authorization`

Requires at least Member.
Type is passed through verbatim to
Type (a bare string) — an invalid value surfaces as
the handler's own validation failure. Re-fetches via GetFeatureFlagQuery so
the response reflects exactly what was persisted (e.g. server-assigned variation ids). A flag
past the plan's per-project cap on active flags is a 403 `plan_limit_reached`.

## Path parameters

- `org` string, required
- `project` string, required

## Request body

- PublicCreateFeatureFlagRequest: Request body for `POST /api/v1/orgs/{org}/projects/{project}/flags`. Named distinctly from the dashboard's own `CreateFeatureFlagRequest` — see PublicCreateEnvironmentRequest for the schemaId-collision rationale (both controllers share one Swagger document). Type is passed through verbatim to Type, which itself takes a bare string (Boolean/String/Number/Json) rather than the domain enum — invalid values surface as the handler's own validation failure, not a controller-level 400. ExpiresAtUtc is optional and advisory; a bare date (`2026-12-01`) means the end of that day in UTC, and a full timestamp is stored exactly. It must be in the future and requires the flag-expiration feature (400 `EXPIRY_IN_PAST` / `FEATURE_NOT_ENABLED`). OwnerEmail optionally names the owner (an active member, matched case-insensitively; requires the flag-ownership feature — 400 `OWNER_NOT_MEMBER` / `FEATURE_NOT_ENABLED`). Omitted, the owner is the caller when the caller is a member, so a service token creates an unowned flag. IsPermanent marks the flag permanent (a kill switch, ops toggle or entitlement that is meant to stay; never reported for cleanup). It requires the flag-permanence feature and can't be combined with ExpiresAtUtc (400 `FEATURE_NOT_ENABLED` / `PERMANENT_FLAG_HAS_EXPIRY`).
  - `key` string, nullable, required
  - `name` string, nullable, required
  - `type` 'Boolean' | 'String' | 'Number' | 'Json', nullable, required: Allowed values: Boolean, String, Number, Json.
  - `description` string, nullable
  - `tags` string[], nullable
  - `initialVariations` PublicCreateVariationDto[], nullable
    - `key` string, nullable, required
    - `name` string, nullable, required
    - `value` string, nullable, required
    - `description` string, nullable
  - `clientSideVisible` boolean
  - `expiresAtUtc` string, date-time, nullable
  - `ownerEmail` string, nullable
  - `isPermanent` boolean

## Response `201`

Created

- PublicFlagResponse: Public detail representation of a feature flag, returned by `GET .../flags/{flag}` and the body of a successful create. Drops internal/dashboard-only fields present on FeatureFlagDto — notably `ProjectId` (implicit in the route) and `Staleness` (an internal analytics signal, not part of the public contract). EffectiveExpiresAtUtc is when the flag counts as past expiry, from its own ExpiresAtUtc or its project's default lifetime; ExpirySource says which (`explicit` / `project-default`), both null when neither applies.
  - `id` string, uuid, required
  - `key` string, nullable, required
  - `name` string, nullable, required
  - `description` string, nullable
  - `type` 'Boolean' | 'String' | 'Number' | 'Json', nullable, required: Allowed values: Boolean, String, Number, Json.
  - `isArchived` boolean, required
  - `clientSideVisible` boolean, required
  - `tags` string[], nullable, required
  - `variations` PublicVariationResponse[], nullable, required
    - `id` string, uuid, required
    - `key` string, nullable, required
    - `name` string, nullable, required
    - `value` string, nullable, required
    - `description` string, nullable
    - `_actions` object: Capability hints: which operations the authenticated caller may perform on this resource, each with allowed + optional reason. Computed from the caller's role and the resource's state. Injected at runtime; safe to ignore. Present only on top-level resource responses — nested occurrences (e.g. a rule inside a targeting-config response) do not carry it.
  - `createdAt` string, date-time, required
  - `updatedAt` string, date-time, required
  - `expiresAtUtc` string, date-time, nullable
  - `owner` PublicFlagOwner: A flag's owner as returned on flag reads. Null means the flag is unowned.
    - `id` string, uuid, required
    - `email` string, nullable, required
    - `name` string, nullable, required
  - `removalPrUrl` string, nullable
  - `isPermanent` boolean, required
  - `effectiveExpiresAtUtc` string, date-time, nullable
  - `expirySource` string, nullable
  - `_actions` object: Capability hints: which operations the authenticated caller may perform on this resource, each with allowed + optional reason. Computed from the caller's role and the resource's state. Injected at runtime; safe to ignore. Present only on top-level resource responses — nested occurrences (e.g. a rule inside a targeting-config response) do not carry it.

## Other responses

- `400`: The request is malformed, or it carries an If-Match header, which this endpoint can't evaluate. Send no If-Match, or `*`.
- `401`: Authentication is required, or the supplied API token is invalid or expired.
- `403`: Forbidden
- `404`: Not Found
- `429`: Rate limit exceeded. Retry after the interval indicated by the Retry-After header.

---

[API](https://skmtc.dev/featureflip/apis/management-api.md) · [All operations](https://skmtc.dev/featureflip/apis/management-api/llms.txt) · [OpenAPI document](https://skmtc.dev/featureflip/apis/management-api/revisions/8a77e8581a45?raw)
