Spectron exposes two HTTP surfaces from the api role:
| Surface | Prefix | Authentication |
|---|---|---|
| End-user API | /api/v1/{context_id}/... | Context API key as Authorization: Bearer |
| Management API | /api/v1/... (no context id in path) | Management key as Authorization: Bearer |
Both surfaces share the same host and port (default http://localhost:9090). MCP is served at /mcp on the same listener.
See Management API for Context and key lifecycle.
Authentication
Every request must include your API key as a Bearer token:
Optional idempotent writes accept:
Optional delegated identity on a single request (depth 1, intersected with the target principal’s grants):
Use this when an agent acts for another principal — for example a supervisor tool calling on behalf of a user. The effective authority is always the intersection of the caller’s key, the caller’s grants, and the target’s grants; delegation cannot widen access.
The same Idempotency-Key (scoped to context + principal) within 24 hours returns the stored response. A different body with the same key, or a duplicate request while the first is still in flight, returns 409 Conflict. Failed writes release the reservation so a retry can re-run.
Base URL
Replace {context_id} with the identifier you chose at bootstrap (for example dev or docs_test).
Scope selectors on the wire
Scope on requests is a DNF selector (ScopeSets): an OR of conjunctive clauses, each clause an AND of hierarchical slash paths (for example org/acme/user/alice; a trailing / is optional).
| Direction | Field | Shape |
|---|---|---|
| Write (facts, sessions, uploads) | scopes | [[\"org/acme/user/alice\"]] — legacy alias scope still accepted |
Read (/query, /context) | lens | Same DNF shape; filters by involvement within the caller’s grant |
A flat ["a", "b"] means a OR b. For a AND b in one clause, nest: [["a", "b"]]. Single-path tagging is unchanged in practice — [["org/acme/user/alice"]] or a bare string.
Register paths before first use:
See Contexts and scope.
JSON responses use camelCase field names (queryMs, sessionId, traceId, …).
Unified verbs
| Verb | Method and path | Purpose |
|---|---|---|
| Remember | POST /api/v1/{ctx}/facts | Single fact or turn; infer controls extraction |
| Remember (bulk) | POST /api/v1/{ctx}/facts/batch | Whole conversation in one request |
| Upload | POST /api/v1/{ctx}/documents | Byte ingest; async parse → chunk → extract → embed |
| Recall | POST /api/v1/{ctx}/query | Unified four-tier router over facts and passages |
| Chat | POST /api/v1/{ctx}/chat | Spectron-as-agent (composed over facts + query + LLM) |
Document-only retrieval remains under POST /api/v1/{ctx}/documents/query. Formatted prompt blocks use POST /api/v1/{ctx}/context.
Facts (remember)
Single fact
infer values: full (LLM extraction, default), triples (caller-supplied structured triples), preview (dry run), none (literal store).
Optional observed_at (RFC 3339) sets the known time (created_at) of derived facts. Use when ingesting serial narrative — novels, episodes, franchise films — so asOf recall matches how far the user has read or watched, not when you bulk-imported the corpus. In the example above, this would be useful to replay a narrative in which a user or character is unaware of the fact until the date indicated. See Narrative playback and spoiler safety. Omitted means wall-clock ingest time.
Response (infer: full): nested extraction (entities, attributes, relations, …), plus sessionId, turnId, and mode. infer: none returns chunkId, sessionId, and turnId for the literal memory chunk.
When supplying triples or structured entities, memory_category on entities, attributes, and relations must be one of identity, knowledge, or context. Invalid values are rejected with 400 Bad Request. Instructions and uncertainties use their own tables; episodic material lives on sessions and turns.
Batch conversation
Preferred path for multi-turn ingest (replaces per-turn POST .../sessions/{id}/turns for new integrations):
extract controls batch extraction strategy: whole_conversation (default) runs one LLM pass over the full transcript; per_message runs extraction once per message.
Returns extractions (one extraction result per message or one for the whole conversation), sessionId, and turnIds.
Sessions (episodic introspection)
Sessions remain for browsing transcripts and session-scoped state. Writes should use /facts or /facts/batch.
list_turns paginates: limit defaults to 100 (max 500); offset skips rows. The CLI sessions show / transcript commands page through turns automatically.
Create session body:
Recall and context
Ranked hits (/query)
| Field | Purpose |
|---|---|
lens | DNF scope selector narrowing the read region (clamped to the key’s grant). |
labels | Descriptive key=value tags that filter hits within the allowed region — labels never widen access on their own. |
scope_view | How broadly to fold results within the grant: strict (default — caller’s region only), crossTeam (cross-principal shared reads), or merged (same-fact records at narrower scopes). crossTeam and merged resolve like strict. None widen past the caller’s grant. |
include | Which result families to return: facts (entities and attributes) and/or passages (document chunks and sections). Default: both. Omitted or empty means no narrowing. Filters the response only — retrieval and trace reinforcement still see the full candidate set. |
mode | Closed set: hybrid, vector, bm25, or graph. Invalid values return 400. |
asOf | Known-time filter — recall what the system would have believed at this instant (supersession walk by created_at for attributes; relations are gated by created_at and open validity). Omitted means current state. |
includeDuplicates | When false (default), passage chunks flagged as near-duplicates (duplicate_of set) are excluded from fused chunk recall so the same text does not occupy multiple ranks. Set true to include them (parity with /documents/query). |
source | Free-form label recorded on the retrieval trace for audit replay; does not affect ranking. |
k on /query and document query default to 10 and are capped at 50 (operator-tunable downward only — see Configuration). The internal retrieval pool is fixed separately from k.
Response includes tier (direct, cache, hybrid, or full_context), hits (each with source as a ResultKind: entity, attribute, memory_chunk, chunk, or section; optional occurredAt — known time of the underlying row for temporal resolution), queryMs, and inline trace (with traceId for GET .../traces/{id}).
Formatted context block (/context)
Returns a string suitable for LLM system-prompt injection.
Chat (SSE optional)
Use Accept: text/event-stream for streaming replies.
State, profile, entities
Tri-temporal reads accept asOf, atInstant, validFrom, and validUntil query parameters on entity GETs.
Reflection, lifecycle, maintenance
reflect runs on-demand synthesis and returns traceId (correlate with GET .../traces/{id}); persist on the body stores lower-trust derived facts when permitted by key type.
POST /forget accepts a natural-language query, optional purge: true for permanent erasure, and optional dryRun: true to return a match count without expiring anything (see Forgetting memories).
consolidate responses reference per-record DecisionKind values: create, update, or supersede.
Documents (upload)
Upload uses multipart/form-data; processing is asynchronous via the job queue.
The metadata part is JSON. Optional fields:
| Field | Purpose |
|---|---|
title, source, mimeType, filename | Display and provenance |
scopes | DNF scope selector narrower than the caller's memory:write region — same semantics as remember. Out-of-region scope returns 403. Omit to tag the document with the caller's full write region. Legacy alias scope. |
labels | Descriptive key=value tags stamped on the document and its chunks (not on reconciled graph rows). Keys must not start with _ (400); count caps return 409. |
observedAt | Optional RFC 3339 known time for derived facts — stamp each page or episode on a narrative timeline so asOf queries stay spoiler-safe (cookbook). Omitted means facts date to ingest time. |
List documents
Query parameters use camelCase (pageSize, mimeType). Pagination is zero-indexed (page=0 is the first page).
Document query (/documents/query)
Document-only ranked retrieval accepts the same mode values as unified recall (vector, bm25, hybrid, hybrid_graph). Optional flags useHyde, decomposeQuery, and useReranker are honoured when configured: HyDE embeds a hypothetical answer for the vector leg; decomposition fans out sub-questions; reranking rescores top candidates with a cross-encoder when SPECTRON_RERANKER_URL is set (see Configuration). All degrade gracefully when the backing provider is absent.
When using graph-density reranking, optional graph_edges selects which structural signals contribute. Each value must be a recognised edge kind — unknown values return 400 Bad Request:
| Value | Signal |
|---|---|
knowledge_has_keyword | Keyword overlap between documents |
section_match | Section-heading similarity |
document_link | Cross-document link density |
document_summary | Document-level summary similarity |
Responses may include hybrid_graph on individual hits when several signals combine; that label describes evidence in the result, not an input filter.
Traces and audit
/traces/stats accepts scope[…], since, and windowHours (rolling window for operational counters). The response includes aggregate counts and health signals for retrieval, decision, and response traces in the window.
Scope gate: callers with grant:manage see Context-wide aggregates. Scope-enforcing keys receive a zeroed stats view for regions outside their grant, mirroring trace listing so that cross-principal usage metadata does not leak.
Self-service keys and /me
Members who hold a brokered or admin-minted key can manage their own keys on the data-plane API without a management credential:
GET /me returns caller introspection: principal identity, principal grants, the key’s attenuating grants, effective grants, and delegatedPrincipalId when delegating. Ungated — members deliberately lack grant:manage, so admin-side principal endpoints are not sufficient.
POST /keys mints a key for the caller’s own principal. Optional grants only attenuate (narrow) per verb; widening returns 400. Returns the plaintext secret once.
Per-Context policy (see Configuration):
allow_self_service_keys— setfalseto require Cloud-brokered keys only.max_token_ttl_seconds— server-side TTL clamp on every mint path (management, broker, self-service).
/audit returns rows with a kind filter (decision, retrieval, or response) and traceId linking back to graph-resident traces. Query with ?kind=decision&limit=50.
Scopes and principals
See Scope as security boundary.
Health
Unauthenticated liveness check.
Connectors
Not available in the current release.
Errors
All errors follow RFC 7807 problem details.