Examples
Copy-paste recipes for common API patterns - Q&A, multi-turn, streaming, retries, mode selection, and a thin client wrapper.
Each recipe targets https://api.neurobro.ai/api/v1 and assumes
NEUROAPI_KEY holds your secret. Recipes only call /agent/ask and
/health; pair them with the API reference for the full
request and response schemas.
1. Simple Q&A (sync)
Use the sync path when the answer is short and you don't need to render tokens as they arrive.
curl -s https://api.neurobro.ai/api/v1/agent/ask \
-H "Content-Type: application/json" \
-H "X-API-Key: $NEUROAPI_KEY" \
-d '{"prompt": "Summarise the latest macroeconomic outlook for the eurozone in one sentence."}' | jqconst res = await fetch("https://api.neurobro.ai/api/v1/agent/ask", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.NEUROAPI_KEY!,
},
body: JSON.stringify({
prompt: "Summarise the latest macroeconomic outlook for the eurozone in one sentence.",
}),
})
const { answer, request_id, usage } = await res.json()import os, httpx
res = httpx.post(
"https://api.neurobro.ai/api/v1/agent/ask",
headers={"X-API-Key": os.environ["NEUROAPI_KEY"]},
json={"prompt": "Summarise the latest macroeconomic outlook for the eurozone in one sentence."},
timeout=60,
)
data = res.json()The response is a single AgentAskResponse document with answer,
mode, request_id, and usage.
2. Multi-turn chat
The API is stateless - the caller owns the conversation. Pass prior
turns in message_history on every request. Roles are "user" and
"assistant".
curl -s https://api.neurobro.ai/api/v1/agent/ask \
-H "Content-Type: application/json" \
-H "X-API-Key: $NEUROAPI_KEY" \
-d '{
"prompt": "How did their gross margins evolve over the same period?",
"message_history": [
{"role": "user", "content": "Compare AAPL and MSFT on FY2024 earnings growth."},
{"role": "assistant", "content": "Both delivered double-digit YoY revenue growth, with..."}
]
}' | jqtype Turn = { role: "user" | "assistant"; content: string }
async function ask(prompt: string, history: Turn[]) {
const res = await fetch("https://api.neurobro.ai/api/v1/agent/ask", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.NEUROAPI_KEY!,
},
body: JSON.stringify({ prompt, message_history: history }),
})
return res.json()
}import os, httpx
def ask(prompt: str, history: list[dict]) -> dict:
res = httpx.post(
"https://api.neurobro.ai/api/v1/agent/ask",
headers={"X-API-Key": os.environ["NEUROAPI_KEY"]},
json={"prompt": prompt, "message_history": history},
timeout=60,
)
return res.json()Persist the transcript on your side; trim oldest turns once the combined token budget gets large.
3. Streaming via Server-Sent Events
Pass "stream": true and the response is text/event-stream. Every
event is a data: line; the stream ends with data: [DONE]\n\n. The
X-Request-Id response header carries the same id you'd see in a sync
response - capture it for support tickets.
curl -N https://api.neurobro.ai/api/v1/agent/ask \
-H "Content-Type: application/json" \
-H "X-API-Key: $NEUROAPI_KEY" \
-d '{"prompt": "Walk me through a top-down technical read on NVDA right now.", "stream": true}'const res = await fetch("https://api.neurobro.ai/api/v1/agent/ask", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.NEUROAPI_KEY!,
},
body: JSON.stringify({
prompt: "Walk me through a top-down technical read on NVDA right now.",
stream: true,
}),
})
const requestId = res.headers.get("x-request-id")
const reader = res.body!.getReader()
const decoder = new TextDecoder()
let buffer = ""
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
let nl: number
while ((nl = buffer.indexOf("\n\n")) !== -1) {
const event = buffer.slice(0, nl)
buffer = buffer.slice(nl + 2)
const line = event.startsWith("data: ") ? event.slice(6) : event
if (line === "[DONE]") return
process.stdout.write(line)
}
}import os, httpx
with httpx.stream(
"POST",
"https://api.neurobro.ai/api/v1/agent/ask",
headers={"X-API-Key": os.environ["NEUROAPI_KEY"]},
json={
"prompt": "Walk me through a top-down technical read on NVDA right now.",
"stream": True,
},
timeout=None,
) as res:
request_id = res.headers.get("x-request-id")
for line in res.iter_lines():
if not line.startswith("data: "):
continue
payload = line[len("data: "):]
if payload == "[DONE]":
break
print(payload, end="", flush=True)Treat each data: payload as opaque text. Don't assume a specific
JSON shape inside the frame - the stable contract is "concatenated
payloads form a partial answer; [DONE] marks the end."
4. Retry with exponential backoff
Retry only on 429 (rate-limited) and 503 (capacity saturated).
Honour Retry-After. Cap retries; never retry blindly on 500 -
/agent/ask is not idempotent.
async function askWithRetry(body: unknown, attempt = 0): Promise<Response> {
const res = await fetch("https://api.neurobro.ai/api/v1/agent/ask", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.NEUROAPI_KEY!,
},
body: JSON.stringify(body),
})
const retryable = res.status === 429 || res.status === 503
if (retryable && attempt < 3) {
const hinted = Number(res.headers.get("retry-after") ?? "0")
const backoff = hinted > 0 ? hinted : 2 ** attempt
await new Promise((r) => setTimeout(r, backoff * 1000))
return askWithRetry(body, attempt + 1)
}
return res
}import os, time, httpx
def ask_with_retry(body: dict, attempt: int = 0) -> httpx.Response:
res = httpx.post(
"https://api.neurobro.ai/api/v1/agent/ask",
headers={"X-API-Key": os.environ["NEUROAPI_KEY"]},
json=body,
timeout=60,
)
if res.status_code in (429, 503) and attempt < 3:
hinted = int(res.headers.get("retry-after", "0") or 0)
backoff = hinted if hinted > 0 else 2 ** attempt
time.sleep(backoff)
return ask_with_retry(body, attempt + 1)
return resIf retries are exhausted, surface X-Request-Id to your operators -
that's what we'll ask for.
5. Picking a mode
Three tiers, three price points: fast = 1, smart = 2, max = 8
cost_units per call. The cost_units field on each successful
response is the contract - see Pricing for the full table.
| Mode | What it's for |
|---|---|
fast | Quick responses with standard reasoning and core market knowledge. Good for asset checks, narrative pulses, and high-volume financial Q&A. |
smart (default) | Stronger reasoning over complex financial questions, with deeper market context. Good for technical analysis, trade setups, and fundamental stock research. |
max | Neurobro's most capable tier: deep multi-asset investigations, fundamental analysis with broad market coverage, and the highest-stakes research. |
When you don't pick, the server defaults to smart. Choose explicitly
in production code so behaviour doesn't drift if the default ever
changes.
6. Steer the agent with a system prompt
Pass system_prompt to shape the agent's tone, role, or output format.
It layers on top of the built-in instructions - it can't override the
agent's persona or safety guardrails, but within those bounds it's the
cleanest way to make answers fit your product.
curl -s https://api.neurobro.ai/api/v1/agent/ask \
-H "Content-Type: application/json" \
-H "X-API-Key: $NEUROAPI_KEY" \
-d '{
"prompt": "How is the EUR/USD pair looking this week?",
"mode": "smart",
"system_prompt": "You are a concise FX strategist. Lead with a one-line bias (long/short/neutral), then three supporting bullets. Flag any figure you are not confident about."
}' | jqconst res = await fetch("https://api.neurobro.ai/api/v1/agent/ask", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.NEUROAPI_KEY!,
},
body: JSON.stringify({
prompt: "How is the EUR/USD pair looking this week?",
mode: "smart",
system_prompt:
"You are a concise FX strategist. Lead with a one-line bias (long/short/neutral), then three supporting bullets. Flag any figure you are not confident about.",
}),
})
const { answer } = await res.json()import os, httpx
res = httpx.post(
"https://api.neurobro.ai/api/v1/agent/ask",
headers={"X-API-Key": os.environ["NEUROAPI_KEY"]},
json={
"prompt": "How is the EUR/USD pair looking this week?",
"mode": "smart",
"system_prompt": (
"You are a concise FX strategist. Lead with a one-line bias "
"(long/short/neutral), then three supporting bullets. Flag any "
"figure you are not confident about."
),
},
timeout=60,
)
answer = res.json()["answer"]Keep it short and declarative - a sentence or two naming the role and
the shape you want. The response is a normal AgentAskResponse; the
steered text is in answer.
7. Structured output with a JSON Schema
Pass output_schema - a JSON Schema with an object root - to get a
validated object back instead of prose. The result lands in output
and answer is null (exactly one is ever set), so your code consumes
it directly without parsing free text.
Structured output is non-streaming only. Reach for max when you need
a hard guarantee - the provider enforces the schema natively. On
fast/smart it's best-effort, validated with automatic retries.
curl -s https://api.neurobro.ai/api/v1/agent/ask \
-H "Content-Type: application/json" \
-H "X-API-Key: $NEUROAPI_KEY" \
-d '{
"prompt": "Give me a structured read on Bitcoin right now.",
"mode": "max",
"output_schema": {
"type": "object",
"properties": {
"stance": { "type": "string", "enum": ["bullish", "bearish", "neutral"] },
"conviction": { "type": "number", "description": "0-1 confidence" },
"support": { "type": "number" },
"resistance": { "type": "number" },
"summary": { "type": "string" }
},
"required": ["stance", "conviction", "support", "resistance", "summary"],
"additionalProperties": false
}
}' | jq .outputtype MarketRead = {
stance: "bullish" | "bearish" | "neutral"
conviction: number
support: number
resistance: number
summary: string
}
const res = await fetch("https://api.neurobro.ai/api/v1/agent/ask", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.NEUROAPI_KEY!,
},
body: JSON.stringify({
prompt: "Give me a structured read on Bitcoin right now.",
mode: "max",
output_schema: {
type: "object",
properties: {
stance: { type: "string", enum: ["bullish", "bearish", "neutral"] },
conviction: { type: "number", description: "0-1 confidence" },
support: { type: "number" },
resistance: { type: "number" },
summary: { type: "string" },
},
required: ["stance", "conviction", "support", "resistance", "summary"],
additionalProperties: false,
},
}),
})
const { output } = (await res.json()) as { output: MarketRead }
console.log(output.stance, output.conviction)import os, httpx
schema = {
"type": "object",
"properties": {
"stance": {"type": "string", "enum": ["bullish", "bearish", "neutral"]},
"conviction": {"type": "number", "description": "0-1 confidence"},
"support": {"type": "number"},
"resistance": {"type": "number"},
"summary": {"type": "string"},
},
"required": ["stance", "conviction", "support", "resistance", "summary"],
"additionalProperties": False,
}
res = httpx.post(
"https://api.neurobro.ai/api/v1/agent/ask",
headers={"X-API-Key": os.environ["NEUROAPI_KEY"]},
json={
"prompt": "Give me a structured read on Bitcoin right now.",
"mode": "max",
"output_schema": schema,
},
timeout=60,
)
output = res.json()["output"] # a dict matching your schemaThe response carries the object on output:
{
"answer": null,
"output": {
"stance": "bullish",
"conviction": 0.62,
"support": 58000,
"resistance": 72000,
"summary": "Holding above the 50-day average with improving spot demand."
},
"mode": "max",
"request_id": "1c9d44f5-2f0e-4c6b-9b2a-8b3a2f0e1d7e",
"usage": { "prompt_tokens": 210, "completion_tokens": 24, "cached_tokens": 0, "cost_units": 8 }
}Two failure modes the API rejects up front, before any quota is spent:
output_schematogether with"stream": truereturns 400 - structured output is non-streaming only.- A schema that can't drive structured output - a non-object root, an
empty
{}, or otherwise malformed - returns 422.
Make every property required and set additionalProperties: false
for the most reliable results, especially on max where conformance
is strict. Mark a field optional only when the agent should be free to
omit it.
8. A thin TypeScript client
A copy-pasteable wrapper that handles auth, retry, sync calls, and streaming. Drop into a single file; no dependencies.
type Mode = "fast" | "smart" | "max"
type Turn = { role: "user" | "assistant"; content: string }
export class NeuroAPI {
constructor(
private apiKey: string,
private baseUrl = "https://api.neurobro.ai/api/v1",
) {}
async ask(input: { prompt: string; mode?: Mode; history?: Turn[] }) {
const res = await this.send({ ...input, stream: false })
if (!res.ok) throw await this.toError(res)
return res.json() as Promise<{
answer: string
mode: Mode
request_id: string
usage: { prompt_tokens: number; completion_tokens: number; cached_tokens: number; cost_units: number }
}>
}
async askStream(
input: { prompt: string; mode?: Mode; history?: Turn[] },
onChunk: (text: string) => void,
): Promise<{ requestId: string }> {
const res = await this.send({ ...input, stream: true })
if (!res.ok) throw await this.toError(res)
const requestId = res.headers.get("x-request-id") ?? ""
const reader = res.body!.getReader()
const decoder = new TextDecoder()
let buffer = ""
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
let nl: number
while ((nl = buffer.indexOf("\n\n")) !== -1) {
const frame = buffer.slice(0, nl)
buffer = buffer.slice(nl + 2)
const payload = frame.startsWith("data: ") ? frame.slice(6) : frame
if (payload === "[DONE]") return { requestId }
onChunk(payload)
}
}
return { requestId }
}
private async send(input: {
prompt: string
mode?: Mode
history?: Turn[]
stream: boolean
}): Promise<Response> {
const body = {
prompt: input.prompt,
mode: input.mode ?? "smart",
message_history: input.history ?? [],
stream: input.stream,
}
let attempt = 0
while (true) {
const res = await fetch(`${this.baseUrl}/agent/ask`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": this.apiKey,
},
body: JSON.stringify(body),
})
if ((res.status === 429 || res.status === 503) && attempt < 3) {
const hinted = Number(res.headers.get("retry-after") ?? "0")
const backoff = hinted > 0 ? hinted : 2 ** attempt
await new Promise((r) => setTimeout(r, backoff * 1000))
attempt++
continue
}
return res
}
}
private async toError(res: Response): Promise<Error> {
const requestId = res.headers.get("x-request-id") ?? "unknown"
let detail = ""
try {
detail = JSON.stringify(await res.json())
} catch {
detail = await res.text()
}
return new Error(`NeuroAPI ${res.status} (request_id=${requestId}): ${detail}`)
}
}Usage:
const api = new NeuroAPI(process.env.NEUROAPI_KEY!)
const { answer } = await api.ask({
prompt: "Give a quick read on the current crypto market pulse.",
})
await api.askStream(
{ prompt: "Stream a multi-asset macro brief.", mode: "max" },
(chunk) => process.stdout.write(chunk),
)