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 / productionMissing, invalid, or revoked keys return 401:
{ "error": "Unauthorized" }Endpoints
| Endpoint | Method | Conditional GET | Cached at edge |
|---|---|---|---|
/v1/features | GET | Yes (ETag / 304) | Yes (200 only) |
/v1/features/:key | GET | No | Yes |
/v1/features/stream | GET | N/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
| Param | Values | Default | Description |
|---|---|---|---|
type | boolean, string, number, json | None | Filter by feature type |
deprecated | true, false | None (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=falsecurl -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"
}
]
}| Field | Description |
|---|---|
key | Feature identifier (stable contract) |
type | boolean, string, number, or json |
value | Resolved effective value for this environment |
enabled | Always true in responses (disabled features are excluded) |
deprecated | Soft lifecycle marker |
inFavorOf | Optional replacement feature key when deprecated |
Array order is not guaranteed. Index by key.
Response headers
| Header | Description |
|---|---|
ETag | Environment 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
| Event | Payload | When |
|---|---|---|
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
| Status | When | Body |
|---|---|---|
200 | Success | JSON payload |
304 | GET /v1/features only, when If-None-Match matches | empty |
400 | Invalid query param | { "error": "..." } |
401 | Bad or missing API key | { "error": "Unauthorized" } |
403 | Key scope violation (e.g. test key off loopback, RFC1918 LAN IP on free plan, live key without deploy access) | { "error": "..." } |
404 | Unknown key or feature not activated in this environment (GET /v1/features/:key) | { "error": "Feature not found" } |
429 | Usage 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.