Skip to content

CLI reference

kdbl-control is the command-line interface to KDBL Context Lake (K-Lake). Most commands call the same REST API as the UI (so they respect tenant isolation and the audit trail), but a few run in direct-DB mode — see the per-command notes below.

Installing

The CLI ships as a static binary inside the K-Lake container image at /usr/local/bin/kdbl-control. Pull it from the image you run in your cluster, or ask your administrator for a host binary.

Once on PATH:

kdbl-control --help

Authentication

The CLI has two modes:

Mode Flags When to use
API --api-url <URL> --api-token <PAT> Day-to-day. Goes through the REST API and respects tenant isolation. Required by init, onboard, doctor, acl, files, entities, classification, discover, capacity, extractors, versions, dead-letters, support, mcp, users, warehouse, tenant ca-cert / access-mode / download-links / quotas / usage / features / source-policy / export, and source extract / security-trim / multichannel / schedule / progress.
Direct database --postgres-url <URL> (+ KDBL_MASTER_KEY for secrets) Bootstrap and disaster-recovery, and the directory family, which is direct-DB only.

crawl, status, source CRUD and most of the tenant family work in either mode: when --api-url is set they go through the API (auth + audit), and fall back to the database when it isn't.

For most work, set:

export KDBL_URL=https://kdbl.example.com
export KDBL_TOKEN=kdblpat_...

and pass --api-url "$KDBL_URL" --api-token "$KDBL_TOKEN". Both flags also read the environment directly — export KDBL_API_URL and KDBL_API_TOKEN instead and you can drop them from every command.

Subcommands

crawl

Enqueue a crawl for a source. Workers pick up the work and expand it from the root prefix.

kdbl-control crawl --source-id 's3://my-bucket'
kdbl-control crawl --source-id 's3://my-bucket' --prefix 'reports/2026/'

Flags:

  • --source-id <ID> or --bucket <NAME> — one is required (--bucket is a shorthand for an S3 source)
  • --prefix <PATH> — narrow the crawl to a subtree (default: full source)
  • --mode <hierarchical|flat> — listing strategy (default hierarchical)
  • --label <NAME> — tag the run for later auditing
  • --force-reextract — re-extract every eligible file, even unchanged ones (default off)
  • --only-ext <EXT,...> — with --force-reextract, re-extract only files with these extensions (comma separated, case-insensitive, leading dot optional). Other files keep their stored version and are skipped if unchanged. Requires --force-reextract.

Recrawls skip unchanged content. By default a recrawl only extracts files that are new or changed since they were last extracted — unchanged content is not re-run, because extraction is GPU-heavy. "Changed" is decided per file by its etag (S3) or, for sources without one (nfs/smbfs/smb), its mtime+size. Use --force-reextract for a deliberate full re-run, e.g. after upgrading the extractor or changing the extract policy. The kdbl_extract_skipped_unchanged_total metric counts how many files a recrawl skips.

Re-extract only some file types — for example only CAD drawings and models after an upgrade improves how they are read, or only PDFs:

# Only CAD files
kdbl-control crawl --source-id 's3://my-bucket' \
  --force-reextract --only-ext dxf,dwg,stp,step,p21,ifc

# Only PDFs
kdbl-control crawl --source-id 's3://my-bucket' --force-reextract --only-ext pdf

A file's extension is the text after the last . of its name (so plan.v2.DWG counts as dwg; a file with no dot matches nothing). The crawl still lists the whole source (or --prefix); only matching files are forced. Combine with --prefix to narrow by path as well.

status

Show queue depth and per-source rollup.

kdbl-control status
kdbl-control status --include-cluster   # cluster-wide, requires admin

With --include-cluster, the report also lists any index an upgrade added that is still being built in the background (for example 15/16 parts built). It is empty once every one is complete; see Upgrading.

doctor

Health-check a deployment end-to-end and print a ✓/⚠/• checklist: API liveness (/healthz) and readiness (/readyz), token validity + kind, the queue/source rollup, the extractor fleet, and whether the MCP endpoint is enabled. Use it as the "you're done" gate right after scripts/bootstrap.sh, or any time to triage. API mode only.

kdbl-control --api-url "$KDBL_URL" --api-token "$KDBL_TOKEN" doctor
kdbl-control --api-url "$KDBL_URL" --api-token "$KDBL_TOKEN" doctor --mcp   # also smoke-test /mcp
kdbl doctor — https://kdbl.example.com

  ✓ API reachable — /healthz 200
  ✓ API ready (DB reachable) — /readyz 200
  ✓ token valid (cluster-admin)
  ✓ queue reachable — pending=0 running=0 done=0 failed=0
  • no sources indexed yet — next: add one with `source add-*` + `crawl`
  ✓ extractor fleet healthy (2/2)
  ✓ MCP endpoint enabled — PRM 200

✓ all checks passed

✗ marks a critical failure (unreachable, not ready, bad token, MCP smoke failure) and makes doctor exit non-zero — so it's CI/scriptable. ⚠ flags something degraded (failed queue tasks, sources with errors, a stale extractor heartbeat); • is informational and expected on a fresh cluster (no sources yet, extraction not deployed, MCP off) — neither fails the gate. Works with a cluster-admin token (cluster-wide rollup) or a user PAT (tenant-scoped).

init

Interactive first-source wizard — the guided Day-1 path that does what source add-* + source extract enable + crawl do, but walks you through it and surfaces the bit newcomers miss: search needs extraction. It connects a source (S3 / SMB / SMBFS), offers to enable content extraction, and offers to start the first crawl. Runs as a tenant user PAT (a cluster-admin token has no tenant — onboard one first and use the PAT it prints).

kdbl-control --api-url "$KDBL_URL" --api-token "$KDBL_TOKEN" init
kdbl init — connecting your first source as alice@acme.com.

What kind of source do you want to connect?
  1) S3 / object store (s3://)
  2) SMB / Windows share — kernel mount (smbfs://)
  3) SMB / Windows share — userspace (smb://)
choice [1]: 1
S3 bucket: acme-docs
source id [s3://acme-docs]:
…
✓ source created
Enable content extraction? (full-text search needs extracted text) [Y/n]: y
✓ extraction enabled — PDFs/Office docs etc. are parsed as they're crawled
Start the first crawl now? [Y/n]: y
✓ crawl started (task 1042)

It prompts on a TTY (passwords are read without echo); for scripting you can pipe answers on stdin in order. NFS sources aren't offered here yet — register those with source add-nfs (direct-DB mode). When you'd rather not be prompted, the non-interactive equivalents are source add-*, source extract enable, and crawl.

source

Manage the source registry.

Subcommand What it does
add-s3 Register an S3 source
add-azblob Register an Azure Blob source
add-smb Register an SMB (userspace) source
add-smbfs Register an SMB source mounted via the kernel
add-nfs Register an NFS source
add-onedrive Register a OneDrive / SharePoint source (Microsoft Graph, app-only)
list List sources in your tenant
show Show a single source in detail
enable / disable Toggle the enabled flag
remove Delete a source and its indexed files
bulk-ingest Toggle the bulk-ingest fast path
meta-caps Choose which optional enrichments to gather (incl. ntfs_acl / nfs4_acl ACL capture)
backfill-meta Enqueue enrichment for files indexed before a cap was enabled
meta-coverage Show how many files have each enrichment populated
subtree Tune how large directory trees are split for parallel crawling (depth, split threshold)
security-trim Set the per-file trimming policy (--mode per_file\|source_only\|open, --fail-closed, --well-known-open for shares that grant Authenticated Users / BUILTIN\Users)
metadata-rules Document dates and keyword fields (ticker, sector, analyst, note type) read from each file's path, first page or running header: show / set / clear / backfill / preview. See Document dates and metadata fields.
access-check Why one user can or cannot see one file: --source-id … --key … --user <email\|id\|principal> replays the trimming decision (tenant admin). See Why can't this user see this file?
multichannel SMB3 multi-channel on an smbfs source
extract Content-extraction control: enable / disable / show / backfill / progress
schedule Scheduled crawls/backfills: add / list / rm / pause / resume / run
progress Live crawl/extract progress for a source
action Manage the Smart Actions pipeline: catalog / list / add / set / reorder / remove
search Hybrid-search control for a source: enable / disable / inherit / show
version-tracking Opt in to file version history, so files revisions / files diff can show what changed. Off by default
exclude-extensions Crawl-time exclusion — matched files are never listed, indexed, or extracted
redaction-summary What redaction has masked in a source: files run, files masked, per-rule totals
set-backup-intent SMB backup-operator intent on an smbfs source

See Sources for add-* examples and Enablement for extract / security-trim / schedule.

Tokens are managed through the REST API and the UI, not the CLI. Mint a PAT from the web console — AI access (MCP) › Tokens › Mint an MCP access token, or press ⌘K / Ctrl+K and type > mint — (or POST /api/tokens; creating a user with POST /api/users returns that user's one-time token).

users

Tenant users and their roles. API mode only; a tenant-admin token acts on its own tenant, a cluster-admin token names one with --tenant.

kdbl-control users list
kdbl-control users promote --user alice@acme.com                 # tenant admin
kdbl-control users demote  --user alice@acme.com
kdbl-control users role    --user bob@acme.com --role reader       # or operator
kdbl-control users warehouse-admin --user data-team@acme.com        # warehouse admin
kdbl-control users warehouse-admin --user data-team@acme.com --remove

A warehouse admin administers the tenant's data warehouses and changes nothing else; combine it with --role reader for a warehouse-team account. See Console sign-in and roles.

acl

Manage a source's access grants — who can see the source and at what role. This is the source-visibility gate; per-file trimming (source security-trim) is separate. API mode only; the token scopes grants to its tenant.

Subcommand What it does
search [text] Find a person or group by name, and print the exact principal to grant
list <source> List the grants on a source
grant <source> <principal> <role> Grant (or update) a principal's role — owner / editor / viewer
revoke <source> <principal> Revoke a principal's grant

<principal> is user:<id> or group:<id> — prefer group: grants (from directory enrichment) so membership drives access.

Nobody memorises those identifiers, so acl search finds them by name and prints the value to pass to acl grant:

kdbl-control acl search sales --kind group
kdbl-control acl grant  's3://my-bucket' 'group:sales' viewer
kdbl-control acl list   's3://my-bucket'
kdbl-control acl revoke 's3://my-bucket' 'group:sales'

Groups your directory already knows about are searchable before anyone in them has signed in, which is the usual order when onboarding a team. A result marked (not-grantable) is a principal K-Lake knows about that has no source-level form — granting it would silently do nothing, so no value is offered.

Grants have full parity across the CLI, the web console's Access grants card, and the REST API (/api/sources/:id/acl).

directory (direct-DB)

Build the alias graph that ties IdP identities to file-native principals (for per-file trimming). Runs in direct-DB mode and needs KDBL_MASTER_KEY to encrypt secrets; secrets come from env vars, never flags. See Directory enrichment.

kdbl-control directory set-graph --tenant acme --entra-tenant <id> --client-id <id>   # KDBL_GRAPH_CLIENT_SECRET in env
kdbl-control directory set-ldap  --tenant acme --url ldaps://dc:636 --bind-user 'CORP\svc' --base-dn 'DC=corp' --upn-rewrite corp.local=corp.com
kdbl-control directory sync-graph --tenant acme   # one-shot run
kdbl-control directory sync-ldap  --tenant acme

files

Browse a source's files and mint signed links that open the original file (re-fetched on demand from the source, access re-checked, audited). API mode only; the calling token's tenant scopes what's visible.

kdbl-control files list --source-id s3://docs-bucket --limit 50          # paginated by key
kdbl-control files show --source-id s3://docs-bucket --key reports/q3.pdf # detail + signed links
kdbl-control files link --source-id s3://docs-bucket --key reports/q3.pdf # print an inline preview link
kdbl-control files link --source-id s3://docs-bucket --key reports/q3.pdf --download   # attachment link

files link prints just the URL (so it's pipe-friendly); files show returns the full detail including preview_url / download_url. The links require the server to have signed downloads enabled (KDBL_DOWNLOAD_SIGNING_SECRET / KDBL_API_PUBLIC_URL / KDBL_INTERNAL_FETCH_TOKEN); they expire after one hour by default.

tenant access-mode

Whether a newly-added source is visible to everyone in the tenant, or to nobody until access is granted. See security trimming.

kdbl-control tenant access-mode --slug acme                 # read, with impact report
kdbl-control tenant access-mode --slug acme --set automatic  # future sources only
kdbl-control tenant access-mode --slug acme --set automatic --apply-to-existing

Switching the mode deliberately does not touch sources that already exist — that needs --apply-to-existing, because it changes what people can see today. The read form reports how many sources that would open, and how many of those do not use per-file permissions.

Who may open a citation ("open original") link, and how long one sign-in keeps opening them: anyone_with_link, signed_in, match_user, or default (follow the identity provider). API mode; tenant admin for their own tenant, cluster admin for any. See Citation links.

kdbl-control tenant download-links --slug acme                        # read
kdbl-control tenant download-links --slug acme --set match_user
kdbl-control tenant download-links --slug acme --set default          # follow the IdP
kdbl-control tenant download-links --slug acme --session-ttl 4h       # 30m, 8h, 7d, default

warehouse

Data warehouses. API mode, with a tenant-admin or warehouse-admin token; model check and model schema need no API.

kdbl-control warehouse list [--json]
kdbl-control warehouse show risk
kdbl-control warehouse probe risk                    # connectivity + identity
kdbl-control warehouse catalog risk --schema risk [--columns]
kdbl-control warehouse queries risk --since 24h --limit 200 [--status error]

# The semantic model, kept as code
kdbl-control warehouse model pull risk -o risk.json  # the published model (--draft: the draft)
kdbl-control warehouse model check risk.json         # offline: parses + structural checks
kdbl-control warehouse model validate risk risk.json # against the warehouse
kdbl-control warehouse model diff risk risk.json     # what would change
kdbl-control warehouse model push risk risk.json     # save as the draft
kdbl-control warehouse model publish risk [risk.json] [--force]
kdbl-control warehouse model sync risk               # read a model kept in the warehouse now
kdbl-control warehouse model schema > warehouse-model.schema.json

# Accuracy against a golden question set
kdbl-control warehouse eval risk golden.json --expected-only

The warehouse commands use exit codes chosen for pipelines: 0 success, 1 an error, 2 the warehouse check raised warnings, and for warehouse eval, 3 accuracy below --min-accuracy. See Model as code and Measuring accuracy.

mcp

kdbl-control mcp config     # what's set, where each value came from, what's missing
kdbl-control mcp set --enabled true --resource-uri https://kdbl.example.com/mcp
kdbl-control mcp set --clear resource_uri   # revert to the deployment default
kdbl-control mcp validate   # check the configuration against reality
kdbl-control mcp smoke      # fetch PRM + run initialize + tools/list against /mcp
kdbl-control mcp audit --tool search_content --limit 50   # query the audit trail

Connecting an identity provider, so people can sign in with your organisation's accounts:

printf '%s' "$IDP_SECRET" | kdbl-control mcp set \
  --idp-issuer https://login.microsoftonline.com/<directory-id>/v2.0 \
  --idp-client-id <application (client) id> \
  --idp-client-secret-stdin

Read the secret from stdin, not --idp-client-secret. An argument lands in /proc/<pid>/cmdline, where every process on the host can read it, and in your shell history.

mcp config then reports whether sign-in is available and, if not, which of issuer / client id / client secret is missing. It also prints the redirect URI to register at your IdP — take it from there rather than typing it, because it must match character for character and a mismatch fails at the IdP with an error that never mentions K-Lake. See OAuth and identity providers.

config / set / validate replace editing deployment configuration and restarting. Every setting — the endpoint address included — takes effect on save, with nothing to restart or re-deploy. set needs a cluster-administrator token, because these settings are shared by every tenant.

validate is the one to reach for when a client "can't connect" — it checks whether the endpoint address is reachable from outside, whether tenants have an MCP audience, and whether any sources are actually exposed.

Tenants and clients:

kdbl-control mcp tenants                        # every tenant, its endpoint and sign-in leg
kdbl-control mcp tenant-apply --tenant acme     # connect a tenant (bare /mcp if free, else /t/acme/mcp)
kdbl-control mcp tenant-clear --tenant acme     # take it off MCP
kdbl-control mcp client create --preset copilot --tenant acme          # a Copilot connector bound to acme's endpoint
kdbl-control mcp client create --name 'K-Lake console' --tenant acme \
  --redirect-uri https://klake.example.com/authn/callback             # a public client for console sign-in
kdbl-control mcp client list
kdbl-control mcp client delete --client-id <id>                       # also revokes its sessions

client create --tenant binds the client to that tenant's endpoint and prints that endpoint's URLs; the client is refused everywhere else. --insecure-redirect accepts a plain-http redirect URI for an evaluation console without TLS. See Multi-tenant deployments and Console sign-in and roles.

dead-letters

Failed work: tasks that ran out of retries on any queue (listing, metadata, extraction, Smart Actions) — what failed, why, whether a later crawl or result already superseded it — and retry or discard them. The same view is the console's Failed work page (see System health). API mode only; dlq is a short alias.

kdbl-control dead-letters summary                       # totals, open vs superseded, by cause, source and day
kdbl-control dead-letters list --state open --since 7d  # entries, newest first
kdbl-control dead-letters show --stream kdbl:list --id <id>
kdbl-control dead-letters retry --reason source_unreachable          # dry run: prints the plan
kdbl-control dead-letters retry --reason source_unreachable --yes    # do it
kdbl-control dead-letters discard --source 'smbfs://fileserver/share' --yes
kdbl-control dead-letters export --format ndjson --out failed.ndjson

Filter with --family (list, subtree, meta, extract, action), --source, --reason (source_unreachable, access_denied, not_found, unreadable_content, out_of_memory, abandoned_by_worker, retries_exhausted, other), --since / --until (30m, 8h, 7d), --state (open, superseded, all) or -q for a substring. retry and discard change nothing without --yes; retry skips entries a later result already superseded unless you add --include-superseded, and re-crawls listing work (one new crawl per source). A tenant token sees its own tenant; a cluster-admin token adds --scope cluster, and an MSP's home tenant --scope partner (narrow either with --tenant <slug>).

extractors

kdbl-control extractors list   # extractor pods and their health

versions

kdbl-control versions          # what version each component is running

Every pod of a consistent deployment reports the same version, so a row that disagrees means an upgrade did not finish. The same view is on the web console's System health page.

entities

Browse the knowledge graph. Every query is trimmed to what the token can see, so documents returns exactly the files a content search would. API mode only.

kdbl-control entities search --query "acme" --kind ORG
kdbl-control entities show 17
kdbl-control entities related 17 --predicate WORKS_FOR
kdbl-control entities co-occurring 17
kdbl-control entities documents 17
kdbl-control entities intersect --ids 12,48,301
kdbl-control entities by-file --source 's3://docs' --key 'contracts/msa.pdf'
kdbl-control entities merge 482 --into 17     # fold a duplicate away
kdbl-control entities unmerge 482             # put it back
kdbl-control entities merged 17               # what was folded into this one

related is an edge the model asserted from the text; co-occurring only means the two entities appear in the same documents. Merges are reversible and reads are merge-aware, so an id list assembled before a merge keeps working.

entities aliases manages the tenant's ticker aliases (reads: any tenant user; writes: tenant administrator):

kdbl-control entities aliases list -q cordwell
kdbl-control entities aliases set CORD --name "Cordwell Digital Infrastructure" --exchange XLON
kdbl-control entities aliases rm CORD --exchange XLON
kdbl-control entities aliases import universe.csv     # ticker,name[,exchange]; --replace for the whole set

source enrich-coverage

kdbl-control source enrich-coverage --source-id 's3://docs'

How many extracted files have been enriched, what is still pending, and how many the per-document work budget cut short. Use it after enabling the enrichment action to watch the backlog drain.

support

Generate and inspect diagnostic packs.

kdbl-control support pack --namespace kdbl          # the archive you send us
kdbl-control support pack --runtime-tests           # also test what actually works
kdbl-control support pack --level metadata          # most private
kdbl-control support preview                        # what a pack would contain
kdbl-control support diff before.tar.gz after.tar.gz
kdbl-control support history                        # who generated packs, and when
kdbl-control support key-id                         # which key this build seals to

pack collects K-Lake's own data through the API and the cluster half through your kubectl credentials — K-Lake holds no Kubernetes credentials of its own. --no-kubectl skips the cluster half. Nothing is transmitted anywhere: the pack is a file you inspect and send.

discover (tenant admin)

Data-estate analytics — "what data do we have?" Prints JSON (pipe to jq). See Estate insights.

kdbl-control discover overview                       # totals + searchability coverage
kdbl-control discover breakdown extension            # by dimension (rank by files)
kdbl-control discover breakdown size --metric bytes --limit 10
kdbl-control discover files 's3://docs-bucket' extension pdf   # files behind a bucket

breakdown <dimension> accepts extension | mime | language | size | age | owner | storage_class | source | exposure with optional --source, --metric {files|bytes}, --limit (max 100). files <source> <dimension> <value> pages with --limit (max 1000) / --after <cursor>. Cluster admins can add --include-cluster for a cross-tenant overview / source breakdown.

capacity

Storage cost and query-latency breakdown for the tenant — the base / lexical / hybrid storage split, chunk and embedding counts, retained-vector bytes, and per-mode query latency (the same figures as the console's Capacity & cost panel). See hybrid search cost.

kdbl-control capacity                    # tenant storage split + query latency
kdbl-control capacity --include-cluster  # cluster-wide (cluster admin)

onboard (cluster admin)

Stand up a new customer in one step: create the tenant (idempotent — reuses an existing one), create its first tenant-admin user, and mint that user's initial PAT. The token is printed once — hand it to the customer over a secure channel. This is the secure self-service path: it goes through the API's auth + audit boundary, so you don't need direct DB or kubectl access to onboard.

kdbl-control --api-url "$KDBL_URL" --api-token "$KDBL_CLUSTER_ADMIN_TOKEN" \
  onboard acme --name "ACME Corp" --admin-name "Jane Admin" --admin-email jane@acme.com
✓ created tenant acme (id=019eb528-…)
✓ created tenant-admin user Jane Admin (id=375e848c-…)

Admin PAT for acme (shown ONCE — store it securely):
kdblpat_…

Flags:

  • <slug> — tenant slug (positional); also the default display name. Keep it DNS/URL-safe.
  • --name <NAME> — tenant display name (defaults to the slug)
  • --admin-name <NAME> — the first tenant-admin user's display name (default admin)
  • --admin-email <EMAIL> — the admin user's email (optional)
  • --oidc-config-json <JSON> — inline OIDC config so the customer can federate sign-in from day one (optional)

API mode only, with a cluster-admin token. onboard needs --api-url/--api-token and the token must be the KDBL_CLUSTER_ADMIN_TOKEN. The admin API is sealed from the public MCP tunnel by the path-scoping gateway, so run it against the in-cluster API — e.g. kubectl -n kdbl port-forward svc/kdbl-api 18080:80 and --api-url http://localhost:18080.

The token is shown once and is never recoverable. Re-running onboard for an existing tenant is safe — it reuses the tenant and mints a new admin user.

tenant (cluster admin)

For the common case (new tenant + first admin + token in one shot) use onboard. The tenant subcommands below are the lower-level CRUD primitives.

kdbl-control tenant create --slug acme --name "ACME Corp"
kdbl-control tenant list
kdbl-control tenant show --slug acme
kdbl-control tenant retention --slug acme            # read the current override
kdbl-control tenant retention --slug acme --days 90  # set
kdbl-control tenant retention --slug acme --clear    # revert to the cluster default

In direct-database mode tenant list prints one line per tenant:

acme (019eb528-…)  name="ACME Corp"  oidc=yes  sso=ready  jit=true  default_role=reader

sso= says whether the console's login page can start single sign-on for the tenant: ready (issuer, client ID and audience are all set), incomplete(needs …) naming what is still missing once an issuer is set, or off. oidc= only says that some sign-in or directory configuration exists. With --api-url the command prints the tenants as JSON instead.

Lifecycle — suspend, resume, export and delete a tenant. See Hosting many customers.

kdbl-control tenant suspend --slug acme --reason "contract ended"
kdbl-control tenant resume  --slug acme
kdbl-control tenant export  --slug acme --out acme.ndjson    # the offboarding export
kdbl-control tenant delete  --slug acme --confirm acme       # asynchronous; not undoable
kdbl-control tenant show    --slug acme                      # status, and purge progress

suspend refuses the tenant's users on every sign-in path and stops its ingest, keeping its data; resume undoes exactly that. delete locks the tenant out at once and purges it in the background; export first if the data must be handed back. export exits non-zero if the stream ended without its closing record, so a zero exit means a complete file.

Quotas and usage (API mode; see Licensing):

kdbl-control tenant quotas --slug acme                                  # read
kdbl-control tenant quotas --slug acme --max-bytes 2TB --max-users 200  # set (replaces)
kdbl-control tenant quotas --slug acme --clear                          # unlimited
kdbl-control tenant usage --from 2026-09-01 --to 2026-09-30 --csv       # daily usage, every tenant
kdbl-control tenant usage --slug acme --from 2026-09-01 --to 2026-09-30 # one tenant

A tenant-admin token may run tenant usage for its own tenant.

Licensed features per tenant (API mode) — which of the licence's paid features a tenant may use. By default a tenant has every feature the licence grants. See Licensing.

kdbl-control tenant features --slug acme                              # show
kdbl-control tenant features --slug acme --enable structured_sources
kdbl-control tenant features --slug acme --disable structured_sources
kdbl-control tenant features --slug acme --all                        # every licensed feature
kdbl-control tenant features --slug acme --none                       # no paid features

Source policy — what a tenant's sources may borrow from the deployment (sources with no secret that authenticate as the deployment's own cloud identity; NFS sources, which connect as the worker's uid/gid) and which hosts they may reach. No flags reads it. On a deployment hosting many customers, new tenants start with both refused. See Hosting many customers.

kdbl-control tenant source-policy --slug acme
kdbl-control tenant source-policy --slug acme --allow-nfs true
kdbl-control tenant source-policy --slug acme --allow-ambient-credentials false
kdbl-control tenant source-policy --slug acme \
  --allow-endpoint '*.acme.example' --allow-endpoint 10.20.0.0/16
kdbl-control tenant source-policy --slug acme --clear-allowlist

TLS trust anchors — install an internal CA so sources can verify certificates a public trust store doesn't know. See TLS trust anchors.

kdbl-control tenant ca-cert add --name "Acme Internal Root" --file acme-root.pem
kdbl-control tenant ca-cert list      # subject, issuer and validity per anchor
kdbl-control tenant ca-cert remove <cert-id>

secret keygen

Generate a fresh master key for credential encryption (used during cluster bootstrap).

kdbl-control secret keygen

secret reseal

Rewrite every stored secret still in the pre-0.1.44 format into the per-tenant, row-bound format (see Security overview). The worker does this once at startup after an upgrade, so you rarely need it; run it to confirm an upgraded deployment is fully migrated, or after restoring an old backup. It is idempotent. Direct-DB mode: connect as the database owner, with KDBL_MASTER_KEY set.

kdbl-control --postgres-url "$KDBL_POSTGRES_URL" secret reseal --dry-run   # count what would change
kdbl-control --postgres-url "$KDBL_POSTGRES_URL" secret reseal             # rewrite; non-zero exit if any secret would not open

bench, bench-sink, bench-queries

Operator-side benchmarking helpers. See --help for each. These are diagnostic — they exercise the live system. Run them against a non-production tenant.

Exit codes

kdbl-control exits 0 on success and non-zero on any failure, with the reason printed to stderr. Apart from the warehouse commands, there are no distinct codes per failure class, so scripts should test for success rather than switch on the value. doctor follows the same rule: it exits non-zero when a critical check fails, and 0 otherwise (warnings included).

Tips

  • Pipe secrets in over stdin (--secret-access-key-stdin, --sas-token-stdin, --account-key-stdin, --connection-string-stdin, --idp-client-secret-stdin) so they don't appear in shell history or process listings. SMB passwords have no flag at all — source add-smb / add-smbfs read them from the terminal without echo, or from stdin when piped.
  • Source IDs containing / are fine in CLI flags — kdbl-control handles URL encoding for you when talking to the API.
  • Run kdbl-control <command> --help for the full flag list on any subcommand.