Knowledge API

Create knowledge bases, upload files, send records and web pages, follow each document, and search with filters and citations — the same paths on self-hosted and Cloud.

Everything the Knowledge page does, and a few things it does not, is under /api/v1/knowledge. The paths, bodies and answers are the same on a self-hosted install and on SynapseOrch Cloud; where the two differ, this page says. Every call takes an API key as a Bearer token. Each endpoint's fields and answers, with samples in four languages, are in the reference, starting at List knowledge bases.

While Knowledge is switched off, every route here answers 404, before the key is even checked.

Endpoints

MethodPathCloud scope
GET/api/v1/knowledge/basesList bases, each with its stateread
POST/api/v1/knowledge/basesCreate a base — 201build
GET/api/v1/knowledge/bases/{kb_id}Get a baseread
PATCH/api/v1/knowledge/bases/{kb_id}Change a basebuild
DELETE/api/v1/knowledge/bases/{kb_id}Delete a base; its files stay in the Vault — 202build
POST/api/v1/knowledge/bases/{kb_id}/cost-previewPrice the base's indexing, with or without changed settingsread
POST/api/v1/knowledge/bases/{kb_id}/uploadsStart an upload — 201build
PUTthe part URL the start gaveSend one part of an upload—
POST/api/v1/knowledge/uploads/{upload_id}/completeComplete an upload — 202build
DELETE/api/v1/knowledge/uploads/{upload_id}Cancel an upload — 204build
POST/api/v1/knowledge/bases/{kb_id}/recordsSend records — 202build
DELETE/api/v1/knowledge/bases/{kb_id}/records/{record_id}Delete a record — 202build
POST/api/v1/knowledge/bases/{kb_id}/urlsAdd web pages — 202build
GET/api/v1/knowledge/bases/{kb_id}/documentsList a base's documentsread
GET/api/v1/knowledge/documents/{doc_id}Get a document, with its reportread
POST/api/v1/knowledge/documents/{doc_id}/retryRead a document again — 202build
DELETE/api/v1/knowledge/documents/{doc_id}Delete a document and its file — 202build
POST/api/v1/knowledge/searchSearchread
GET/api/v1/knowledge/queries/{query_id}Read a search's traceread
GET/api/v1/knowledge/bases/{kb_id}/costsWhat a base has costread

The scope column is what a call needs on SynapseOrch Cloud: every key has read and run, and build only when it was made with Can also build (What a key can do). A self-hosted key reaches every route. The page's web-source progress and Evaluate are not on the API.

A request body with a field the endpoint does not know is refused with 422. Lists are plain JSON arrays. Errors are {"detail": …}.

Bases

curl -X POST http://localhost:3000/api/v1/knowledge/bases \
  -H "Authorization: Bearer sk-syn-..." -H "Content-Type: application/json" \
  -d '{"name": "Contracts", "folder": "contracts",
       "description": "Signed supplier contracts, 2022 on.",
       "retrieval": {"top_k": 10}}'

Only name and folder are required; everything else takes the defaults described in Search and settings:

FieldDefault
name—1–200 characters
description""Up to 2,000 characters
folder—A Vault folder, created if missing; it may not overlap another base's
status"active""disabled" switches the base off: not searched, and uploads, records and web pages are refused with 409
language"simple"For keyword matching: simple or a language such as english, french, german
visitor_accessfalseWhether widget and channel visitors may search it
budget_monthly_usdnullNo budget
chunking{"mode": "structure", "target_tokens": 450, "max_tokens": 800, "overlap_pct": 0}mode is structure, fixed, page or none
embedding{"model": "", "dims": null}"" is the workspace default
rerank{"enabled": null, "model": null, "top_n": 8, "candidates": 50}enabled: null reranks where a reranker is configured
fusion{"rrf_k": 60, "overfetch": 50, "weights": {"lexical": 1, "vector": 1}}
retrieval{"top_k": 8, "score_threshold": null, "parent_expansion": "off", "neighbor_window": 1, "max_context_tokens": 6000}parent_expansion is off, neighbors or section
enrichment{"mode": "prefix", "contextual_model": null, "extract_metadata": false}mode: "contextual" turns on Contextual Retrieval
web{"max_depth": 3, "max_pages": 500, "rps_per_host": 1, "recrawl_days": 7, "sources": []}Crawl limits, within the deployment's; recrawl_days: 0 fetches only when asked

enrichment.extract_metadata is not built yet: true is refused with a 422, on a create and on an edit that turns it on. A base stored with it keeps it.

PATCH sends the base's revision — the one your edit started from — and only the fields to change. A stale revision is refused with 409, so two edits never overwrite each other silently. "clear_budget": true removes the budget. A change to chunking, embedding, enrichment or language rebuilds the index beside the serving one, and the answer says "reindex_started": true.

A base's state reports counts_by_status, this month's spend (month_spend_usd), why it is paused if it is (paused_reason), the serving and building index versions, and — during a rebuild — build: docs_total, docs_done, docs_failed, passages_done, passages_total, the current stage, eta_s in seconds, and waiting when a rate limit is being waited out.

Uploads

A file goes up in three steps, so a large one never travels in a single request:

# 1. Start: name, size and type
curl -X POST http://localhost:3000/api/v1/knowledge/bases/kb_…/uploads \
  -H "Authorization: Bearer sk-syn-..." -H "Content-Type: application/json" \
  -d '{"filename": "msa-globex.pdf", "size_bytes": 12001743, "mime": "application/pdf"}'
{
  "upload_id": "kup_…",
  "part_size": 8388608,
  "parts": [
    {"part_number": 1, "url": "…", "method": "PUT", "headers": {"X-Synapse-Upload-Token": "…"}},
    {"part_number": 2, "url": "…", "method": "PUT", "headers": {"X-Synapse-Upload-Token": "…"}}
  ],
  "expires_at": "2026-10-05T09:12:00Z"
}

That is a self-hosted answer; on SynapseOrch Cloud each url points straight at storage and headers is {}.

  1. Send each part with PUT to its url: exactly part_size bytes (the last part is the rest), with exactly the part's headers — and nothing else, no Authorization. Keep each answer's ETag header as it came, quotes included.
  2. Complete with the ETags:
curl -X POST http://localhost:3000/api/v1/knowledge/uploads/kup_…/complete \
  -H "Authorization: Bearer sk-syn-..." -H "Content-Type: application/json" \
  -d '{"parts": [{"part_number": 1, "etag": "\"9b2c…\""}, {"part_number": 2, "etag": "\"41ef…\""}]}'

The answer is 202 with the new document, queued. One rule covers both products — PUT each part URL with exactly its headers — although the URLs differ:

Self-hostedSynapseOrch Cloud
Part URLSynapse's own, /api/v1/knowledge/uploads/{upload_id}/parts/{n}Straight to storage
Part headersX-Synapse-Upload-Token, a token for this upload aloneNone ({})
A part URL lastsUntil expires_at (24 hours)An hour; past it, cancel and start again
Who completes or cancelsAny keyOnly the key or app that started it
Uploads open at onceNo limit5 per workspace (429 beyond)

path in the start puts the file in a subfolder of the base (not _web or _records), and metadata sets its metadata. A type the base does not take is 415; a file over the size limit, 413; a switched-off base, 409. A part whose upload is unknown, or whose token is wrong, answers 404.

Cancel an upload you will not complete — a part failed, or the complete was refused — with DELETE /api/v1/knowledge/uploads/{upload_id}. Its parts are deleted, and on SynapseOrch Cloud it stops counting against the five.

Records

curl -X POST http://localhost:3000/api/v1/knowledge/bases/kb_…/records \
  -H "Authorization: Bearer sk-syn-..." -H "Content-Type: application/json" \
  -d '{"records": [
        {"id": "T-1042", "title": "Refund after cancellation",
         "text": "Customer cancelled within 14 days and asked for a refund…",
         "doc_date": "2026-09-30", "metadata": {"team": "billing", "priority": "high"}},
        {"id": "SKU-77", "json": {"name": "Desk lamp", "price": 49, "colours": ["black", "white"]}}
      ]}'

Each record has either text or json, and optionally id, title, doc_date (YYYY-MM-DD) and metadata. It is stored as _records/<id>.json in the base's folder, and sending the same id again replaces it. A record without an id gets a new one each time it is sent. Ids are letters, digits, ., _ and -, up to 128 characters. Up to 100 records go in one request, each up to 1,000,000 bytes. DELETE …/records/{record_id} deletes one.

Web pages

curl -X POST http://localhost:3000/api/v1/knowledge/bases/kb_…/urls \
  -H "Authorization: Bearer sk-syn-..." -H "Content-Type: application/json" \
  -d '{"urls": ["https://docs.example.com/guide/"], "kind": "site", "max_depth": 2, "max_pages": 100}'

kind is page (the default), sitemap or site; max_depth and max_pages replace the base's for these addresses, within the deployment's limits. The answer is 202 with crawl_id and how many addresses were accepted, and the crawl runs on — see Web pages for what it reads. Each address becomes a source in the base's web.sources. To remove one, PATCH web.sources without it; a source id that another base holds is refused with 422.

Documents

GET /api/v1/knowledge/bases/{kb_id}/documents lists a base's documents, paged by after (the last id seen) and limit (up to 1,000), and narrowed by status or path_prefix. GET /api/v1/knowledge/documents/{doc_id} adds the document's report: each stage of the last attempt, its warnings, and whether a retry can help.

Field
statusqueued, parsing, indexing, indexed, partial, failed, skipped, paused, blocked or deleting — see Document status
status_reason, errorWhy, as a code and in words
source, source_urlupload, web, record or vault; a web page's address
path, title, doc_date, metadata, mime, size_bytesThe file
pages, chunks, tokens, version, indexed_atWhat was indexed, and when
progressWhile the document is read or indexed: stage (parse, ocr, enrich, embed, index), done and total with their unit (pages or passages), eta_s, and waiting — {"reason": "rate_limited", "provider": …, "until": …} — while a rate limit is waited out. On SynapseOrch Cloud a wait past two minutes puts the document back in queued; its progress keeps the stage it reached, with waiting.until when it carries on

POST …/retry reads a resting document again (409 while it is being worked on); DELETE deletes the document and its file.

Knowing when a document is ready

Self-hosted: there are no Knowledge webhooks. Poll GET /api/v1/knowledge/documents/{doc_id} until status is one of indexed, partial, failed, skipped, paused or blocked, or watch a base's state.counts_by_status.

SynapseOrch Cloud: a webhook can tell you when a document is indexed, partly indexed or failed. Deliveries are best effort, so the document is still the truth.

curl -X POST http://localhost:3000/api/v1/knowledge/search \
  -H "Authorization: Bearer sk-syn-..." -H "Content-Type: application/json" \
  -d '{"query": "When can the supplier end the contract?",
       "kb_ids": ["kb_…"],
       "filters": {"doc_type": "contract", "year": {"$gte": 2024}},
       "top_k": 5}'
FieldDefault
query—Up to 2,000 characters
kb_ids—1 to 10 bases
filtersnoneSee Filters
path_prefixnoneOnly documents under this folder of the base, matched whole folder by whole folder
top_kThe bases' own1–50
rerankThe bases' owntrue or false for this search
score_thresholdThe base's own, for one baseDrop hits below it
neighbors00–3 passages either side of each hit, in its text
mode"hybrid""lexical" or "vector" for one search alone
snapshot_idsEach base's serving index{"kb_…": "ksnap_…"} pins a base to one version of its index
include_tracefalseAdds the trace to the answer
{
  "query_id": "kq_…",
  "hits": [
    {
      "label": "[K1]",
      "content": "Either party may terminate this agreement with 30 days' written notice…",
      "context": "Globex Payroll MSA (2025) › 12 Term and termination",
      "score": 0.91,
      "scores": {"lexical": 0.42, "vector": 0.81, "rerank": 0.91},
      "metadata": {"doc_type": "contract", "year": 2025},
      "citation": {
        "label": "[K1]", "path": "contracts/msa-globex.pdf", "source": "upload",
        "title": "Master Services Agreement", "page_start": 7, "page_end": 7,
        "heading_path": ["12 Term and termination"], "snippet": "Either party may terminate…",
        "indexed_at": "2026-10-02T11:04:09Z", "doc_id": "kdoc_…", "chunk_id": "kch_…"
      }
    }
  ],
  "degraded": [],
  "cost": {"total_est_usd": 0.002501, "total_charged_microusd": 2501, "by_stage": {"embed": 0.000001, "rerank": 0.0025}}
}

Each hit's citation also carries char_start/char_end, source_url, sha256, doc_version and snapshot_id. degraded lists what the search did without — vector_unavailable, rerank_failed, rerank_unconfigured, embed_failed, budget_paused, snapshot_retired — each explained in Test search. total_charged_microusd is what SynapseOrch Cloud's wallet paid; on a self-hosted install it is null.

GET /api/v1/knowledge/queries/{query_id} returns a search's trace — the request, each search's candidates with their ranks and scores, the cuts and the hits — for 30 days. An unknown or expired one is 404. A search naming an unknown base, or a pinned version that is gone, is 404 too.

Filters

A filter is a JSON object over a document's metadata and a few fields every document has. The search, the Retrieve step and an agent's search_knowledge all use the same language.

{"doc_type": "contract", "year": {"$gte": 2024}, "$or": [{"region": "EU"}, {"tags": "urgent"}]}
  • Each key of an object must match: the object is an AND.
  • A plain value means equal; a list must be spelled {"$in": [...]}.
  • Operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, and $and, $or (each a list) and $not (one object).
  • Every document has path, source (upload, web, record or vault), mime, title, doc_date (YYYY-MM-DD) and indexed_at (an ISO time).
  • $ne matches a document that lacks the field. Ranges compare numbers with numbers and text with text. A list field equals a value when it contains it, so {"tags": "urgent"} matches ["legal", "urgent"].
  • At most 4 levels deep, 50 conditions and 100 values in an $in.

The filter runs inside the search, so top_k hits always match it. A malformed filter is refused with 422, its type naming the problem — filter.unknown_operator, filter.too_deep, filter.type_mismatch and so on.

An agent's search_knowledge takes the same filter as a JSON string, because some models cannot call a tool whose argument may be more than one type.

Costs

POST …/cost-preview prices indexing the base — with its settings, or with {"overrides": {…}} in the shape of a PATCH — without saving anything:

{"docs": 24, "pages": 311, "total_est_usd": 0.0042, "budget_remaining_usd": 4.2,
 "stages": [{"stage": "embed", "model": "synapse.titan-embed-v2", "units": 168000, "unit": "tokens",
             "est_usd": 0.0042, "cost_source": "platform"}]}

GET …/costs reports what the base has spent, grouped by group_by — day (the default), stage, document or model — from since to until (ISO dates or times; this month by default). Each group has key, est_usd, charged_microusd (SynapseOrch Cloud), input_tokens and units. See Costs and budgets.

Limits on SynapseOrch Cloud

  • Searches: 120 a minute per key, shared with MCP's search_knowledge. Past it: 429 Rate limit exceeded: Knowledge searches per minute for this key. Retry after … s.
  • Starting work — an upload's start and complete, records, web pages, a retry — counts toward the key's 60 starts a minute.
  • Every call counts toward the key's 300 requests a minute and the organisation's 1,200; see the API overview.

A self-hosted install applies none of these.

On this page