API reference
Store, recall, and the keys for both.
Two endpoints do the work — /memories and /recall — and a third issues the key for them. A key lasts seven days and holds twenty chunks, searched alongside a demonstration corpus so recall has something to rank on the first call. Regional pinning and customer-managed keys are not exposed yet; responses carry "sandbox": true until they are.
Quickstart
Take a key, store a chunk, ask a question. Paste the whole block into a terminal.
KEY=$(curl -s -X POST $ITAN_HOST/api/keys | jq -r .key)
curl -s -X POST $ITAN_HOST/api/memories \
-H "Authorization: Bearer $KEY" \
-d '{"source":"notes.md","text":"Rate limit is 600 rpm per key."}'
curl -s -X POST $ITAN_HOST/api/recall \
-H "Authorization: Bearer $KEY" \
-d '{"query":"what is the rate limit?","max_tokens":2000}' \
| jq '{sufficient, tokens_estimated, prompt}'Issue a key
POST /api/keys
Returns an HMAC-signed sandbox key. Takes no body, and nothing is recorded — the signature is what gets verified on later requests. Five per minute per address.
{
"key": "itan_sb_1785404658352.gSYI...zQ.i8Xk...",
"expires_at": "2026-08-06T09:14:22.104Z",
"scope": "memories:write recall:read",
"sandbox": true
}Store a chunk
POST /api/memories
Stores one chunk under the calling key. It is queryable on the next recall — no indexing delay to wait out. GET the same path to list what the key holds.
- textstringrequired
- The content to store. 2,000 characters or fewer in the sandbox.
- sourcestring
- Where it came from. Returned on every chunk that cites it. Defaults to "untitled".
{
"id": "m_i8Xk_1",
"source": "notes.md",
"chunk": 1,
"queryable": true,
"stored_for_key": 1,
"sandbox": true
}Delete chunks
DELETE /api/memories
Removes one chunk when you pass ?id=, or everything the key holds when you do not. The response is the count that no longer exists — the next recall cannot return any of it.
{
"deleted": 3,
"scope": "key",
"remaining": 0,
"sandbox": true
}Recall context
POST /api/recall
Runs the strategy you name over the demonstration corpus and your stored chunks, then returns them ranked and cited plus a prompt-ready string. Sixty per minute.
- querystringrequired
- The question. Embedded the same way your stored text was.
- top_kinteger
- How many chunks to return, 1–20. Defaults to 8.
- max_tokensinteger
- Budget for the whole returned prompt, 64–32000. Returns as many ranked chunks as fit and no more. Overrides top_k, which becomes the ceiling on the pool it packs from.
- as_ofstring
- ISO date. Runs the query against the corpus as it stood that day — anything written later is invisible, including your own chunks.
- strategystring
- One of semantic, hybrid, recency, mmr, graph, rerank. Defaults to hybrid.
{
"chunks": [
{
"text": "Overage billing shall not exceed 115%...",
"source": "acme_msa_2024.pdf",
"chunk": 41,
"score": 1,
"written_at": "2024-03-01"
}
],
"prompt": "Answer using only the context below...",
"sufficient": true,
"sufficiency_reason": "3 of 3 query terms appear in the returned context.",
"strategy": "hybrid",
"tokens_estimated": 397,
"returned": 8,
"latency_ms": 3,
"sandbox": true
}prompt is the field to use. It carries the instruction, the question, and the numbered chunks, so it drops straight into a system prompt. What each strategy does.
Ask for a budget, not a count
You know how much room the context has; you do not know how many chunks fill it. Send max_tokens and recall packs ranked chunks until the assembled prompt would exceed it, then stops. tokens_estimated is what it spent — an estimate at four characters per token, not a tokeniser reading, and it errs high.
Know when the answer is not in there
Ranked retrieval always returns its best chunks, even when the best is merely the least bad — which is where most grounded hallucination starts. sufficient is false when the returned context does not cover the question, so you can branch instead of finding out from the model. sufficiency_reason names what was missing. It is a lexical coverage test: it catches a question the corpus has nothing to say about, and will not catch a paraphrase that shares no vocabulary with its own answer.
Ask what you knew in March
as_of runs the query against the corpus as it stood on a past date. Anything written later is invisible — not down-ranked, absent — and the ranking statistics are rebuilt over what remains. Recency decays from that date rather than from today.
Errors
Every failure returns a JSON body with a stable error code and a human-readable message. Match on the code, print the message.
- 401
- missing_authorization
- No Authorization header. Send Bearer <key>.
- 401
- invalid_key
- Signature did not verify. The key was altered or was not issued by us.
- 401
- key_expired
- Sandbox keys last seven days. Request another.
- 400
- invalid_json
- The body was not parseable JSON.
- 422
- invalid_text
- text was missing, empty, or over 2,000 characters.
- 422
- invalid_query
- query was missing or empty.
- 422
- invalid_top_k
- top_k was not an integer between 1 and 20.
- 422
- invalid_max_tokens
- max_tokens was not an integer between 64 and 32000.
- 422
- invalid_as_of
- as_of was not an ISO 8601 date.
- 422
- invalid_strategy
- strategy was not one of the six.
- 409
- quota_exceeded
- A sandbox key holds 20 chunks. Delete some, or request another key.
- 404
- not_found
- The id given to DELETE is not a chunk this key holds.
- 429
- rate_limited
- 60 recalls and 30 writes per minute. Retry-After is set.