Skip to content

API reference

KDBL Context Lake (K-Lake) exposes a REST API. The UI and the kdbl-control CLI both use it, so anything you see in the console can be scripted.

Base URL

Everything except the health probes lives under /api:

https://<your-kdbl-host>/api/...

Authentication

All /api/* endpoints require a bearer token. The two accepted token types:

  • Personal access token (PAT) — minted from /api/tokens or the UI. Format: kdblpat_<base64>.
  • OIDC bearer token — issued by your tenant's identity provider. It must carry an issuer (iss) and an audience (aud) claim that match the tenant's sign-in settings; a token that leaves either out is refused with 401 (auth.token_missing_issuer / auth.token_missing_audience), even when the identity provider signed it. Entra ID, Okta, Google and K-Lake's own sign-in server always include both; Keycloak needs an Audience mapper on the client.

Send the token in the Authorization header:

Authorization: Bearer kdblpat_...

Unauthenticated endpoints (no token required):

  • GET /healthz — liveness
  • GET /readyz — readiness
  • GET /api/authn/discover?tenant=<slug> — returns the tenant's sign-in issuer, client ID, audience and the issuer's authorization_endpoint + token_endpoint (read from its discovery document). 404 when the tenant has no SSO configured; 503 when the issuer's metadata cannot be fetched

Conventions

  • All identifiers in path segments are URL-encoded. s3://my-bucket becomes s3%3A%2F%2Fmy-bucket.
  • Request and response bodies are JSON.
  • Timestamps are ISO-8601 UTC.
  • Listing endpoints accept ?limit= and ?after= for cursor pagination (default 100 rows, hard ceiling 1000). The body stays a flat array; when there may be another page the response carries a Link: <...>; rel="next" header to follow. Treat the cursor as opaque. The Estate insights file drill-down (/api/discover/files) is the exception — it returns next_cursor in its body.
  • Errors return a JSON body with an appropriate 4xx or 5xx status — see Errors.

Errors

Every error answers with the same envelope:

{
  "error": "bad request: issuer must be an http(s) URL",
  "code": "sso.issuer_invalid",
  "params": { "field": "issuer" }
}
  • error — the message in English. Show it as-is when you have nothing better.
  • code — always present: a stable, machine-readable code, lowercase and dotted as <area>.<reason>. Branch on code, never on the text of error, which may be reworded.
  • params — present when the code has values to show (a field name, a count, a limit): the values a translated message fills in.

A code, once published, is never renamed or reused. An error without a more specific code carries its kind's generic one:

Code Status
bad_request 400
unauthorized 401
forbidden 403
not_found 404
conflict 409
payload_too_large 413
too_many_requests 429 — params carries limit and retry_after_secs
service_unavailable 503 — try again later
internal 500 — never carries detail; it is logged server-side

There are about 370 specific codes. They are grouped by area, so the part before the dot tells you what the request was about:

Area What it covers Examples
auth Who may call; token problems auth.tenant_admin_required (403), auth.token_missing_audience (401)
token Personal access tokens token.scope_unknown (400), token.scope_insufficient (403)
request, db Malformed or conflicting requests request.invalid_json (400), request.invalid_cursor (400), request.field_required (400, field)
source, principal, endpoint_policy Sources, their configuration, grants and schedules source.not_found (404, source_id), source.id_conflict (409), source.endpoint_refused (400)
file Files and their versions file.not_found (404), file.revision_not_found (404)
search Content search search.query_empty (400), search.offset_unsupported (400)
entity, ticker_alias The knowledge graph and ticker aliases entity.not_found (404), ticker_alias.ticker_invalid (400)
classification The classification scheme classification.label_unknown (400)
action Smart Actions action.step_ambiguous (400)
discover Estate insights discover.drilldown_unsupported (400)
dead_letter Failed work dead_letter.state_invalid (400), dead_letter.cluster_scope_required (400)
tenant, tenant_lifecycle, settings, download_links Tenants and their settings tenant.not_found (404, slug), tenant.suspended (403), tenant.retention_above_default (400, field, max)
user Users and roles user.last_admin (409), user.role_invalid (400)
sso, directory, entra Console sign-in and directory sync sso.issuer_invalid (400), sso.shared_issuer_conflict (409), directory.ldap_config_incomplete (400)
mcp The MCP endpoint, its sign-in, clients and terminology mcp.token_scope_refused (403, scopes), mcp.redirect_uri_insecure (400), mcp.client_not_found (404)
quota, licence, feature Quotas, the licence and licensed features quota.sources_exceeded (403, used, max), feature.not_licensed (403, feature)
usage Usage reports usage.window_too_long (400)
warehouse, warehouse_audit Data warehouses warehouse.not_found (404), warehouse.slug_taken (409)
ca_cert, console_tls, branding Certificates and branding ca_cert.chain_refused (400), console_tls.no_pending_request (409)
partner, delegation Hosting many customers partner.not_found (404), delegation.tenant_admin_required (403)
support, cloud_extraction, deployment Diagnostic packs, cloud extraction and the deployment support.full_level_unacknowledged (400), deployment.master_key_missing (503)

The console shows each code's message in the reader's language; an integration can do the same by keying its own text on code and filling in params.

Endpoints

Health

Method Path Description
GET /healthz Liveness probe. Always returns 200 if the process is up.
GET /readyz Readiness probe. Returns 200 only if dependencies are reachable.

Sources

Method Path Description
GET /api/sources List sources in the calling tenant
POST /api/sources Create a source (see body below)
GET /api/sources/:id Source detail
DELETE /api/sources/:id Remove a source and its indexed files
GET /api/sources/:id/stats File count, bytes, last indexed time
GET /api/sources/:id/health Last successful crawl + last error
GET /api/sources/:id/crawls Recent crawl runs and outcomes
POST /api/sources/:id/crawl Trigger a crawl. Body (all optional): prefix, mode, label, force_reextract (re-extract unchanged files too), force_reextract_extensions (narrow a forced re-run to these file extensions, e.g. ["pdf"]; requires force_reextract, else 400)
GET /api/sources/:id/files Paginated file listing
GET /api/sources/:id/files/:key Single file detail
GET /api/sources/:id/files/:key/access?user= Why a user can or cannot see this file: the trimming decision replayed with both sides' identities (tenant admin). See Why can't this user see this file?
POST /api/sources/:id/enabled Toggle the enabled flag
POST /api/sources/:id/bulk-ingest Toggle the bulk-ingest fast path
POST /api/sources/:id/meta-caps Set which optional enrichments to gather
POST /api/sources/:id/backfill-meta Enqueue enrichment for previously-indexed files
GET /api/sources/:id/meta-coverage How many files have each enrichment populated
POST /api/sources/:id/subtree Tune how large directory trees are split into parallel work units ({depth?, split_threshold?})
POST /api/sources/:id/security-trim Set the per-file trim policy ({mode, fail_closed?})
POST /api/sources/:id/multichannel SMB3 multi-channel (smbfs only)
GET/PUT/DELETE /api/sources/:id/metadata-rules The source's metadata rules (document dates and keyword fields such as ticker, sector, analyst and note type), the fields they produce and the background re-read's progress. PUT replaces the rules and re-reads the source's existing files in the background; DELETE removes them
POST /api/sources/:id/metadata-rules/backfill Re-read every file with the current rules
POST /api/sources/:id/metadata-rules/preview Dry run of rules against sample paths or the source's first files. Writes nothing
GET/POST /api/sources/:id/boilerplate Tenant admin. Boilerplate demotion for this source: the settings in force (enabled, min_files, min_share) and what has been found. POST takes any of the three settings; null restores a default
POST /api/sources/:id/boilerplate/backfill / …/reset Tenant admin. Count any files not yet counted, or forget what was learned and recount
GET /api/content/search Search the extracted content of every source the caller can see (?q=&limit=&mode=&group=&source_id=). q takes bare words (relevance-ranked, any-of), "quoted phrases", -exclusions, A OR B, and matches reference-like tokens such as INC-2026-0714 as a whole; once identifier-parts search is enabled, a word also matches part of an underscore-joined identifier (kozijn finds IFC_kozijn_90x114, ranked below the word on its own); file names and recovered original names are searched too. mode is hybrid (default) or lexical; hybrid degrades to lexical when the source or tenant has it off. group=file returns one hit per document (its best chunk, plus chunk_hits and up to three siblings). Each hit carries match_tier (phrase / all / partial — phrase hits are ranked first), name_match, mode, snippet, rank, a citation and a url. source_id narrows to one source.
GET /api/sources/:id/content/search The same search scoped to one source; same parameters and hit shape (minus source_id).
GET/POST /api/sources/:id/extract Read (GET) or set (POST) the content-extraction policy: enabled, extension allow/deny lists, size cap, path globs
GET /api/sources/:id/extract/coverage Extraction status rollup
GET /api/sources/:id/extract/progress Live extraction progress
GET /api/sources/:id/crawl-progress Live crawl progress
GET/POST /api/sources/:id/schedules List / add a recurring crawl or backfill
PATCH/DELETE /api/sources/:id/schedules/:sid Update / remove a schedule
POST /api/sources/:id/schedules/:sid/run Run a schedule now

Create source body:

{
  "source_id": "s3://my-bucket",
  "protocol": "s3",
  "config": { "bucket": "my-bucket", "region": "us-east-1" },
  "secret": { "access_key_id": "AKIA...", "secret_access_key": "..." }
}

Set secret to null to use ambient credentials on the worker (S3 IRSA / instance profile, or Azure Entra ID Workload / Managed Identity for azblob).

The config and secret shapes vary per protocol — see Sources for fields per protocol. For example, an Azure Blob source uses "protocol": "azblob", "config": { "account": "...", "container": "..." }, and a tagged secret such as { "auth": "sas", "sas_token": "..." } or { "auth": "account_key", "account_key": "..." }.

Files & downloads (open the original)

K-Lake extracts text and doesn't store the original bytes, so opening a file means re-fetching it from its source on demand. To let a browser open/verify a citation, the API mints short-lived signed links (one per search hit) that name the user they were minted for and one file. The download route verifies the link, applies the tenant's citation link policy, re-checks access, then streams the bytes via the extractor — the API never holds source credentials. Every download, and every refused one, is audited (tool = files/download).

Each tenant chooses who may open a citation link: anyone_with_link (the link is the credential), signed_in (the reader must be signed in to the tenant, and sees the file only if they may see it anyway) or match_user (only the user the link was minted for). A browser with no session is sent to sign in and returns to the link. See Citation links for the policy, its default and the sign-in lifetime.

Method Path Description
GET /api/files/download?t=<token> Stream the original file behind a signed link. No bearer — the signed link carries the credential. Whether the response is an inline preview or an attachment is fixed when the link is minted. 401 if the link is invalid or expired, or — under signed_in / match_user — the browser has no session (answered with a sign-in page or a 303 redirect to the identity provider); 403 under match_user when the signed-in user is not the one the link was minted for; 404 if the reader can no longer see the file. A Range request is honoured, so audio and video seek.
POST /api/files/download-session Bearer. Give the caller's browser a download session for their tenant on this host (204) — how the console lets links open for a signed-in console user. Tenant users only: 403 for the cluster-admin token.
DELETE /api/files/download-session Bearer. End the caller's download session on this host (the console's sign-out).
GET / PUT / DELETE /api/tenants/:slug/token-pricing Read, set or remove the tenant's AI token price: {currency, per_million_tokens}, with currency one of USD, GBP or EUR and the price above 0 and at most 1000, kept to four decimal places. The dashboard's token-savings card values saved tokens at it. Tenant admin for their own tenant (me works), cluster admin for any.
GET / PUT /api/tenants/:slug/download-links Read or set the tenant's citation link policy (mode: anyone_with_link | signed_in | match_user, or null for the default) and sign-in lifetime (session_ttl_secs). Tenant admin for their own tenant (me works), cluster admin for any.

The links are produced for you on the responses that reference files, so you rarely build the URL yourself:

  • GET /api/sources/:id/content/search — each hit carries a url (an inline preview link to the original), alongside key/seq/snippet/rank.
  • GET /api/sources/:id/files/:key — the file detail carries both preview_url (inline) and download_url (attachment).

All three are omitted when signed downloads aren't configured (KDBL_DOWNLOAD_SIGNING_SECRET / KDBL_API_PUBLIC_URL / KDBL_INTERNAL_FETCH_TOKEN unset), so a client should treat them as optional. Links expire after one hour by default (downloads.ttlSeconds in the chart, KDBL_DOWNLOAD_TTL_SECS).

Access control (per source)

Method Path Description
GET /api/sources/:id/acl List principals and roles
POST /api/sources/:id/acl Grant a principal access
DELETE /api/sources/:id/acl/:principal Revoke a principal

Status

Method Path Description
GET /api/status Tenant queue depth and per-source rollup
GET /api/status?include_cluster=true Cluster-wide rollup (requires cluster admin)

Discover — data estate (tenant admin)

Data-estate analytics. All three require a tenant-admin token; include_cluster=true additionally requires a cluster-admin token. See Estate insights.

Method Path Description
GET /api/discover/overview Estate totals (files, bytes, sources, last-indexed) + extraction/searchability coverage. ?source_id= to scope, ?include_cluster=true for cross-tenant.
GET /api/discover/breakdown?dimension=<d> Top-N breakdown. dimension ∈ extension|mime|language|size|age|owner|storage_class|source|exposure; optional source_id, metric=files|bytes, limit (max 100).
GET /api/discover/files?source_id=&dimension=&value= Files behind a bucket (keyset-paginated: limit max 1000, after=<cursor>). Drillable dimensions: extension|mime|owner|storage_class|language.

Capacity — storage & query cost

Method Path Description
GET /api/capacity Storage split (base / lexical / hybrid), chunk + embedding counts, retained-vector bytes, and per-mode query latency for the tenant. ?include_cluster=true for cluster-wide (cluster admin).

Entities — the knowledge graph

Every response is access-trimmed to the calling token. See Knowledge graph.

Method Path Description
GET /api/entities Search by name prefix and/or kind; with no filter, the most-mentioned.
GET /api/entities/{id} One entity. visible_document_count is trimmed to the caller; mention_count is the tenant-wide total.
GET /api/entities/{id}/related Asserted relations (degree-capped).
GET /api/entities/{id}/co-occurring Entities appearing in the same documents — a weaker claim than related.
GET /api/entities/{id}/documents Documents mentioning it — the citation path.
GET /api/entities/documents Documents mentioning a set of entities (the intersection question). Merge-aware.
GET /api/entities/by-file What a document is about — the reverse lookup. Includes the companies its ticker field names through the ticker aliases.
GET /api/entities/aliases Ticker aliases: ?q= (a ticker prefix or part of the name), &status= (active default, rejected, all), &after=, &limit=. Any tenant user
POST /api/entities/aliases Create or replace one: {ticker, name, exchange?, entity_id?, status?}. Tenant admin
DELETE /api/entities/aliases/:ticker ?exchange=. Tenant admin
POST /api/entities/aliases/import {csv, replace?}, ticker,name[,exchange] per line, at most 20,000 rows. Tenant admin
GET /api/entities/{id}/merged The entities folded into this one by a previous merge.
POST /api/entities/{id}/merge Fold a duplicate into another entity. Reversible.
POST /api/entities/{id}/unmerge Detach it again.

TLS trust anchors (tenant admin)

Method Path Description
GET /api/ca-certs Installed trust anchors, with subject, issuer and validity parsed from each certificate.
POST /api/ca-certs Install a PEM trust anchor.
DELETE /api/ca-certs/{cert_id} Remove one. Sources stop trusting it on the next configuration refresh.
GET /api/sources/{source_id}/tls A source's trust mode and effective bundle.
POST /api/sources/{source_id}/tls Set the mode: inherit (default), custom (a named subset), or none (platform roots only).

Support — diagnostic packs

The in-band half of a diagnostic pack. These endpoints stay reachable when an expired licence has blocked everything else.

Method Path Description
GET /api/support/preview What a pack would contain at a given level, collecting nothing.
GET /api/support/diagnostics Collect the in-band half. ?level=metadata|operational|full; full needs a cluster-admin token and an explicit acknowledgement.
GET /api/support/runtime-tests Exercise the dependencies and report what actually works — not just what is configured.
GET /api/support/history Who generated packs, when, at what level, and each pack's digest. No copy of the pack itself is kept.

Tokens (self-service)

Method Path Description
GET /api/tokens List the calling user's tokens (metadata only — no secrets)
POST /api/tokens Mint a new PAT. The raw token is returned once.
DELETE /api/tokens/:id Revoke a token. Takes effect on every API server within five seconds

Lifetime. Every token minted from 0.1.50 expires. Set expires_at (RFC 3339) in the request to choose when; without it, the token expires at the deployment's ceiling, 365 days by default (chart value mcp.patMaxTtlDays), and a later expires_at is brought down to the ceiling. The response's expires_at is the expiry that was set. An expires_at that is not in the future is refused (400). Tokens minted before 0.1.50 are left as they are; re-mint any that should have an end date.

Scopes. POST /api/tokens {"name": "...", "scopes": [...]} narrows what a token can do. No scopes means the token carries its owner's full authority (every token minted before scopes existed keeps working on the REST API). A scope never widens: the owner's role and access rules still apply on top.

Scope Admits under /api On the MCP endpoint
read Any GET the owner could make (plus asking a warehouse a question, and validating, suggesting or importing a warehouse model) — nothing that changes anything Accepted
search Search, file viewing and the entity explorer only, whatever the owner's role Accepted
mcp Nothing: for an MCP client Accepted
admin Full authority, stated explicitly Refused on its own (403, scope_refused); accepted beside mcp, search or read
kdbl.search, kdbl.read Legacy names (what the console's AI access (MCP) › Tokens tab mints): restrict nothing Accepted
(none) Full authority Accepted for this release only, with each use logged — see Token scopes

Unknown scopes are refused when minting, and a scoped token can only mint tokens within its own scopes. Re-mint unscoped tokens used by MCP clients with the mcp scope before the next release, which refuses them on MCP.

Users (tenant admin)

Method Path Description
GET /api/users List users in the tenant
POST /api/users Create a user (returns an initial PAT, shown once). role defaults to operator
GET /api/users/me Current caller, including role and tenant_slug — the one route here open to any authenticated user. For an administrator it also carries authority: who manages the tenant (managed_by: tenant = self-managed, partner, platform), whether the caller may change its sign-in, and whether they may manage the deployment (see who manages what)
GET /api/users/:id User detail
PATCH /api/users/:id Update email, display name, groups, admin flag, role
DELETE /api/users/:id Remove a user

Roles. Every tenant user has a role, reader or operator, alongside the separate is_tenant_admin flag. A reader can use search and explore and nothing else: GET /api/content/search, the source list and detail, file detail and revisions, everything under /api/entities, and their own PATs under /api/tokens; every other route, and every write, returns 403 operator role required. Security trimming applies to readers exactly as to everyone else. An operator can do everything a tenant user could before roles existed, and existing users are operators. SSO users provisioned on first sign-in get the tenant's default_role; membership of a group in the tenant's operator groups makes them an operator on every sign-in, and a role set by hand is never overruled by the directory. See Console sign-in and roles.

A warehouse admin (is_warehouse_admin, a flag beside the role like is_tenant_admin) administers the tenant's data warehouses — every /api/warehouses route — and may read, but not change, the admin views a warehouse team needs: the Estate insights aggregates (/api/discover/overview and /breakdown, not the file-level listing), the MCP activity and configuration, the principal search used to pick a warehouse's allowed groups, tenant usage (/api/usage), and the admin audit log narrowed to warehouse changes. Everything else follows their role, so a reader who is a warehouse admin is the warehouse-team account: they cannot touch sources, crawls, extraction, classification, actions, users, the identity provider, MCP settings or tokens, CA certificates or branding. Set it with PATCH /api/users/:id {"is_warehouse_admin": true} (tenant admin), kdbl-control users warehouse-admin --user <email>, or the tenant's warehouse-admin groups (warehouse_admin_groups in the sign-in config). See Data warehouses.

Tenants (cluster admin)

Method Path Description
GET /api/tenants List tenants
POST /api/tenants Create a tenant
GET /api/tenants/:slug Tenant detail
PATCH /api/tenants/:slug Update tenant configuration
DELETE /api/tenants/:slug Delete a tenant — asynchronous: 202; the tenant is locked out at once and purged in the background. GET /api/tenants/:slug reports status: deleting and purge progress until it returns 404. See Hosting many customers
POST /api/tenants/:slug/suspend Suspend (optional {"reason": "…"}): every sign-in path refuses the tenant's users with 403 tenant suspended and its ingest stops; data is kept
POST /api/tenants/:slug/resume Undo a suspension (409 while the tenant is being deleted)
GET /api/tenants/:slug/export The offboarding export: an NDJSON stream of the tenant's configuration (no secrets), users (no token hashes), sources, file metadata, file text, entities, relations and classifications, ending in an end record. Cluster admin, or a tenant admin for their own tenant
GET / PUT /api/tenants/:slug/quotas The tenant's quotas, current usage and whether each is ok / warn / reached. Tenant admin reads their own; only a cluster admin sets them ({} clears them)
GET / PUT /api/tenants/:slug/source-policy What the tenant's sources may borrow from the deployment and which hosts they may reach. Tenant admin reads their own (me works); only a cluster admin changes it
GET /api/tenants/:slug/retention Tenant retention override. A tenant admin may read their own tenant; a cluster admin reads any.
PATCH /api/tenants/:slug/retention Set tenant retention override. A tenant admin may set their own, but only to a value at or below the cluster default.
GET /api/tenants/:slug/sso Read the sign-in config: issuer, client_id, audience, groups_claim, admin/operator/warehouse-admin groups, JIT, default role, tenant_claim, editable (whether this caller may change it), managed_by (tenant | partner | platform) and customer_manages_sign_in. Never returns a client secret. Cluster admin and platform operator: any tenant. Partner staff acting in the tenant, and the tenant's own admin: that tenant only
PATCH /api/tenants/:slug/sso Merge sign-in keys (absent = leave, ""/[] = clear; tenant_claim: {"claim","value"}, both empty = clear). Issuer must be an http(s) URL. It may be one other tenants use only when every tenant on it stays unambiguous: each has an API audience, and tenants sharing an audience all have a tenant_claim on the same claim with different values; otherwise 409 says which tenants and what to set. Judged only when issuer, audience or tenant_claim change; the same check applies to an oidc_config sent to POST/PATCH /api/tenants. Cluster admin, platform operator, partner staff acting in the tenant whose partner access is admin or admin_and_content, or the tenant's own admin when the tenant is self-managed or its provider allows it. A change that would lock administrators out is refused (see Sign-in lockout warnings). Changes are audited under the caller's name. See who can change sign-in
PUT /api/tenants/:slug/sso/self-service {"customer_manages_sign_in": true\|false}: the provider's switch that lets a managed tenant's own admins change its sign-in (default off; no effect on a self-managed tenant). Cluster admin, platform operator, or partner staff acting in the tenant; never the tenant's own admin (403)
POST /api/tenants/:slug/sso/shared-issuer Dry run for {issuer, audience, tenant_claim}: the other tenants on that issuer and the problem a save would be refused for, or null. A tenant the caller may not administer comes back with anonymous: true and no slug, its audience and claim value shown only when they equal the ones sent. Same callers as PATCH
POST /api/tenants/:slug/sso/check Fetch the stored issuer's metadata with the tenant's trust anchors; reports endpoints or the failure, plus which required keys are unset. Same callers as GET
GET /api/tenants/:slug/directory Read non-secret directory-correlation config + has_*_secret flags
PATCH /api/tenants/:slug/directory Merge graph / ldap / principal_mappings config. Tenant admin for their own tenant
PUT /api/tenants/:slug/directory/graph-secret {"client_secret"}: the Graph client secret, sealed on the server and never returned. Tenant admin for their own tenant
PUT /api/tenants/:slug/directory/ldap-secret {"bind_password"}: the LDAP bind password, sealed the same way. Tenant admin for their own tenant
GET /api/cluster/retention Cluster-wide retention default

Sign-in lockout warnings

PATCH /api/tenants/:slug/sso, PUT /api/mcp/tenants/:slug and DELETE /api/mcp/tenants/:slug change how a tenant's users sign in. When a change would lock administrators out, it is refused and nothing is saved:

HTTP/1.1 409 Conflict
{
  "error": "this change could lock administrators out: … Send `acknowledge_lockout: true` to save it anyway.",
  "lockout": [
    { "kind": "session_ends", "message": "You signed in through https://… After this change …" }
  ]
}

kind is one of:

  • session_ends: the caller signed in through the current issuer, and the issuer or API audience changes, so their session ends.
  • loses_admin: the caller is an administrator only through a directory group, and none of their groups is in the new admin groups.
  • no_admin_can_sign_in: no administrator could sign in after the change.

Repeat the request with acknowledge_lockout: true in the body (for the DELETE, ?acknowledge_lockout=true) to save anyway. The cluster-admin token is never warned. A personal access token keeps working whatever the sign-in settings say, so it is the way back in.

Console HTTPS certificate

The certificate the console is served with. Callers: the deployment's administrator (a cluster admin, or the tenant admin of a deployment whose only tenant manages itself). Everyone else gets 403. No route returns a private key, and every write is recorded in the admin audit log. See Console HTTPS certificate.

Method Path Description
GET /api/console-tls Status: the installed certificate (subject, names, issuer, validity, serial, SHA-256 fingerprint, days_left, expiry = ok / warning (under 30 days) / critical (under 7) / expired), pending_csr, https_active, force_https, managed, and whether it is applied
POST /api/console-tls/csr Generate a private key on the server and a certificate request: {common_name, organization, organizational_unit, locality, state, country, dns_names[], ip_addresses[], key_type} (ecdsa-p256 default, rsa-2048, rsa-3072). Returns csr_pem only. Replaces a pending request
GET / DELETE /api/console-tls/csr Read or discard the pending request
POST /api/console-tls/certificate Install the CA's answer to the pending request: {certificate} as PEM (leaf and intermediates, or a PKCS#7 block) or base64 of a DER certificate or .p7b. 409 with no pending request; 400 for a certificate for another key, an expired one, a CA certificate or a broken chain
POST /api/console-tls/import Install a certificate made elsewhere: {certificate, private_key} (PEM, unencrypted key) or {pkcs12, password} (base64 .pfx). Same checks
DELETE /api/console-tls Remove the certificate and return to plain HTTP (also turns force_https off)
PUT /api/console-tls/redirect {force_https}. Turning it on needs an installed certificate and a request that arrived over HTTPS (409 otherwise), so nobody locks themselves out

PATCH /api/tenants/:slug with {"features": [...]} narrows which licensed features a tenant has (null = every licensed feature, [] = none); only a cluster admin may set it. See Licensing.

Features, usage and audit

Method Path Description
GET /api/features Any signed-in user. Each licensed feature for the caller's tenant: licensed, enabled and its limits. The console shows and hides sections on it
GET /api/usage?from=&to= Tenant admin: the tenant's own daily usage (JSON with daily rows and totals; &format=csv for CSV). A window may span up to 400 days; the default is the last 30
GET /api/usage/tenants?from=&to=&tenant= Cluster admin: every tenant's daily usage — the billing export (&format=csv for CSV)
GET /api/audit/admin The admin audit log, newest first: one append-only row for every state-changing API request — including refused ones — with who made it (principal and email), how they held the authority, what (method and route), for which tenant, and the response status. No request body is stored. Tenant admins see their own tenant; cluster admins see every tenant (?tenant=<slug> narrows). ?principal=, ?limit= (default 100, max 1000), ?before=<unix time> to page, ?format=csv for an export

Warehouses

Questions asked of data warehouses, and their administration. Available where the licence includes data warehouses. "Admin" means a tenant admin or a warehouse admin.

Method Path Who Purpose
GET /api/warehouses Any user List warehouses
POST /api/warehouses Admin Register a warehouse (/api/warehouses/test probes an unsaved one)
GET PATCH DELETE /api/warehouses/:id Admin Read, update, remove
POST /api/warehouses/:id/clone Admin Clone the connection, with its credential and groups, and optionally its published model or draft (into the clone's draft)
GET /api/warehouses/:id/access?user= Admin, warehouse admin Whether one person may ask the warehouse, which models they reach, and why
GET / POST /api/warehouses/:id/models Any user / Admin List the connection's models (non-admins see the published ones offered to them); create a model
GET / PATCH / DELETE /api/warehouses/:id/models/:model Admin Read, update or delete one model
GET / PUT / POST /api/warehouses/:id/models/:model/… Admin The model: save a draft, diff, validate, suggest from the catalog, import, suggested values, publish, approve / reject, review comments, changelog, check, sync, accuracy runs and golden cases
POST /api/warehouses/:id/models/:model/assist Admin AI suggestions for a draft. Off unless the licence has structured_sources_assist and a tenant admin has switched AI help on
POST /api/warehouses/:id/models/:model/ask Admin Ask in words; the AI builds and runs the query as the caller. Sends query results to your AI provider. Off unless a tenant admin allows Ask
GET /api/warehouses/:id/models/:model/schema Any user the model admits The AI-facing model, as the caller sees it
POST /api/warehouses/:id/models/:model/query Any user the model admits Ask: {question}, {query} or {sql}, plus limit
GET /api/warehouses/:id/queries, /api/warehouses/queries/export Admin The query log (JSON or CSV)
GET / PUT /api/tenant/warehouse-assist Tenant admin The AI help settings: switch, provider, write-only key, gateway, example values, Ask, daily token budget

The routes from before 0.1.50 without a model in the path (/api/warehouses/:id/model…, /schema, /query, /evals) still work for this release only, acting on the connection's default model. Move to the per-model routes before the next release. The full list is in the Data warehouses API reference.

MCP

The Model Context Protocol surface. See MCP server and connecting MCP clients. The cluster-admin token is refused on /mcp (403), and a PAT needs the mcp, search or read scope there (see Tokens).

Per-tenant endpoints (cluster admin unless noted): GET /api/mcp/tenants lists every tenant with its endpoint, scoped flag and sign-in leg (federation: tenant | deployment | none); PUT /api/mcp/tenants/:slug ({scoped, take_over, acknowledge_lockout}) connects a tenant — the first gets the bare /mcp, later ones /t/<slug>/mcp; DELETE takes it off. GET/PUT/DELETE /api/mcp/tenants/:slug/federation sets the tenant's own upstream identity provider (a tenant's own admin may call it too; the secret is write-only). GET/POST /api/mcp/tenants/:slug/keys and DELETE …/keys/:kid manage the tenant's own signing keys. Every route below also exists under /t/<slug>/… for a tenant on its own endpoint, with its discovery documents at /.well-known/oauth-protected-resource/t/<slug>/mcp, /.well-known/oauth-authorization-server/t/<slug> and /t/<slug>/.well-known/jwks.json. See Multi-tenant deployments.

Method Path Description
GET /.well-known/oauth-protected-resource RFC 9728 Protected Resource Metadata (unauthenticated)
GET /.well-known/oauth-protected-resource/mcp Path-scoped PRM (unauthenticated)
POST /mcp Streamable-HTTP JSON-RPC endpoint (OAuth 2.1 bearer; aud = the resource URI)
GET /api/mcp/audit Query the MCP audit trail (tenant-scoped; ?tool=&principal=&limit=)
GET /api/mcp/usage/tools Tokens returned to assistants per MCP tool, for the last 30 days and all time: each tool's calls, characters, tokens and share of what was returned, largest first. The Tokens by tool card on the AI access page's Flow tab. Tenant-wide; any member of the tenant may read it, and a cluster admin gets every tenant summed

Who may change what. The deployment-wide MCP settings (PUT /api/mcp/config, the remote-access tunnel token, clients at the bare /mcp endpoint) belong to the deployment's administrator: a cluster admin, or the tenant admin of a deployment whose only tenant manages itself. GET /api/mcp/config reports can_edit for the caller. A tenant's own admin may also connect or disconnect their own tenant (PUT/DELETE /api/mcp/tenants/:slug) and register or remove its clients (POST/DELETE /api/mcp/clients with tenant: "<their slug>") when they may manage its sign-in. On a deployment with other tenants they may only point it at its own /t/<slug>/mcp.

K-Lake runs its own OAuth 2.1 authorization server at the MCP origin: it issues and signs the access tokens the MCP endpoint accepts, and federates the actual sign-in to your identity provider. The endpoints below are served once the deployment has its token-signing keys mounted. Clients discover and use them themselves — you never call them by hand — but they are the endpoints to check when a connection fails. See OAuth and identity providers.

Method Path Description
GET /.well-known/oauth-authorization-server Authorization server metadata (RFC 8414, unauthenticated)
GET /.well-known/oauth-authorization-server/mcp Path-scoped form of the same document, for clients that ask for it
GET /.well-known/openid-configuration OIDC discovery for the same server
GET /.well-known/jwks.json Public keys the access tokens are signed with
POST /register Dynamic client registration (RFC 7591) — how an assistant registers itself
GET /authorize Start sign-in; renders the K-Lake consent screen
GET /oauth/callback Return leg from your IdP
POST /token Exchange the code, and refresh (rotating refresh tokens)

Example: end-to-end

Add a source, trigger a crawl, watch progress:

# Add
curl -X POST -H "Authorization: Bearer $KDBL_TOKEN" \
     -H "Content-Type: application/json" \
     "$KDBL_URL/api/sources" \
     -d '{
       "source_id": "s3://docs-bucket",
       "protocol": "s3",
       "config": { "bucket": "docs-bucket", "region": "us-east-1" },
       "secret": { "access_key_id": "AKIA...", "secret_access_key": "..." }
     }'

# Crawl
curl -X POST -H "Authorization: Bearer $KDBL_TOKEN" \
     "$KDBL_URL/api/sources/s3%3A%2F%2Fdocs-bucket/crawl"

# Watch
curl -H "Authorization: Bearer $KDBL_TOKEN" \
     "$KDBL_URL/api/sources/s3%3A%2F%2Fdocs-bucket/stats"

Rate limits and pagination

A caller over a limit is answered 429 Too Many Requests with a Retry-After header. Back off for that long and retry.

  • MCP, on every deployment: each user is held to a request rate (10 per second, burst 40, by default), and failed sign-ins are throttled per address and per token. See Exposing MCP to the internet.
  • Deployments hosting many customers: each tenant has its own request rate and a share of the query-path concurrency (MCP tool calls, search, reranking), so one customer cannot crowd out the others. See Hosting many customers.
  • Warehouses: questions are throttled per warehouse and per user. See Data warehouses.

For bulk reads use the listing endpoints with ?limit= and follow the cursor rather than hammering point lookups.