Local Serve API#
decant serve runs the archive owner, source watcher, local HTTP API,
Server-Sent Events stream, and React UI in one Bun process. It listens on
http://127.0.0.1:3000 by default.
The reference contract is openapi.yaml. It describes every
/api/* operation, parameter, request body, response schema, and stable error
code. A running server exposes the same OpenAPI 3.1 document as JSON at
GET /api/openapi.json; its info.version is the running Decant version.
This page records the operational semantics around that contract.
UI routes#
GET /(Analytics; grouped under Overview in the sidebar)GET /projectsGET /sessionsGET /sessions/:idGET /searchGET /analyticsGET /insightsGET /toolsGET /filesGET /settingsGET /reports/analytics?from=YYYY-MM-DD&to=YYYY-MM-DDGET /reports/session/:id
The report UI routes render a light, print-ready preview with Back, Download
HTML, and Save as PDF controls. They read the local-only report operations;
the session preview intentionally omits transcript content.
Access control#
The API has no authentication. Any request that reaches the listener and passes
the local guard can read or mutate the whole archive.
- The default loopback bind admits local processes only.
- On a non-loopback bind, loopback source addresses and the trusted peers
resolved at startup are admitted; every other source receives
403 forbidden_remote. - Trusted-peer sources use replacement precedence, not a union. The first
present source wins:--trusted-peer, thenDECANT_TRUSTED_PEERSwhenever
the variable is set, thenDECANT_TRUST_DEFAULT_GATEWAY=1.
DECANT_TRUSTED_PEERS=therefore means βtrust nobody,β not βfall through.β - The gateway option contributes one address only when Decant proves the
default route is a container veth to an on-link gateway inside
172.16.0.0/12. It fails closed for host networking, macvlan/ipvlan,
multi-homed hosts, and other unproven shapes. See
distribution.md. - The
Hostcheck is not authentication: a non-browser client can send
Host: localhost. TheOriginandSec-Fetch-Sitechecks on writes are
browser-drive protections, not credentials.
On a loopback bind, a command-line write may omit Origin. On a non-loopback
bind, a write that supplies neither Origin nor Sec-Fetch-Site is rejected
even when the source is trusted. Supply a loopback Origin for an explicit
command-line write; for example:
curl --fail --silent --show-error \
--request POST \
--header 'Content-Type: application/json' \
--header 'Origin: http://127.0.0.1:3000' \
--data '{}' \
http://127.0.0.1:3000/api/sync
If the client connects to a non-loopback address directly, it must also send a
loopback Host header. Admission still depends on the actual source address;
changing Host or Origin never makes an untrusted peer trusted.
Response and archive semantics#
JSON errors use the stable envelope { "error": string, "code": string, ... }.
Expected recovery cases retain specific codes, including archive_locked,
schema_drift, schema_too_new, schema_too_old, session_not_found, and
validation failures. Unexpected failures return generic internal_error prose;
the structured stderr log retains the diagnostic.
DECANT_NO_SYNC and --no-sync suppress Decant-initiated startup, watch, and
sweep syncs. They do not disable POST /api/sync.
Sync responses include repriced when stored session or activity costs changed.
A repricing-only sync also invalidates server economics caches and emits
archive_updated, even when ingested is zero.
Session archive/delete state is local metadata. Archiving hides a session from
default lists, searches, and aggregate statistics. The include_archived
parameter on session-list and statistics operations opts it back into those
operations; full-text and command-palette search remain limited to visible
sessions. Deletion creates a tombstone keyed to source identity, so a later sync
does not restore the session. Neither operation modifies the source JSONL file.
Deletion removes the live rows. SQLite may leave deleted text recoverable in
freed pages until decant db vacuum rewrites the archive. See
What the archive stores.
Report operations return self-contained, zero-JavaScript HTML. Session reports
omit transcript content by design.
Session listing and command-palette index#
GET /api/sessions returns a bare newest-first array, not an envelope with a
total or continuation token. Its limit defaults to 50 and has an effective
maximum of 100. Increment offset by the number of rows received; a final short
or empty page marks the end. When the result count is an exact multiple of the
page size, one empty request is required to confirm the end.
GET /api/sessions/search-index returns lightweight metadata for every visible,
non-archived top-level session. It is the command palette's local fuzzy-search
haystack and intentionally omits transcript content.
Server-Sent Events#
GET /api/events returns text/event-stream. The current event names are:
helloβ connection acknowledgementpingβ heartbeat, normally every five secondsreadyβ source watcher initializedsync_progressβ bounded progress snapshot for a running syncsyncβ terminal successful sync reportarchive_updatedβ archive-derived UI data changed;reasondistinguishes
background watcher activity from manual syncs, statistics rebuilds, and
session-state changeserrorβ watcher or sync failurestoppedβ source watcher stopped
Each data field is JSON and includes a matching type. The OpenAPI operation's
x-sse-events extension defines the payload schema for each name.