Docs

Concepts, API, caching, and SDKs. Sign up to first feature in under five minutes.

Feature API reference

Base URL: https://api.featuretoggle.com

The feature API is read-only. Authenticate every request with an API key.

Authentication

Authorization: Bearer ft_test_...   # development (localhost only)
Authorization: Bearer ft_live_...    # staging / production

Missing, invalid, or revoked keys return 401:

{ "error": "Unauthorized" }

Endpoints

EndpointMethodConditional GETCached at edge
/v1/featuresGETYes (ETag / 304)Yes (200 only)
/v1/features/:keyGETNoYes
/v1/features/streamGETN/A (SSE)No

GET /v1/features

Returns all enabled features for the API key's environment. A feature is enabled when it has been activated in that environment (FeatureValue.enabled === true). Features that have not been activated in the environment are omitted, and no parameter includes them.

Query parameters

ParamValuesDefaultDescription
typeboolean, string, number, jsonNoneFilter by feature type
deprecatedtrue, falseNone (includes both)true = deprecated only; false = non-deprecated only

Invalid values return 400:

{ "error": "..." }

Examples

GET /v1/features
GET /v1/features?type=boolean
GET /v1/features?type=boolean&deprecated=false
curl -s https://api.featuretoggle.com/v1/features \
  -H "Authorization: Bearer ft_test_YOUR_KEY"

Response 200

{
  "features": [
    {
      "key": "new-checkout",
      "type": "boolean",
      "value": true,
      "enabled": true,
      "deprecated": false
    },
    {
      "key": "old-checkout",
      "type": "boolean",
      "value": true,
      "enabled": true,
      "deprecated": true,
      "inFavorOf": "new-checkout"
    }
  ]
}
FieldDescription
keyFeature identifier (stable contract)
typeboolean, string, number, or json
valueResolved effective value for this environment
enabledAlways true in responses (disabled features are excluded)
deprecatedSoft lifecycle marker
inFavorOfOptional replacement feature key when deprecated

Array order is not guaranteed. Index by key.

Response headers

HeaderDescription
ETagEnvironment version as a quoted string, e.g. "42"

Conditional GET (304)

Send the ETag from the last 200 as If-None-Match:

curl -s -D - https://api.featuretoggle.com/v1/features \
  -H "Authorization: Bearer ft_test_YOUR_KEY" \
  -H 'If-None-Match: "42"'

If features are unchanged → 304 Not Modified (empty body, same ETag). The SDK keeps its existing cache.

304 responses include Cache-Control: no-store and are not cached at the edge.

CORS

Browser requests are allowed from any origin. Allowed request headers: Authorization, If-None-Match. Exposed response headers: ETag.

GET /v1/features/:key

Returns one enabled feature by key.

curl -s https://api.featuretoggle.com/v1/features/new-checkout \
  -H "Authorization: Bearer ft_test_YOUR_KEY"

Response 200

Bare feature object (not wrapped in features):

{
  "key": "new-checkout",
  "type": "boolean",
  "value": true,
  "enabled": true,
  "deprecated": false
}

Response 404

Unknown key, or feature not activated in this environment:

{ "error": "Feature not found" }

This route has no conditional GET. It always returns full data or 404.

GET /v1/features/stream

Server-Sent Events stream for live feature changes. Open after the first successful bulk fetch.

curl -N https://api.featuretoggle.com/v1/features/stream \
  -H "Authorization: Bearer ft_test_YOUR_KEY" \
  -H "Accept: text/event-stream"

Events

EventPayloadWhen
connected{ "featuresVersion": 42 }Stream opens
features-changed{ "featuresVersion": 43 }Environment version bumped
ping{}Heartbeat (~15s)

Example:

event: connected
data: {"featuresVersion":5}

event: features-changed
data: {"featuresVersion":6}

event: ping
data: {}

On features-changed, compare featuresVersion to the cached ETag and refetch when newer. The SDK does this automatically in default stream: 'auto' mode.

The stream is not cached at the edge. Keep one connection per SDK instance; reconnect with exponential backoff on disconnect.

HTTP status codes

StatusWhenBody
200SuccessJSON payload
304GET /v1/features only, when If-None-Match matchesempty
400Invalid query param{ "error": "..." }
401Bad or missing API key{ "error": "Unauthorized" }
403Key scope violation (e.g. test key off loopback, RFC1918 LAN IP on free plan, live key without deploy access){ "error": "..." }
404Unknown key or feature not activated in this environment (GET /v1/features/:key){ "error": "Feature not found" }
429Usage limit exceeded{ "error": "..." }

Response shape

Each enabled feature in a successful JSON body matches this shape (TypeScript notation):

type FeatureResponse = {
  key: string;
  type: "boolean" | "string" | "number" | "json";
  value: unknown;
  enabled: boolean;
  deprecated: boolean;
  inFavorOf?: string;
};

Official SDKs use the same shape. See SDKs.