Tenant Knowledge library API
Tenant-published knowledge is a per-organization, admin-managed set of text reference documents — a filename, a content_type, and the text content itself — uniformly visible to every member of that organization. Unlike the per-project Knowledge feature (whose files are scoped to one project's collaborators, with a per-row source-ACL for connector-synced content), a published tenant-knowledge item has no per-row access control at all: publishing it makes it visible to the whole tenant, full stop. There is no separate "published" flag or draft state — creating an item publishes it immediately, and deleting it unpublishes it (mirrors the Prompt library's admin-maintained + shared-to-members model). This is an API-only surface today — there is no admin UI for it yet.
Base URL: https://<your-gateway-host>/admin/v1
Admin-gated routes (publish / maintain)
Required role for POST / GET / DELETE under /tenant-knowledge: admin, tenant_admin, or ki_manager. A caller with any lower role is rejected 403; an unauthenticated caller 401. The client is never the authorization boundary. Tenant scope is decided server-side, identically to the Prompt library API: tenant_admin / ki_manager are always pinned to their own tenant; admin targets one tenant via ?tenant_id=<id>.
POST /tenant-knowledge
Publish a new knowledge item. Body (JSON object):
| Field | Accepted | Rejected → 400 |
|---|---|---|
filename |
string, 1–255 characters (code points), valid UTF-8, every /-segment non-empty and not ./.. (required; the same validator as project knowledge) |
missing / empty / non-string / > 255 chars / malformed UTF-8 / a control character or backslash / a leading, trailing or doubled / / a . or .. segment |
content |
string, valid UTF-8, no NUL bytes, ≤ 7.5 MB (required) | missing / non-string / empty / malformed UTF-8 / contains a NUL byte / over the size limit |
content_type |
string (optional, default text/plain; null / "" = absent), valid UTF-8, at most 128 characters |
a non-string / malformed UTF-8 / longer than 128 characters / an image/* type other than image/svg+xml — this is a text-knowledge item, not a file upload |
Success → 201 { "id": "<new-id>" }. tenant_id, created_by, created_at are server-set and never accepted from the client. A filename that already exists in the same tenant — compared case-, accent- and trailing-space-insensitively (collation utf8mb4_uca1400_ai_ci) — is rejected with 409 and a body naming the request's spelling: {"error": "A knowledge item named \"policy.md\" already exists in this tenant. Delete it first.", "code": "knowledge_name_conflict"}. Branch on code (the prose may change); the existing item is untouched. Any other storage failure is a 500 with the fixed text failed to publish knowledge item — the database error is logged, never returned.
GET /tenant-knowledge
List the tenant's published knowledge (metadata only — no content), ordered by filename.
[
{ "id": "…", "filename": "onboarding-policy.md", "content_type": "text/plain", "size_bytes": 812, "token_count": 0, "created_by": "…", "created_at": 1753000000 }
]
GET /tenant-knowledge/<id>
One item's full content. Unknown id, or an id belonging to another tenant → 404 { "error": "knowledge item not found" } — the boundary never leaks another tenant's existence.
DELETE /tenant-knowledge/<id>
Unpublish (remove) an item. Unknown / another tenant → 404 { "error": "knowledge item not found" }. Success → 200 { "deleted": true }.
Member read-only routes
GET /me/tenant-knowledge and GET /me/tenant-knowledge/<id> require only a valid authenticated session — no GOVERNANCE_MANAGE permission — mirroring GET /me/shared-commands. They are scoped to the caller's own tenant (resolved server-side from the session, never a client value): a member can list and read every item their tenant's admins have published, and a guessed id from another tenant reads as 404, never disclosed.
// GET /me/tenant-knowledge
[
{ "id": "…", "filename": "onboarding-policy.md", "content_type": "text/plain", "size_bytes": 812, "token_count": 0, "created_by": "…", "created_at": 1753000000 }
]
// GET /me/tenant-knowledge/<id>
{ "id": "…", "filename": "onboarding-policy.md", "content_type": "text/plain", "extracted_text": "…", "token_count": 0, "created_by": "…", "created_at": 1753000000 }
Notes
- No per-row ACL. This is the deliberate difference from per-project Knowledge: a published tenant-knowledge item is visible to every member of the tenant unconditionally. It is not filtered by project membership, connector-sync source grants, or the generated-file share gate — those mechanisms do not apply here.
- Full-gauntlet isolation. Every admin route and every member route is scoped
tenant_id = ?(never client-suppliable). A member of one organization can never read, list, or unpublish another organization's knowledge, regardless of the id guessed. - v1 scope. Content is direct text only — there is no file-upload ingest pipeline (OCR/PDF extraction) for tenant-published knowledge yet; an item is served immediately on creation. Wiring published tenant knowledge into a chat's retrieved context (the system-prompt composer) is a separate, not-yet-built integration.