Appearance
Configs
Configs are the same primitive as flags, but with one of four types — string, number, boolean, json — and an optional JSON Schema for type=json configs. The endpoints mirror flags exactly.
The Config object (joined view):
json
{
"id": "9f7a32b5-…",
"projectId": "1234abcd-…",
"envId": "5678ef01-…",
"key": "checkout.max-items",
"type": "number",
"description": "Maximum items per cart.",
"jsonSchema": null,
"defaultValue": 100,
"rules": [
{ "if": { "field": "plan", "$equals": "free" }, "value": 10 }
],
"createdAt": "2026-04-12T10:00:00Z",
"updatedAt": "2026-04-29T14:33:00Z"
}jsonSchema is null for non-json types. For json configs, when set, every default value and rule value is validated against the schema at save time.
List configs
http
GET /api/v1/envs/{envId}/configsResponse: 200 → Config[] | 404 not_found
Get a config
http
GET /api/v1/envs/{envId}/configs/{key}Response: 200 → ConfigErrors: 403 scope_denied, 404 not_found.
Create a config
http
POST /api/v1/projects/{id}/configs
Content-Type: application/json
{
"key": "checkout.max-items",
"type": "number",
"defaultValue": 100,
"rules": [],
"description": "Maximum items per cart.",
"jsonSchema": null
}Bearer scope: write action. Session role: editor+.
Seeds env-state into every existing environment with the supplied values in one transaction.
type is one of string, number, boolean, json. defaultValue is required and must match the type. For type: json, null is allowed both as defaultValue and as any rule value; for other types, null is a 400 invalid_request.
Response: 201 → ConfigProjectErrors: 400 invalid_request, 403 scope_denied / approval_not_supported, 404 not_found, 409 key_collision.
Update config metadata
http
PATCH /api/v1/projects/{id}/configs/{key}
Content-Type: application/json
If-Match: W/"<configETag>"
{
"description": "Max cart items (caps for free tier).",
"jsonSchema": null
}Bearer scope: write action. Session role: editor+.
Updates description and (for type: json) jsonSchema. Eval-irrelevant; doesn't rematerialize.
Response: 200 → ConfigProjectErrors: 400 invalid_request, 403 scope_denied / approval_not_supported, 404 not_found, 412 precondition_failed.
Delete a config
http
DELETE /api/v1/projects/{id}/configs/{key}Bearer scope: delete action. Session role: editor+.
Cascades to every environment's state.
Response: 204 No ContentErrors: 403 scope_denied / approval_not_supported, 404 not_found.
Replace environment state
http
PUT /api/v1/envs/{envId}/configs/{key}/state
Content-Type: application/json
If-Match: W/"<envStateETag>"
{
"defaultValue": 50,
"rules": [
{ "if": { "field": "plan", "$equals": "free" }, "value": 10 }
]
}Bearer scope: write action. Session role: editor+.
Full-replace of (rules, defaultValue) for this environment. Type validation per the config's declared type. Approval-gated tokens with rules-only deltas divert to the proposal flow (202 + Location header); mixed-dimension changes 403 with approval_not_supported.
Response: 200 → Config (joined view) | 202 → Proposal (approval-gated divert). Errors: 400 invalid_request, 403 scope_denied / approval_not_supported, 404 not_found, 412 precondition_failed.