HTTP API
The CLI, the SDKs, and the dashboard are all clients of this API. Anything they can do, you can do with curl.
Base URL and authentication
https://api.flagrship.dev/api/v1Every request under /api/v1 carries an API key as a bearer token. The key determines the organization, the environment, and what you are allowed to do. There is no environment parameter anywhere; the key is the environment.
$ curl https://api.flagrship.dev/api/v1/flags \ -H "Authorization: Bearer sk_test_your_staging_key"| Scope | Grants | Key prefixes |
|---|---|---|
| read | GET on everything, including /evaluate | sk_read_ |
| write | read, plus POST on flags: create, enable, disable, rollout, rollback | sk_test_, sk_live_ |
| admin | write, plus lock, unlock, archive, and key management | sk_admin_ |
Errors
Every error has the same shape. The request ID is also in the response headers; quote it when asking for help.
1{2 "error": {3 "code": "VALIDATION_ERROR",4 "message": "Rollout percentage must be between 0 and 100.",5 "field": "percentage",6 "request_id": "5608458f-925f-4a86-9d83-8d92a49b22c5"7 }8}| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_ERROR | Bad body, missing field, out-of-range value. field names the offender. |
| 401 | UNAUTHORIZED | Missing, unknown, revoked, or expired key. |
| 403 | FORBIDDEN | The key lacks the scope this endpoint needs. |
| 404 | NOT_FOUND | No such flag, key, or environment in this organization. |
| 409 | CONFLICT | Duplicate flag key, locked flag, nothing to roll back, already revoked. |
| 429 | RATE_LIMITED | Too many requests from your IP. The message says how long to wait. |
| 500 | INTERNAL_ERROR | Our fault, and the message says nothing else. The request ID is in the body. |
Flags
/flagsscope: writeCreate a flag. It exists in every environment at once, disabled and at 0%.
{ "key": "new-checkout", "name": "New Checkout", "description": "optional", "flagType": "BOOLEAN"}key: 3–120 lowercase letters, digits, hyphens; must start and end alphanumeric. Returns 201 with the flag as seen from the key's environment.
1{2 "key": "new-checkout",3 "name": "New Checkout",4 "description": null,5 "flagType": "BOOLEAN",6 "environment": "staging",7 "enabled": true,8 "rolloutPercentage": 25,9 "targetingRules": null,10 "locked": false,11 "lockReason": null,12 "createdAt": "2026-09-15T14:40:54.203Z",13 "updatedAt": "2026-09-15T23:04:44.330Z"14}/flagsscope: readEvery active flag in the environment, newest first. ?search= filters by key or name. Returns { environment, flags: [...] }.
/flags/:keyscope: readOne flag, with its config for this environment. 404 if it does not exist or is archived.
/flags/:key/enablescope: writeSets enabled. Rollout percentage is preserved. No body.
/flags/:key/disablescope: writeClears enabled and resets rollout to 0. The previous percentage survives in the audit log. No body.
/flags/:key/rolloutscope: write{ "percentage": 25 }Integer 0–100. The flag must be enabled first, or you get a 400 that says so.
/flags/:key/rollbackscope: writeRestore the state from before the most recent change in this environment, and record the rollback as a change of its own. A second call undoes the first. 409 if there is nothing to roll back to. No body.
/flags/:key/lockscope: admin{ "reason": "CVE-2026-1234" }Reason is required, 1–500 characters. While locked, enable, disable, rollout, and rollback return 409 with the reason in the message. 409 if already locked.
/flags/:key/unlockscope: adminClears the lock. 409 if not locked. No body.
/flags/:keyscope: adminArchive (soft delete). The flag disappears from /flags and /evaluate immediately; its history stays. Refused with 409 while the flag is locked in any environment.
/flags/:key/historyscope: readThe last 50 audit entries for the flag, newest first.
1{2 "flag": "new-checkout",3 "entries": [4 {5 "action": "flag.rollout",6 "environment": "staging",7 "actor": { "name": "Nisarg Patel", "email": "nisarg@flagrship.dev", "via": "api_key" },8 "before": { "enabled": true, "rolloutPercentage": 0, "locked": false, "lockReason": null },9 "after": { "enabled": true, "rolloutPercentage": 25, "locked": false, "lockReason": null },10 "metadata": { "keyPrefix": "sk_test_75GA", "ip": "127.0.0.1", "requestId": "…" },11 "at": "2026-09-15T14:40:54.741Z"12 }13 ]14}SDK sync
/evaluatescope: readEvery active flag's config for the key's environment, in the shape the SDKs consume. This endpoint has no parameters. It never accepts a user identifier; bucketing happens in the SDK.
1{2 "environment": "staging",3 "flags": [4 { "key": "dark-mode", "enabled": true, "rolloutPercentage": 100, "targetingRules": null },5 { "key": "new-checkout", "enabled": true, "rolloutPercentage": 25, "targetingRules": null }6 ]7}The response carries a strong ETag. Send it back as If-None-Match and you get a 304 with no body when nothing has changed.
$ curl -i https://api.flagrship.dev/api/v1/evaluate \ -H "Authorization: Bearer sk_read_…" \ -H 'If-None-Match: "kM3Qn8tYw2xZa1bC4dE5fG6hI7j"'HTTP/1.1 304 Not Modifiedetag: "kM3Qn8tYw2xZa1bC4dE5fG6hI7j"cache-control: private, no-cacheAPI keys
All three require an admin key.
/keysscope: admin{ "name": "CI reader", "scopes": ["read"]}The new key lands in the calling key's environment. There is no way to name a different one: a staging admin key must not be a way to mint a production key. Scopes are hierarchical: admin implies write implies read. The response includes the raw key once; only its SHA-256 hash is stored.
1{2 "id": "b4dd593e-efdc-4553-aecf-c3b0edb21ff2",3 "name": "CI reader",4 "environment": "staging",5 "scopes": ["read"],6 "keyPrefix": "sk_read_your",7 "createdAt": "2026-09-15T22:20:46.831Z",8 "key": "sk_read_your_new_key_shown_once"9}/keysscope: adminEvery key in the organization with prefix, scopes, environment, last use, and whether it is still active. Never the key itself.
/keys/:idscope: adminRevoke. The key stops authenticating immediately. It is not deleted; revoked keys are evidence. 409 if already revoked.
Rate limits
600 requests per minute per IP address, across every endpoint including /health. Going over returns 429 with RATE_LIMITED and a wait time. An SDK polls twice a minute and a CLI command is one call, so this only bites abuse.
The limit is not what protects a key from being guessed - a key is 24 random bytes, so guessing is not a threat a rate limit meaningfully changes. It is there so one noisy client cannot take the API away from everyone else.
Health
/healthPublic, no key needed. Runs a real query, so a dead database connection fails it. Path is /health, not under /api/v1.
{ "status": "ok", "uptime": 3.63 }