Knowledge Base (RAG)
Syntropy's Knowledge Base API lets connected products store documents, run embedding-based ingestion, and serve retrieval-augmented answers — without managing any vector infrastructure directly.
Authentication
KB endpoints sit under /api/{accountId}/... and use project API keys (not session auth). Pass the key as a Bearer token or x-api-key header.
Required scopes:
| Scope | Endpoints |
|---|---|
kb:write | Register documents |
kb:read | Query, poll document status |
Endpoints
POST /api/{accountId}/kb/docs
GET /api/{accountId}/kb/docs/{documentId}
POST /api/{accountId}/kb/query
Register a document
POST /api/{accountId}/kb/docs
Registers a document for ingestion. The document must already be uploaded to Syntropy Blob Storage; pass its blob key. Ingestion is asynchronous — the job is enqueued immediately and you can poll status via GET /kb/docs/{documentId}.
Re-registering the same blobKey for a tenant resets the document to pending and re-triggers ingestion, making it safe to call on updates.
Request
POST /api/{accountId}/kb/docs
Authorization: Bearer syn_sk_xxx
Content-Type: application/json
{
"tenantId": "firm_abc123",
"kbId": "default",
"blobKey": "tenants/firm_abc123/docs/contract-2026.pdf",
"title": "Service Agreement 2026",
"metadata": {
"practiceArea": "corporate",
"jurisdiction": "NY"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
tenantId | string | ✓ | Opaque string identifying the sub-tenant (e.g. a law firm ID). Auto-registered on first use. |
kbId | string | Knowledge base partition. Defaults to "default". | |
blobKey | string | ✓ | Key in Syntropy Blob Storage pointing to the document file. |
title | string | ✓ | Human-readable document title. |
metadata | object | Arbitrary key-value metadata stored on the document. |
Response 201
{
"documentId": "uuid",
"jobId": "uuid",
"status": "pending"
}
Use documentId to poll ingestion status.
Get document status
GET /api/{accountId}/kb/docs/{documentId}
Poll this endpoint after registering a document to check ingestion progress.
Response 200
{
"document": {
"id": "uuid",
"tenantId": "firm_abc123",
"kbId": "default",
"blobKey": "tenants/firm_abc123/docs/contract-2026.pdf",
"title": "Service Agreement 2026",
"metadata": { "practiceArea": "corporate" },
"sourceType": "upload",
"ingestionStatus": "complete",
"ingestionError": null,
"createdAt": "2026-03-20T10:00:00.000Z",
"updatedAt": "2026-03-20T10:01:23.000Z"
}
}
ingestionStatus values:
| Value | Meaning |
|---|---|
pending | Queued, not yet picked up |
processing | Worker is actively ingesting |
complete | Chunks written, document is queryable |
failed | Ingestion failed; see ingestionError |
Query the knowledge base
POST /api/{accountId}/kb/query
Embeds the question, runs a vector similarity search over the tenant's documents, and returns an LLM-generated answer with source citations.
Request
POST /api/{accountId}/kb/query
Authorization: Bearer syn_sk_xxx
Content-Type: application/json
{
"tenantId": "firm_abc123",
"kbId": "default",
"question": "What are the termination clauses in the 2026 service agreement?",
"topK": 8
}
| Field | Type | Required | Description |
|---|---|---|---|
tenantId | string | ✓ | Tenant to search within. |
kbId | string | Knowledge base partition. Defaults to "default". | |
question | string | ✓ | The question to answer. |
topK | integer | Number of chunks to retrieve (1–50, default 8). |
Response 200
{
"answer": "The agreement may be terminated by either party with 30 days written notice...",
"citations": [
{
"documentId": "uuid",
"blobKey": "tenants/firm_abc123/docs/contract-2026.pdf",
"title": "Service Agreement 2026",
"snippet": "Either party may terminate this Agreement upon 30 days written notice...",
"score": 0.9312
}
]
}
score is cosine similarity (0–1); higher is more relevant. Citations are deduplicated by document and ordered by relevance.
Blob key conventions
Documents should be uploaded to Syntropy Blob Storage using a path that scopes them to the tenant:
tenants/{tenantId}/docs/{uuid} # documents for ingestion
tenants/{tenantId}/intake-attachments/… # intake session uploads
tenants/{tenantId}/assets/… # branding / static assets
Use createPresignedUploadUrl from @eclosion-tech/syntropy-blob-storage to issue short-lived upload URLs for direct browser uploads, then pass the resulting blob key to POST /kb/docs.
Ingestion pipeline
- Blob is fetched from storage
- Text is extracted (PDF, plain text)
- Text is chunked (~1000 chars, 200 char overlap)
- Chunks are embedded via
text-embedding-3-small(1536 dims) - Embeddings are written to
kb_chunkswith an HNSW index - Document status updated to
complete
On failure, status is set to failed and ingestionError contains the reason. Re-registering the document triggers a clean re-ingestion.
Multi-tenancy
All data is scoped to (accountId, tenantId). A query for tenantId: "firm_abc" can never return documents belonging to tenantId: "firm_xyz", even within the same Syntropy account. Isolation is enforced at both the application and database (RLS) layers.