Developers

qbrin API Reference

Ask your company brain over HTTP. Authenticate with an API key, push in your knowledge, then POST a question and get a source-cited answer — or a clear abstain. This reference covers every endpoint, field and status code.

Overview

The qbrin REST API turns everything your team has connected into cited, grounded answers. Every response either links to the exact source it came from, or tells you it does not know — it is built to cite or abstain rather than guess. All requests are made over HTTPS to a single base URL.

Base URLhttps://app.qbrin.com/api
Cited or it abstains

Answers carry citations to the source, or return a plain "not in your records". Designed for zero false accepts.

Permission-aware

Answers respect the source permissions of the key's owner.

JSON or streaming

Get the whole answer as JSON, or stream tokens over Server-Sent Events for a live UI.

Scoped keys

Issue read-only, read-write or admin keys per integration, and revoke any of them in one click.

Authentication

Every request authenticates with an API key sent as a Bearer token. Create one in the console under Settings → API keys. The key is shown once at creation and always starts with qbrin_ — store it somewhere safe, because it can never be retrieved again.

Authorization: Bearer qbrin_3f9a…c1d7

Key scopes

Give each integration the least privilege it needs. Scope is enforced per HTTP method, so a read-only key that leaks can never write.

ScopeCan do
readGET requests only — search and read-only lookups. Note: asking a question is a POST, so it needs write.
writeEverything read can, plus POST / PUT / PATCH / DELETE — asking questions, adding content, feedback.
adminFull access. Note: ZIP upload additionally requires the key's owner to be a workspace admin.
Keep keys server-side. An API key carries the permissions of the person who created it. Never ship one in browser or mobile code — proxy calls through your own backend. A missing or invalid key returns 401; a key used beyond its scope returns 403.

Add content

Two ways to push knowledge in over the API. Both feed the same pipeline — text is chunked, embedded into the vector index and entity-extracted automatically. There is no separate "embed" call; you watch it complete (next section) and then it is answerable.

POST/api/documentsscope: write

Send text or markdown directly. Best for programmatic, per-document ingestion.

FieldTypeDescription
contentstringrequiredThe document body (plain text or markdown). Up to ~800,000 characters.
titlestringoptionalA title for the document. Defaults to "Untitled document".
curl https://app.qbrin.com/api/documents \
  -H "Authorization: Bearer qbrin_3f9a…c1d7" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Refund Policy", "content": "Customers may request a full refund within 30 days…" }'
Deduplicated by content. Re-posting identical title + text returns the same document instead of a duplicate. Documents added this way are org-wide (visible to every member under access control).
POST/api/uploads/zipadmin only

Bulk-ingest a folder of files by uploading a single .zip. qbrin streams the archive to disk (never buffering it in memory), verifies it is a real ZIP by its magic bytes, extracts the text from supported files, and ingests each one. The request returns immediately with a job id; a worker does the heavy lifting.

RequirementDetail
Content-Typemultipart/form-data, exactly one file field.
FormatA genuine .zip archive (checked by header, not extension).
Max size100 MB compressed.
AvailabilityThe key's owner must be a workspace admin, and file upload must be enabled for your workspace.
curl https://app.qbrin.com/api/uploads/zip \
  -H "Authorization: Bearer qbrin_3f9a…c1d7" \
  -F "file=@handbook.zip"
qbrin keeps no original files. Only the extracted text is embedded and stored; the uploaded archive is deleted after processing. Take the returned jobIdand poll the status endpoint below to follow extraction and embedding.

Embedding & status

You never call an embed endpoint directly. Everything you ingest flows through one pipeline — extract text, chunk, embed into the vector index, extract entities — and only becomes answerable once the embedding step finishes. These endpoints let you watch that happen.

queuedextractembeddingdone

Track a ZIP upload

GET/api/uploads/{jobId}/status

Poll with the jobId from the upload response until phase is "done".

{
  "jobId": "upload-7c1e9a4b2f0d",
  "phase": "embedding",        // queued | extract | embedding | done | error
  "state": "done",
  "ingested": 42,              // files turned into documents
  "skipped": 3,                // unsupported / empty files
  "embed": { "total": 380, "embedded": 294 },   // chunks embedded — progress bar
  "files": [ "handbook/onboarding.md", "handbook/pto.pdf" ],
  "error": null
}
embed.embedded / embed.total is a ready-made progress percentage. When phase flips to "done", every chunk is embedded and the content is live in /api/ask and /api/search.

Track a single document

GET/api/documents/{id}

For content added via POST /api/documents, fetch the document and read chunkCounts — it moves from pending to embedded as the vector index fills in.

{
  "document": { "id": "doc_a91f22e0", "title": "Refund Policy", "extractStatus": "done" },
  "chunkCounts": { "pending": 0, "embedded": 6, "extracted": 6 }
}

Use GET /api/documents?limit=50 to list recently ingested documents. Embedding is fully managed — the model, dimensions and hardware are a server-side detail you do not configure per request.

Ask

POST/api/askscope: write

The core endpoint. Send a question; qbrin retrieves across the connected sources, composes an answer grounded in what it found, verifies it, and returns the answer with its citations. If the answer is not supported, it abstains instead of guessing. Asking logs a query, so it is a POST and needs a write (or admin) key.

Request body

FieldTypeDescription
questionstringrequiredThe question to answer. Up to 4,000 characters.
kintegeroptionalHow many source excerpts to retrieve. 1–20, default 12.

Example request

curl https://app.qbrin.com/api/ask \
  -H "Authorization: Bearer qbrin_3f9a…c1d7" \
  -H "Content-Type: application/json" \
  -d '{ "question": "What is our refund window?", "k": 8 }'

Response 200

{
  "answer": "Customers may request a full refund within 30 days of purchase. [1]",
  "queryId": "qy_8fb2a1c0",
  "citations": [
    {
      "n": 1,
      "documentId": "doc_a91f",
      "source": "gdrive",
      "title": "Refund Policy.pdf",
      "snippet": "…a full refund within 30 days of purchase…",
      "authorEmail": "ops@acme.com",
      "occurredAt": "2026-03-11T00:00:00Z"
    }
  ],
  "trust": {
    "decision": "allow",
    "level": "verified",
    "axes": { "provenance": "cited", "entailment": "passed" },
    "certificateId": "a1b2c3d4e5f6"
  },
  "usage": { "prompt_tokens": 1840, "completion_tokens": 42 },
  "timings": { "retrieveMs": 210, "totalMs": 640 }
}
When qbrin does not know: the reliable, path-independent signal is an empty citationsarray together with the short "not enough information" sentinel in answer. Check citations.length === 0 to detect an abstain, and treat it as a first-class outcome, not an error.

The trust object

When present, an answer also carries a trust certificate so you can decide programmatically whether to show, flag or withhold it. Use it as an additional signal on top of the citation check above.

FieldValuesMeaning
decisionallow abstain blockShip it, it did not know, or it was withheld as unsupported.
levelverified · cited_unverified · abstained · blockedHow strongly the answer was checked.
axesprovenance, entailment, …Per-check results; unmeasured checks read not_assessed rather than a fake pass.
certificateIdstringA stable id for auditing or logging the exact verdict.
A good default when the trust object is present: render the answer on decision === "allow", show your own "no answer found" UI on "abstain", and log "block" for review.

Streaming (Server-Sent Events)

Send Accept: text/event-stream to stream the answer token-by-token. Read named events until done.

EventPayload
tokenAn answer fragment. A reset marks a verified replacement — discard what streamed and take the next stream.
citationsThe sources, so you can render them alongside the text.
doneFinal envelope: queryId, trust (when present), usage, timings.
followups2–3 suggested next questions (may arrive after done).
errorA failure occurred mid-stream; payload is { message }. Stop consuming and surface or retry — a done will not arrive.

You may also see informational qwsec and fallback events. Ignore any event name you do not recognise so the stream stays forward-compatible.

curl -N https://app.qbrin.com/api/ask \
  -H "Authorization: Bearer qbrin_3f9a…c1d7" \
  -H "Accept: text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{ "question": "Summarize last quarter wins" }'

event: token
data: {"text":"We closed 12 enterprise "}
event: citations
data: {"citations":[…]}
event: done
data: {"queryId":"qy_8fb2","trust":{"decision":"allow"}}

Answer feedback

POST/api/{queryId}/feedbackscope: write

Tell qbrin whether an answer was useful, using the queryId returned by /api/ask. This tunes future answers.

FieldDescription
helpfulrequired1 helpful · 0 neutral · -1 not helpful.
curl https://app.qbrin.com/api/qy_8fb2a1c0/feedback \
  -H "Authorization: Bearer qbrin_3f9a…c1d7" \
  -H "Content-Type: application/json" \
  -d '{ "helpful": 1 }'

Manage API keys

You will normally create and revoke keys in the console (Settings → API keys), but the same operations are available programmatically. These run in your signed-in session, not with a Bearer key.

MethodPathPurpose
GET/api/auth/tokensList your keys (name, prefix, last used) — never the secret.
POST/api/auth/tokensCreate a key: name, optional scopes and expiresInDays. Returns the raw token once.
DELETE/api/auth/tokens/{id}Revoke a key immediately.
// 201 Created — the raw secret is returned exactly once, at token.token
{
  "token": {
    "id": "tok_a1b2c3",
    "name": "ci-pipeline",
    "prefix": "qbrin_3f9a",
    "scopes": ["read"],
    "expiresAt": null,
    "createdAt": "2026-07-12T00:00:00Z",
    "token": "qbrin_3f9a2b7c8d1e…c1d7"
  }
}
The raw secret is the nested token.token field, returned only in this create response. Store it immediately — it can never be retrieved again. The list endpoint only ever returns the prefix, never the secret.

Errors & limits

Errors return a JSON body { "error": "…" } with a standard HTTP status.

StatusMeaning & fix
400Bad request — e.g. missing question/content, or over the character limit. Fix the payload.
401Missing or invalid API key. Check the Authorization header.
403The key's scope does not permit this action (e.g. a read key doing a POST, or ZIP upload without admin). Issue a wider-scoped key.
404Resource not found — e.g. an unknown queryId, document or job id.
413Payload too large — a document over ~800k characters, or a ZIP over 100 MB.
429Rate limited. Back off and retry with exponential backoff.
503A required model or embedder is not configured server-side. Transient — retry or contact support.

Rate limits

Requests are rate-limited per key to protect the service — comfortable for normal interactive and batch use. If you expect sustained high volume, reach out at hello@qbrin.com and we will raise your ceiling.

See it answer

See it answer your hardest question.

Bring one real question your team keeps re-asking. We'll connect a source, read-only, and show you the answer, sourced, in seconds, in a 20-minute walkthrough. Nothing changes in your tools.

20-minute walkthrough
  • One source connected, read-only
  • Your real question answered, with sources
  • Nothing changes in your tools
Pick a timeor email hello@qbrin.com