---
title: Flagpole API reference
description: Endpoints, parameters, and error codes for the Flagpole feature-flag REST API.
url: https://pr-11-c0fdf0ef9f59.thally.app/guides/flagpole-api
---

# Flagpole API reference

Endpoints, parameters, and error codes for the Flagpole feature-flag REST API.

## Base URL and authentication

All request and response bodies are JSON. `GET /health` is always public; every `/v1` route requires `Authorization: Bearer <token>` when the server is started with an API token.

## Endpoints

| Method   | Path                       | Description                                            | Body / params                                                                                                                                                                 | Success                                            |
| -------- | -------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| `GET`    | `/health`                  | Liveness check.                                        | —                                                                                                                                                                             | `200` `{ "status": "ok" }`                         |
| `GET`    | `/v1/flags`                | List all flags.                                        | `?tag=<t>` (optional) returns only flags carrying that tag                                                                                                                    | `200` `{ "flags": [Flag] }`                        |
| `POST`   | `/v1/flags`                | Create a flag.                                         | `key` (string, required), `enabled` (boolean, required), `description` (string, optional), `rolloutPercentage` (integer 0–100, optional), `tags` (array of strings, optional) | `201` `Flag`                                       |
| `GET`    | `/v1/flags/:key`           | Fetch one flag.                                        | `:key` path param                                                                                                                                                             | `200` `Flag`                                       |
| `PATCH`  | `/v1/flags/:key`           | Update a flag.                                         | `enabled` (boolean), `description` (string), `rolloutPercentage` (integer 0–100), and/or `tags` (array of strings) — at least one                                             | `200` `Flag`                                       |
| `POST`   | `/v1/flags/:key/toggle`    | Flip a flag's `enabled` state without a body.          | `:key` path param                                                                                                                                                             | `200` `Flag`                                       |
| `PUT`    | `/v1/flags/:key/tags/:tag` | Attach one tag to a flag (idempotent).                 | `:key` and `:tag` path params; the same tag rules as `tags` apply                                                                                                             | `200` `Flag`                                       |
| `DELETE` | `/v1/flags/:key/tags/:tag` | Remove one tag from a flag.                            | `:key` and `:tag` path params                                                                                                                                                 | `200` `Flag`                                       |
| `DELETE` | `/v1/flags/:key`           | Delete a flag.                                         | `:key` path param                                                                                                                                                             | `204` (no body)                                    |
| `GET`    | `/v1/flags/:key/evaluate`  | Evaluate a flag (hot path for pollers).                | `:key` path param; `?unit=<string>` (optional) buckets the unit for percentage rollouts                                                                                       | `200` `{ "key", "enabled", "rolloutPercentage"? }` |
| `GET`    | `/v1/flags/:key/history`   | Change history for a flag.                             | `:key` path param; `?limit=<n>` (optional) returns only the most recent `n` events (integer 1–500)                                                                            | `200` `{ "key", "events": [FlagEvent] }`           |
| `GET`    | `/v1/tags`                 | List distinct tags across all flags with usage counts. | —                                                                                                                                                                             | `200` `{ "tags": [{ "tag", "count" }] }`           |
| `GET`    | `/v1/tags/:tag/flags`      | List all flags carrying a specific tag.                | `:tag` path param                                                                                                                                                             | `200` `{ "flags": [Flag] }`                        |
| `DELETE` | `/v1/tags/:tag`            | Retire a tag: remove it from every flag carrying it.   | `:tag` path param                                                                                                                                                             | `200` `{ "tag", "removedFrom" }`                   |

## Tags

Tags group related flags — by team, surface, launch, or anything else — so you can slice a growing flag list without a naming convention. Attach them on create, replace them later with a PATCH, or add and remove one tag at a time with `PUT` / `DELETE /v1/flags/:key/tags/:tag`. To see every flag behind a tag, call `GET /v1/tags/:tag/flags`.

A flag carries at most 10 tags. Each tag is 1–50 characters of lowercase kebab-case — letters, digits, and single dashes, e.g. `checkout` or `q3-launch` — with no duplicates within a flag. Anything else returns `400 invalid_tags`.

A PATCH replaces the whole tag set (there is no merge); pass an empty array to remove every tag, after which the `tags` field disappears from the flag entirely.

Filter the flag list by tag, or ask for the full tag inventory: `GET /v1/tags` returns every distinct tag on a live flag with the number of flags carrying it, sorted by tag name. Deleted flags do not contribute. Filtering by a tag no flag carries returns an empty list, not an error, and an empty `?tag=` is treated as absent.

Tags are persisted with the flag when `FLAGPOLE_DATA_FILE` is set, and tag changes appear in the flag's history like any other field.

`DELETE /v1/tags/:tag` retires a tag across the whole flag set in one call: the tag is removed from every flag carrying it and the response reports how many flags were affected. The flags themselves are otherwise untouched, each affected flag records an updated history event, and the call returns `404 tag_not_found` when no live flag carries the tag.

## Error codes

| Status | Code                         | When                                                                                      |
| ------ | ---------------------------- | ----------------------------------------------------------------------------------------- |
| `400`  | `invalid_json`               | Body is not valid JSON.                                                                   |
| `400`  | `invalid_key`                | Missing or malformed `key` on create.                                                     |
| `400`  | `invalid_enabled`            | `enabled` is not a boolean.                                                               |
| `400`  | `invalid_description`        | `description` is not a string.                                                            |
| `400`  | `invalid_rollout_percentage` | `rolloutPercentage` is not an integer between 0 and 100.                                  |
| `400`  | `invalid_tags`               | `tags` is not an array of up to 10 unique lowercase kebab-case strings (1–50 chars each). |
| `400`  | `empty_update`               | PATCH body has none of `enabled`, `description`, `rolloutPercentage`, or `tags`.          |
| `400`  | `invalid_limit`              | History `limit` is not an integer between 1 and 500.                                      |
| `401`  | `unauthorized`               | Missing or wrong bearer token.                                                            |
| `404`  | `flag_not_found`             | No flag with that key.                                                                    |
| `404`  | `tag_not_found`              | `DELETE /v1/tags/:tag` named a tag that no live flag carries.                             |
| `404`  | `not_found`                  | Unknown route.                                                                            |
| `409`  | `flag_exists`                | Create with a key that already exists.                                                    |