API reference
Ask, search and ingest from your own systems. Every call is scoped, rate-limited, cost-capped and recorded — so handing a key to a service stays a decision you can audit.
Quickstart
Create a key in Settings → API keys. It is shown once. Send it as a bearer token.
curl https://api.brainstack.space/v1/ask \
-H "Authorization: Bearer bsk_live_..." \
-H "Content-Type: application/json" \
-d '{"question": "What is our refund policy?"}'{
"answer": "Refunds are allowed within 30 days [1].",
"sources": [
{ "n": 1, "title": "policies-2026", "page": 7,
"text": "...", "score": 0.87, "source_type": "pdf" }
],
"trace": [ { "n": 1, "kind": "knowledge", "label": "Searching knowledge" } ],
"usage": { "input_tokens": 3200, "output_tokens": 180,
"cost_usd": 0.0042, "latency_ms": 4100,
"first_token_ms": 900, "model": "claude-haiku-4-5" },
"conversation_id": "…", "trace_id": "…"
}Authentication
Authorization: Bearer bsk_live_… on every request. Keys are workspace-scoped: the workspace is resolved from the key itself, never from anything you send, so a key can only ever reach its own tenant's data.
We store only a SHA-256 hash of your key. If you lose it, revoke it and mint another — we genuinely cannot recover it.
Environments
bsk_live_ and bsk_test_ keys get separate quotas, separate cost caps, and separate usage reporting, so test traffic can be excluded from your numbers. They read and write the same workspace data — this is not a sandbox.
Scopes
A key carries only the scopes you tick when you create it. Give each integration the least it needs.
| ask:write | Ask questions and get grounded, cited answers. |
| search:read | Search the workspace's knowledge without calling a model. |
| documents:read | List documents and read ingestion status. |
| documents:write | Upload, ingest and delete documents. |
| actions:write | Let the agent act in company systems over MCP. |
| analytics:read | Read this key's own usage and cost. |
actions:write is the one to think about: it gives the key's agent session the same company-system tools a manager has. Without it, those tools are never discovered — they do not exist for that key.
Endpoints
| Route | Scope | What |
|---|---|---|
| GET /v1/me | — | Introspect the calling key. Needs no scope. |
| POST /v1/ask | ask:write | A grounded, cited answer. JSON or SSE. |
| POST /v1/search | search:read | Retrieval only — no model call. |
| GET /v1/documents | documents:read | List documents and their status. |
| GET /v1/documents/{id} | documents:read | Poll one document until ready. |
| POST /v1/documents | documents:write | Upload a PDF (multipart). 202. |
| POST /v1/documents/url | documents:write | Ingest a web page. 202. |
| DELETE /v1/documents/{id} | documents:write | Remove a document and its vectors. |
| GET /v1/usage | analytics:read | This key's own calls, spend and errors. |
The full machine-readable schema is at https://api.brainstack.space/openapi.json.
Search without a model
/v1/search runs the full hybrid retrieval stack — dense vectors, BM25, and reciprocal-rank fusion — and returns the passages. No LLM call, so it is fast, cheap and deterministic. Use it when you want to bring your own model, or to inspect what a question would retrieve.
curl https://api.brainstack.space/v1/search \
-H "Authorization: Bearer bsk_live_..." \
-H "Content-Type: application/json" \
-d '{"query": "ERR_4021", "top_k": 5}'Streaming
Pass "stream": true (or send Accept: text/event-stream) to get server-sent events: trace, sources, delta, reset, done.
A resetevent means the agent's self-check rejected its own draft and is rewriting — discard the text you have rendered so far and keep reading.
Ingestion is asynchronous
Uploads return 202 with a document id. Poll GET /v1/documents/{id} until status is ready — it walks queued → extracting → chunking → embedding → ready, with chunks_done / chunk_count for progress. Webhooks are not available yet.
Limits and caps
Every key has an hourly request limit and, optionally, a monthly spend cap. Rate-limited responses carry X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and Retry-After. Back off and retry; do not spin.
Errors
Every error has the same shape. Branch on code, never on the prose.
{
"code": "insufficient_scope",
"message": "This key lacks the `documents:write` scope.",
"request_id": "8f2c…",
"required": "documents:write"
}| 401 | invalid_api_key | Missing, malformed or unknown key. |
| 401 | key_revoked | The key was revoked. It will never work again. |
| 401 | key_expired | The key passed its expiry date. |
| 403 | insufficient_scope | The key lacks the scope this route needs. |
| 429 | rate_limited | Over the per-key or per-workspace hourly limit. |
| 429 | cost_cap_exceeded | Over this key's monthly spend cap. |
| 404 | document_not_found | No such document in this workspace. |
| 404 | conversation_not_found | No such conversation for this key. |
| 503 | model_not_configured | The answer model isn't configured server-side. |
Every response carries X-Request-Id. Quote it when something goes wrong — it is one lookup on our side.
What we record
Every call — including the ones we refuse — is written to a usage ledger with its route, status, latency and request id. Answers additionally record tokens, cost and the tools the agent used. All of it is visible to workspace admins per key, and to the key itself at /v1/usage.
Key creation, scope changes and revocation are written to an append-only audit log that nothing can delete.