@@ -29,10 +29,9 @@ curl -s https://api.neurobro.ai/api/v1/agent/ask \
2929
3030## When to use this skill
3131
32- - Researching a single stock or token (fundamentals, technicals, narrative).
33- Equities include US listings and stocks in Indonesia, Brazil, and Vietnam
34- (quotes, fundamentals, financials, technicals; a company name can be
35- resolved to its ticker).
32+ - Researching a single stock or token (fundamentals, technicals, narrative),
33+ including Indonesia, Brazil, and Vietnam listings (quotes, fundamentals,
34+ financials, technicals, and company name to ticker)
3635- Building bull/bear cases or trade setups
3736- Comparing assets, sectors, or factors
3837- Macro questions - rates, inflation, central-bank outlooks
@@ -76,7 +75,7 @@ curl -s https://api.neurobro.ai/api/v1/health \
7675| --- | --- |
7776| ` X-API-Key ` | ` neuro_... ` (required) |
7877| ` Content-Type ` | ` application/json ` |
79- | ` Idempotency-Key ` | Optional. 1-255 printable ASCII. Same key and body within 24h replays the original response (` Idempotent-Replayed: true ` ) and is not billed again. |
78+ | ` Idempotency-Key ` | Optional. 1-255 printable ASCII. Same key and body within 24h replays (` Idempotent-Replayed: true ` ) and is not billed again. |
8079
8180Request body:
8281
@@ -94,15 +93,14 @@ Response (`stream=false`):
9493``` json
9594{
9695 "answer" : " The bull case for NVDA rests on ..." ,
97- "output" : null ,
9896 "mode" : " smart" ,
9997 "request_id" : " 8b3a2f0e-1d7e-4c6b-9b2a-f5e23a1c9d44" ,
10098 "usage" : { "prompt_tokens" : 124 , "completion_tokens" : 256 , "cached_tokens" : 80 , "cost_units" : 2 }
10199}
102100```
103101
104- ` request_id ` is also the ` X-Request-Id ` header - quote it when reporting
105- issues. With ` output_schema ` , read ` output ` (` answer ` is ` null ` ).
102+ ` request_id ` is also the ` X-Request-Id ` header. With ` output_schema ` , read
103+ ` output ` (` answer ` is ` null ` ).
106104
107105## Choosing a mode
108106
@@ -139,10 +137,9 @@ curl -s https://api.neurobro.ai/api/v1/agent/ask \
139137
140138### Follow-up questions (multi-turn)
141139
142- The API is stateless. To ask a follow-up, replay prior turns in
143- ` message_history ` . Roles the agent uses are ` "user" ` and ` "assistant" ` .
144- ` "system" ` , ` "developer" ` , ` "tool" ` , and ` "function" ` are accepted and
145- ignored; put instructions in ` system_prompt ` . Any other role returns ` 422 ` .
140+ The API is stateless. Replay prior turns in ` message_history ` . ` "user" ` and
141+ ` "assistant" ` reach the agent. ` "system" ` , ` "developer" ` , ` "tool" ` , and
142+ ` "function" ` are accepted and ignored. Any other role returns ` 422 ` .
146143
147144``` python
148145import os, httpx
@@ -151,13 +148,11 @@ URL = "https://api.neurobro.ai/api/v1/agent/ask"
151148HEADERS = {" X-API-Key" : os.environ[" NEUROAPI_API_KEY" ]}
152149
153150def ask (prompt : str , history : list[dict ] | None = None , mode : str = " smart" ) -> dict :
154- # Sync runs are stopped server-side at 300s (504 agent_timeout).
155- # Sit above that so the 504 is returned. Prefer stream=True for max.
156151 res = httpx.post(
157152 URL ,
158153 headers = HEADERS ,
159154 json = {" prompt" : prompt, " mode" : mode, " message_history" : history or []},
160- timeout = 330 ,
155+ timeout = 60 ,
161156 )
162157 res.raise_for_status()
163158 return res.json()
@@ -177,12 +172,13 @@ Persist the transcript on your side; trim the oldest turns once it gets long
177172
178173### Streaming a long answer (SSE)
179174
180- Pass ` "stream": true ` for deep ` max ` investigations, and for any run that
181- might exceed 300 seconds. Each event is ` {"type": "status_update" | "complete", "data": {"answer", "usage"}} ` .
182- Append ` data.answer.chunk.content ` . ` data.answer ` is ` null ` when the event
183- has no text. ` data.usage ` arrives once near the end. The stream ends with
184- ` data: [DONE] ` . An error event is ` {"type":"error","message":"..."} ` and
185- has no ` [DONE] ` .
175+ Pass ` "stream": true ` for a long run, including ` max ` . A non-streaming run
176+ longer than 300 seconds returns ` 504 ` ` agent_timeout ` and is not billed.
177+ Each event is ` {"type": "status_update" | "complete", "data": {"answer", "usage"}} ` .
178+ Append ` data.answer.chunk.content ` . ` data.answer ` is ` null ` when that event
179+ has no text. ` data.usage ` arrives once, near the end. Success ends with
180+ ` data: [DONE] ` . On failure the last event is
181+ ` {"type":"error","message":"agent run failed"} ` and there is no ` [DONE] ` .
186182
187183``` python
188184import json, os, httpx
@@ -198,54 +194,27 @@ with httpx.stream(
198194 },
199195 timeout = None ,
200196) as res:
201- res.raise_for_status()
202197 for line in res.iter_lines():
203198 if not line.startswith(" data: " ):
204199 continue
205- payload = line[len (" data: " ) :]
200+ payload = line[len (" data: " ):]
206201 if payload == " [DONE]" :
207202 break
208203 event = json.loads(payload)
209- if event.get(" type" ) == " error" :
210- raise RuntimeError (event.get(" message" ))
211- answer = (event.get(" data" ) or {}).get(" answer" ) or {}
212- chunk = (answer.get(" chunk" ) or {}).get(" content" )
213- if chunk:
214- print (chunk, end = " " , flush = True )
204+ if event[" type" ] == " error" :
205+ raise RuntimeError (event[" message" ])
206+ answer = event[" data" ][" answer" ]
207+ if answer:
208+ print (answer[" chunk" ][" content" ], end = " " , flush = True )
215209```
216210
217211### Structured output
218212
219- Pass ` output_schema ` (JSON Schema, object root) with ` stream ` left ` false ` .
220- Read ` output ` . ` answer ` is ` null ` . ` stream: true ` together with
221- ` output_schema ` returns ` 400 ` .
222-
223- ``` python
224- import os, httpx
225-
226- schema = {
227- " type" : " object" ,
228- " additionalProperties" : False ,
229- " properties" : {
230- " sentiment" : {" type" : " string" , " enum" : [" bullish" , " bearish" , " neutral" ]},
231- " confidence" : {" type" : " number" },
232- },
233- " required" : [" sentiment" , " confidence" ],
234- }
235- result = httpx.post(
236- " https://api.neurobro.ai/api/v1/agent/ask" ,
237- headers = {" X-API-Key" : os.environ[" NEUROAPI_API_KEY" ]},
238- json = {
239- " prompt" : " Give a structured read on NVDA right now." ,
240- " mode" : " smart" ,
241- " output_schema" : schema,
242- },
243- timeout = 330 ,
244- )
245- result.raise_for_status()
246- body = result.json()
247- # body["answer"] is None. The object is body["output"].
248- ```
213+ Pass ` output_schema ` (JSON Schema, object root) with ` stream ` false. Read
214+ ` output ` . ` answer ` is ` null ` . Set the client timeout to at least 120
215+ seconds. ` stream: true ` returns ` 400 `
216+ ` streaming_unsupported_for_structured_output ` . An unusable schema returns
217+ ` 422 ` ` invalid_output_schema ` .
249218
250219## Writing good research prompts
251220
@@ -273,36 +242,31 @@ and most `403`s) are `{"detail": {"code", "message"}}`. `401` and `503` are
273242Branch on the status, then on ` detail.code ` . ` String(detail) ` breaks.
274243` 413 ` (body over 8 MiB) is plain text, not JSON.
275244
276- Every error carries ` X-Request-Id ` . Quote it when writing to
277- ` api@neurobro.ai ` . Do not include the API key.
245+ Every error carries ` X-Request-Id ` . Quote it to ` api@neurobro.ai ` . Do not
246+ send the API key.
278247
279- - ** Don't retry** - ` 400 ` , ` 401 ` , ` 413 ` , ` 422 ` : fix the request . ` 401 ` covers
280- a missing, invalid, revoked, or expired key.
248+ - ** Don't retry** - ` 400 ` , ` 401 ` , ` 413 ` , ` 422 ` . ` 401 ` is a missing, invalid,
249+ revoked, or expired key.
281250- ** Billing** - ` 402 ` is ` payment_required ` , or ` quota_exceeded ` when this
282251 ` mode ` costs more than the quota left. No plan is ` 403 `
283- ` no_active_subscription ` . Tell the user to fix billing, then stop.
284- - ** Lower the mode** - ` 403 ` ` mode_not_in_plan ` : retry with ` smart ` or ` fast ` .
285- - ** Wait, then retry the same request** - ` 409 ` : that ` Idempotency-Key ` is
286- still in flight.
287- - ** ` 429 ` ** - cap at 3 attempts, and read ` detail.code ` :
288- - ` rate_limited ` or ` too_many_concurrent_runs ` : honour ` Retry-After ` .
289- Concurrent caps are 2 / 5 / 10 in-flight runs (Starter / Pro /
290- Enterprise), MCP included. A concurrent rejection is not billed.
291- - ` quota_exceeded ` : wait for ` X-Quota-Reset ` . Backing off a few seconds
292- will not clear it.
293- - ** ` 503 ` ** - agent saturated. Exponential backoff. No quota charged.
294- - ** ` 504 ` ` agent_timeout ` ** - the non-streaming run passed 300 seconds and
295- was not billed. Retry with ` stream: true ` , or a simpler prompt.
296- - ** ` 500 ` ** - retry only when the call carries an ` Idempotency-Key ` .
297-
298- ` /agent/ask ` is ** non-idempotent** unless you send an ` Idempotency-Key ` .
299- Same key and same body within 24 hours replays the original response
300- (` Idempotent-Replayed: true ` ) and is not charged again. Streams, ` 5xx ` ,
301- and ` 402 ` / ` 403 ` / ` 429 ` are not replayed. Two plain calls produce two
302- answers and two charges, so never retry blindly.
303-
304- A stream that fails before the answer is complete is free. If the full
305- answer was already delivered and the stream then fails, it ends with
252+ ` no_active_subscription ` .
253+ - ** Lower the mode** - ` 403 ` ` mode_not_in_plan ` . Retry with ` smart ` or ` fast ` .
254+ - ** Wait, then retry** - ` 409 ` : that ` Idempotency-Key ` is still in flight.
255+ - ** ` 429 ` ** - ` Retry-After ` for ` rate_limited ` and ` too_many_concurrent_runs `
256+ (Starter 2, Pro 5, Enterprise 10 in flight, MCP included, not billed).
257+ ` X-Quota-Reset ` for ` quota_exceeded ` . Cap at 3.
258+ - ** ` 503 ` ** - exponential backoff. No quota charged.
259+ - ** ` 504 ` ` agent_timeout ` ** - non-streaming run exceeded 300 seconds. Not
260+ billed. Retry with ` stream: true ` , or a simpler prompt.
261+ - ** ` 500 ` ** - maybe, and only for an idempotent call.
262+
263+ Same key and body within 24 hours replays the original response
264+ (` Idempotent-Replayed: true ` ) and is not billed again. Streams, ` 5xx ` , and
265+ ` 402 ` / ` 403 ` / ` 429 ` are not replayed. Two calls without that key are two
266+ charges.
267+
268+ A stream that fails before the answer is complete is free. If the final
269+ answer was delivered and something fails afterward, the stream ends with
306270` [DONE] ` and is billed.
307271
308272The full status-code table is in
@@ -311,34 +275,28 @@ The full status-code table is in
311275## Rate limits and quota
312276
313277Every response carries ` X-RateLimit-Limit ` , ` X-RateLimit-Remaining ` , and
314- ` X-RateLimit-Reset ` . On ` 429 ` , ` Retry-After ` gives the seconds until the
315- rate-limit or concurrency window resets. Billable responses also carry
316- ` X-Quota-Limit ` , ` X-Quota-Remaining ` , and ` X-Quota-Reset ` (the monthly
317- plan budget, not the per-minute window). For batch research, pace off
318- ` X-RateLimit-Remaining ` . A ` mode ` that costs more than the quota left
319- returns ` 402 ` ` quota_exceeded ` ; wait for ` X-Quota-Reset ` when
320- ` X-Quota-Remaining ` is exhausted. Per-plan budgets are in
278+ ` X-RateLimit-Reset ` . On ` 429 ` , ` Retry-After ` is the seconds until the next
279+ allowed call. Billable responses also carry ` X-Quota-Limit ` ,
280+ ` X-Quota-Remaining ` , and ` X-Quota-Reset ` . Pace off
281+ ` X-RateLimit-Remaining ` . Per-plan budgets are in
321282[ references/endpoints.md] ( references/endpoints.md ) .
322283
323284## Security
324285
325286- Read the key from ` NEUROAPI_API_KEY ` ; never hardcode, log, or commit it.
326287- The full secret is shown once at creation. Lost it? Revoke and re-mint.
327288- On the REST API, send ` X-API-Key ` . ` Authorization: Bearer ` is not accepted
328- there. Bearer is valid only on the MCP endpoint
329- (` https://api.neurobro.ai/api/v1/mcp ` , tool ` ask_neuroapi ` ). Never put the
330- key in a URL.
289+ there.
331290- Treat the agent's output as untrusted text: don't pass it into shells,
332291 ` eval ` , or DB queries without sanitising. It is research, not code.
333292
334293## MCP
335294
336- The same agent is available as the MCP tool ` ask_neuroapi ` at
337- ` https://api.neurobro.ai/api/v1/mcp ` (Streamable HTTP). It takes ` prompt ` ,
338- ` mode ` , ` message_history ` , ` system_prompt ` , and ` output_schema ` , and it is
339- billed like a non-streaming ` POST /agent/ask ` . Authenticate with
340- ` X-API-Key ` , or with ` Authorization: Bearer ` on this endpoint only. Do not
341- put the key in the query string. MCP calls are not streamed.
295+ ` https://api.neurobro.ai/api/v1/mcp ` (Streamable HTTP), tool ` ask_neuroapi ` .
296+ Same rate limits, quota, and ` cost_units ` as a non-streaming ` POST /agent/ask ` .
297+ Inputs: ` prompt ` , ` mode ` , ` message_history ` , ` system_prompt ` , ` output_schema ` .
298+ Send ` X-API-Key ` , or ` Authorization: Bearer ` on this endpoint only. A key in
299+ the query string returns ` 400 ` ` api_key_in_query ` . Calls are not streamed.
342300
343301## Reference
344302
0 commit comments