AI Research Stream
Stream a typed AI research run — thinking stages, tool calls, citations, and article content — over SSE.
The AI research endpoint runs a deep-research agent and streams its progress as Server-Sent Events. It powers the editor's Research & Write panel: as the agent plans, searches the web, reflects, and writes, it emits typed events you can render live (stage labels, citation lists, and incremental article content).
This is a first-party, session-authenticated endpoint used by the MisarBlog editor — it is not part of the API-key REST surface. Requests must carry a valid MisarBlog session; there is no mbk_… key parameter.
Endpoint
POST /api/v1/ai/researchRequest
const res = await fetch("/api/v1/ai/research", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: "Rust vs Go for backend services in 2026" }),
});Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
query | string | Yes | The research topic or question. topic is accepted as an alias if query is omitted. |
Response
Content-Type: text/event-stream
The stream is a passthrough of the research agent's typed events. Each data: line is a JSON object with a type field, and the stream ends with data: [DONE].
data: {"type":"thinking","stage":"plan"}
data: {"type":"tool_call"}
data: {"type":"tool_result","citations":[{"url":"https://…","title":"…"}]}
data: {"type":"reflection","covered":["performance"],"gaps":["tooling"]}
data: {"type":"content","delta":"## Performance\n\nBoth languages…"}
data: {"type":"done","result":{"content":"# Rust vs Go…","citations":[…]}}
data: [DONE]Event types
type | Payload fields | Meaning |
|---|---|---|
thinking | stage | Agent entered a stage (see stages below). |
tool_call | — | Agent invoked a research tool (e.g. web search). |
tool_result | citations | New sources discovered. Each citation: { url, title?, snippet? }. |
reflection | covered, gaps | Topics covered so far and remaining gaps (string[]). |
content | delta | Incremental article text — concatenate delta values in order. |
done | result | Final { content, citations }. Marks a completed run. |
error | message | The run failed; message describes why. |
Stages
The stage field on thinking events is one of: clarify, plan, execute, reflect, synthesize.
Consuming the stream
interface Citation { url: string; title?: string; snippet?: string }
const res = await fetch("/api/v1/ai/research", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query }),
});
const reader = res.body!.getReader();
const dec = new TextDecoder();
let buffer = "";
let article = "";
const citations: Citation[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += dec.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const raw = line.slice(6).trim();
if (raw === "[DONE]") break;
const event = JSON.parse(raw);
switch (event.type) {
case "thinking": updateStage(event.stage); break;
case "tool_result": citations.push(...(event.citations ?? [])); break;
case "content": article += event.delta ?? ""; break;
case "done": article = event.result?.content ?? article; break;
case "error": throw new Error(event.message ?? "Research failed");
}
}
}Errors
| Status | Description |
|---|---|
400 | Invalid JSON body, or missing query / topic. |
401 | No valid MisarBlog session. |
502 | The upstream research agent returned an error. |
A failed run mid-stream is delivered as an error event rather than a status change:
data: {"type":"error","message":"Research failed"}
data: [DONE]