Chat completions
POST/v1/chat/completions
Answer a conversation in the OpenAI chat-completions shape, through memory, routing, connector tools and agent mode, streamed or in one reply.
The main endpoint. It takes the OpenAI chat-completions request, so any OpenAI-compatible client works unchanged, and runs it through Memtro: memories retrieved and put in front of the model, the model chosen by routing or named by you, Memtro's memory and connector tools available to it, and in agent mode a multi-step investigation before the answer. Facts from the exchange are learned afterwards, into the memory the key may write.
Set stream: true for server-sent events. A request can run for up to five minutes, which agent runs need; keep client timeouts generous.
Example
Against https://platform.memtro.com, with your key in MEMTRO_API_KEY.
curl https://platform.memtro.com/v1/chat/completions \
-H "Authorization: Bearer $MEMTRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"messages": [{"role": "user", "content": "Who owns the billing migration?"}],
"memtro": {"mode": "agent"}
}'
from openai import OpenAI
client = OpenAI(base_url="https://platform.memtro.com/v1", api_key="sk-memtro-…")
completion = client.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "Who owns the billing migration?"}],
extra_body={"memtro": {"mode": "agent"}},
)
print(completion.choices[0].message.content)
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://platform.memtro.com/v1", apiKey: process.env.MEMTRO_API_KEY });
const stream = await client.chat.completions.create({
model: "auto:agent",
messages: [{ role: "user", content: "Who owns the billing migration?" }],
stream: true,
stream_options: { include_usage: true },
});
for await (const chunk of stream) process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
A non-streaming reply.
{
"id": "chatcmpl-k3Vq…",
"object": "chat.completion",
"created": 1794470400,
"model": "anthropic/claude-opus-5",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "Priya owns the billing migration. BIL-142 moved to her on 3 November." },
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 4210,
"completion_tokens": 188,
"total_tokens": 4398,
"prompt_tokens_details": { "cached_tokens": 3072 }
},
"memtro": {
"mode": "agent",
"engine": "native",
"routing": {
"requested": "auto",
"model": "anthropic/claude-opus-5",
"complexity": 3,
"method": "heuristic",
"reason": "…",
"fallbacks": ["…"],
"kind": "general"
},
"citations": [
{ "system": "linear", "label": "BIL-142 Billing migration", "url": "https://linear.app/…" }
],
"tools": [
{ "tool": "linear_search", "input": { "query": "billing migration" }, "ok": true, "summary": "3 issues" }
]
}
}
Authentication
An API key, as Authorization: Bearer or x-api-key. The key's scope is a ceiling on what the request reaches.
- Every reply, errors included, carries
x-request-idandrequest-idheaders with the same id, startingchatcmpl-. Quote it to support. - Browsers may call it from any origin: it answers CORS preflights and allows the
Authorization,x-api-keyand Anthropic version headers.
Request body
JSON, with Content-Type: application/json.
modelstringOptionalA model id or routing id; see Model ids. A
:agentor:chatsuffix forces the mode. Under the organisation's model lock a named model is answered bymemtroinstead, without an error.messagesarray of objectsRequiredThe conversation, at least one message.
6 fields of
messagesrolestringRequiredWho said it.
contentstring or array of objects or nullOptionalText, or an array of content parts such as
{"type": "text", "text": …}and{"type": "image_url", "image_url": {"url": …}}. An image URL must be adata:URI or an absolute http(s) URL, or the request is refused with a 400. Parts with no equivalent on the model's wire are dropped.namestringOptionalA name for the participant.
tool_callsarray of objectsOptionalOn an assistant turn, the calls it made to your tools:
id,typeandfunction(name,arguments).tool_call_idstringOptionalOn a
toolturn, the call this result answers.cache_controlobjectOptionalA cache breakpoint,
{"type": "ephemeral"}: everything up to here is a stable prefix. Only Anthropic models take explicit breakpoints; elsewhere it is ignored.
streambooleanOptionalAnswer as server-sent events. See Streaming.
stream_optionsobjectOptionalStream settings.
1 field of
stream_optionsinclude_usagebooleanOptionalAdd a final chunk with
choices: []andusagebefore[DONE].
temperaturenumberOptionalSampling temperature, passed to the provider.
top_pnumberOptionalNucleus sampling, passed to the provider.
max_tokensintegerOptionalA ceiling on the answer's tokens; a positive integer.
max_completion_tokensintegerOptionalThe same ceiling under its newer name; wins over
max_tokens.stopstring or array of stringsOptionalWhere to stop generating.
toolsarray of objectsOptionalYour own tools. When the model calls one, the reply carries
tool_callsfor you to run, and you send the results back astoolmessages. Memtro's own tools are separate and need no declaring. A request that declares tools runs inchatmode unless it asks foragent. Withclaude-code/*andcodex/*models your tools are bridged into the CLI run.2 fields of
toolstype"function"RequiredAlways
function.functionobjectRequiredThe tool.
3 fields of
functionnamestringRequiredIts name.
descriptionstringOptionalWhat it does, for the model.
parametersobjectOptionalIts arguments, as JSON Schema.
tool_choicestring or objectOptionalnone,auto,required, or{"type": "function", "function": {"name": …}}to force one tool.reasoning_effortstringOptionalHow hard to think.
high,xhighandmaxask for deep thinking,mediumandlowfor standard, and the provider's own thinking setting is set to match. Any other value,noneincluded, leaves the provider's default.response_formatobjectOptionalThe shape the answer must take, handed to the provider's own structured-output setting. Any other
typeis refused withunsupported_parameter.2 fields of
response_formattypestringOptionaltextasks for nothing;json_objectfor some JSON object;json_schemafor the schema below.json_schemaobjectOptionalFor
json_schema. The same keys sent flat besidetypeare read too.3 fields of
json_schemanamestringOptionalA name for the schema.
descriptionstringOptionalWhat it describes.
schemaobjectRequiredA JSON Schema object.
nintegerOptionalOnly
1is accepted: one choice is returned per request, andnabove 1 is refused withunsupported_parameterrather than answered with one choice.logprobs, top_logprobsanyOptionalRefused with
unsupported_parameter: token probabilities are not returned.user, store, metadata, seed, frequency_penalty, presence_penalty, logit_bias, parallel_tool_callsanyOptionalAccepted and ignored. See Compatibility.
memtroobjectOptionalMemtro's options; see the
memtroobject.brainis accepted as an older name.11 fields of
memtromodestringOptionalagentruns the multi-step harness: plan, query every relevant system, cross-reference, answer once. A:agentor:chatsuffix on the model id does the same. See Agent mode.memorystringOptionalWhere facts learned from this exchange are stored, and a ceiling the key can lower but the request cannot raise: an organisation key never reads or writes personal memory, and a personal key set to "mine only" never reaches the organisation's.
offstores nothing.retrievebooleanOptionalPut relevant memories and graph context in front of the model.
toolsboolean or "memory"OptionalMemtro's own tools:
truefor the memory tools and your connector tools,"memory"for the memory tools alone,falsefor none.show_progressbooleanOptionalIn agent mode, stream a text copy of the tool log as
reasoning_content. The structuredmemtro.activityevents are sent either way.show_thinkingbooleanOptionalReturn the model's thinking on wires that have a block for it.
cachebooleanOptionalServe identical connector tool calls from the short-lived result cache.
as_ofstringOptionalA calendar date,
YYYY-MM-DD: answer from memory as it stood at the end of that day. Retrieval and the memory tools report what was believed then, and nothing from the exchange is learned. Anything that is not a real date is refused withinvalid_as_of. See Time travel.sessionstringOptionalAn opaque id for this conversation, 1 to 200 characters, so repeated turns are recognised as one session: it groups usage and lets retrieval be reused between turns.
projectstringOptionalThe repository or project this session is working in, 1 to 200 characters (for example
owner/repo), so memory about it is preferred. Ignored when the graph has never heard of it.verifybooleanOptionalCheck the drafted answer against the retrieved context with a second model call before returning it, dropping claims nothing in the context supports. One extra model call per answer.
Response
200application/json
A chat.completion, plus a memtro object when there is something to report.
idstringOptionalchatcmpl-…, the same id as thex-request-idheader.object"chat.completion"OptionalAlways
chat.completion.createdintegerOptionalUnix time, in seconds.
modelstringOptionalThe model that was picked.
choicesarray of objectsOptionalExactly one choice.
4 fields of
choicesindexintegerOptionalAlways
0.messageobjectOptionalThe answer.
3 fields of
messagerole"assistant"OptionalAlways
assistant.contentstring or nullOptionalThe text, or null when the model only called your tools.
tool_callsarray of objectsOptionalCalls to your tools, each with
id,type: "function"andfunction(name,argumentsas a JSON string).
finish_reasonstringOptionalWhy it ended.
logprobsnullOptionalAlways null.
usageobjectOptionalTokens.
4 fields of
usageprompt_tokensintegerOptionalInput tokens.
completion_tokensintegerOptionalOutput tokens.
total_tokensintegerOptionalThe two added.
prompt_tokens_detailsobjectOptionalcached_tokens: input the provider served from its cache.
memtroobjectOptionalMode, routing, citations, grounding and Memtro's tool calls; see What comes back.
200text/event-stream
With stream: true: chat.completion.chunk events, then data: [DONE]. See Streaming.
Streaming
Each event is a data: line holding a chat.completion.chunk. The first chunk opens the assistant turn and carries memtro with the mode and the routing decision. Text arrives as delta.content, the model's thinking and agent progress as delta.reasoning_content, and calls to your tools as delta.tool_calls. Each of Memtro's own tool calls sends a memtro.activity event when it starts and when it finishes; with show_progress off those come on chunks with choices: [] and no text. A chunk with choices: [] then carries the closing memtro (Memtro's tool calls, and answered_by when another model answered), a chunk carries finish_reason, stream_options.include_usage adds a usage chunk, and data: [DONE] ends the stream.
If the run fails after the stream has started, the status is already 200: an error chunk is sent instead, and the stream closes without [DONE]. Treat a stream that ends without [DONE] as failed.
data: {"id":"chatcmpl-k3Vq…","object":"chat.completion.chunk","created":1794470400,"model":"anthropic/claude-opus-5","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"logprobs":null}],"memtro":{"mode":"agent","engine":"native","routing":{"requested":"auto","model":"anthropic/claude-opus-5","complexity":3,"method":"heuristic","reason":"…","fallbacks":["…"],"kind":"general"}}}
data: {…,"choices":[{"index":0,"delta":{"reasoning_content":"▸ linear_search(query=\"billing migration\")"},"finish_reason":null,"logprobs":null}],"memtro":{"activity":{"id":"call_1","tool":"linear_search","input":{"query":"billing migration"},"phase":"start"}}}
data: {…,"choices":[{"index":0,"delta":{"reasoning_content":"\n ✓ 3 issues"},"finish_reason":null,"logprobs":null}],"memtro":{"activity":{"id":"call_1","tool":"linear_search","phase":"done","ok":true,"summary":"3 issues"}}}
data: {…,"choices":[{"index":0,"delta":{"content":"Priya owns the billing migration"},"finish_reason":null,"logprobs":null}]}
data: {…,"choices":[],"memtro":{"tools":[{"tool":"linear_search","input":{"query":"billing migration"},"ok":true,"summary":"3 issues"}]}}
data: {…,"choices":[{"index":0,"delta":{},"finish_reason":"stop","logprobs":null}]}
data: {…,"choices":[],"usage":{"prompt_tokens":4210,"completion_tokens":188,"total_tokens":4398,"prompt_tokens_details":{"cached_tokens":3072}}}
data: [DONE]
# A failure after the stream started: no [DONE] follows.
data: {"error":{"message":"…","type":"server_error","param":null,"code":null}}
Errors
In the OpenAI envelope, {"error": {"message", "type", "param", "code"}}. What each code means and what to do about it is in the error catalogue.
| Status | Code | When |
|---|---|---|
| 400 | none | The body failed validation. |
| 400 | unsupported_parameter |
|
| 400 | invalid_as_of |
|
| 400 | routing_unavailable |
|
| 400 | provider_not_configured | No key for the provider the model needs. |
| 400 | invalid_provider_base_url | An OpenAI-compatible endpoint's base URL cannot be used: not a URL, not |
| 400 | provider_base_url_not_allowed | An OpenAI-compatible endpoint's host is not approved for the organisation, is private or internal, does not resolve, or redirected, whether refused before the request or during it. |
| 401 | invalid_api_key | The key is missing, malformed, revoked or expired. |
| 402 | subscription_inactive | The organisation has no active subscription. |
| 404 | model_not_found | Unknown model id. |
| 429 | allowance_exhausted | The month's allowance for the request's level is spent. |
| provider's | upstream_error | The provider returned an error. |
| 503 | engine_unavailable | The CLI a |
| 500 | none | An internal error. |
Notes
Model lock. Organisations start with the model lock on: a request naming a specific model is answered by memtro instead of being refused, and when the request is routed, memtro.routing.locked is true. GET /v1/models then lists only the routing ids and memtro.
Client tools with CLI models. Your tools work with claude-code/* and codex/* models too: they are bridged into the CLI run and come back as tool_calls. When you send the results, the run carries on where it stopped.