Skip to content
Get Started

Answers API

Answers API (Chat Completions)

POST /v1/chat/completions — a closed, grounded completion over the Subject's real health data.

POST /v1/chat/completions
Authorization: Bearer mb_live_*
Content-Type: application/json

The Answers API is a closed, grounded completion: one question in, one evidence-backed answer out. The platform’s agent gathers the Subject’s real health data with server-side tools (you can’t add or remove them here), then returns one clean answer plus a traceable tool trace. Set stream: true for SSE.

It is deliberately closed — no client tools, no response formatting, no multi-turn state. That makes it easy to embed: call it directly, or wrap it as one tool inside your own agent. If you need client-side tools, stored conversations, or previous_response_id chaining, use the Agent API (POST /v1/responses).

All /v1 endpoints share one base URL — pick the cluster your account uses:

https://api.mirobody.ai/v1 # Global
https://api.mirobody.cn/v1 # China

Japan and EU clusters are in preparation — see Regions. Model providers and pricing can differ by region, so read GET /v1/models from the cluster you call.

Terminal window
curl https://api.mirobody.ai/v1/chat/completions \
-H "Authorization: Bearer $MIROBODY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mirobody-flash",
"messages": [{"role": "user", "content": "How is my fasting glucose trending?"}],
"user": "alice"
}'
from openai import OpenAI
client = OpenAI(api_key="mb_live_...", base_url="https://api.mirobody.ai/v1")
resp = client.chat.completions.create(
model="mirobody-flash",
messages=[{"role": "user", "content": "How is my fasting glucose trending?"}],
user="alice", # tenant-isolation key (a Subject)
)
print(resp.choices[0].message.content)
# resp.choices[0].message.reasoning_content # provider-supplied reasoning text (may be empty)
# resp.choices[0].message.tool_steps # server tool trace: {id, name, arguments, result}
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.MIROBODY_API_KEY,
baseURL: "https://api.mirobody.ai/v1",
});
const resp = await client.chat.completions.create({
model: "mirobody-flash",
messages: [{ role: "user", content: "How is my fasting glucose trending?" }],
user: "alice",
});
console.log(resp.choices[0].message.content);
FieldTypeDescription
modelstringmirobody-flash (default) or mirobody-expert. See Models.
messagesarrayOpenAI {role, content}. The surface is stateless — send the full history each call; a system message shapes tone/format (see System prompts).
streambooltrue → SSE stream of chat.completion.chunk frames.
userstringTenant-isolation key → a Subject. Pass each end-user’s stable id. See multi-tenancy.

retention and session_id are not fields on this stateless surface. Set retention when writing data, files, or readings stored by /v1/standardize. For server-side conversation state, use the Agent API.

This surface is a closed grounded completion — tools run server-side only, and the answer is free-form text. Parameters that contradict that are explicitly rejected with 400 (code: unsupported_parameter, param names the offender) rather than silently swallowed:

ParameterResult
tools, functions400 unsupported_parameter — bring your own tools via the Agent API
tool_choice, function_call400 unsupported_parameter
response_format400 unsupported_parameter — structured output is not available on this surface
n > 1400 unsupported_parameter — always a single choice

message.content carries one clean final answer. Optional provider reasoning text and server tool calls use separate channels, so the answer itself is not mixed with tool narration.

{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1783741077,
"model": "mirobody-flash",
"choices": [{
"index": 0,
"finish_reason": "stop",
"message": {
"role": "assistant",
"content": "Your fasting glucose has trended down ~8% over the last 30 days ...",
"reasoning_content": "", // provider-supplied reasoning text; may be empty
"tool_steps": [ // server tool trace, ordered
{ "id": "call_abc", "name": "query_health_data",
"arguments": { "query": "fasting glucose last 90 days" },
"result": "{...}" }
]
}
}],
"usage": {
"prompt_tokens": 12, // YOUR visible input only
"completion_tokens": 210,
"total_tokens": 222,
"prompt_tokens_details": { "system_tokens": 10096 }, // reported separately from visible prompt_tokens
"completion_tokens_details": { "reasoning_tokens": 0 },
"billed_tokens": { "input": 20391, "output": 841, "total": 21232 } // the actual token total you're metered on
},
"health_records": [ { "tool": "query_health_data", "data": "..." } ],
"citations": [ { "tool": "search_medical_evidence", "data": "..." } ]
}
ChannelContent
message.contentFinal answer only (the “reply” channel).
message.reasoning_contentProvider-supplied reasoning text. It may be empty and is not a guarantee of complete internal reasoning.
message.tool_steps[]The server tool trace — each {id, name, arguments, result}, merged by id. Always returned; there is no opt-in flag and no truncation switch.
health_recordsTraceable health-data tool outputs the answer used — {tool, data} pairs. This is where the explainability lives.
citationsExternal-evidence outputs (search_medical_evidence / read_source) — {tool, data} pairs; empty when no evidence was consulted.
usageToken accounting — see below.

reasoning_content and tool_steps are additive channels — clients that only read message.content keep working.

usage.prompt_tokens reports the input you actually sent (your messages); the platform’s system prompt and tool schemas are reported separately in prompt_tokens_details.system_tokens. completion_tokens_details.reasoning_tokens reports provider reasoning tokens. Counts are summed across every model call in the agent turn.

usage.billed_tokens is the actual token total you’re metered on. Multiplying it by the catalog’s standard input/output rates gives a cost estimate; prompt-cache discounts can make actual metered cost lower.

With stream: true, frames arrive in order. The first chunk carries delta.role: "assistant" (OpenAI parity — many clients key message init off it), then optional reasoning/tool deltas, then a single uninterrupted answer stream:

data: {"object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant","content":""}}]}
data: {"object":"chat.completion.chunk","choices":[{"delta":{"reasoning_content":"<fragment>"}}]}
data: {"object":"chat.completion.chunk","choices":[{"delta":{"tool_steps":[{"id":"call_abc","name":"query_health_data","arguments":{...}}]}}]}
data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"<answer fragment>"}}]}
data: {"object":"chat.completion.chunk","choices":[{"delta":{},"finish_reason":"stop"}],"health_records":[...],"citations":[...],"usage":{...}}
data: [DONE]

Merge tool_steps by id (streamed steps carry {id, name, arguments}; full result payloads are available on the non-stream response). The final frame (before [DONE]) carries the top-level health_records / citations and usage. An upstream failure arrives as an SSE {"error": ...} frame before the stream ends. Clients that only read delta.content keep working.

A system message sets the tone, persona, format, and language of the answer. Server-tool availability and Subject scoping remain platform-controlled; the prompt cannot grant access to another Subject or add tools.

Beyond plain tool-calling, the hosted agent can plan multi-step work, delegate subtasks, run sandboxed code for numeric analysis (trends, correlations), and read the Subject’s uploaded files. You don’t invoke any of this — the agent does, against the Subject’s data.

The tools the hosted agent can call:

  • query_health_data — search and aggregate the Subject’s records
  • list_clinical_records — list clinical documents / FHIR-backed records
  • list_family_members — resolve care-circle members the Subject is allowed to query
  • search_medical_evidence — search literature, guidelines/consensus, and registered trials (feeds citations)
  • read_source — read one result by its returned ref; arbitrary URL fetching is not supported

The /v1 agent’s main loop is read-only. Health facts mentioned in stored conversations are extracted automatically (see State & memory), not written by a tool during the answer. Planning, delegation, and code-analysis tools are internal and not part of the API contract. ask_user serves the web app but is disabled on the /v1 surfaces because an API caller has no widget to answer it. Files uploaded through /v1/files become readable to the agent once processed.

To add your own tools, use the Agent API’s function calling — this surface intentionally has no tool injection.

When the answer involves a trend / comparison / distribution, it may embed a fenced ```vis-chart block of pure-data JSON ({type, title, axisXTitle, axisYTitle, data}). Rendering is the client’s job — the API only returns the data spec. Detect the fenced block and render it (line / area / bar / pie).