Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The HTTP Server

improv-server serves a JSON HTTP API over one model store:

improv-server model.db 127.0.0.1:3000   # or IMPROV_ADDR=...; model defaults to improv.db
improv-server 'postgres://me@127.0.0.1/sales?sslmode=disable&model=plan'

Endpoints

MethodPathBodyNotes
GET/healthPublic liveness probe.
GET/modelThe whole model.
GET/measuresId, name, kind, categories.
GET/measures/:id/valuesStored inputs, or computed values.
POST/measures/:id/eval{"edits": [{"measure": 101, "coord": [[1, 10], [2, 20]], "value": 90}], "persist": false}What-if edits through the live engine; "persist": true writes them. Ids are numeric; "value": null clears a cell. The answer names coordinates by category and item name.
POST/measures/:id/cells{"coord": {"dims": {"1": 10, "2": 20}}, "value": 90}Set one input cell (category id → item id).
POST/nl/parse{"text": "revenue is price times quantity"}Controlled English to a formula.
POST/nl/describe{"formula": …}A formula (as /nl/parse returns it) to controlled English.
GET/scheduler/statusRefresh scheduler state.

Values written through the API are numbers.

Authentication

Every route except /health needs Authorization: Bearer <token> once a token is configured. Tokens are compared in constant time.

VariableMeaning
IMPROV_API_TOKEN, IMPROV_API_TOKENSRead-write tokens (one, or comma-separated).
IMPROV_API_TOKENS_RORead-only tokens (comma-separated). GETs and what-if /eval work; POST /cells and "persist": true return 403.
IMPROV_ALLOW_OPEN=1Allow open mode (no tokens) on a non-loopback address. Without it the server refuses to start.

Writes

A write must name an existing input measure and a coordinate with exactly one item for each of the measure's categories; values must be finite. Anything else is a 400. Writes are serialized, so concurrent writers do not lose updates. The server caches the loaded model. If another process (the GUI, the CLI, SQL on a Postgres model) saved since the server loaded it, the server's next write is refused with 409 Conflict, writing nothing, and the cache is dropped, so a retry reloads and succeeds. Reads serve the cached model until then.

Limits

LimitValue
Request body1 MiB (413 above)
Edits per /eval10,000 (400 above)
Request time30 s (408), including time queued for a slot
Requests in flight64; the rest queue
Header read10 s; IMPROV_HEADER_TIMEOUT_MS overrides

The server speaks HTTP/1.1 only.

Internal errors return {"error": "internal error", "request_id": N}; the detail is logged to stderr under the same id.

Refresh scheduler

The scheduler refreshes CALL (external-function) measures in the background. Refreshing runs code stored in the model, so it is off by default.

VariableMeaning
IMPROV_SCHEDULER=1Enable the scheduler.
IMPROV_EXTFN_ALLOWPath to the allowlist file.
IMPROV_SCHEDULER_TICK_SECSTick period (default 5).

The scheduler runs a function only if its digest is listed in the allowlist file, one lowercase hex digest per line (# comments allowed). Other functions are skipped, and a warning is logged. The digest is the SHA-256 of the function name, a NUL byte and the body:

printf '%s\0%s' double 'result = args[0] * 2' | sha256sum

Scheduler refreshes always run in the Restricted sandbox, whatever the function's pure flag says.