Docs

A quick reference for the exp REST API. The full contract — every endpoint, object and error — lives in the repository.

Read the full contract (CONTRACT.md) →

Base URL

https://exp.3m4.net/api

Authentication

Three kinds of credentials, each for one job:

  • Dashboard sessions: JWT bearer tokens issued by /auth/login. Hold every scope.
  • Management API: API keys with a role (viewer / editor / admin). Header X-API-Key: exp_live_… (or Authorization: Bearer).
  • Evaluation API: environment client keys. Header X-Environment-Key: exp_env_…. They identify one (project, environment) and can only evaluate.

Projects & environments

A project owns environments and flags. Creating one seeds production, staging and development and returns their client keys once.

MethodPathScopeNotes
GET/projects?q=projects:read
POST/projectsprojects:write{name, key?, description?} → 201 · 402 limit_reached
GET/projects/:idprojects:read
PATCH/projects/:idprojects:write{name?, description?}
DELETE/projects/:idprojects:deletecascades
GET/projects/:id/exportflags:readexp.project.v1
GET/projects/:id/environmentsprojects:read
POST/projects/:id/environmentsprojects:write{key, name?} → client_key once
DELETE/projects/:id/environments/:envIdprojects:deletelast one: 400 last_environment
POST/projects/:id/environments/:envId/rotate-keyprojects:writeold key stops immediately
curl -X POST https://exp.3m4.net/api/projects \
  -H "X-API-Key: exp_live_…" -H "Content-Type: application/json" \
  -d '{"name": "Web app"}'

Flags

A flag has a key, a kind (boolean, string, number, json), a default value, tags and one state per environment. Archived flags are kept but never evaluated.

MethodPathScopeNotes
GET/projects/:id/flags?q=&page=&per_page=&include_archived=flags:readordered by key
POST/projects/:id/flagsflags:write{key, name?, kind?, default_value?, tags?, states?} · 409 key_taken · 402
GET/projects/:id/flags/:flagflags:read:flag = UUID or key
PATCH/projects/:id/flags/:flagflags:writechanging kind resets state values
DELETE/projects/:id/flags/:flagflags:delete
curl -X POST https://exp.3m4.net/api/projects/$PROJECT/flags \
  -H "X-API-Key: exp_live_…" -H "Content-Type: application/json" \
  -d '{"key": "new-checkout", "kind": "boolean", "tags": ["checkout"]}'

States

A state is enabled + value + rollout_percent for one flag in one environment. Set it with PUT; the response is the whole flag.

MethodPathScopeNotes
PUT/projects/:id/flags/:flag/states/:envKeyflags:write{enabled?, value?, rollout_percent?} → Flag
GET/projects/:id/flags/evaluate?environment=&distinct_id=flags:readpreview, not metered
curl -X PUT https://exp.3m4.net/api/projects/$PROJECT/flags/new-checkout/states/staging \
  -H "X-API-Key: exp_live_…" -H "Content-Type: application/json" \
  -d '{"enabled": true, "rollout_percent": 25}'

Copy flags

Copies flag definitions (and optionally their states) into another project of the same account. One transaction: if the target would exceed its flag limit nothing is copied.

MethodPathScopeNotes
POST/projects/:id/flags/copyflags:write{target_project_id, flag_keys?, include_states?, on_conflict?} → {copied, overwritten, skipped}

Evaluation semantics

For one flag in one environment, the evaluation API answers like this:

  1. State enabled == false → off.
  2. rollout_percent < 100: bucket = first 8 hex chars of sha256("<flag_key>:<distinct_id>") as an integer, modulo 100. In when bucket < rollout_percent. Without distinct_id the request is out unless rollout_percent == 100.
  3. On → boolean: value = true; other kinds: value = state.value ?? flag.default_value.
  4. Off → boolean: value = false; other kinds: value = null.

The dashboard and the management API expose the same logic as a preview (GET /projects/:id/flags/evaluate), which is neither metered nor rate-limited as evaluation.

MethodPathScopeNotes
GET/eval/flags?distinct_id=X-Environment-Keymetered +1
GET/eval/flags/:key?distinct_id=X-Environment-Keymetered +1 · 404 for unknown/archived
curl "https://exp.3m4.net/api/eval/flags?distinct_id=user-42" \
  -H "X-Environment-Key: exp_env_…"

Errors & limits

Errors are JSON: { "error": { "code", "message" } }. Creating past a plan limit answers 402 limit_reached with error.which = projects | environments | flags | api_keys. Evaluations over the monthly budget are still served.

MCP (AI agents)

The MCP server exposes the management API as tools, filtered by the role of the key you connect with. Destructive tools ask for confirmation.

Local (stdio, npm)
claude mcp add exp --scope user --env EXP_API_KEY=exp_live_… -- npx -y exp-mcp
Remote (Streamable HTTP)
claude mcp add --transport http exp https://exp.3m4.net/mcp --header "Authorization: Bearer exp_live_…"