NeuroAPI
Best PracticesExamples

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."}' | jq
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: "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..."}
    ]
  }' | jq
type 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 res

If 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.

ModeWhat it's for
fastQuick 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.
maxNeurobro'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."
  }' | jq
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: "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 .output
type 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 schema

The 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_schema together with "stream": true returns 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.

neuroapi.ts
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),
)

What's next

On this page