Memory API
Endpoint reference
Five endpoints today, and what is coming. Every field below is the wire name.
POST/ingest
Push one unit of content. Runs the full pipeline and queues per tenant, so content applies in the order it is sent.
| Field | Description |
|---|---|
contentstringrequired | The raw text. No shape is imposed. |
labelstring | Human-readable descriptor for the kind of content. Defaults to "Ingested data point". |
idstring | Stable identifier for this source. Re-sending the same id replaces the stored point. Omit and one is generated. |
timestampISO 8601 | When the content happened, not when it was sent. Backdatable. Defaults to now. |
metadataobject | Arbitrary JSON stored alongside the point and never interpreted. |
appendboolean | Splice content onto the stored point instead of replacing it, for a growing log or a continuing conversation. Only the new tail is re-embedded. |
groupstring | Free-form grouping key, max 200 characters. Points sharing a group are distilled into one dated record. |
authorstring | Who produced this content. Attached to the source row rather than to each chunk of it, so a multi-speaker transcript keeps one author for the source and the speakers stay inside the text. |
teamIdstring | The team's hash, required only when the token reaches more than one project. It is a hash string, not the numeric id. |
audiencestring or arraycoming soon | NOT AVAILABLE YET. This field is not bound: send it and it is ignored, and the point lands visible to the whole project. It will scope a data point to particular end users, and it is the next milestone on the roadmap below. |
{
"teamId": "wZ4mKq",
"ingestionId": "b41c9e2a7f6d4c118a3e",
"sourceId": "call-8821",
"status": "queued"
}GET/ingest/{ingestionId}
Poll until the queue has applied the content and finished its deferred maintenance. Pass teamId as a query parameter if the token reaches more than one project.
{
"teamId": "wZ4mKq",
"ingestionId": "b41c9e2a7f6d4c118a3e",
"status": "completed",
"settled": true,
"blocked": false,
"readiness": {
"raw": 1.0,
"comprehension": 1.0,
"consolidation": 1.0,
"parked": 0
}
}- Gate on
settled, not onstatus.settledis true only when every stage has drained with nothing parked.statuscan read"completed"while distillation is still outstanding, and reading then returns a half-written graph. blocked: truemeans every remaining row parked. Stop polling and inspect the feed rather than waiting out the timeout.readinessis a live progress vector, enough to show a real progress bar instead of a spinner.- The queue is FIFO, so the last ingestion id of a feed covers the whole feed. There is no need to poll every id.
PUT/ingest/group
Replace the entire contents of a group in one call. Every point currently in the group is superseded by the items you send, so this is the call for a source that is re-synced rather than appended to: a document that changed, a thread re-fetched in full, a nightly export of a record.
| Field | Description |
|---|---|
groupstringrequired | The group to replace, max 200 characters. Same key POST /ingest takes. |
itemsarrayrequired | The full new contents of the group. Each item carries the fields below. |
metadataobject | Arbitrary JSON applied to the batch and never interpreted. |
teamIdstring | The team's hash, required only when the token reaches more than one project. |
An item takes the same fields as a single /ingest, minus group (the route already names it) and minus append (a replace is the whole content by definition): content (required), label, id, timestamp and author.
{
"group": "acme-pilot",
"items": [
{ "content": "Nadia confirmed the pilot ships March 14. Budget is 32k.",
"label": "Account review call",
"timestamp": "2026-02-03T09:00:00Z",
"author": "Nadia Haddad" },
{ "content": "Budget for the Acme pilot moved to 40k.",
"label": "Email from Nadia",
"timestamp": "2026-07-28T16:00:00Z",
"author": "Nadia Haddad" }
]
}Accepted and queued exactly like POST /ingest, and polled the same way through GET /ingest/{ingestionId}.
POST/recall
Returns ranked evidence with its sources and no generated text, for running a separate model over the context.
| Field | Description |
|---|---|
querystringrequired | A natural-language question. Retrieval plans its own traversals from it. |
maxTokensinteger | A token budget for the returned evidence rather than a result count. Evidence is included most-relevant-first until the budget is spent. Minimum 256. Omit for everything. |
limitinteger | Cap on evidence matches returned. |
queryTimestampISO 8601 | Anchors relative-time reasoning ("last week") to a point in the past. Use it when replaying a historical conversation. |
includeTimelineboolean | Include the complete dated record for the subject, not only the top matches. |
verbatimboolean | Return raw source excerpts instead of the distilled form. Ordering questions need this, because only the raw corpus preserves within-source order. |
tagstring | Free-form correlation tag, echoed into usage records. |
teamIdstring | The team's hash, required only when the token reaches more than one project. It is a hash string, not the numeric id. |
{
"teamId": "wZ4mKq",
"results": [ /* typed matches, each with its source and score */ ],
"evidence": [
"Nadia confirmed the pilot ships March 14 (Account review call, 2026-02-03)",
"Budget for the Acme pilot moved to 40k (Email from Nadia, 2026-07-28)"
],
"truncated": false,
"timelineIsVerbatim": false,
"intent": "Lookup",
"degradedArms": [],
"usage": {
"recallTokensIn": 812,
"recallTokensOut": 96,
"recallCostUsd": 0.000214
}
}degradedArmsnames any retrieval arm that stayed degraded after its retry, which tells a partial search apart from a complete one that found little. An empty array means every arm ran.truncatedmeans the token budget cut the result set. Bothresultsandevidenceare cut identically.
POST/answer
Recall plus a grounded answer. It shares most of /recall's fields but NOT limit or maxTokens: the answerer sizes its own context, which is what budget names. Sending either is ignored rather than honoured.
| Field | Description |
|---|---|
querystringrequired | The question to answer. |
modepipeline | agent | pipeline (default) is one shot: retrieve, then answer. agent runs a loop that can refine its own retrieval before committing, at higher latency and cost. |
responseSchemaJSON Schema | Force a structured answer. Supported in pipeline mode only; sending it with mode: "agent" is rejected rather than silently ignored. |
budgetstring | Named context budget for the answerer, in place of the token budget /recall takes. |
queryTimestampISO 8601 | Anchors relative-time reasoning to a point in the past, exactly as on /recall. |
includeTimelineboolean | Include the complete dated record for the subject, not only the top matches. |
verbatimboolean | Answer from raw source excerpts instead of the distilled form. |
tagstring | Free-form correlation tag, echoed into usage records. |
teamIdstring | The team's hash, required only when the token reaches more than one project. |
{
"teamId": "wZ4mKq",
"answer": "March 14, 2026. Nadia confirmed it on the February 3 account review call.",
"evidence": [
"Nadia confirmed the pilot ships March 14 (Account review call, 2026-02-03)"
],
"abstained": false,
"answerer": "gemini-3.5-flash",
"intent": "Lookup",
"recordKept": 12,
"recordTotal": 12,
"degradedArms": [],
"usage": {
"answerTokensIn": 2140,
"answerTokensOut": 38,
"answerCachedTokensIn": 1792,
"answerCostUsd": 0.000431,
"recallTokensIn": 812,
"recallTokensOut": 96,
"recallCostUsd": 0.000214
}
}abstained: truemeans the memory did not hold the answer, so the model declined to generate one. Handle it as a normal outcome.recordKeptandrecordTotalseparate "the record never loaded" from "the record loaded and the answer still missed": a retrieval problem versus a reasoning one.- In
agentmode the response carriestoolCallsinstead ofevidence, listing the retrieval steps the loop took.
Entities, relationships and hops
| Route | What it will do |
|---|---|
GET/entities | Search entities by text and type. The list view over the graph, for populating a UI rather than answering a question. |
POST/entities | Create an entity directly, instead of waiting for extraction to derive it from content. |
GET/entities/{id} | One entity with its three layers: the mentions it was built from, the dated observations drawn out of them, and the value that holds now. |
PATCH/entities/{id} | Correct or set values on an entity without re-ingesting the content it came from. |
DELETE/entities/{id} | Remove an entity along with the facts derived from it. |
GET/entities/{id}/memories | The memories behind one entity: verbatim mentions with their sources and timestamps, and the observations drawn from them. |
GET/entities/{id}/relationships | The typed relationships into and out of an entity, with the dates they hold for. |
GET/entities/{id}/hops | Traverse outward to a given depth, for walking a neighbourhood of the graph rather than querying it. |