This document describes every REST endpoint exposed by Engram. All endpoints are served at http://localhost:3000 by default.
| method | path | description |
|---|---|---|
| POST | /sessions | create a new session (optional agent_id) |
| POST | /sessions/{session_id}/messages | add a message |
| GET | /sessions/{session_id}/context | get assembled context |
| POST | /sessions/{session_id}/search | semantic search over long-term memory |
| DELETE | /sessions/{session_id} | delete a session and all its memories |
| PUT | /sessions/{session_id}/core-memory | add a core memory fact |
| PUT | /sessions/{session_id}/visibility | set session visibility (Private or Shared) |
| GET | /sessions/{session_id}/knowledge | get full knowledge graph for the session |
| GET | /sessions/{session_id}/knowledge/entities/{entity_name} | get all entities connected to a named entity |
| GET | /sessions/{session_id}/knowledge/path | find shortest path between two entities |
| GET | /sessions/{session_id}/knowledge/export | export knowledge graph (JSON or Graphviz DOT) |
| GET | /knowledge/global | get the cross-session global knowledge graph |
| GET | /knowledge/global/entities/{name} | get entities connected to a named entity (global) |
| GET | /knowledge/global/entities/{name}/sources | get sessions that contributed a named entity |
| GET | /knowledge/global/path | find shortest path in the global graph |
| GET | /knowledge/global/export | export global graph (JSON or Graphviz DOT) |
| GET | /knowledge/global/conflicts | list conflicting facts across sessions |
| GET | /sessions/{session_id}/summaries | list consolidated summaries for a session |
| POST | /sessions/{session_id}/consolidate | manually trigger consolidation (leader only) |
| GET | /sessions/{session_id}/at | reconstruct session state at a past point (cluster) |
| GET | /sessions/{session_id}/history | list the session's committed timeline (cluster) |
| GET | /sessions/{session_id}/diff | structural diff between two points (cluster) |
| POST | /sessions/{session_id}/checkpoints | pin a named checkpoint to a log index (cluster) |
| GET | /sessions/{session_id}/checkpoints | list a session's named checkpoints |
| GET | /health | health check |
| GET | /metrics | Prometheus metrics |
| GET | /api-docs/openapi.json | OpenAPI specification |
| GET | /swagger-ui/ | Swagger UI |
| GET | /cluster | cluster status (cluster mode only) |
| POST | /cluster/init | initialize the Raft cluster |
| POST | /cluster/add-learner | add a learner node |
| POST | /cluster/change-membership | promote learners to full voting members |
Create a new session. Optionally supply an agent_id to associate the session with a named agent. In cluster mode, a non-empty agent_id is persisted via a RegisterSession Raft command so the mapping survives restarts.
request body: optional
{
"agent_id": "agent-42"
}success response:
- status: 200
- body:
{
"session_id": "b1e2c3d4-5678-1234-9abc-def012345678"
}error responses:
- 500: internal server error
examples:
# no agent association
curl -X POST http://localhost:3000/sessions
# with agent association
curl -X POST http://localhost:3000/sessions \
-H 'content-type: application/json' \
-d '{"agent_id":"agent-42"}'Add a message to a session.
path parameters:
session_id(string): session identifier
request body:
{
"id": "optional-client-generated-uuid",
"role": "user",
"content": "hello, what is rust?"
}id is optional. role and content are required.
success response:
- status: 204 (no content)
error responses:
- 400: invalid request body
- 422: missing required fields
- 500: failed to store the message
- 503: embedding queue unavailable
example:
curl -X POST http://localhost:3000/sessions/{session_id}/messages \
-H 'content-type: application/json' \
-d '{"role":"user","content":"hello, what is rust?"}'Get the assembled context for a session.
path parameters:
session_id(string): session identifier
query parameters:
max_tokens(integer, optional, default: 8000): max tokens for contextsimilarity_threshold(float, optional, default: 0.7): min similarity for long-term memorieslong_term_top_k(integer, optional, default: 10): max number of long-term memories
success response:
- status: 200
- body:
{
"context": "core memories:\n- user name is alex\n\nconversation:\nuser: hello\n..."
}error responses:
- 400: invalid query parameters
- 404: session not found
- 500: failed to assemble context
example:
curl http://localhost:3000/sessions/{session_id}/contextSemantic search over long-term memory.
path parameters:
session_id(string): session identifier
request body:
{
"query": "rust async",
"top_k": 5
}Both query and top_k are required.
success response:
- status: 200
- body:
{
"results": [
{ "text": "...", "score": 0.92 }
]
}error responses:
- 400: invalid request body
- 500: failed to search session memory
example:
curl -X POST http://localhost:3000/sessions/{session_id}/search \
-H 'content-type: application/json' \
-d '{"query":"rust async","top_k":5}'Delete a session and all its memories.
path parameters:
session_id(string): session identifier
success response:
- status: 204 (no content)
error responses:
- 500: failed to delete session
example:
curl -X DELETE http://localhost:3000/sessions/{session_id}Add a core memory fact to a session.
path parameters:
session_id(string): session identifier
request body:
{
"fact": "user prefers dark mode"
}fact is required and must not be empty.
success response:
- status: 204 (no content)
error responses:
- 400: missing or empty fact
- 500: failed to add core memory
example:
curl -X PUT http://localhost:3000/sessions/{session_id}/core-memory \
-H 'content-type: application/json' \
-d '{"fact":"user prefers dark mode"}'Set the visibility of a session. Sessions are Private by default. Setting a session to Shared causes its extracted knowledge to be merged into the global knowledge graph.
In cluster mode this goes through Raft so all nodes see the same visibility state. In standalone mode the command is accepted and no-ops.
path parameters:
session_id(string): session identifier
request body:
{
"visibility": "Shared"
}visibility must be "Shared" or "Private".
success response:
- status: 204 (no content)
error responses:
- 400: invalid request body
- 307: redirect to leader (cluster mode, follower received the request)
- 500: failed to set visibility
example:
curl -X PUT http://localhost:3000/sessions/{session_id}/visibility \
-H 'content-type: application/json' \
-d '{"visibility":"Shared"}'Health check endpoint.
success response:
- status: 200
example:
curl http://localhost:3000/healthPrometheus metrics endpoint.
success response:
- status: 200
- body: prometheus text format
example:
curl http://localhost:3000/metricsOpenAPI specification (JSON).
success response:
- status: 200
- body: openapi json
example:
curl http://localhost:3000/api-docs/openapi.json | jqSwagger UI for interactive API docs.
success response:
- status: 200
- body: html
example:
curl http://localhost:3000/swagger-ui/These endpoints are only available when the node is started in cluster mode (i.e., NODE_ID is set). Standalone nodes return 503.
Returns the current Raft cluster status for this node.
success response:
- status: 200
- body:
{
"node_id": 1,
"role": "Leader",
"leader_id": 1,
"term": 3,
"last_applied_index": 12,
"members": [
{ "id": 1, "addr": "node-1:9001", "last_log_index": 12 },
{ "id": 2, "addr": "node-2:9001", "last_log_index": 12 },
{ "id": 3, "addr": "node-3:9001", "last_log_index": 11 }
]
}error responses:
- 503: cluster mode not enabled (NODE_ID not set)
example:
curl http://localhost:3000/clusterInitializes the Raft cluster. Call this once from any node after all nodes are running. Reads NODE_ID, RAFT_ADDR (or RAFT_ADVERTISE_ADDR), and CLUSTER_PEERS from the node's environment to build the initial membership set.
request body: none
success response:
- status: 200
error responses:
- 500: cluster initialization failed (e.g., already initialized)
- 503: cluster mode not enabled
example:
curl -X POST http://localhost:3000/cluster/initAdds a new node as a learner. Learners receive log replication but do not vote in elections. Promote to a full member with /cluster/change-membership.
request body:
{ "node_id": 4, "addr": "node-4:9001" }success response:
- status: 200
error responses:
- 500: failed to add learner
- 503: cluster mode not enabled
example:
curl -X POST http://localhost:3000/cluster/add-learner \
-H 'content-type: application/json' \
-d '{"node_id":4,"addr":"node-4:9001"}'Changes the cluster membership to the given set of node IDs. Nodes in the new set that are currently learners are promoted to full members; nodes not in the new set are removed.
request body:
{ "members": [1, 2, 3] }success response:
- status: 200
error responses:
- 500: membership change failed
- 503: cluster mode not enabled
example:
curl -X POST http://localhost:3000/cluster/change-membership \
-H 'content-type: application/json' \
-d '{"members":[1,2,3,4]}'These endpoints query the per-session knowledge graph built by the background knowledge extraction pipeline. Entities and relationships are extracted automatically from every message added to the session. Results reflect all committed messages; there may be a brief delay while extraction jobs are processed.
Returns all entities and edges in the session's knowledge graph.
path parameters:
session_id(string): session identifier
success response:
- status: 200
- body:
{
"session_id": "abc123",
"entities": [
{ "name": "Alice", "entity_type": "Person", "attributes": {} },
{ "name": "OpenAI", "entity_type": "Organization", "attributes": {} }
],
"edges": [
{ "from": "Alice", "to": "OpenAI", "relationship_type": "works_at" }
]
}example:
curl http://localhost:3000/sessions/{session_id}/knowledgeReturns all entities directly connected to the named entity, including both incoming and outgoing relationships.
path parameters:
session_id(string): session identifierentity_name(string): the entity to look up
success response:
- status: 200
- body:
{
"entity_name": "Alice",
"related": [
{
"name": "OpenAI",
"entity_type": "Organization",
"relationship_type": "works_at",
"direction": "Outgoing"
}
]
}error responses:
- 404: entity not found in this session's graph
example:
curl http://localhost:3000/sessions/{session_id}/knowledge/entities/AliceFinds the shortest directed path (BFS over outgoing edges) between two named entities.
path parameters:
session_id(string): session identifier
query parameters:
from(string, required): source entity nameto(string, required): target entity name
success response:
- status: 200
- body (
pathisnullif no path exists):
{
"from": "Alice",
"to": "Bob",
"path": [
{ "from": "Alice", "relationship_type": "works_at", "to": "OpenAI" },
{ "from": "OpenAI", "relationship_type": "employs", "to": "Bob" }
]
}example:
curl "http://localhost:3000/sessions/{session_id}/knowledge/path?from=Alice&to=Bob"Exports the knowledge graph in JSON or Graphviz DOT format.
path parameters:
session_id(string): session identifier
query parameters:
format(string, optional, default:json):jsonordot
success response:
- status: 200
- content-type:
application/jsonfor JSON,text/vnd.graphvizfor DOT - body (JSON):
{
"session_id": "abc123",
"entities": [...],
"edges": [...]
}- body (DOT):
digraph knowledge {
"Alice" [label="Alice\n(Person)"];
"OpenAI" [label="OpenAI\n(Organization)"];
"Alice" -> "OpenAI" [label="works_at"];
}
example:
curl "http://localhost:3000/sessions/{session_id}/knowledge/export?format=dot"The global graph aggregates entities and relationships from all sessions whose visibility is set to Shared. These endpoints return data from the local in-memory global graph. In cluster mode the global graph is eventually consistent with the leader.
Returns all entities and edges in the global knowledge graph.
success response:
- status: 200
- body:
{
"entities": [
{ "name": "Alice", "entity_type": "Person", "attributes": {} },
{ "name": "OpenAI", "entity_type": "Organization", "attributes": {} }
],
"edges": [
{ "from": "Alice", "to": "OpenAI", "relationship_type": "works_at" }
]
}example:
curl http://localhost:3000/knowledge/globalReturns all entities directly connected to the named entity in the global graph, including both incoming and outgoing relationships.
path parameters:
name(string): the entity to look up
success response:
- status: 200
- body:
{
"entity_name": "Alice",
"related": [
{
"name": "OpenAI",
"entity_type": "Organization",
"relationship_type": "works_at",
"direction": "Outgoing"
}
]
}example:
curl http://localhost:3000/knowledge/global/entities/AliceReturns the list of session IDs that contributed the named entity to the global graph.
path parameters:
name(string): the entity to look up
success response:
- status: 200
- body:
{
"entity_name": "Alice",
"sources": ["session-abc123", "session-def456"]
}example:
curl http://localhost:3000/knowledge/global/entities/Alice/sourcesFinds the shortest directed path (BFS over outgoing edges) between two named entities in the global graph.
query parameters:
from(string, required): source entity nameto(string, required): target entity name
success response:
- status: 200
- body (
pathisnullif no path exists):
{
"from": "Alice",
"to": "Bob",
"path": [
{ "from": "Alice", "relationship_type": "works_at", "to": "OpenAI" },
{ "from": "OpenAI", "relationship_type": "employs", "to": "Bob" }
]
}example:
curl "http://localhost:3000/knowledge/global/path?from=Alice&to=Bob"Exports the global knowledge graph in JSON or Graphviz DOT format.
query parameters:
format(string, optional, default:json):jsonordot
success response:
- status: 200
- content-type:
application/jsonfor JSON,text/vnd.graphvizfor DOT
example:
curl "http://localhost:3000/knowledge/global/export?format=dot"Returns all detected conflicts in the global graph. A conflict occurs when two different sessions report different relationship types between the same pair of entities.
success response:
- status: 200
- body:
{
"conflicts": [
{
"entity": "Alice",
"related": "OpenAI",
"relationship_types": ["works_at", "founded"],
"sessions": ["session-abc123", "session-def456"]
}
]
}example:
curl http://localhost:3000/knowledge/global/conflictsThese endpoints give access to the consolidated memory produced by the leader's summarization scheduler. When a session's short-term message count exceeds CONSOLIDATION_THRESHOLD, the leader summarizes the oldest messages, replicates the result as an ApplySummary command, and every node atomically stores the summary and trims the consumed raw messages.
Returns all consolidated summaries for a session, ordered by Raft log index.
path parameters:
session_id(string): session identifier
success response:
- status: 200
- body:
{
"session_id": "abc123",
"summaries": [
{
"id": "11111111-1111-1111-1111-111111111111",
"text": "Alice discussed her role at OpenAI and her preference for Rust.",
"created_at_index": 72,
"consumed_message_ids": ["m1", "m2", "m3"],
"consumed_count": 3,
"model": "gpt-4o-mini",
"prompt_version": "summarize_v1"
}
]
}error responses:
- 500: failed to retrieve summaries
example:
curl http://localhost:3000/sessions/{session_id}/summariesManually triggers consolidation for a session. The leader summarizes all messages beyond CONSOLIDATION_TARGET_WINDOW, stores the result, and trims the consumed raw messages. Useful for debugging, verification, and reproducible cluster tests.
In cluster mode, followers return 307 with a Location header pointing to the leader.
path parameters:
session_id(string): session identifier
request body: none
success response:
- status: 202 (accepted)
- body:
{
"summary_id": "11111111-1111-1111-1111-111111111111"
}error responses:
- 307: redirect to leader (cluster mode, follower received the request)
- 409: consolidation already in flight for this session
- 422: session has fewer messages than the target window; nothing to consolidate
- 500: summarization or replication failed
example:
curl -X POST http://localhost:3000/sessions/{session_id}/consolidateStage 5 adds time-travel reads and named checkpoints. Reconstruction replays this node's own Raft log against its nearest retained snapshot into a throwaway in-memory state machine. The log records committed results, so replay is deterministic and read-only. Reads need no consensus and run node-locally; only creating a checkpoint is a cluster mutation and routes through Raft.
Retention keeps the last HISTORY_SNAPSHOTS snapshots plus the log tail above the oldest retained snapshot. A point older than that window is gone: reconstructing or diffing it returns 410 Gone.
These endpoints require the reconstructor, which only exists in cluster mode. Standalone nodes return 501 Not Implemented for /at, /history, /diff, and (for creation) /checkpoints.
Reconstructs the session's state at a past point. Select the point with exactly one of the query parameters below.
path parameters:
session_id(string): session identifier
query parameters (exactly one required):
index(integer): a Raft log indexcheckpoint(string): a named checkpoint, resolved to its pinned indexat(string, RFC 3339): resolves to the newest message at or before this timestamp
success response:
- status: 200
- body:
{
"at_index": 42,
"messages": [ { "id": "m1", "role": "user", "content": "hello", "timestamp": "2026-07-08T12:00:00Z" } ],
"facts": ["user prefers dark mode"],
"entities": [ { "name": "Alice", "entity_type": "Person", "attributes": {} } ],
"relationships": [ { "from": "Alice", "to": "OpenAI", "relationship_type": "works_at" } ],
"summaries": [ { "id": "...", "text": "...", "created_at_index": 30, "consumed_message_ids": [], "consumed_count": 3, "model": "gpt-4o-mini", "prompt_version": "summarize_v1" } ]
}error responses:
- 400: none, more than one, or invalid selector (e.g. malformed
attimestamp) - 404: named checkpoint not found, or no message at/before the given timestamp
- 410: the requested point is below the retained window
- 501: standalone mode (no reconstructor)
examples:
curl "http://localhost:3000/sessions/{session_id}/at?index=42"
curl "http://localhost:3000/sessions/{session_id}/at?checkpoint=v1.0"
curl "http://localhost:3000/sessions/{session_id}/at?at=2026-07-08T12:00:00Z"Returns the session's committed changes in log-index order over the retained window.
path parameters:
session_id(string): session identifier
success response:
- status: 200
- body (each entry is
{index, kind, session_id}):
{
"history": [
{ "index": 40, "kind": "AddMessage", "session_id": "abc123" },
{ "index": 41, "kind": "ApplySummary", "session_id": "abc123" },
{ "index": 42, "kind": "CreateCheckpoint", "session_id": "abc123" }
]
}error responses:
- 501: standalone mode (no reconstructor)
example:
curl http://localhost:3000/sessions/{session_id}/historyStructural diff between two points. "Added" means present at to but not from; identities collide by a stable key per kind, so re-ordering or attribute-only churn does not register.
path parameters:
session_id(string): session identifier
query parameters (both required):
from(integer): earlier log indexto(integer): later log index;frommust be<= to
success response:
- status: 200
- body (
message_count_deltais signed, so a consolidation trim shows negative):
{
"entities_added": [],
"entities_removed": [],
"relationships_added": [],
"relationships_removed": [],
"facts_added": ["user prefers dark mode"],
"facts_removed": [],
"summaries_added": [],
"summaries_removed": [],
"message_count_delta": 2
}error responses:
- 400:
from>to - 410: either endpoint is below the retained window
- 501: standalone mode (no reconstructor)
example:
curl "http://localhost:3000/sessions/{session_id}/diff?from=30&to=42"Pins a name to a log index on every node. Omitting at_index pins the current applied index ("now"), stamped before submit so replay stays deterministic. This is a cluster mutation and routes through Raft; a follower 307s to the leader.
Create is idempotent and non-repointing: re-creating an existing name keeps its original index, and the response echoes the stored index, not the requested one.
path parameters:
session_id(string): session identifier
request body:
{
"name": "v1.0",
"at_index": 42
}name is required and must not be empty. at_index is optional (defaults to the current applied index).
success response:
- status: 201
- body:
{
"session_id": "abc123",
"name": "v1.0",
"at_index": 42
}error responses:
- 400: empty name
- 307: redirect to leader (cluster mode, follower received the request)
- 501: standalone mode
- 500: replication failed
example:
curl -X POST http://localhost:3000/sessions/{session_id}/checkpoints \
-H 'content-type: application/json' \
-d '{"name":"v1.0","at_index":42}'Lists the session's checkpoints. Reads the shared checkpoint store directly, so it works on any node without reconstruction (available in standalone mode too).
path parameters:
session_id(string): session identifier
success response:
- status: 200
- body:
{
"checkpoints": [
{ "session_id": "abc123", "name": "v1.0", "at_index": 42 }
]
}error responses:
- 500: failed to list checkpoints
example:
curl http://localhost:3000/sessions/{session_id}/checkpoints