Search API
Web-grounded search-and-answer endpoint — returns a synthesized answer with inline url_citation source annotations, powered by live web retrieval and misar-pro.
Overview
POST /dev/v1/search answers a question against the live web. It retrieves fresh results (via searxng), synthesizes an answer with misar-pro, and returns that answer together with url_citation annotations that map character ranges in the answer back to their source pages.
Use this endpoint when you need citations and sources. For an ungrounded chat reply, use the Chat Completions endpoint; for a conversational web-grounded reply without structured citations, pass model: "online" to Chat Completions.
Every response includes "grounded": true and a sources array, so you can render footnotes or a "Sources" list without a second request.
Endpoint
/v1/searchRuns a web-grounded search and returns a synthesized answer with source citations. Requires Authorization: Bearer YOUR_API_KEY.
Request body
Provide either query or messages (not both).
querystringbodyThe question to answer. Use this for a single-shot search.
messagesArray<{role, content}>bodyA conversation (role = system | user | assistant) when you need multi-turn context. The last user message is treated as the search query.
modelstringbodydefault: misar-proSynthesis model. Defaults to misar-pro for grounded answers; pass misar-flash for faster, lower-cost synthesis. See Models.
Response fields
idstringUnique identifier for the search response, e.g. search-abc123.
objectstringAlways search.completion.
modelstringThe model that synthesized the answer.
groundedbooleantrue when the answer was produced from retrieved web sources.
answerstringThe synthesized answer text. Character indices in annotations refer to positions in this string.
annotationsArray<object>Inline citations. Each item has type: "url_citation" and a url_citation object with start_index, end_index (character range in answer), url, and title.
sourcesArray<object>The retrieved web sources: url, title, and a short snippet.
usageobjectToken counts: prompt_tokens, completion_tokens, total_tokens.
{
"query": "When did the James Webb Space Telescope launch?"
}{
"id": "search-abc123",
"object": "search.completion",
"model": "misar-pro",
"grounded": true,
"answer": "The James Webb Space Telescope launched on 25 December 2021 aboard an Ariane 5 rocket from French Guiana.",
"annotations": [
{
"type": "url_citation",
"url_citation": {
"start_index": 0,
"end_index": 62,
"url": "https://www.nasa.gov/mission/webb/",
"title": "James Webb Space Telescope — NASA"
}
}
],
"sources": [
{
"url": "https://www.nasa.gov/mission/webb/",
"title": "James Webb Space Telescope — NASA",
"snippet": "Webb launched on 25 Dec 2021 aboard an Ariane 5 from Kourou, French Guiana."
}
],
"usage": {
"prompt_tokens": 512,
"completion_tokens": 88,
"total_tokens": 600
}
}Citations
Each entry in annotations marks a span of the answer string that a specific source supports. start_index and end_index are character offsets into answer, so you can slice the substring and wrap it in a link:
function withCitations(res: SearchResponse): string {
// Apply from the end so earlier offsets stay valid as we insert markup.
let out = res.answer;
const spans = [...res.annotations].sort(
(a, b) => b.url_citation.start_index - a.url_citation.start_index,
);
for (const { url_citation: c } of spans) {
const cited = out.slice(c.start_index, c.end_index);
out =
out.slice(0, c.start_index) +
`[${cited}](${c.url})` +
out.slice(c.end_index);
}
return out;
}Examples
curl https://api.misar.io/dev/v1/search \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "What is the latest stable Node.js LTS release?"
}'const res = await fetch("https://api.misar.io/dev/v1/search", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
query: "What is the latest stable Node.js LTS release?",
model: "misar-pro",
}),
});
const { answer, sources, grounded } = await res.json();
console.log(grounded ? answer : "(ungrounded)");
for (const s of sources) console.log(`- ${s.title}: ${s.url}`);import requests
res = requests.post(
"https://api.misar.io/dev/v1/search",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"query": "What is the latest stable Node.js LTS release?"},
)
data = res.json()
print(data["answer"])
for s in data["sources"]:
print(f"- {s['title']}: {s['url']}")Pass messages instead of query to keep conversational context across turns — the API grounds against the most recent user message while using the earlier turns for context.
Status codes
| Status | Meaning |
|---|---|
200 OK | Grounded answer returned. |
400 Bad Request | Neither query nor messages supplied, or an unknown model. |
401 Unauthorized | Missing, malformed, revoked, or wrong-product API key. |
429 Too Many Requests | Rate limit exceeded — retry after the Retry-After header. |
503 Service Unavailable | Web retrieval was temporarily unavailable; retry with backoff. |
MisarDev keys are product-bound. A key from another Misar product returns 401 here — see Authentication.