HTTP API
The API is the lowest common denominator: anything that can make an HTTP request can use Grounded. It's a small REST surface with the same return shapes as the console and MCP, served by the same process on the same origin.
Base URL & auth #
The service listens on GROUNDED_API_HOST (default 127.0.0.1)
and GROUNDED_API_PORT (default 7437). Auth is off by default for
localhost. Set GROUNDED_API_TOKEN (or install with --token)
to require a bearer token — GET /health and GET /llms.txt
stay exempt (an agent has to be able to read how to authenticate before it has a token); a bad token
returns 401. The token is a single shared secret for the whole
cabinet: scope and agent values on a request are
self-declared by the caller, not enforced per-token — see
doc lanes for what that means for scoping.
Routes #
| Method | Path | Purpose |
|---|---|---|
| GET | /health | Installed version, adapter, embeddings, add-on capabilities, Databox state, and counts. Unauthenticated. |
| GET | /openapi.json | OpenAPI 3.1 document. |
| GET | /llms.txt | The agent-facing manual — routes, envelope shape, lane semantics, gotchas. Unauthenticated, plain markdown. |
| POST | /recall | Hybrid recall → cited cards. Doc lane defaults to scopes: ["global"]. |
| POST | /impact | Dependency pre-flight for a literal subject (container, port, path) — lexical-only, crosses lanes citation-only. See below. |
| POST | /brief | Assemble a startup brief. Related-docs lane defaults to docScopes: ["global"]. Pass timezone (IANA) to render session dates in your zone — default UTC; an unknown zone is a 400. |
| GET | /get/:typedId | Full record by fact:/session:/doc: id. |
| GET | /facts | List facts (?scope&scopes&status&limit&offset; status defaults to active, or pass archived/all). |
| POST | /facts | Add a fact (201). |
| PATCH | /facts/:id | Edit a fact in place (only sent fields change). |
| DEL | /facts/:id | Delete a fact. Idempotent: 200 {deleted, id} — deleted:false means the row was already absent, never a 404. |
| GET | /sessions | List sessions (?project&limit). |
| POST | /sessions | Add a session (201). |
| GET | /sessions/:id | One session. |
| PATCH | /sessions/:id | Edit a session in place — a partial patch: omitted fields keep their stored values, and the row is re-embedded only when summary or details change. Accepts summary, details, project, workspace, agent, machine, tags, source; any other key is a 400. |
| DEL | /sessions/:id | Delete a session — hard delete of the row and its index entries, not reversible. Idempotent: 200 {deleted, id} — deleted:false means the row was already absent, never a 404. |
| POST | /docs/ingest | Ingest paths → IngestReport (tag with a scope lane, default global). |
| POST | /docs/prune | Reconcile doc rows against disk → {missing, removed}; pass {remove:true} to delete orphans instead of marking them missing. |
| GET | /docs | List docs (?source&scope&scopes&documents&limit&offset; unfiltered by scope unless you pass one — see doc lanes). |
| GET | /docs/:id | One doc chunk. |
| GET | /vision | List vision (?scope&status; default active). |
| POST | /vision | Set vision — edits the one active record per scope in place (201). |
| DEL | /vision/:id | Delete a vision record. Idempotent: 200 {deleted, id} — deleted:false means the row was already absent, never a 404. |
| GET | /addons | The add-on catalog and each add-on's state. |
| POST | /addons/:id/install | Install and turn on an add-on (202, runs as a job). Local only. |
| POST | /addons/:id/disable | Turn an add-on off. Local only. |
| GET | /databox | Databox boxes and their state; meta.enabled: false when Databox is off. |
| GET | /databox/:box · /databox/:box/schema | One box's status and last build; its tables and columns. |
| POST | /databox/:box/query | One read-only SQL statement: {sql, limit?, timeoutMs?}. |
| POST | /databox/:box/index | Rebuild the index: {force?, wait?}. Local only. |
| GET | /sharing | LAN sharing status and the pairing code. Local only. |
| POST | /sharing | Turn sharing on and open a pairing window: {minutes?}, 1–60, default 3. Local only. |
| DEL | /sharing | Turn sharing off. Local only. |
Local only means the route answers on the computer running Core and is refused with
403 over LAN sharing.
Dependency pre-flight #
POST /impact takes a subject, not a query — a
literal token like a container name, a port, or a path, not a natural-language question. Body:
{subject, limit?, sources?, project?, scopes?}. It's the check you
run before you stop, remove, delete, or rename infrastructure: what depends on this? A dependency
tripwire, not a search.
It's lexical-only by construction — there's no lexicalOnly option
because there's no vector lane to turn off. That means impact holds on the zero-LLM path: set the
embedding provider to none and dependency checks keep working exactly as before.
Impact is the one operation that crosses a lane
boundary. Recall drops out-of-lane docs entirely; impact returns them citation-only:
path, citation, and scope survive,
title and snippet come back null,
and inScope: false says why. You learn a dependency exists and where to look,
without its content crossing the boundary — declare the lane in scopes to
read it. Lane gating is docs-only: facts and sessions are never laned, so they always come back
inScope: true, scope: "global".
meta.available counts withheld hits too — a found-but-hidden document
isn't an absent one. And limit is authoritative here: it overrides the ranking
source caps, because a dependency pre-flight isn't a
top-10 reading list. Citations are chunk-grained: doc:{source}/{path}#chunk{N}.
The TypeScript client exposes this as client.impact(subject, opts); the MCP
tool is ground_impact (see MCP tools).
The {data, meta} envelope #
Every list-shaped response — facts, sessions, docs, vision, recall, and impact — comes back
as an envelope, not a bare array: {data, meta}. data
is the array itself; meta is a DeliveryMeta:
{returned, available, truncated, limit, bySource?}.
available is a real computed count of what matched — never
data.length — so a caller can tell "this is everything" from "this is
page one of more" without a second round trip.
Recall's data holds compact cards — sourceType,
typedId, title, score,
matchedBy, citation, and a snippet under 200 characters,
never full bodies — and meta.bySource breaks the count down per source
type. A brief returns startupNote, vision,
recentSessions, facts, and relatedDocs,
plus rendered text when you ask for markdown — its list-shaped members
carry the same envelope. On the brief's facts lane that envelope gains one more field,
meta.indexed: the count of facts rendered as compressed
index lines rather than full text. It is never
folded into returned, and an indexed fact is not in
droppedItems — it survived, just compressed. These are the exact
shapes the console renders and MCP returns.
Errors #
Errors carry a stable code and map to HTTP status: validation →
400, not found → 404, an embedding failure →
503, storage/config → 500. An unknown path returns
404 {"code":"NOT_FOUND"}. DELETE is the
exception: /facts/:id, /sessions/:id and
/vision/:id are idempotent and never 404. GET
on a missing row still does.
OpenAPI #
The full OpenAPI 3.1 document is served live at GET /openapi.json — point
your client generator or an API explorer at it. A thin typed TypeScript client
(packages/client in the source repo)
wraps the same surface if you'd rather not hand-roll fetches.