Flags docs
API reference

Flags API.

The management API the console and the MCP server use, and the public config delivery route. Every endpoint, request and response, generated from the live OpenAPI spec.

Base URL

https://api.flags.productcraft.co

Auth

Authorization: Bearer …

Spec version

OpenAPI 3.0.0 · v0.1.0

Delivery

The public config download SDKs poll: `GET /v1/configs/{sdk_key}/config.json` with ETag/304, compressed bodies and CORS for browsers. Authenticated by the SDK key alone; not part of the management client.

get/v1/configs/{sdk_key}/config.json

Download a config

The config JSON (schema 1) every SDK downloads and evaluates locally: no user data is sent. Authenticated by the SDK key in the path, which can read only this config in this environment; no API key or session. Poll with `If-None-Match` (the last `ETag`) and get 304 while nothing changed; changes show within about 5 seconds. Responses are `br`, `gzip` or identity per `Accept-Encoding`, CORS `*` without credentials. Limits: 20 requests per second per (SDK key, client IP) and 20 per client IP; a matching 304 is never refused for the key's sake; unknown keys 2 per second per IP. If the database is unreachable the API keeps serving its last good copy with `X-PC-Config-Stale: 1` for up to 24 hours. `HEAD` answers the same headers without the body.

Path parameters

sdk_key*string

The SDK key of one config in one environment (`pcf_sdk_` + 38 characters). A malformed key or a wrong checksum is a 404.

Response · 200 The config JSON document (schema 1, no envelope).

object

Example

Request

GET https://api.flags.productcraft.co/v1/configs/{sdk_key}/config.json

Response

{}

Configs

A config is the unit an SDK downloads: one JSON document per environment, with its own SDK key. Most projects need only `main`.

get/v1/workspaces/{workspace_id}/projects/{project_id}/configsAuth

List configs

The project's configs, in `ordinal` order.

Path parameters

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Query parameters

cursorstring

Opaque cursor for pagination: the `next_cursor` of the previous page.

limitinteger

Page size.

Response · 200 A page of configs.

data*array

Configs, in `ordinal` order.

pagination*object

The cursor for the next page.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}/configs
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "key": "main",
      "name": "Main",
      "description": "",
      "ordinal": 0,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "pagination": {}
}
post/v1/workspaces/{workspace_id}/projects/{project_id}/configsAuth

Create a config

Adds a config to the project, with a published state in every environment. Each (config, environment) pair has its own SDK key. Needs `flags.project.manage`.

Path parameters

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Headers

Idempotency-Keystring

Idempotency key (1-256 characters, `[A-Za-z0-9_-:]`). A retry with the same key and body returns the first response for 24 h.

Request body

key*string

Stable key, unique in the project; must be a lowercase slug: a-z, 0-9 and "-", 1-63 characters, not starting with "-".

Example: "checkout"

name*string

Display name, 1-100 characters.

Example: "Checkout"

descriptionstring

Free-text description, at most 1000 characters.

Example: ""

ordinalinteger

Sort position. Defaults to after the last one.

Example: 1

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Example: "Ticket FLAG-142"

Response · 201 The new config.

id*string · uuid

The config's id.

workspace_id*string · uuid

The workspace that owns the config.

project_id*string · uuid

The project the config belongs to.

key*string

Stable key, unique in the project.

Example: "main"

name*string

Display name.

Example: "Main"

description*string

Free-text description (may be empty).

Example: ""

ordinal*integer

Sort position in the project (ascending).

Example: 0

created_at*string · date-time

When the config was created.

updated_at*string · date-time

When the config last changed.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}/configs
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "key": "checkout",
  "name": "Checkout",
  "description": "",
  "ordinal": 1,
  "note": "Ticket FLAG-142"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "key": "main",
  "name": "Main",
  "description": "",
  "ordinal": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}
get/v1/workspaces/{workspace_id}/configs/{config_id}Auth

Get a config

Path parameters

config_id*string · uuid

Id of the config.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The config.

id*string · uuid

The config's id.

workspace_id*string · uuid

The workspace that owns the config.

project_id*string · uuid

The project the config belongs to.

key*string

Stable key, unique in the project.

Example: "main"

name*string

Display name.

Example: "Main"

description*string

Free-text description (may be empty).

Example: ""

ordinal*integer

Sort position in the project (ascending).

Example: 0

created_at*string · date-time

When the config was created.

updated_at*string · date-time

When the config last changed.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "key": "main",
  "name": "Main",
  "description": "",
  "ordinal": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}
patch/v1/workspaces/{workspace_id}/configs/{config_id}Auth

Update a config

Key, name, description or order. Only the fields you send change. Needs `flags.project.manage`.

Path parameters

config_id*string · uuid

Id of the config.

workspace_id*string · uuid

Id of the workspace.

Request body

keystring

New key; must be a lowercase slug: a-z, 0-9 and "-", 1-63 characters, not starting with "-". SDKs address configs by SDK key, not by this key.

Example: "checkout"

namestring

Display name, 1-100 characters.

Example: "Checkout"

descriptionstring

Free-text description, at most 1000 characters.

ordinalinteger

Sort position.

Example: 1

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Example: "Ticket FLAG-142"

Response · 200 The updated config.

id*string · uuid

The config's id.

workspace_id*string · uuid

The workspace that owns the config.

project_id*string · uuid

The project the config belongs to.

key*string

Stable key, unique in the project.

Example: "main"

name*string

Display name.

Example: "Main"

description*string

Free-text description (may be empty).

Example: ""

ordinal*integer

Sort position in the project (ascending).

Example: 0

created_at*string · date-time

When the config was created.

updated_at*string · date-time

When the config last changed.

Example

Request

PATCH https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "key": "checkout",
  "name": "Checkout",
  "description": "string",
  "ordinal": 1,
  "note": "Ticket FLAG-142"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "key": "main",
  "name": "Main",
  "description": "",
  "ordinal": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}
delete/v1/workspaces/{workspace_id}/configs/{config_id}Auth

Delete a config

Deletes the config with its flags, values and SDK keys in every environment. Needs `flags.project.manage`.

Path parameters

config_id*string · uuid

Id of the config.

workspace_id*string · uuid

Id of the workspace.

Query parameters

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Response · 204

Deleted.

Example

Request

DELETE https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}
Authorization: Bearer YOUR_TOKEN

Environments

Environments of a project (for example Development, Staging, Production). Each has its own values and SDK keys; protected environments need `flags.protected.update` for value changes.

get/v1/workspaces/{workspace_id}/projects/{project_id}/environmentsAuth

List environments

Every environment of the project, in `ordinal` order.

Path parameters

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The environments.

data*array

Every environment of the project (at most 5), in `ordinal` order.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}/environments
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "key": "production",
      "name": "Production",
      "color": "#c92a2a",
      "ordinal": 1,
      "protected": true,
      "require_note": false,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}
post/v1/workspaces/{workspace_id}/projects/{project_id}/environmentsAuth

Create an environment

Adds an environment to every config of the project. The key `production` starts protected unless you say otherwise. Needs `flags.project.manage`.

Path parameters

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Headers

Idempotency-Keystring

Idempotency key (1-256 characters, `[A-Za-z0-9_-:]`). A retry with the same key and body returns the first response for 24 h.

Request body

key*string

Stable key, unique in the project; must be a lowercase slug: a-z, 0-9 and "-", 1-63 characters, not starting with "-". Cannot change later.

Example: "staging"

name*string

Display name, 1-100 characters.

Example: "Staging"

colorstring

Display color, `#rrggbb`. Defaults to gray.

Example: "#e67700"

ordinalinteger

Sort position. Defaults to after the last one.

Example: 1

protectedboolean

Require `flags.protected.update` for value changes. Defaults to true for the key `production`, else false.

require_noteboolean

Require a `note` on value changes. Defaults to false.

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Example: "Ticket FLAG-142"

Response · 201 The new environment.

id*string · uuid

The environment's id.

workspace_id*string · uuid

The workspace that owns the environment.

project_id*string · uuid

The project the environment belongs to.

key*string

Stable key, unique in the project. Cannot change.

Example: "production"

name*string

Display name.

Example: "Production"

color*string

Display color, `#rrggbb`.

Example: "#c92a2a"

ordinal*integer

Sort position in the project (ascending).

Example: 1

protected*boolean

Protected environments need `flags.protected.update` (owners and admins by default) for any value change, on top of `flags.value.update`.

Example: true

require_note*boolean

Value changes in this environment must carry a `note`.

Example: false

created_at*string · date-time

When the environment was created.

updated_at*string · date-time

When the environment last changed.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}/environments
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "key": "staging",
  "name": "Staging",
  "color": "#e67700",
  "ordinal": 1,
  "protected": false,
  "require_note": false,
  "note": "Ticket FLAG-142"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "key": "production",
  "name": "Production",
  "color": "#c92a2a",
  "ordinal": 1,
  "protected": true,
  "require_note": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}
get/v1/workspaces/{workspace_id}/projects/{project_id}/environments/{environment_id}Auth

Get an environment

Path parameters

environment_id*string · uuid

Id of the environment.

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The environment.

id*string · uuid

The environment's id.

workspace_id*string · uuid

The workspace that owns the environment.

project_id*string · uuid

The project the environment belongs to.

key*string

Stable key, unique in the project. Cannot change.

Example: "production"

name*string

Display name.

Example: "Production"

color*string

Display color, `#rrggbb`.

Example: "#c92a2a"

ordinal*integer

Sort position in the project (ascending).

Example: 1

protected*boolean

Protected environments need `flags.protected.update` (owners and admins by default) for any value change, on top of `flags.value.update`.

Example: true

require_note*boolean

Value changes in this environment must carry a `note`.

Example: false

created_at*string · date-time

When the environment was created.

updated_at*string · date-time

When the environment last changed.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}/environments/{environment_id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "key": "production",
  "name": "Production",
  "color": "#c92a2a",
  "ordinal": 1,
  "protected": true,
  "require_note": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}
patch/v1/workspaces/{workspace_id}/projects/{project_id}/environments/{environment_id}Auth

Update an environment

Name, color, order, `protected` and `require_note`; the key never changes. Changing `protected` or `require_note` is recorded in the workspace audit log. Needs `flags.project.manage`.

Path parameters

environment_id*string · uuid

Id of the environment.

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Request body

namestring

Display name, 1-100 characters.

Example: "Staging"

colorstring

Display color, `#rrggbb`.

Example: "#e67700"

ordinalinteger

Sort position.

Example: 1

protectedboolean

Protect the environment (or lift the protection).

require_noteboolean

Require a `note` on value changes.

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Example: "Ticket FLAG-142"

Response · 200 The updated environment.

id*string · uuid

The environment's id.

workspace_id*string · uuid

The workspace that owns the environment.

project_id*string · uuid

The project the environment belongs to.

key*string

Stable key, unique in the project. Cannot change.

Example: "production"

name*string

Display name.

Example: "Production"

color*string

Display color, `#rrggbb`.

Example: "#c92a2a"

ordinal*integer

Sort position in the project (ascending).

Example: 1

protected*boolean

Protected environments need `flags.protected.update` (owners and admins by default) for any value change, on top of `flags.value.update`.

Example: true

require_note*boolean

Value changes in this environment must carry a `note`.

Example: false

created_at*string · date-time

When the environment was created.

updated_at*string · date-time

When the environment last changed.

Example

Request

PATCH https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}/environments/{environment_id}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "name": "Staging",
  "color": "#e67700",
  "ordinal": 1,
  "protected": false,
  "require_note": false,
  "note": "Ticket FLAG-142"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "key": "production",
  "name": "Production",
  "color": "#c92a2a",
  "ordinal": 1,
  "protected": true,
  "require_note": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}
delete/v1/workspaces/{workspace_id}/projects/{project_id}/environments/{environment_id}Auth

Delete an environment

Deletes the environment with its values in every config, and its SDK keys. A project keeps at least one environment. Needs `flags.project.manage`.

Path parameters

environment_id*string · uuid

Id of the environment.

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Query parameters

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Response · 204

Deleted.

Example

Request

DELETE https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}/environments/{environment_id}
Authorization: Bearer YOUR_TOKEN

Evaluate

Test evaluations: evaluate a sample user against a published config with the same engine the SDKs run, and see the value, the reason and the rule or percentage option that decided. Nothing is stored and the user is never logged.

post/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/evaluateAuth

Test an evaluation

Evaluates a sample user against the published config of this environment (the current revision, or `revision`) with the same engine the SDKs run, and returns the evaluation details per key: value, reason, the rule and percentage option that decided, and soft-miss warnings. Each setting's own value is the default. Nothing is stored and the user is never logged. Limited to 30 calls a minute per credential; the evaluation has a 250 ms budget (422 `EVALUATION_BUDGET_EXCEEDED`, ask for fewer keys).

Path parameters

config_id*string · uuid

Id of the config.

environment_id*string · uuid

Id of an environment of the same project.

workspace_id*string · uuid

Id of the workspace.

Request body

userobject

The user to evaluate for, in the SDK's shape: `identifier`, `email`, `country` and `custom: {…}`; flat attributes are accepted too (`custom` wins when both name one). Attribute names are yours and kept as sent. `null` or omitted: no user (rules that need one are soft misses with `missing_user`). Attribute values are text, numbers, booleans or arrays of text; anything else is a soft miss (`invalid_attribute`), as in the SDKs. Dates: send unix seconds, `{"$date": "<ISO 8601>"}`, or a plain ISO 8601 string for an attribute that a `before`/`after` condition of this config uses (converted to unix seconds; a console convenience, SDKs take a `Date` or unix seconds). Never stored or logged.

Example: {"identifier":"user-2","email":"ada@acme.com","custom":{"app_version":"2.1.0"}}

keysarray

Setting keys to evaluate (at most 200). Omitted: every setting. An unknown key returns a `FLAG_NOT_FOUND` row.

Example: ["new_checkout","banner_text"]

revisioninteger

Evaluate this published revision instead of the current one (404 `REVISION_NOT_FOUND` once pruned).

Example: 41

Response · 200 The evaluation details.

revision*integer

The config revision evaluated.

Example: 42

data*array

One row per evaluated key: the requested keys in request order, or every setting in config order.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/evaluate
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "user": {
    "identifier": "user-2",
    "email": "ada@acme.com",
    "custom": {
      "app_version": "2.1.0"
    }
  },
  "keys": [
    "new_checkout",
    "banner_text"
  ],
  "revision": 41
}

Response

{
  "revision": 42,
  "data": [
    {
      "key": "new_checkout",
      "value": true,
      "variant": "rule:1/pct:0",
      "reason": "SPLIT",
      "error_code": null,
      "error_message": null,
      "rule_index": 1,
      "percentage_index": 0,
      "is_default_value": false,
      "warnings": [
        {
          "code": "missing_attribute",
          "message": "The user has no attribute \"app_version\".",
          "key": "new_checkout",
          "rule_index": 1,
          "attribute": "app_version"
        }
      ]
    }
  ]
}

History

The change history of a project: every write with who made it, its note, the config revisions it published, a diff and a human summary. Restore a version, export the history as CSV or JSON. Confidential lists show as counts, never values.

get/v1/workspaces/{workspace_id}/projects/{project_id}/changesAuth

List changes

The history of a project, newest first: every write with who made it (user, API key, or a client acting for a user), its note, the config revisions it published and a human summary ("Production: value false → true"). Filter by config, environment, setting, segment, actor or time. Confidential lists show as counts, never values.

Path parameters

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Query parameters

cursorstring

Opaque cursor for pagination: the `next_cursor` of the previous page.

limitinteger

Page size.

config_idstring · uuid

Only changes of this config (its settings, values and keys).

environment_idstring · uuid

Only changes in this environment (value writes, SDK keys).

setting_idstring · uuid

Only changes of this setting: its metadata and its values in every environment.

segment_idstring · uuid

Only changes of this segment.

actor_idstring

Only changes made by this actor (`actor.id`: a platform user id or an API key id).

sincestring · date-time

Only changes at or after this time (ISO 8601).

untilstring · date-time

Only changes before this time (ISO 8601).

Response · 200 A page of changes.

data*array

Changes, newest first.

pagination*object

The cursor for the next page.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}/changes
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "config_id": "00000000-0000-0000-0000-000000000000",
      "environment_id": "00000000-0000-0000-0000-000000000000",
      "entity_type": "setting_value",
      "entity_id": "5b1f0c2e-8d4a-4e6b-9f3a-1c2d3e4f5a6b",
      "entity_key": "new_checkout",
      "action": "setting_value.updated",
      "actor": {},
      "note": "Ticket FLAG-142",
      "revisions": [
        {
          "config_id": "00000000-0000-0000-0000-000000000000",
          "environment_id": "00000000-0000-0000-0000-000000000000",
          "revision": 7,
          "url": "https://api.flags.productcraft.co/v1/workspaces/9b2f6c1e-0d6a-4a63-9a43-2f1c3e4d5a6b/configs/1f0c…/environments/7a2e…/revisions/7",
          "previous_url": "string"
        }
      ],
      "reverts_change_id": "00000000-0000-0000-0000-000000000000",
      "summary": [
        "Production: value false → true"
      ],
      "diff_too_large": false,
      "size_bytes": 0,
      "created_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "pagination": {}
}
get/v1/workspaces/{workspace_id}/projects/{project_id}/changes/exportAuth

Export changes

The filtered history as a file, newest first, at most 50,000 rows, streamed. `csv`: one row per change with the summary (no bodies). `json`: JSON Lines (`application/x-ndjson`), one change object per line with `before` and `after` (confidential lists as counts). Cells a spreadsheet would run as a formula start with `'`.

Path parameters

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Query parameters

config_idstring · uuid

Only changes of this config (its settings, values and keys).

environment_idstring · uuid

Only changes in this environment (value writes, SDK keys).

setting_idstring · uuid

Only changes of this setting: its metadata and its values in every environment.

segment_idstring · uuid

Only changes of this segment.

actor_idstring

Only changes made by this actor (`actor.id`: a platform user id or an API key id).

sincestring · date-time

Only changes at or after this time (ISO 8601).

untilstring · date-time

Only changes before this time (ISO 8601).

formatenum (2)

`csv` (one row per change, with the summary; no `before` / `after`) or `json` (JSON Lines: one change object per line, with `before` and `after`).

Response · 200

The export file (`Content-Disposition: attachment`).

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}/changes/export
Authorization: Bearer YOUR_TOKEN
get/v1/workspaces/{workspace_id}/changes/{change_id}Auth

Get a change

One change with `before`, `after`, a JSON Patch `diff` (RFC 6902) and the summary. When `before` and `after` together exceed 64 KB, `diff_too_large` is true and the bodies are left out: compare the revisions in `revisions` (`url`, `previous_url`) instead.

Path parameters

change_id*string · uuid

Id of the change.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The change.

id*string · uuid

Id of the change.

project_id*string · uuid

The project.

config_id*string · uuid

The config, for changes of a config, its settings, values or SDK keys.

environment_id*string · uuid

The environment, for value writes and SDK key changes.

entity_type*string

What changed: `project`, `environment`, `config`, `setting`, `setting_value`, `segment` or `sdk_key` (open: more may come).

Example: "setting_value"

entity_id*string · uuid

Id of the entity (for `setting_value`, the setting).

Example: "5b1f0c2e-8d4a-4e6b-9f3a-1c2d3e4f5a6b"

entity_key*string

The key of the entity at the time (an environment key for SDK keys).

Example: "new_checkout"

action*string

`<entity>.<verb>`, e.g. `setting_value.updated`, `setting.created`, `segment.deleted`, `sdk_key.rotated`, `confidential.revealed` (open: more may come).

Example: "setting_value.updated"

actor*object

Who made the change.

note*string

The note sent with the write.

Example: "Ticket FLAG-142"

revisions*array

Config revisions the change published (empty when it changed nothing SDKs download).

reverts_change_id*string · uuid

The change this one restored (`POST …/changes/{id}/revert`).

summary*array

Human-readable lines, e.g. "Production: value false → true" or "rule 2 percentage 10 → 25". Confidential lists show as counts ("+3 / −1 values"), never values. Empty when the change is too large to summarise (`diff_too_large`).

Example: ["Production: value false → true"]

diff_too_large*boolean

Whether `before` and `after` together exceed 64 KB: then there is no diff and no summary; compare the two revisions instead.

size_bytes*integer

Bytes `before` and `after` take up in history storage (they count toward the 200 MiB per project).

created_at*string · date-time

When the change was made.

before*object

The entity before the change (null for a create). Confidential lists show as `{hashed, count, list_id, editable}`, never values. Null when `diff_too_large`: read the revisions instead.

after*object

The entity after the change (null for a delete), in the same form as `before`. Null when `diff_too_large`.

diff*array

JSON Patch (RFC 6902) from `before` to `after`. Null when `diff_too_large`.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/changes/{change_id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000",
  "entity_type": "setting_value",
  "entity_id": "5b1f0c2e-8d4a-4e6b-9f3a-1c2d3e4f5a6b",
  "entity_key": "new_checkout",
  "action": "setting_value.updated",
  "actor": {},
  "note": "Ticket FLAG-142",
  "revisions": [
    {
      "config_id": "00000000-0000-0000-0000-000000000000",
      "environment_id": "00000000-0000-0000-0000-000000000000",
      "revision": 7,
      "url": "https://api.flags.productcraft.co/v1/workspaces/9b2f6c1e-0d6a-4a63-9a43-2f1c3e4d5a6b/configs/1f0c…/environments/7a2e…/revisions/7",
      "previous_url": "string"
    }
  ],
  "reverts_change_id": "00000000-0000-0000-0000-000000000000",
  "summary": [
    "Production: value false → true"
  ],
  "diff_too_large": false,
  "size_bytes": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "before": {},
  "after": {},
  "diff": [
    {
      "op": {},
      "path": "/rules/1/percentages/0/percentage",
      "value": 25
    }
  ]
}
post/v1/workspaces/{workspace_id}/changes/{change_id}/revertAuth

Restore a version

Writes the change's `before` back as a new change (`reverts_change_id` points to this one), through the same checks as an edit: validation, the protected-environment and note rules, and publishing. Supported for `setting_value.updated`, `segment.updated` and `setting.updated`. Send the entity's current `version` (the value or segment); a setting is restored only while it is still as this change left it. Needs `flags.value.update` plus what the original edit needed (`flags.value.update` on the environment and setting, or `flags.update`; `flags.protected.update` in protected environments).

Path parameters

change_id*string · uuid

Id of the change.

workspace_id*string · uuid

Id of the workspace.

Headers

Idempotency-Keystring

Idempotency key (1-256 characters, `[A-Za-z0-9_-:]`). A retry with the same key and body returns the first response for 24 h.

Request body

versioninteger

The current `version` of the entity (the setting value or the segment) you are restoring over; 409 `VERSION_CONFLICT` when it moved. Settings have no version: for `setting.updated` it is ignored and the restore is refused with 409 when the setting no longer matches the change's `after`.

Example: 4

notestring

A change note (at most 500 characters), saved on the new history entry. Environments with `require_note` refuse value writes without one.

Example: "Roll back FLAG-142"

Response · 201 The new change (the restore).

id*string · uuid

Id of the change.

project_id*string · uuid

The project.

config_id*string · uuid

The config, for changes of a config, its settings, values or SDK keys.

environment_id*string · uuid

The environment, for value writes and SDK key changes.

entity_type*string

What changed: `project`, `environment`, `config`, `setting`, `setting_value`, `segment` or `sdk_key` (open: more may come).

Example: "setting_value"

entity_id*string · uuid

Id of the entity (for `setting_value`, the setting).

Example: "5b1f0c2e-8d4a-4e6b-9f3a-1c2d3e4f5a6b"

entity_key*string

The key of the entity at the time (an environment key for SDK keys).

Example: "new_checkout"

action*string

`<entity>.<verb>`, e.g. `setting_value.updated`, `setting.created`, `segment.deleted`, `sdk_key.rotated`, `confidential.revealed` (open: more may come).

Example: "setting_value.updated"

actor*object

Who made the change.

note*string

The note sent with the write.

Example: "Ticket FLAG-142"

revisions*array

Config revisions the change published (empty when it changed nothing SDKs download).

reverts_change_id*string · uuid

The change this one restored (`POST …/changes/{id}/revert`).

summary*array

Human-readable lines, e.g. "Production: value false → true" or "rule 2 percentage 10 → 25". Confidential lists show as counts ("+3 / −1 values"), never values. Empty when the change is too large to summarise (`diff_too_large`).

Example: ["Production: value false → true"]

diff_too_large*boolean

Whether `before` and `after` together exceed 64 KB: then there is no diff and no summary; compare the two revisions instead.

size_bytes*integer

Bytes `before` and `after` take up in history storage (they count toward the 200 MiB per project).

created_at*string · date-time

When the change was made.

before*object

The entity before the change (null for a create). Confidential lists show as `{hashed, count, list_id, editable}`, never values. Null when `diff_too_large`: read the revisions instead.

after*object

The entity after the change (null for a delete), in the same form as `before`. Null when `diff_too_large`.

diff*array

JSON Patch (RFC 6902) from `before` to `after`. Null when `diff_too_large`.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/changes/{change_id}/revert
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "version": 4,
  "note": "Roll back FLAG-142"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000",
  "entity_type": "setting_value",
  "entity_id": "5b1f0c2e-8d4a-4e6b-9f3a-1c2d3e4f5a6b",
  "entity_key": "new_checkout",
  "action": "setting_value.updated",
  "actor": {},
  "note": "Ticket FLAG-142",
  "revisions": [
    {
      "config_id": "00000000-0000-0000-0000-000000000000",
      "environment_id": "00000000-0000-0000-0000-000000000000",
      "revision": 7,
      "url": "https://api.flags.productcraft.co/v1/workspaces/9b2f6c1e-0d6a-4a63-9a43-2f1c3e4d5a6b/configs/1f0c…/environments/7a2e…/revisions/7",
      "previous_url": "string"
    }
  ],
  "reverts_change_id": "00000000-0000-0000-0000-000000000000",
  "summary": [
    "Production: value false → true"
  ],
  "diff_too_large": false,
  "size_bytes": 0,
  "created_at": "2026-01-01T00:00:00.000Z",
  "before": {},
  "after": {},
  "diff": [
    {
      "op": {},
      "path": "/rules/1/percentages/0/percentage",
      "value": 25
    }
  ]
}

Imports

Move in from ConfigCat in one step: a dry run reports what maps, what does not and what changes form; the real run creates a project with the same environments, configs, flags, segments and targeting, and keeps every user in the same percentage bucket.

post/v1/workspaces/{workspace_id}/importsAuth

Import from ConfigCat

Reads a ConfigCat product through ConfigCat's Public Management API with credentials you send for this one request (never stored, logged or cached), and maps it to a new Flags project: environments, configs, feature flags and settings (seed = the ConfigCat key, so every user stays in the same percentage bucket), segments, targeting rules, percentage options and prerequisite flags; sensitive comparators become confidential comparators re-hashed under the project salt. Send `dry_run: true` first: it reads and reports (`counts`, `mapped`, `unsupported`, `warnings`) and writes nothing. A real run (`dry_run: false`) creates the project and answers 201; while the report lists `unsupported` items it answers 422 `IMPORT_UNSUPPORTED` unless `skip_unsupported: true`. One synchronous call with a 120 s budget for the ConfigCat reads (paced to ConfigCat's rate limits). Needs `flags.project.manage` and `flags.create`. No Idempotency-Key: the body carries credentials, and a retry of a finished import answers 409 `KEY_ALREADY_EXISTS` (the project name is taken).

Path parameters

workspace_id*string · uuid

Id of the workspace.

Request body

source*object

Where to import from. Only `configcat` today.

Example: "configcat"

credentials*object

ConfigCat Public Management API credentials (Basic auth). Write-only.

configcat_product_id*string · uuid

Id of the ConfigCat product to import (Product settings in the ConfigCat dashboard, or `GET /v1/products`).

Example: "08d8a0b0-0000-4c1f-8a1c-1a2b3c4d5e6f"

dry_run*boolean

`true`: read and report only, write nothing (the first step). `false`: create the project.

Example: true

target_project_namestring

Name of the new project. Defaults to the ConfigCat product's name; another project with this name is a 409.

Example: "Checkout"

skip_unsupportedboolean

Import even when the dry run lists `unsupported` items: those rules (or segments, settings) are left out and named in the history. Without it, a real run with unsupported items answers 422 `IMPORT_UNSUPPORTED`.

Response · 200 Dry run: the report; nothing was written.

dry_run*boolean

Echo of the request: true when nothing was written.

Example: true

configcat_product_id*string · uuid

The ConfigCat product that was read.

Example: "08d8a0b0-0000-4c1f-8a1c-1a2b3c4d5e6f"

report*object

The mapping report (the same on the dry run and the real run).

project*object

The new project (real run); null on a dry run.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/imports
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "source": "configcat",
  "credentials": {},
  "configcat_product_id": "08d8a0b0-0000-4c1f-8a1c-1a2b3c4d5e6f",
  "dry_run": true,
  "target_project_name": "Checkout",
  "skip_unsupported": false
}

Response

{
  "dry_run": true,
  "configcat_product_id": "08d8a0b0-0000-4c1f-8a1c-1a2b3c4d5e6f",
  "report": {},
  "project": {}
}

Limits

The preview limits of Flags and how much of them the workspace uses.

get/v1/workspaces/{workspace_id}/limitsAuth

Get Flags limits and usage

The preview limits that apply to this workspace and how much of the counted ones it uses.

Path parameters

workspace_id*string · uuid

Id of the workspace.

Response · 200 Limits and usage.

limits*array

The preview limits. A write past one answers 422 `LIMIT_EXCEEDED`.

usage*object

Current usage of the counted limits.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/limits
Authorization: Bearer YOUR_TOKEN

Response

{
  "limits": [
    {
      "limit": "projects",
      "scope": "workspace",
      "max": 3
    }
  ],
  "usage": {}
}

Projects

A project groups the environments, configs, flags and segments of one product. New projects start with Development, a protected Production and one config, `main`.

get/v1/workspaces/{workspace_id}/projectsAuth

List projects

The workspace's projects, oldest first, each with its environments and configs.

Path parameters

workspace_id*string · uuid

Id of the workspace.

Query parameters

cursorstring

Opaque cursor for pagination: the `next_cursor` of the previous page.

limitinteger

Page size.

Response · 200 A page of projects.

data*array

Projects, oldest first.

pagination*object

The cursor for the next page.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "name": "Storefront",
      "description": "",
      "ordinal": 0,
      "environments": [
        {
          "id": "00000000-0000-0000-0000-000000000000",
          "workspace_id": "00000000-0000-0000-0000-000000000000",
          "project_id": "00000000-0000-0000-0000-000000000000",
          "key": "production",
          "name": "Production",
          "color": "#c92a2a",
          "ordinal": 1,
          "protected": true,
          "require_note": false,
          "created_at": "2026-01-01T00:00:00.000Z",
          "updated_at": "2026-01-01T00:00:00.000Z"
        }
      ],
      "configs": [
        {
          "id": "00000000-0000-0000-0000-000000000000",
          "workspace_id": "00000000-0000-0000-0000-000000000000",
          "project_id": "00000000-0000-0000-0000-000000000000",
          "key": "main",
          "name": "Main",
          "description": "",
          "ordinal": 0,
          "created_at": "2026-01-01T00:00:00.000Z",
          "updated_at": "2026-01-01T00:00:00.000Z"
        }
      ],
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "pagination": {}
}
post/v1/workspaces/{workspace_id}/projectsAuth

Create a project

Creates the project with the environments of `environment_preset` (default `dev_prod`: Development and a protected Production) and one config, `main`, unless you pass `environments` / `configs`. Every config gets a published state in every environment. Needs `flags.project.manage`.

Path parameters

workspace_id*string · uuid

Id of the workspace.

Headers

Idempotency-Keystring

Idempotency key (1-256 characters, `[A-Za-z0-9_-:]`). A retry with the same key and body returns the first response for 24 h.

Request body

name*string

Display name, 1-100 characters, unique in the workspace.

Example: "Storefront"

descriptionstring

Free-text description, at most 1000 characters.

environment_presetobject

The environments to start with when `environments` is not given: `dev_prod` (Development, Production) or `dev_staging_prod` (Development, Staging, Production). Production is protected.

environmentsarray

Your own environment list instead of a preset (1-5).

configsarray

Your own config list instead of the default "Main" config (1-10).

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Example: "Ticket FLAG-142"

Response · 201 The new project, with its environments and configs.

id*string · uuid

The project's id.

workspace_id*string · uuid

The workspace that owns the project.

name*string

Display name, unique in the workspace (case-insensitive).

Example: "Storefront"

description*string

Free-text description (may be empty).

Example: ""

ordinal*integer

Sort position in the workspace (ascending).

Example: 0

environments*array

The project's environments, in `ordinal` order.

configs*array

The project's configs, in `ordinal` order.

created_at*string · date-time

When the project was created.

updated_at*string · date-time

When the project last changed.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "name": "Storefront",
  "description": "string",
  "environment_preset": {},
  "environments": [
    {
      "key": "production",
      "name": "Production",
      "color": "#c92a2a",
      "protected": false,
      "require_note": false
    }
  ],
  "configs": [
    {
      "key": "main",
      "name": "Main",
      "description": "string"
    }
  ],
  "note": "Ticket FLAG-142"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "name": "Storefront",
  "description": "",
  "ordinal": 0,
  "environments": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "key": "production",
      "name": "Production",
      "color": "#c92a2a",
      "ordinal": 1,
      "protected": true,
      "require_note": false,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "configs": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "key": "main",
      "name": "Main",
      "description": "",
      "ordinal": 0,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}
get/v1/workspaces/{workspace_id}/projects/{project_id}Auth

Get a project

The project with its environments and configs.

Path parameters

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The project.

id*string · uuid

The project's id.

workspace_id*string · uuid

The workspace that owns the project.

name*string

Display name, unique in the workspace (case-insensitive).

Example: "Storefront"

description*string

Free-text description (may be empty).

Example: ""

ordinal*integer

Sort position in the workspace (ascending).

Example: 0

environments*array

The project's environments, in `ordinal` order.

configs*array

The project's configs, in `ordinal` order.

created_at*string · date-time

When the project was created.

updated_at*string · date-time

When the project last changed.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "name": "Storefront",
  "description": "",
  "ordinal": 0,
  "environments": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "key": "production",
      "name": "Production",
      "color": "#c92a2a",
      "ordinal": 1,
      "protected": true,
      "require_note": false,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "configs": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "key": "main",
      "name": "Main",
      "description": "",
      "ordinal": 0,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}
patch/v1/workspaces/{workspace_id}/projects/{project_id}Auth

Update a project

Rename, describe or reorder the project. Only the fields you send change. Needs `flags.project.manage`.

Path parameters

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Request body

namestring

Display name, 1-100 characters, unique in the workspace.

Example: "Storefront"

descriptionstring

Free-text description, at most 1000 characters.

ordinalinteger

Sort position in the workspace.

Example: 0

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Example: "Ticket FLAG-142"

Response · 200 The updated project.

id*string · uuid

The project's id.

workspace_id*string · uuid

The workspace that owns the project.

name*string

Display name, unique in the workspace (case-insensitive).

Example: "Storefront"

description*string

Free-text description (may be empty).

Example: ""

ordinal*integer

Sort position in the workspace (ascending).

Example: 0

environments*array

The project's environments, in `ordinal` order.

configs*array

The project's configs, in `ordinal` order.

created_at*string · date-time

When the project was created.

updated_at*string · date-time

When the project last changed.

Example

Request

PATCH https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "name": "Storefront",
  "description": "string",
  "ordinal": 0,
  "note": "Ticket FLAG-142"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "name": "Storefront",
  "description": "",
  "ordinal": 0,
  "environments": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "key": "production",
      "name": "Production",
      "color": "#c92a2a",
      "ordinal": 1,
      "protected": true,
      "require_note": false,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "configs": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "key": "main",
      "name": "Main",
      "description": "",
      "ordinal": 0,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z"
}
delete/v1/workspaces/{workspace_id}/projects/{project_id}Auth

Delete a project

Deletes the project with all of its environments, configs, flags, segments, SDK keys and history. Pass `confirm` with the exact project name. SDKs using its keys stop receiving updates. Needs `flags.project.manage`; recorded in the workspace audit log.

Path parameters

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Query parameters

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

confirm*string

The project's exact name, to confirm the delete. Deleting a project deletes its environments, configs, flags, segments, SDK keys and history.

Response · 204

Deleted.

Example

Request

DELETE https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}
Authorization: Bearer YOUR_TOKEN

Revisions

Published config revisions per environment: every write that changes what SDKs download publishes a new revision of the config JSON. List them and read any kept revision byte for byte as SDKs get it.

get/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/revisionsAuth

List config revisions

Published revisions of the config in one environment, newest first. Every write that changes what SDKs download publishes a revision; edits that do not (a description, a name) do not. The last 100 revisions, or 20 MiB of compressed snapshots, are kept, and the current one always.

Path parameters

config_id*string · uuid

Id of the config.

environment_id*string · uuid

Id of an environment of the same project.

workspace_id*string · uuid

Id of the workspace.

Query parameters

cursorstring

Opaque cursor for pagination: the `next_cursor` of the previous page.

limitinteger

Page size.

Response · 200 A page of revisions.

data*array

Published revisions, newest first (the last 100, or 20 MiB of compressed snapshots, are kept).

pagination*object

The cursor for the next page.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/revisions
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "revision": 2,
      "generation": 1,
      "etag": "q8w4Yb0cJ0dY2t1vUo3n7A",
      "size_bytes": 412,
      "published_at": "2026-01-01T00:00:00.000Z",
      "change_id": "00000000-0000-0000-0000-000000000000",
      "current": true
    }
  ],
  "pagination": {}
}
get/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/revisions/{revision}Auth

Get a config revision

The config JSON (schema 1) of one revision, byte for byte what SDKs download: keys, types, values, rules, segments and hashes, but no names, descriptions or cleartext of confidential lists. The response body is the document itself (no envelope).

Path parameters

config_id*string · uuid

Id of the config.

environment_id*string · uuid

Id of an environment of the same project.

revision*integer

The revision number.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The config JSON document (schema 1). `ETag` carries the revision's ETag.

object

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/revisions/{revision}
Authorization: Bearer YOUR_TOKEN

Response

{}

Sdk-Keys

SDK keys, one primary per config and environment: list them masked, reveal them (audited), rotate with a grace period during which the old key keeps working, or end the grace period early.

get/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/sdk-keysAuth

List SDK keys

The SDK keys of this config in this environment, masked (prefix and last four characters): the primary key, and the secondary key while a rotation's grace period runs. The first call for a new environment mints its primary key.

Path parameters

config_id*string · uuid

Id of the config.

environment_id*string · uuid

Id of an environment of the same project.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The masked keys.

data*array

The primary key, then the secondary key while a grace period runs.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/sdk-keys
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "slot": {},
      "key_prefix": "pcf_sdk_4fQx",
      "last4": "k9Zt",
      "created_at": "2026-01-01T00:00:00.000Z",
      "expires_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}
post/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/sdk-keys/revealAuth

Reveal SDK keys

The full primary key, and the secondary key while a grace period runs. Needs `flags.sdk_key.reveal`; every call is recorded in the history (`sdk_key.revealed`) and the workspace audit log. Never cached. Whoever holds a key can download the whole config of this environment: keep server keys server-side.

Path parameters

config_id*string · uuid

Id of the config.

environment_id*string · uuid

Id of an environment of the same project.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The full keys.

data*array

The primary key, then the secondary key while a grace period runs.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/sdk-keys/reveal
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "slot": {},
      "key_prefix": "pcf_sdk_4fQx",
      "last4": "k9Zt",
      "created_at": "2026-01-01T00:00:00.000Z",
      "expires_at": "2026-01-01T00:00:00.000Z",
      "key": "pcf_sdk_4fQxV2mB8cJ0nR7tW1yZ3aD5gH6kL9pS0q1Uk9Zt"
    }
  ]
}
post/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/sdk-keys/rotateAuth

Rotate the SDK key

Mints a new primary key. The current primary becomes the secondary key and keeps working for `grace_hours` (default 24, at most 168; `0` revokes it at once); a previous secondary key is revoked. SDK servers pick up the change within about 5 seconds. The response is masked: reveal the new key separately.

Path parameters

config_id*string · uuid

Id of the config.

environment_id*string · uuid

Id of an environment of the same project.

workspace_id*string · uuid

Id of the workspace.

Headers

Idempotency-Keystring

Idempotency key (1-256 characters, `[A-Za-z0-9_-:]`). A retry with the same key and body returns the first response for 24 h.

Request body

grace_hoursinteger

How long the current key keeps working as the secondary key, in hours (0 to 168). `0` revokes it at once. An existing secondary key is revoked by the rotation.

notestring

Why, recorded in the history.

Response · 200 The masked keys after the rotation.

data*array

The primary key, then the secondary key while a grace period runs.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/sdk-keys/rotate
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "grace_hours": 0,
  "note": "string"
}

Response

{
  "data": [
    {
      "slot": {},
      "key_prefix": "pcf_sdk_4fQx",
      "last4": "k9Zt",
      "created_at": "2026-01-01T00:00:00.000Z",
      "expires_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}
delete/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/sdk-keys/secondaryAuth

End the grace period

Revokes the secondary key now instead of at its `expires_at`. SDK servers still using it get 404 within about 5 seconds. Recorded in the history (`sdk_key.grace_ended`) and the workspace audit log.

Path parameters

config_id*string · uuid

Id of the config.

environment_id*string · uuid

Id of an environment of the same project.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The masked keys left (the primary key).

data*array

The primary key, then the secondary key while a grace period runs.

Example

Request

DELETE https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/sdk-keys/secondary
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "slot": {},
      "key_prefix": "pcf_sdk_4fQx",
      "last4": "k9Zt",
      "created_at": "2026-01-01T00:00:00.000Z",
      "expires_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}

Segments

Reusable sets of ANDed user conditions per project, targeted by rules of any config with `in` / `not_in`. Confidential (hashed) conditions keep only salted hashes in the SDK config and the cleartext encrypted for editing and reveal.

get/v1/workspaces/{workspace_id}/projects/{project_id}/segmentsAuth

List segments

The project's segments, oldest first, with `usage_count`.

Path parameters

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Query parameters

cursorstring

Opaque cursor for pagination: the `next_cursor` of the previous page.

limitinteger

Page size.

Response · 200 A page of segments.

data*array

Segments, oldest first.

pagination*object

The cursor for the next page.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}/segments
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "key": "qa_staff",
      "name": "QA staff",
      "description": "",
      "seed": "qa_staff",
      "conditions": [
        {
          "user": {}
        }
      ],
      "version": 1,
      "usage_count": 2,
      "created_by": "string",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "pagination": {}
}
post/v1/workspaces/{workspace_id}/projects/{project_id}/segmentsAuth

Create a segment

A reusable set of ANDed user conditions that any config of the project can target with `{segment: {segment_id, comparator: in | not_in}}`. Confidential lists are stored as hashes plus encrypted cleartext; the response shows only their count.

Path parameters

project_id*string · uuid

Id of the project.

workspace_id*string · uuid

Id of the workspace.

Headers

Idempotency-Keystring

Idempotency key (1-256 characters, `[A-Za-z0-9_-:]`). A retry with the same key and body returns the first response for 24 h.

Request body

key*string

Unique in the project; must start with a letter and contain only letters, digits, "_", "." and "-", at most 128 characters. Becomes the segment's permanent seed.

Example: "qa_staff"

namestring

Display name, 1-200 characters. Defaults to the key.

Example: "QA staff"

descriptionstring

Free-text description, at most 1000 characters.

conditions*array

1-20 conditions, ANDed in order; each is `{user: {attribute, comparator, values}}` (segments hold user conditions only). Confidential comparators take cleartext `values` (stored hashed with the segment's seed and the project salt, and encrypted at rest), `values_hashed`, or `list_id` with `values_add` / `values_remove` to edit a stored list.

Example: [{"user":{"attribute":"email","comparator":"ends_with_any_hashed","values":["@acme.test"]}}]

notestring

A change note (at most 500 characters), saved on the history entry. A segment used in an environment with `require_note` cannot change without one.

Example: "Add the beta testers domain"

Response · 201 The new segment.

id*string · uuid

The segment's id.

workspace_id*string · uuid

The workspace that owns it.

project_id*string · uuid

The project; a segment can be used by any config of it.

key*string

Unique in the project, case-sensitive; SDKs see it in the config JSON.

Example: "qa_staff"

name*string

Display name.

Example: "QA staff"

description*string

Free-text description (may be empty).

Example: ""

seed*string

The key at creation. It never changes, and confidential values are hashed with it, so renaming the segment keeps them valid.

Example: "qa_staff"

conditions*array

1-20 conditions, ANDed in order; each is `{user: {attribute, comparator, values}}` (segments hold user conditions only). Confidential comparators take cleartext `values` (stored hashed with the segment's seed and the project salt, and encrypted at rest), `values_hashed`, or `list_id` with `values_add` / `values_remove` to edit a stored list. Confidential lists are shown as `{hashed: true, count, list_id, editable}`.

version*integer

Increments on every change; send it with an update to detect concurrent edits.

Example: 1

usage_count*integer

How many settings use the segment in at least one environment.

Example: 2

created_by*string

Who created it (user or API key id).

created_at*string · date-time

When it was created.

updated_at*string · date-time

When it last changed.

used_by*array

Every (setting, environment) whose rules use the segment. A segment listed anywhere cannot be deleted.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/projects/{project_id}/segments
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "key": "qa_staff",
  "name": "QA staff",
  "description": "string",
  "conditions": [
    {
      "user": {
        "attribute": "email",
        "comparator": "ends_with_any_hashed",
        "values": [
          "@acme.test"
        ]
      }
    }
  ],
  "note": "Add the beta testers domain"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "key": "qa_staff",
  "name": "QA staff",
  "description": "",
  "seed": "qa_staff",
  "conditions": [
    {
      "user": {}
    }
  ],
  "version": 1,
  "usage_count": 2,
  "created_by": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "used_by": [
    {
      "setting_id": "00000000-0000-0000-0000-000000000000",
      "setting_key": "new_checkout",
      "config_id": "00000000-0000-0000-0000-000000000000",
      "config_key": "main",
      "environment_id": "00000000-0000-0000-0000-000000000000",
      "environment_key": "production"
    }
  ]
}
get/v1/workspaces/{workspace_id}/segments/{segment_id}Auth

Get a segment

The segment with its conditions and every (setting, environment) that uses it (`used_by`).

Path parameters

segment_id*string · uuid

Id of the segment.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The segment.

id*string · uuid

The segment's id.

workspace_id*string · uuid

The workspace that owns it.

project_id*string · uuid

The project; a segment can be used by any config of it.

key*string

Unique in the project, case-sensitive; SDKs see it in the config JSON.

Example: "qa_staff"

name*string

Display name.

Example: "QA staff"

description*string

Free-text description (may be empty).

Example: ""

seed*string

The key at creation. It never changes, and confidential values are hashed with it, so renaming the segment keeps them valid.

Example: "qa_staff"

conditions*array

1-20 conditions, ANDed in order; each is `{user: {attribute, comparator, values}}` (segments hold user conditions only). Confidential comparators take cleartext `values` (stored hashed with the segment's seed and the project salt, and encrypted at rest), `values_hashed`, or `list_id` with `values_add` / `values_remove` to edit a stored list. Confidential lists are shown as `{hashed: true, count, list_id, editable}`.

version*integer

Increments on every change; send it with an update to detect concurrent edits.

Example: 1

usage_count*integer

How many settings use the segment in at least one environment.

Example: 2

created_by*string

Who created it (user or API key id).

created_at*string · date-time

When it was created.

updated_at*string · date-time

When it last changed.

used_by*array

Every (setting, environment) whose rules use the segment. A segment listed anywhere cannot be deleted.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/segments/{segment_id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "key": "qa_staff",
  "name": "QA staff",
  "description": "",
  "seed": "qa_staff",
  "conditions": [
    {
      "user": {}
    }
  ],
  "version": 1,
  "usage_count": 2,
  "created_by": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "used_by": [
    {
      "setting_id": "00000000-0000-0000-0000-000000000000",
      "setting_key": "new_checkout",
      "config_id": "00000000-0000-0000-0000-000000000000",
      "config_key": "main",
      "environment_id": "00000000-0000-0000-0000-000000000000",
      "environment_key": "production"
    }
  ]
}
patch/v1/workspaces/{workspace_id}/segments/{segment_id}Auth

Update a segment

Key, name, description or conditions; only the fields you send change. Edit a confidential list with `list_id` plus `values_add` / `values_remove`. A segment used in a protected environment also needs `flags.protected.update` there.

Path parameters

segment_id*string · uuid

Id of the segment.

workspace_id*string · uuid

Id of the workspace.

Request body

keystring

New key; must start with a letter and contain only letters, digits, "_", "." and "-", at most 128 characters. The seed does not change, so confidential hashes stay valid.

Example: "qa_team"

namestring

Display name, 1-200 characters.

descriptionstring

Free-text description, at most 1000 characters.

conditionsarray

Replaces the conditions. 1-20 conditions, ANDed in order; each is `{user: {attribute, comparator, values}}` (segments hold user conditions only). Confidential comparators take cleartext `values` (stored hashed with the segment's seed and the project salt, and encrypted at rest), `values_hashed`, or `list_id` with `values_add` / `values_remove` to edit a stored list. To keep a confidential list, send its `list_id` (or echo the `{hashed: true, list_id, …}` object as `values`).

Example: [{"user":{"attribute":"email","comparator":"ends_with_any_hashed","list_id":"3f0c6a3e-0f5e-4b8e-9d1a-2c7b9f1e8a10","values_add":["@beta.test"]}}]

versioninteger

The `version` you read; when sent, a mismatch answers 409 `VERSION_CONFLICT`.

Example: 1

notestring

A change note (at most 500 characters), saved on the history entry. A segment used in an environment with `require_note` cannot change without one.

Example: "Add the beta testers domain"

Response · 200 The updated segment.

id*string · uuid

The segment's id.

workspace_id*string · uuid

The workspace that owns it.

project_id*string · uuid

The project; a segment can be used by any config of it.

key*string

Unique in the project, case-sensitive; SDKs see it in the config JSON.

Example: "qa_staff"

name*string

Display name.

Example: "QA staff"

description*string

Free-text description (may be empty).

Example: ""

seed*string

The key at creation. It never changes, and confidential values are hashed with it, so renaming the segment keeps them valid.

Example: "qa_staff"

conditions*array

1-20 conditions, ANDed in order; each is `{user: {attribute, comparator, values}}` (segments hold user conditions only). Confidential comparators take cleartext `values` (stored hashed with the segment's seed and the project salt, and encrypted at rest), `values_hashed`, or `list_id` with `values_add` / `values_remove` to edit a stored list. Confidential lists are shown as `{hashed: true, count, list_id, editable}`.

version*integer

Increments on every change; send it with an update to detect concurrent edits.

Example: 1

usage_count*integer

How many settings use the segment in at least one environment.

Example: 2

created_by*string

Who created it (user or API key id).

created_at*string · date-time

When it was created.

updated_at*string · date-time

When it last changed.

used_by*array

Every (setting, environment) whose rules use the segment. A segment listed anywhere cannot be deleted.

Example

Request

PATCH https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/segments/{segment_id}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "key": "qa_team",
  "name": "string",
  "description": "string",
  "conditions": [
    {
      "user": {
        "attribute": "email",
        "comparator": "ends_with_any_hashed",
        "list_id": "3f0c6a3e-0f5e-4b8e-9d1a-2c7b9f1e8a10",
        "values_add": [
          "@beta.test"
        ]
      }
    }
  ],
  "version": 1,
  "note": "Add the beta testers domain"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "key": "qa_staff",
  "name": "QA staff",
  "description": "",
  "seed": "qa_staff",
  "conditions": [
    {
      "user": {}
    }
  ],
  "version": 1,
  "usage_count": 2,
  "created_by": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "used_by": [
    {
      "setting_id": "00000000-0000-0000-0000-000000000000",
      "setting_key": "new_checkout",
      "config_id": "00000000-0000-0000-0000-000000000000",
      "config_key": "main",
      "environment_id": "00000000-0000-0000-0000-000000000000",
      "environment_key": "production"
    }
  ]
}
delete/v1/workspaces/{workspace_id}/segments/{segment_id}Auth

Delete a segment

Deletes an unused segment. Needs `flags.delete`.

Path parameters

segment_id*string · uuid

Id of the segment.

workspace_id*string · uuid

Id of the workspace.

Query parameters

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Response · 204

Deleted.

Example

Request

DELETE https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/segments/{segment_id}
Authorization: Bearer YOUR_TOKEN
get/v1/workspaces/{workspace_id}/segments/{segment_id}/confidentialAuth

Reveal confidential values of a segment

The cleartext of the segment's confidential lists, decrypted. Needs `flags.confidential.read`; every call is recorded in the history (`confidential.revealed`). Never cached.

Path parameters

segment_id*string · uuid

Id of the segment.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The confidential lists.

data*array

Every confidential list, in rule and condition order.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/segments/{segment_id}/confidential
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "rule_index": 0,
      "condition_index": 0,
      "attribute": "email",
      "comparator": "ends_with_any_hashed",
      "list_id": "00000000-0000-0000-0000-000000000000",
      "values": [
        "@acme.test"
      ],
      "revealable": true
    }
  ]
}

Settings

Feature flags (`boolean`) and typed settings (`string`, `integer`, `number`, `json`) of a config, and their value and targeting per environment: rules on user attributes and prerequisite flags, percentage splits, and optimistic concurrency through `version`.

get/v1/workspaces/{workspace_id}/configs/{config_id}/settingsAuth

List settings

The config's feature flags and settings, in `ordinal` order, with filters.

Path parameters

config_id*string · uuid

Id of the config.

workspace_id*string · uuid

Id of the workspace.

Query parameters

cursorstring

Opaque cursor for pagination: the `next_cursor` of the previous page.

limitinteger

Page size.

tagstring

Only settings with this tag.

typeenum (5)

Only settings of this type.

qstring

Case-insensitive substring of the key or name.

lifecycleenum (2)

Only settings with this lifecycle.

owner_idstring · uuid

Only settings owned by this user.

Response · 200 A page of settings.

data*array

Settings, in `ordinal` order.

pagination*object

The cursor for the next page.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/settings
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "workspace_id": "00000000-0000-0000-0000-000000000000",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "config_id": "00000000-0000-0000-0000-000000000000",
      "key": "new_checkout",
      "name": "New checkout",
      "description": "",
      "hint": "",
      "type": "boolean",
      "tags": [
        "checkout"
      ],
      "lifecycle": "temporary",
      "owner_id": "00000000-0000-0000-0000-000000000000",
      "seed": "new_checkout",
      "ordinal": 0,
      "created_by": "string",
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "pagination": {}
}
post/v1/workspaces/{workspace_id}/configs/{config_id}/settingsAuth

Create a setting

Adds a feature flag (`boolean`) or a typed setting to the config, with a value in every environment (`initial_value`, else the type's zero). Needs `flags.create`; a non-zero `initial_value` is also a value write in every environment (protected ones need `flags.protected.update`).

Path parameters

config_id*string · uuid

Id of the config.

workspace_id*string · uuid

Id of the workspace.

Headers

Idempotency-Keystring

Idempotency key (1-256 characters, `[A-Za-z0-9_-:]`). A retry with the same key and body returns the first response for 24 h.

Request body

key*string

The key SDKs read it by, unique in the config; must start with a letter and contain only letters, digits, "_", "." and "-", at most 128 characters.

Example: "new_checkout"

namestring

Display name, 1-200 characters. Defaults to the key.

Example: "New checkout"

type*object

`boolean` for a feature flag; `string`, `integer`, `number` or `json` for a setting. Cannot change later.

Example: "boolean"

descriptionstring

Free-text description, at most 1000 characters.

hintstring

Short hint, at most 500 characters.

tagsarray

Tags, at most 20, each 1-50 characters.

Example: ["checkout"]

lifecycleobject

Defaults to `temporary`.

owner_idstring · uuid

A workspace member who owns the flag. Checked against the workspace members (422 `OWNER_NOT_A_MEMBER`).

initial_valueobject

The value in every environment. Defaults to the type's zero: `false`, `""`, `0`, `0`, `"{}"`. A non-zero value in a protected environment needs `flags.protected.update`. Must match the setting type: `boolean` true/false; `string` a string; `integer` a safe integer; `number` a finite number; `json` a string holding JSON text (for example `"{\"limit\":5}"`). At most 100 KB. Strings are stored and returned byte for byte.

Example: false

ordinalinteger

Sort position. Defaults to after the last one.

Example: 0

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Example: "Ticket FLAG-142"

Response · 201 The new setting with its values.

id*string · uuid

The setting's id.

workspace_id*string · uuid

The workspace that owns it.

project_id*string · uuid

The project.

config_id*string · uuid

The config it belongs to.

key*string

The key SDKs read it by; unique in the config, case-sensitive.

Example: "new_checkout"

name*string

Display name.

Example: "New checkout"

description*string

Free-text description (may be empty).

Example: ""

hint*string

A short hint shown next to the flag in the console (may be empty).

Example: ""

type*object

`boolean` is a feature flag; the others are typed settings. Cannot change.

Example: "boolean"

tags*array

Free-form tags.

Example: ["checkout"]

lifecycle*object

`temporary` (a release flag you plan to remove) or `permanent` (an ops switch or a setting).

Example: "temporary"

owner_id*string · uuid

The platform user who owns the flag (a workspace member), or null.

seed*string

The value percentage splits hash with: the key at creation (or the ConfigCat key on import). It never changes, so renaming a key does not move users between variations.

Example: "new_checkout"

ordinal*integer

Sort position in the config (ascending).

Example: 0

created_by*string

Who created it (user or API key id).

created_at*string · date-time

When it was created.

updated_at*string · date-time

When its metadata last changed.

values*array

Its value and targeting in every environment, in environment order.

dependents*object

What depends on this setting: check before deleting or renaming it.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/settings
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "key": "new_checkout",
  "name": "New checkout",
  "type": "boolean",
  "description": "string",
  "hint": "string",
  "tags": [
    "checkout"
  ],
  "lifecycle": {},
  "owner_id": "00000000-0000-0000-0000-000000000000",
  "initial_value": false,
  "ordinal": 0,
  "note": "Ticket FLAG-142"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "key": "new_checkout",
  "name": "New checkout",
  "description": "",
  "hint": "",
  "type": "boolean",
  "tags": [
    "checkout"
  ],
  "lifecycle": "temporary",
  "owner_id": "00000000-0000-0000-0000-000000000000",
  "seed": "new_checkout",
  "ordinal": 0,
  "created_by": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "values": [
    {
      "setting_id": "00000000-0000-0000-0000-000000000000",
      "environment_id": "00000000-0000-0000-0000-000000000000",
      "environment_key": "production",
      "value": false,
      "rules": [
        {}
      ],
      "percentages": [
        {
          "percentage": 10,
          "value": true
        }
      ],
      "percentage_attribute": null,
      "version": 3,
      "updated_by": {},
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "dependents": {}
}
get/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/valuesAuth

List values in an environment

Every setting of the config with its value and targeting in one environment (the flag list), in `ordinal` order. `include=states` adds each setting's compact state in every environment.

Path parameters

config_id*string · uuid

Id of the config.

environment_id*string · uuid

Id of an environment of the same project.

workspace_id*string · uuid

Id of the workspace.

Query parameters

cursorstring

Opaque cursor for pagination: the `next_cursor` of the previous page.

limitinteger

Page size.

includeenum (1)

`states` adds each setting's compact state in every environment of the project.

Response · 200 A page of settings with their values.

data*array

Settings of the config with their value in this environment, in `ordinal` order.

pagination*object

The cursor for the next page.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/values
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "setting": {},
      "value": {},
      "states": {
        "development": {
          "value": true,
          "has_rules": false
        },
        "production": {
          "value": false,
          "has_rules": true
        }
      }
    }
  ],
  "pagination": {}
}
get/v1/workspaces/{workspace_id}/configs/{config_id}/compareAuth

Compare environments

Each setting's value and targeting side by side in 2-5 environments, with `differs` per setting.

Path parameters

config_id*string · uuid

Id of the config.

workspace_id*string · uuid

Id of the workspace.

Query parameters

cursorstring

Opaque cursor for pagination: the `next_cursor` of the previous page.

limitinteger

Page size.

environment_ids*string

Comma-separated environment ids of the config's project, 2-5 of them.

Response · 200 A page of settings compared.

environments*array

The compared environments, in request order.

data*array

One row per setting, in `ordinal` order.

pagination*object

The cursor for the next page.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/compare
Authorization: Bearer YOUR_TOKEN

Response

{
  "environments": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "key": "production",
      "name": "Production"
    }
  ],
  "data": [
    {
      "setting": {},
      "values": [
        {
          "setting_id": "00000000-0000-0000-0000-000000000000",
          "environment_id": "00000000-0000-0000-0000-000000000000",
          "environment_key": "production",
          "value": false,
          "rules": [
            null
          ],
          "percentages": [
            null
          ],
          "percentage_attribute": null,
          "version": 3,
          "updated_by": {},
          "updated_at": "2026-01-01T00:00:00.000Z"
        }
      ],
      "differs": true
    }
  ],
  "pagination": {}
}
get/v1/workspaces/{workspace_id}/settings/{setting_id}Auth

Get a setting

The setting with its value and targeting in every environment, and what depends on it (`dependents`).

Path parameters

setting_id*string · uuid

Id of the setting.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The setting.

id*string · uuid

The setting's id.

workspace_id*string · uuid

The workspace that owns it.

project_id*string · uuid

The project.

config_id*string · uuid

The config it belongs to.

key*string

The key SDKs read it by; unique in the config, case-sensitive.

Example: "new_checkout"

name*string

Display name.

Example: "New checkout"

description*string

Free-text description (may be empty).

Example: ""

hint*string

A short hint shown next to the flag in the console (may be empty).

Example: ""

type*object

`boolean` is a feature flag; the others are typed settings. Cannot change.

Example: "boolean"

tags*array

Free-form tags.

Example: ["checkout"]

lifecycle*object

`temporary` (a release flag you plan to remove) or `permanent` (an ops switch or a setting).

Example: "temporary"

owner_id*string · uuid

The platform user who owns the flag (a workspace member), or null.

seed*string

The value percentage splits hash with: the key at creation (or the ConfigCat key on import). It never changes, so renaming a key does not move users between variations.

Example: "new_checkout"

ordinal*integer

Sort position in the config (ascending).

Example: 0

created_by*string

Who created it (user or API key id).

created_at*string · date-time

When it was created.

updated_at*string · date-time

When its metadata last changed.

values*array

Its value and targeting in every environment, in environment order.

dependents*object

What depends on this setting: check before deleting or renaming it.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/settings/{setting_id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "key": "new_checkout",
  "name": "New checkout",
  "description": "",
  "hint": "",
  "type": "boolean",
  "tags": [
    "checkout"
  ],
  "lifecycle": "temporary",
  "owner_id": "00000000-0000-0000-0000-000000000000",
  "seed": "new_checkout",
  "ordinal": 0,
  "created_by": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "values": [
    {
      "setting_id": "00000000-0000-0000-0000-000000000000",
      "environment_id": "00000000-0000-0000-0000-000000000000",
      "environment_key": "production",
      "value": false,
      "rules": [
        {}
      ],
      "percentages": [
        {
          "percentage": 10,
          "value": true
        }
      ],
      "percentage_attribute": null,
      "version": 3,
      "updated_by": {},
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "dependents": {}
}
patch/v1/workspaces/{workspace_id}/settings/{setting_id}Auth

Update a setting

Key, name, description, hint, tags, lifecycle, owner or order; only the fields you send change. The type cannot change. Renaming the key keeps the seed (users stay in their split buckets) and needs `flags.protected.update` when the project has a protected environment.

Path parameters

setting_id*string · uuid

Id of the setting.

workspace_id*string · uuid

Id of the workspace.

Request body

keystring

New key; must start with a letter and contain only letters, digits, "_", "." and "-", at most 128 characters. SDK code reading the old key gets its default, so check `dependents` and your code first. Needs `flags.protected.update` when the project has a protected environment. The seed does not change.

Example: "new_checkout_v2"

namestring

Display name, 1-200 characters.

descriptionstring

Free-text description, at most 1000 characters.

hintstring

Short hint, at most 500 characters.

tagsarray

Replaces the tags: at most 20, each 1-50 characters.

lifecycleobject

`temporary` or `permanent`.

owner_idstring · uuid

A workspace member, or null to clear.

ordinalinteger

Sort position.

typeobject

Cannot change: sending a type other than the current one answers 422 `SETTING_TYPE_IMMUTABLE`.

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Example: "Ticket FLAG-142"

Response · 200 The updated setting.

id*string · uuid

The setting's id.

workspace_id*string · uuid

The workspace that owns it.

project_id*string · uuid

The project.

config_id*string · uuid

The config it belongs to.

key*string

The key SDKs read it by; unique in the config, case-sensitive.

Example: "new_checkout"

name*string

Display name.

Example: "New checkout"

description*string

Free-text description (may be empty).

Example: ""

hint*string

A short hint shown next to the flag in the console (may be empty).

Example: ""

type*object

`boolean` is a feature flag; the others are typed settings. Cannot change.

Example: "boolean"

tags*array

Free-form tags.

Example: ["checkout"]

lifecycle*object

`temporary` (a release flag you plan to remove) or `permanent` (an ops switch or a setting).

Example: "temporary"

owner_id*string · uuid

The platform user who owns the flag (a workspace member), or null.

seed*string

The value percentage splits hash with: the key at creation (or the ConfigCat key on import). It never changes, so renaming a key does not move users between variations.

Example: "new_checkout"

ordinal*integer

Sort position in the config (ascending).

Example: 0

created_by*string

Who created it (user or API key id).

created_at*string · date-time

When it was created.

updated_at*string · date-time

When its metadata last changed.

values*array

Its value and targeting in every environment, in environment order.

dependents*object

What depends on this setting: check before deleting or renaming it.

Example

Request

PATCH https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/settings/{setting_id}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "key": "new_checkout_v2",
  "name": "string",
  "description": "string",
  "hint": "string",
  "tags": [
    "string"
  ],
  "lifecycle": {},
  "owner_id": "00000000-0000-0000-0000-000000000000",
  "ordinal": 0,
  "type": {},
  "note": "Ticket FLAG-142"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "workspace_id": "00000000-0000-0000-0000-000000000000",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "key": "new_checkout",
  "name": "New checkout",
  "description": "",
  "hint": "",
  "type": "boolean",
  "tags": [
    "checkout"
  ],
  "lifecycle": "temporary",
  "owner_id": "00000000-0000-0000-0000-000000000000",
  "seed": "new_checkout",
  "ordinal": 0,
  "created_by": "string",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "values": [
    {
      "setting_id": "00000000-0000-0000-0000-000000000000",
      "environment_id": "00000000-0000-0000-0000-000000000000",
      "environment_key": "production",
      "value": false,
      "rules": [
        {}
      ],
      "percentages": [
        {
          "percentage": 10,
          "value": true
        }
      ],
      "percentage_attribute": null,
      "version": 3,
      "updated_by": {},
      "updated_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "dependents": {}
}
delete/v1/workspaces/{workspace_id}/settings/{setting_id}Auth

Delete a setting

Deletes the setting and its values in every environment. Needs `flags.delete`.

Path parameters

setting_id*string · uuid

Id of the setting.

workspace_id*string · uuid

Id of the workspace.

Query parameters

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Response · 204

Deleted.

Example

Request

DELETE https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/settings/{setting_id}
Authorization: Bearer YOUR_TOKEN
get/v1/workspaces/{workspace_id}/settings/{setting_id}/values/{environment_id}Auth

Get a value

The setting's value and targeting in one environment, with its `version`.

Path parameters

setting_id*string · uuid

Id of the setting.

environment_id*string · uuid

Id of an environment of the same project.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The value.

setting_id*string · uuid

The setting.

environment_id*string · uuid

The environment.

environment_key*string

The environment's key.

Example: "production"

value*object

Served when no rule and no split decides (and to everyone when there are none).

Example: false

rules*array

Targeting rules, first match wins (at most 50). Each rule has `conditions` and exactly one of `serve` (a value) or `percentages` (a split).

percentages*array

Flag-level split after the rules (null for none).

percentage_attribute*string

The user attribute the splits hash; null means `identifier`.

Example: null

version*integer

Send it back with the next write; a stale one answers 409 `VERSION_CONFLICT`.

Example: 3

updated_by*object

Who wrote the value last.

updated_at*string · date-time

When the value last changed.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/settings/{setting_id}/values/{environment_id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "setting_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000",
  "environment_key": "production",
  "value": false,
  "rules": [
    {}
  ],
  "percentages": [
    {
      "percentage": 10,
      "value": true
    }
  ],
  "percentage_attribute": null,
  "version": 3,
  "updated_by": {},
  "updated_at": "2026-01-01T00:00:00.000Z"
}
put/v1/workspaces/{workspace_id}/settings/{setting_id}/values/{environment_id}Auth

Replace a value

Replaces the value and the whole targeting (rules, flag-level split, split attribute) in one environment. Send the `version` you read.

Path parameters

setting_id*string · uuid

Id of the setting.

environment_id*string · uuid

Id of an environment of the same project.

workspace_id*string · uuid

Id of the workspace.

Request body

version*integer

The `version` you read; 409 `VERSION_CONFLICT` when it moved.

Example: 3

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Example: "Ticket FLAG-142"

value*object

Served when no rule and no split decides. Must match the setting type: `boolean` true/false; `string` a string; `integer` a safe integer; `number` a finite number; `json` a string holding JSON text (for example `"{\"limit\":5}"`). At most 100 KB. Strings are stored and returned byte for byte.

Example: false

rulesarray

Targeting rules, first match wins (at most 50). Each rule has `conditions` and exactly one of `serve` (a value) or `percentages` (a split). Omitted = none.

percentagesarray

Flag-level split after the rules; null or omitted for none.

percentage_attributestring

The user attribute splits hash (1-256 characters, returned exactly as sent); null or omitted for `identifier`.

Example: null

Response · 200 The new value.

setting_id*string · uuid

The setting.

environment_id*string · uuid

The environment.

environment_key*string

The environment's key.

Example: "production"

value*object

Served when no rule and no split decides (and to everyone when there are none).

Example: false

rules*array

Targeting rules, first match wins (at most 50). Each rule has `conditions` and exactly one of `serve` (a value) or `percentages` (a split).

percentages*array

Flag-level split after the rules (null for none).

percentage_attribute*string

The user attribute the splits hash; null means `identifier`.

Example: null

version*integer

Send it back with the next write; a stale one answers 409 `VERSION_CONFLICT`.

Example: 3

updated_by*object

Who wrote the value last.

updated_at*string · date-time

When the value last changed.

Example

Request

PUT https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/settings/{setting_id}/values/{environment_id}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "version": 3,
  "note": "Ticket FLAG-142",
  "value": false,
  "rules": [
    {}
  ],
  "percentages": [
    {
      "percentage": 10,
      "value": true
    }
  ],
  "percentage_attribute": null
}

Response

{
  "setting_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000",
  "environment_key": "production",
  "value": false,
  "rules": [
    {}
  ],
  "percentages": [
    {
      "percentage": 10,
      "value": true
    }
  ],
  "percentage_attribute": null,
  "version": 3,
  "updated_by": {},
  "updated_at": "2026-01-01T00:00:00.000Z"
}
patch/v1/workspaces/{workspace_id}/settings/{setting_id}/values/{environment_id}Auth

Update a value

The quick edits: `value` (the toggle), `percentage_attribute`, and `clear_rules: true` to drop every rule and split so `value` is served to everyone (the kill switch). Send the `version` you read.

Path parameters

setting_id*string · uuid

Id of the setting.

environment_id*string · uuid

Id of an environment of the same project.

workspace_id*string · uuid

Id of the workspace.

Request body

version*integer

The `version` you read; 409 `VERSION_CONFLICT` when it moved.

Example: 3

notestring

A change note (at most 500 characters), saved on the history entry. Environments with `require_note` refuse value writes without one.

Example: "Ticket FLAG-142"

valueobject

The new plain value (the on/off toggle for a flag). Must match the setting type: `boolean` true/false; `string` a string; `integer` a safe integer; `number` a finite number; `json` a string holding JSON text (for example `"{\"limit\":5}"`). At most 100 KB. Strings are stored and returned byte for byte.

Example: true

percentage_attributestring

The attribute splits hash; null for `identifier`.

clear_rulesboolean

Remove every rule and the flag-level split, so `value` is served to everyone (the kill switch). Restore the targeting from the history if needed.

Example: false

Response · 200 The new value.

setting_id*string · uuid

The setting.

environment_id*string · uuid

The environment.

environment_key*string

The environment's key.

Example: "production"

value*object

Served when no rule and no split decides (and to everyone when there are none).

Example: false

rules*array

Targeting rules, first match wins (at most 50). Each rule has `conditions` and exactly one of `serve` (a value) or `percentages` (a split).

percentages*array

Flag-level split after the rules (null for none).

percentage_attribute*string

The user attribute the splits hash; null means `identifier`.

Example: null

version*integer

Send it back with the next write; a stale one answers 409 `VERSION_CONFLICT`.

Example: 3

updated_by*object

Who wrote the value last.

updated_at*string · date-time

When the value last changed.

Example

Request

PATCH https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/settings/{setting_id}/values/{environment_id}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "version": 3,
  "note": "Ticket FLAG-142",
  "value": true,
  "percentage_attribute": "string",
  "clear_rules": false
}

Response

{
  "setting_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000",
  "environment_key": "production",
  "value": false,
  "rules": [
    {}
  ],
  "percentages": [
    {
      "percentage": 10,
      "value": true
    }
  ],
  "percentage_attribute": null,
  "version": 3,
  "updated_by": {},
  "updated_at": "2026-01-01T00:00:00.000Z"
}
get/v1/workspaces/{workspace_id}/settings/{setting_id}/values/{environment_id}/confidentialAuth

Reveal confidential values

The cleartext of this value's confidential lists, decrypted. Needs `flags.confidential.read` on the environment and the setting; every call is recorded in the history (`confidential.revealed`). Never cached.

Path parameters

setting_id*string · uuid

Id of the setting.

environment_id*string · uuid

Id of an environment of the same project.

workspace_id*string · uuid

Id of the workspace.

Response · 200 The confidential lists, in rule and condition order.

data*array

Every confidential list, in rule and condition order.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/settings/{setting_id}/values/{environment_id}/confidential
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "rule_index": 0,
      "condition_index": 0,
      "attribute": "email",
      "comparator": "ends_with_any_hashed",
      "list_id": "00000000-0000-0000-0000-000000000000",
      "values": [
        "@acme.test"
      ],
      "revealable": true
    }
  ]
}
post/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/copyAuth

Copy targeting between environments

Copies value and targeting (rules, splits, split attribute) of `setting_ids`, or of every setting that differs (`all_differing: true`), from `source_environment_id` into this environment. All or nothing: send the target `version` of every setting that will be copied in `versions`; if any moved, nothing is written and 409 lists them. Publishes one config revision, with one history entry per setting. Needs `flags.value.update` on this environment and each setting, plus `flags.protected.update` when it is protected.

Path parameters

config_id*string · uuid

Id of the config.

environment_id*string · uuid

Id of the target environment (same project as the config).

workspace_id*string · uuid

Id of the workspace.

Headers

Idempotency-Keystring

Idempotency key (1-256 characters, `[A-Za-z0-9_-:]`). A retry with the same key and body returns the first response for 24 h.

Request body

source_environment_id*string · uuid

The environment to copy targeting from (same project).

setting_idsarray

The settings to copy (at most 500). Send this or `all_differing: true`.

all_differingboolean

Copy every setting whose value or targeting differs between the two environments. Send this or `setting_ids`.

Example: true

versions*object

The target environment's current `version` of every setting that will be copied, by setting id. If any is stale or missing, nothing is written and 409 `VERSION_CONFLICT` lists every one (`details.conflicts`).

Example: {"0b8f8a2c-4d3e-4f5a-8b6c-7d8e9f0a1b2c":3}

notestring

A change note (at most 500 characters), saved on the new history entry. Environments with `require_note` refuse value writes without one.

Example: "Roll back FLAG-142"

Response · 200 What was copied.

data*array

The settings whose targeting was copied (empty when nothing differed).

revision*integer

The config revision the copy published in the target environment (null when nothing changed).

Example: 12

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/configs/{config_id}/environments/{environment_id}/copy
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "source_environment_id": "00000000-0000-0000-0000-000000000000",
  "setting_ids": [
    "00000000-0000-0000-0000-000000000000"
  ],
  "all_differing": true,
  "versions": {
    "0b8f8a2c-4d3e-4f5a-8b6c-7d8e9f0a1b2c": 3
  },
  "note": "Roll back FLAG-142"
}

Response

{
  "data": [
    {
      "setting_id": "00000000-0000-0000-0000-000000000000",
      "key": "new_checkout",
      "version": 4,
      "change_id": "00000000-0000-0000-0000-000000000000"
    }
  ],
  "revision": 12
}

Stats

Workspace counts for dashboards: projects, configs, flags and settings, segments, recent changes, and history usage per project against the retention caps.

get/v1/workspaces/{workspace_id}/statsAuth

Get Flags stats

Counts for the workspace: projects, configs, flags and settings, segments, changes in the last 7 days and when the latest change was made, plus each project’s history usage against the retention caps (50,000 rows or 200 MiB per project; `warning` from 80%). Counts only: no keys, values or rules.

Path parameters

workspace_id*string · uuid

Id of the workspace.

Response · 200 The stats.

projects*integer

Projects in the workspace.

Example: 1

configs*integer

Configs across all projects.

Example: 2

settings*integer

Feature flags and settings across all configs.

Example: 37

segments*integer

Segments across all projects.

Example: 4

changes_last_week*integer

Changes made by people and API keys in the last 7 days (system housekeeping such as snapshot pruning is not counted).

Example: 12

last_change_at*string · date-time

When the latest change by a person or API key was made; null when there is none.

Example: "2026-11-10T09:30:00.000Z"

history_usage*array

History usage per project against the retention caps.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/stats
Authorization: Bearer YOUR_TOKEN

Response

{
  "projects": 1,
  "configs": 2,
  "settings": 37,
  "segments": 4,
  "changes_last_week": 12,
  "last_change_at": "2026-11-10T09:30:00.000Z",
  "history_usage": [
    {
      "project_id": "3f9a1c52-7b1e-4d4a-9f0e-2a6c8b1d5e70",
      "name": "Storefront",
      "rows": 1240,
      "bytes": 524288,
      "max_rows": 50000,
      "max_bytes": 209715200,
      "warning": false
    }
  ]
}

Webhooks

Signed webhooks (Standard Webhooks) for `config.published` (a new config revision in an environment) and `sdk_key.rotated`, with endpoints filtered by project, config or environment, an event log, retries, Send test and replay.

get/v1/workspaces/{workspace_id}/webhooks/event-typesAuth

List the events an endpoint can subscribe to.

Each type with a plain description, its group and an example `data` object (what "Send test" delivers).

Path parameters

workspace_id*string · uuid

Id of the workspace.

Response · 200

data*array

Every event type this product sends.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/webhooks/event-types
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "type": "config.published",
      "description": "A user signed up or was created by an admin.",
      "category": "Users",
      "example": {}
    }
  ]
}
get/v1/workspaces/{workspace_id}/webhooks/endpointsAuth

List webhook endpoints, oldest first.

At most 10, so this is always one page. Signing secrets are never returned here, only their last 4 characters.

Path parameters

workspace_id*string · uuid

Id of the workspace.

Response · 200

data*array

The endpoints, newest first.

pagination*object

Always one page (an endpoint limit applies).

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/webhooks/endpoints
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "scope_type": "workspace",
      "scope_id": "string",
      "url": "https://api.acme.com/webhooks",
      "description": "Production receiver",
      "event_types": [],
      "enabled": false,
      "status": {},
      "disabled_at": "2026-01-01T00:00:00.000Z",
      "disabled_reason": {},
      "consecutive_failures": 0,
      "first_failure_at": "2026-01-01T00:00:00.000Z",
      "signing_secret_hint": "x9Q=",
      "prev_signing_secret_active": false,
      "prev_signing_secret_expires_at": "2026-01-01T00:00:00.000Z",
      "legacy_signature": false,
      "created_at": "2026-01-01T00:00:00.000Z",
      "updated_at": "2026-01-01T00:00:00.000Z",
      "project_id": "00000000-0000-0000-0000-000000000000",
      "config_id": "00000000-0000-0000-0000-000000000000",
      "environment_id": "00000000-0000-0000-0000-000000000000"
    }
  ],
  "pagination": {}
}
post/v1/workspaces/{workspace_id}/webhooks/endpointsAuth

Add a webhook endpoint. Returns its signing secret once.

Deliveries are signed with the Standard Webhooks headers `webhook-id`, `webhook-timestamp` and `webhook-signature` (`v1,<base64 HMAC-SHA256 of "<id>.<timestamp>.<body>">`); the returned `whsec_…` secret works unchanged with any Standard Webhooks library. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h, 6 h and 24 h.

Path parameters

workspace_id*string · uuid

Id of the workspace.

Headers

Idempotency-Keystring

Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.

Request body

url*string · uri

Where to send events: an absolute https:// URL on a public host.

Example: "https://api.acme.com/webhooks"

descriptionstring

A label for people.

Example: "Production receiver"

event_typesarray

Event types from `GET …/webhooks/event-types`. Empty or omitted means every event, including types added later.

Example: ["config.published","sdk_key.rotated"]

enabledboolean

Send `false` to create it switched off.

Example: true

project_idstring · uuid

Only events of this project. Omit or null for every project.

config_idstring · uuid

Only events of this config (of `project_id`, when given).

environment_idstring · uuid

Only events of this environment, e.g. Production (of `project_id`, when given).

Response · 201

id*string · uuid

The endpoint's id.

scope_type*string

What owns the endpoint: `workspace`, `app` or `community`.

Example: "workspace"

scope_id*string

Id of the owning workspace, app or community.

url*string · uri

Where events are sent.

Example: "https://api.acme.com/webhooks"

description*string

A label for people; null when unset.

Example: "Production receiver"

event_types*array

Subscribed event types. Empty means every event, including types added later.

Example: []

enabled*boolean

The on/off switch (`PATCH … { enabled }`).

status*object

`enabled` receives events. `disabled` was switched off. `auto_disabled` was switched off after 20 failed deliveries in a row or 24 hours of failures; `PATCH { enabled: true }` turns it back on.

disabled_at*string · date-time

When it was switched off; null while enabled.

disabled_reason*object

`manual`: switched off by a caller. `failing`: switched off automatically after failed deliveries. Null while enabled.

consecutive_failures*number

Failed automatic deliveries in a row; a success resets it.

first_failure_at*string · date-time

Start of the current failure streak.

signing_secret_hint*string

Last 4 characters of the signing secret.

Example: "x9Q="

prev_signing_secret_active*boolean

True while the previous secret still signs too (after a rotation, for its grace period).

prev_signing_secret_expires_at*string · date-time

Until then the previous secret signs too (after a rotation).

legacy_signature*boolean

True for endpoints carried over from the product's previous webhook system: they also receive the old signature header until its sunset date.

created_at*string · date-time

When the endpoint was created.

updated_at*string · date-time

When the endpoint last changed.

project_id*string · uuid

Only events of this project; null for every project.

config_id*string · uuid

Only events of this config; null for every config.

environment_id*string · uuid

Only events of this environment; null for every environment.

signing_secret*string

The signing secret, shown once: pass it unchanged to any Standard Webhooks verifier. Store it now; it cannot be shown again.

Example: "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/webhooks/endpoints
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "url": "https://api.acme.com/webhooks",
  "description": "Production receiver",
  "event_types": [
    "config.published",
    "sdk_key.rotated"
  ],
  "enabled": true,
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "scope_type": "workspace",
  "scope_id": "string",
  "url": "https://api.acme.com/webhooks",
  "description": "Production receiver",
  "event_types": [],
  "enabled": false,
  "status": {},
  "disabled_at": "2026-01-01T00:00:00.000Z",
  "disabled_reason": {},
  "consecutive_failures": 0,
  "first_failure_at": "2026-01-01T00:00:00.000Z",
  "signing_secret_hint": "x9Q=",
  "prev_signing_secret_active": false,
  "prev_signing_secret_expires_at": "2026-01-01T00:00:00.000Z",
  "legacy_signature": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000",
  "signing_secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
}
get/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}Auth

Get one webhook endpoint (without its secret).

The endpoint's URL, subscription, on/off state, failure streak and the last 4 characters of its signing secret. The full secret is only returned when the endpoint is created or its secret is rotated.

Path parameters

workspace_id*string · uuid

Id of the workspace.

endpoint_id*string · uuid

Id of the endpoint.

Response · 200

id*string · uuid

The endpoint's id.

scope_type*string

What owns the endpoint: `workspace`, `app` or `community`.

Example: "workspace"

scope_id*string

Id of the owning workspace, app or community.

url*string · uri

Where events are sent.

Example: "https://api.acme.com/webhooks"

description*string

A label for people; null when unset.

Example: "Production receiver"

event_types*array

Subscribed event types. Empty means every event, including types added later.

Example: []

enabled*boolean

The on/off switch (`PATCH … { enabled }`).

status*object

`enabled` receives events. `disabled` was switched off. `auto_disabled` was switched off after 20 failed deliveries in a row or 24 hours of failures; `PATCH { enabled: true }` turns it back on.

disabled_at*string · date-time

When it was switched off; null while enabled.

disabled_reason*object

`manual`: switched off by a caller. `failing`: switched off automatically after failed deliveries. Null while enabled.

consecutive_failures*number

Failed automatic deliveries in a row; a success resets it.

first_failure_at*string · date-time

Start of the current failure streak.

signing_secret_hint*string

Last 4 characters of the signing secret.

Example: "x9Q="

prev_signing_secret_active*boolean

True while the previous secret still signs too (after a rotation, for its grace period).

prev_signing_secret_expires_at*string · date-time

Until then the previous secret signs too (after a rotation).

legacy_signature*boolean

True for endpoints carried over from the product's previous webhook system: they also receive the old signature header until its sunset date.

created_at*string · date-time

When the endpoint was created.

updated_at*string · date-time

When the endpoint last changed.

project_id*string · uuid

Only events of this project; null for every project.

config_id*string · uuid

Only events of this config; null for every config.

environment_id*string · uuid

Only events of this environment; null for every environment.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "scope_type": "workspace",
  "scope_id": "string",
  "url": "https://api.acme.com/webhooks",
  "description": "Production receiver",
  "event_types": [],
  "enabled": false,
  "status": {},
  "disabled_at": "2026-01-01T00:00:00.000Z",
  "disabled_reason": {},
  "consecutive_failures": 0,
  "first_failure_at": "2026-01-01T00:00:00.000Z",
  "signing_secret_hint": "x9Q=",
  "prev_signing_secret_active": false,
  "prev_signing_secret_expires_at": "2026-01-01T00:00:00.000Z",
  "legacy_signature": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000"
}
patch/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}Auth

Change a webhook endpoint: URL, description, events, on/off.

Only the fields sent change. `enabled: true` (or a new URL) also clears an automatic disable. Switching off cancels queued retries. The secret is unchanged; rotate it with `POST …/rotate-secret`.

Path parameters

workspace_id*string · uuid

Id of the workspace.

endpoint_id*string · uuid

Id of the endpoint.

Headers

Idempotency-Keystring

Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.

Request body

urlstring · uri

A new absolute https:// URL on a public host.

Example: "https://api.acme.com/webhooks"

descriptionstring

A new label; null clears it.

Example: "Production receiver"

event_typesarray

Replaces the subscription. Event types from `GET …/webhooks/event-types`. Empty or omitted means every event, including types added later.

Example: ["config.published","sdk_key.rotated"]

enabledboolean

Switch it on or off. Switching on also clears an automatic disable.

Example: true

project_idstring · uuid

Project filter; null removes it.

config_idstring · uuid

Config filter; null removes it.

environment_idstring · uuid

Environment filter; null removes it.

Response · 200

id*string · uuid

The endpoint's id.

scope_type*string

What owns the endpoint: `workspace`, `app` or `community`.

Example: "workspace"

scope_id*string

Id of the owning workspace, app or community.

url*string · uri

Where events are sent.

Example: "https://api.acme.com/webhooks"

description*string

A label for people; null when unset.

Example: "Production receiver"

event_types*array

Subscribed event types. Empty means every event, including types added later.

Example: []

enabled*boolean

The on/off switch (`PATCH … { enabled }`).

status*object

`enabled` receives events. `disabled` was switched off. `auto_disabled` was switched off after 20 failed deliveries in a row or 24 hours of failures; `PATCH { enabled: true }` turns it back on.

disabled_at*string · date-time

When it was switched off; null while enabled.

disabled_reason*object

`manual`: switched off by a caller. `failing`: switched off automatically after failed deliveries. Null while enabled.

consecutive_failures*number

Failed automatic deliveries in a row; a success resets it.

first_failure_at*string · date-time

Start of the current failure streak.

signing_secret_hint*string

Last 4 characters of the signing secret.

Example: "x9Q="

prev_signing_secret_active*boolean

True while the previous secret still signs too (after a rotation, for its grace period).

prev_signing_secret_expires_at*string · date-time

Until then the previous secret signs too (after a rotation).

legacy_signature*boolean

True for endpoints carried over from the product's previous webhook system: they also receive the old signature header until its sunset date.

created_at*string · date-time

When the endpoint was created.

updated_at*string · date-time

When the endpoint last changed.

project_id*string · uuid

Only events of this project; null for every project.

config_id*string · uuid

Only events of this config; null for every config.

environment_id*string · uuid

Only events of this environment; null for every environment.

Example

Request

PATCH https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "url": "https://api.acme.com/webhooks",
  "description": "Production receiver",
  "event_types": [
    "config.published",
    "sdk_key.rotated"
  ],
  "enabled": true,
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "scope_type": "workspace",
  "scope_id": "string",
  "url": "https://api.acme.com/webhooks",
  "description": "Production receiver",
  "event_types": [],
  "enabled": false,
  "status": {},
  "disabled_at": "2026-01-01T00:00:00.000Z",
  "disabled_reason": {},
  "consecutive_failures": 0,
  "first_failure_at": "2026-01-01T00:00:00.000Z",
  "signing_secret_hint": "x9Q=",
  "prev_signing_secret_active": false,
  "prev_signing_secret_expires_at": "2026-01-01T00:00:00.000Z",
  "legacy_signature": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000"
}
delete/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}Auth

Delete a webhook endpoint.

Queued retries are dropped. Its past attempts stay in the event log until the events expire.

Path parameters

workspace_id*string · uuid

Id of the workspace.

endpoint_id*string · uuid

Id of the endpoint.

Response · 204

Deleted.

Example

Request

DELETE https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}
Authorization: Bearer YOUR_TOKEN
post/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}/rotate-secretAuth

Rotate the signing secret. The new one is returned once.

For `grace_hours` (default 24) the previous secret keeps signing too: `webhook-signature` then lists one `v1,` signature per secret, so receivers can switch over without dropping a delivery.

Path parameters

workspace_id*string · uuid

Id of the workspace.

endpoint_id*string · uuid

Id of the endpoint.

Headers

Idempotency-Keystring

Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.

Request body

grace_hoursnumber

Hours the previous secret keeps signing too (0 ends it now).

Example: 24

Response · 200

id*string · uuid

The endpoint's id.

scope_type*string

What owns the endpoint: `workspace`, `app` or `community`.

Example: "workspace"

scope_id*string

Id of the owning workspace, app or community.

url*string · uri

Where events are sent.

Example: "https://api.acme.com/webhooks"

description*string

A label for people; null when unset.

Example: "Production receiver"

event_types*array

Subscribed event types. Empty means every event, including types added later.

Example: []

enabled*boolean

The on/off switch (`PATCH … { enabled }`).

status*object

`enabled` receives events. `disabled` was switched off. `auto_disabled` was switched off after 20 failed deliveries in a row or 24 hours of failures; `PATCH { enabled: true }` turns it back on.

disabled_at*string · date-time

When it was switched off; null while enabled.

disabled_reason*object

`manual`: switched off by a caller. `failing`: switched off automatically after failed deliveries. Null while enabled.

consecutive_failures*number

Failed automatic deliveries in a row; a success resets it.

first_failure_at*string · date-time

Start of the current failure streak.

signing_secret_hint*string

Last 4 characters of the signing secret.

Example: "x9Q="

prev_signing_secret_active*boolean

True while the previous secret still signs too (after a rotation, for its grace period).

prev_signing_secret_expires_at*string · date-time

Until then the previous secret signs too (after a rotation).

legacy_signature*boolean

True for endpoints carried over from the product's previous webhook system: they also receive the old signature header until its sunset date.

created_at*string · date-time

When the endpoint was created.

updated_at*string · date-time

When the endpoint last changed.

project_id*string · uuid

Only events of this project; null for every project.

config_id*string · uuid

Only events of this config; null for every config.

environment_id*string · uuid

Only events of this environment; null for every environment.

signing_secret*string

The signing secret, shown once: pass it unchanged to any Standard Webhooks verifier. Store it now; it cannot be shown again.

Example: "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}/rotate-secret
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "grace_hours": 24
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "scope_type": "workspace",
  "scope_id": "string",
  "url": "https://api.acme.com/webhooks",
  "description": "Production receiver",
  "event_types": [],
  "enabled": false,
  "status": {},
  "disabled_at": "2026-01-01T00:00:00.000Z",
  "disabled_reason": {},
  "consecutive_failures": 0,
  "first_failure_at": "2026-01-01T00:00:00.000Z",
  "signing_secret_hint": "x9Q=",
  "prev_signing_secret_active": false,
  "prev_signing_secret_expires_at": "2026-01-01T00:00:00.000Z",
  "legacy_signature": false,
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000",
  "signing_secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
}
post/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}/testAuth

Send a test event to this endpoint now and return the result.

Delivers the catalogue example of `event_type`, signed like any delivery, with `"test": true` in the body. Works while the endpoint is switched off or not subscribed to that type, is logged in the event log, and is never retried or counted toward an automatic disable. Waits up to 15 s for your receiver.

Path parameters

workspace_id*string · uuid

Id of the workspace.

endpoint_id*string · uuid

Id of the endpoint.

Headers

Idempotency-Keystring

Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.

Request body

event_type*enum (2)

Which event to send; its catalogue example is the `data`.

Example: "config.published"

Response · 200 The attempt, successful or not.

id*string · uuid

The attempt's id.

event_id*string

The event that was delivered.

Example: "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f"

endpoint_id*string · uuid

The endpoint it was delivered to.

event_type*string

The event's type.

Example: "user.created"

attempt_number*number

1 for the first delivery; retries and replays count up.

trigger*object

`automatic` (first delivery or a retry), `replay` or `test`.

url*string · uri

Where it was sent.

status_code*number

HTTP status; null when no response came back.

succeeded*boolean

True for a 2xx response.

error_message*string

Why the attempt failed when no 2xx came back; null on success.

Example: "request timed out"

response_body*string

The first 1 KiB of the response body.

latency_ms*number

Milliseconds until the response; null when none came back.

attempted_at*string · date-time

When the attempt was made.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/webhooks/endpoints/{endpoint_id}/test
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "event_type": "config.published"
}

Response

{
  "id": "00000000-0000-0000-0000-000000000000",
  "event_id": "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f",
  "endpoint_id": "00000000-0000-0000-0000-000000000000",
  "event_type": "user.created",
  "attempt_number": 0,
  "trigger": {},
  "url": "string",
  "status_code": 0,
  "succeeded": false,
  "error_message": "request timed out",
  "response_body": "string",
  "latency_ms": 0,
  "attempted_at": "2026-01-01T00:00:00.000Z"
}
get/v1/workspaces/{workspace_id}/webhooks/eventsAuth

List webhook events, newest first (kept for 30 days).

Every event logged while at least one endpoint existed, with the latest state per endpoint and when the next retry is due.

Path parameters

workspace_id*string · uuid

Id of the workspace.

Query parameters

typearray

Only these types. Repeat the parameter or comma-separate.

endpoint_idstring · uuid

Only events sent (or queued) to this endpoint.

statusenum (4)

Only events in this delivery status.

created_afterstring · date-time

Only events created at or after this instant.

created_beforestring · date-time

Only events created before this instant.

limitinteger

Page size.

cursorstring

The previous page's `pagination.next_cursor`.

project_idstring · uuid

Only events of this project.

config_idstring · uuid

Only events of this config.

environment_idstring · uuid

Only events of this environment.

Response · 200

data*array

The events, newest first.

pagination*object

The cursor for the next page.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/webhooks/events
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f",
      "type": "config.published",
      "created_at": "2026-01-01T00:00:00.000Z",
      "test": false,
      "status": {},
      "payload": {},
      "deliveries": [
        {
          "endpoint_id": "00000000-0000-0000-0000-000000000000",
          "attempt_number": 0,
          "status_code": 0,
          "succeeded": false,
          "attempted_at": "2026-01-01T00:00:00.000Z",
          "next_attempt_at": "2026-01-01T00:00:00.000Z"
        }
      ],
      "project_id": "00000000-0000-0000-0000-000000000000",
      "config_id": "00000000-0000-0000-0000-000000000000",
      "environment_id": "00000000-0000-0000-0000-000000000000"
    }
  ],
  "pagination": {}
}
get/v1/workspaces/{workspace_id}/webhooks/events/{event_id}Auth

Get one webhook event with every delivery attempt.

The event as it was sent (`type`, `data`) and every delivery attempt to every endpoint, with its trigger, HTTP status, error and the start of the response body. Use it to debug a receiver, then `replay` to send it again.

Path parameters

workspace_id*string · uuid

Id of the workspace.

event_id*string

The event id, `evt_<uuid>`.

Response · 200

id*string

Sent as `webhook-id`; the same on every retry and replay.

Example: "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f"

type*object

The event type (one of this product's catalogue).

Example: "config.published"

created_at*string · date-time

When the event happened.

test*boolean

Made by "Send test".

status*object

`pending`: a delivery is queued. `failed`: the latest attempt to some endpoint failed and none is queued. `delivered`: every endpoint answered 2xx. `not_sent`: no endpoint subscribed.

payload*object

The JSON body exactly as sent.

deliveries*array

Latest attempt per endpoint (an endpoint whose first attempt is still queued is not listed yet).

project_id*string · uuid

The project the event is about.

config_id*string · uuid

The config the event is about.

environment_id*string · uuid

The environment the event is about.

attempts*array

Every attempt, newest first.

Example

Request

GET https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/webhooks/events/{event_id}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f",
  "type": "config.published",
  "created_at": "2026-01-01T00:00:00.000Z",
  "test": false,
  "status": {},
  "payload": {},
  "deliveries": [
    {
      "endpoint_id": "00000000-0000-0000-0000-000000000000",
      "attempt_number": 0,
      "status_code": 0,
      "succeeded": false,
      "attempted_at": "2026-01-01T00:00:00.000Z",
      "next_attempt_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "project_id": "00000000-0000-0000-0000-000000000000",
  "config_id": "00000000-0000-0000-0000-000000000000",
  "environment_id": "00000000-0000-0000-0000-000000000000",
  "attempts": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "event_id": "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f",
      "endpoint_id": "00000000-0000-0000-0000-000000000000",
      "event_type": "user.created",
      "attempt_number": 0,
      "trigger": {},
      "url": "string",
      "status_code": 0,
      "succeeded": false,
      "error_message": "request timed out",
      "response_body": "string",
      "latency_ms": 0,
      "attempted_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}
post/v1/workspaces/{workspace_id}/webhooks/events/{event_id}/replayAuth

Send a logged event again, to its endpoints or to one endpoint.

Same body and `webhook-id`, freshly signed, to every switched-on endpoint that receives this event type now, or only to `endpoint_id`. Waits for the receivers (up to 15 s each) and returns the attempts. A success cancels a retry still queued for that endpoint.

Path parameters

workspace_id*string · uuid

Id of the workspace.

event_id*string

The event id, `evt_<uuid>`.

Query parameters

endpoint_idstring · uuid

Send to this endpoint only.

Headers

Idempotency-Keystring

Opaque client-generated string (1-256 chars, `[A-Za-z0-9_-:]`) that makes a retry of this request safe for 24 hours. The same key with a different body returns 409 `IDEMPOTENCY_KEY_REUSE`.

Response · 200

event_id*string

The replayed event (the same id as the original).

attempts*array

One attempt per endpoint it was sent to.

Example

Request

POST https://api.flags.productcraft.co/v1/workspaces/{workspace_id}/webhooks/events/{event_id}/replay
Authorization: Bearer YOUR_TOKEN

Response

{
  "event_id": "string",
  "attempts": [
    {
      "id": "00000000-0000-0000-0000-000000000000",
      "event_id": "evt_3f0c3c1e-8a2b-4c55-9d3f-0a1b2c3d4e5f",
      "endpoint_id": "00000000-0000-0000-0000-000000000000",
      "event_type": "user.created",
      "attempt_number": 0,
      "trigger": {},
      "url": "string",
      "status_code": 0,
      "succeeded": false,
      "error_message": "request timed out",
      "response_body": "string",
      "latency_ms": 0,
      "attempted_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}