CLI reference
One Python file, standard library only, the same REST API a curl call would use. Output is JSON when stdout is not a terminal (or with --json), a table when it is. Anywhere a project_id or a catalog's warehouse_id is accepted, the name works too. The CLI says catalog where the API says warehouse: lhbox catalog … is the command group (lhbox warehouse … still works), and every --catalog flag takes --warehouse as well.
Install
curl -fsSL https://lakehousebox.com/install.sh | sh # ~/.local/bin/lhbox, SHA-256 verified, no sudo
export LHBOX_URL=https://api.lakehousebox.com
Configuration
| what | how |
|---|---|
LHBOX_URL / --url | API base URL. The built-in default is https://api.lakehousebox.com (the service); set it to your own address for a self-hosted or local stack, e.g. http://localhost:8080. |
LHBOX_API_KEY / --api-key | The key. Otherwise read from the credentials file. |
LHBOX_CREDENTIALS | Path of the credentials file (0600, keyed by API URL; written by signup --save and login; a relative path is taken from the current directory). Without it: $XDG_CONFIG_HOME/lhbox/credentials.json when XDG_CONFIG_HOME is an absolute path, else ~/.config/lhbox/credentials.json. |
LHBOX_PROFILE / --profile | Which saved login to use, one per account or organisation on this machine (lhbox profile list); otherwise the one lhbox profile use chose, else default. |
LHBOX_NO_BROWSER | Set: lhbox login prints the approval link and never opens a browser. |
LHBOX_NO_VERSION_CHECK / --no-version-check | Set: no daily line saying a newer lhbox is published. |
Exit codes
| code | meaning | what to do |
|---|---|---|
| 0 | ok | |
| 1 | invalid input | the values you sent are wrong; fix them, never retry unchanged |
| 2 | limit or conflict | a real quota or a real 409; stop and change the plan |
| 3 | auth | key missing, wrong or revoked |
| 4 | not found | no such project, catalog, namespace or table |
| 5 | server or unreachable | ours or the network's; safe to retry with backoff |
| 6 | usage error | the command line itself is malformed; fix the command, never retry |
The server's typed error body goes to stderr verbatim: it names the limit, the current value and the remedy. The errors met most, what each wants, and every code: Errors and help.
Commands
Rendered from the CLI's own --help at build time: the lhbox published at /cli/lhbox. An older lhbox may lack a command or flag shown here, and refuses it as a usage error: lhbox --version names yours, lhbox update --check compares it with the published one, lhbox update installs it.
lhbox
usage: lhbox [-h] [--version] [--url URL] [--api-key API_KEY]
[--profile PROFILE] [--json] [--human]
{signup,terms,account,login,new,claim,status,discard,recover,api-key,org,agent,integration,token,whoami,doctor,bug,update,logout,profile,env,query,usage,audit,admin,project,catalog,file,namespace,table,credentials,connect,duckdb,sink,index,slice,mcp} ...
LakehouseBox: an open lakehouse you control.
positional arguments:
{signup,terms,account,login,new,claim,status,discard,recover,api-key,org,agent,integration,token,whoami,doctor,bug,update,logout,profile,env,query,usage,audit,admin,project,catalog,file,namespace,table,credentials,connect,duckdb,sink,index,slice,mcp}
signup create your account and catalog: email -> code -> API
key
terms the Terms version in force and whether you accepted it
account your account
login sign this computer in as you: allow it in your browser
(or with a code: --no-browser). The key acts as you,
as on the website, for 30 days; it is saved here,
never printed. A key limited to some catalogs is made
on the website (Settings > Access keys)
new no account yet: a catalog for this agent now; a person
claims it later
claim a person keeps the catalog `lhbox new` made: a link
and a code for them
status the time an unclaimed catalog (lhbox new) has left
discard end an unclaimed catalog (lhbox new) now: it is
deleted with its data and every key, as at its expiry,
and this machine forgets the key
recover retired: says how to get back in (reset the password,
or `lhbox login` again)
api-key API keys of the caller
org organisations: members, domains, invitations
agent hosted agents, in preview for the organisations it is
enabled for (tokens, which this command managed until
0.4.0, are `lhbox token`)
integration integrations: MCP servers your hosted agents may use,
as you; in preview for the organisations hosted agents
are enabled for
token tokens: machine identities of the organisation, each
with its own key, its own grants and an organisation
role
whoami who am I: identity, organisation, my level on each
catalog, limits with headroom and whether each is
enforced, the next step -- brief lines on a terminal,
a brief JSON object when piped
doctor check the whole path to a DuckDB session: API,
credential, catalog (FAIL while it is still
provisioning), recipe and level, a read through the
catalog, write-policy (HEAD and a one-key list: the
bucket accepts the identity, no object written), the
DuckDB here, and whether a newer lhbox is published
(WARN); PASS/FAIL/SKIP/WARN per line, exit 1 on any
FAIL. Writes nothing unless --write-probe
bug report a bug in LakehouseBox: opens the public
tracker's bug form
(github.com/lakehousebox/lakehousebox) filled in with
this machine's lhbox, OS, Python and DuckDB versions;
sends nothing itself. Piped: the link and the details
as JSON, for an agent to show its person
update install the lhbox the site publishes (its install
script) when this one is older; --check only says.
lhbox also prints one stderr line a day when it is
behind (silence: LHBOX_NO_VERSION_CHECK=1 or --no-
version-check)
logout revoke the key this machine's login holds and remove
it from the credentials file (--local: only remove it)
profile the saved logins on this machine, one per account or
organisation
env the environment lhbox reads
query run one read statement (SELECT, DESCRIBE, SUMMARIZE,
EXPLAIN) on the server over the catalogs you can read:
20 s, at most 2,000 rows and about 50,000 characters
-- a quick look; lhbox duckdb for complete results
usage limits, current usage and headroom (which limits are
enforced), one row per catalog -- a brief JSON object
when piped, tables on a terminal; --catalog for one
catalog's storage and traffic by day
audit who did what: your events, or your organisation's
(owner/admin)
admin install administration (a self-hosted install: status,
first-run setup) and the operator's plans and limits;
LHBOX_OPERATOR_KEY or, where named, --login with an
install administrator's own password and code; never
the agent key
project projects (placement is immutable)
catalog catalogs: one Iceberg catalog per lakehouse, with its
own storage and identity (the API names one by
catalog_id, under /v1/catalogs)
file the catalog's files (its file bucket, beside the
tables): cp, sync, put, get, ls, rm, aws s3 style,
with the catalog side written lhbox://<catalog>/<key>
-- no aws CLI or boto3 needed
(https://lakehousebox.com/docs/files/)
namespace namespaces (created on demand by table create)
table tables
credentials short-lived, table-scoped storage credentials: `lhbox
credentials <catalog>.<ns>.<table>`; the secret key
and session token are ******** unless --show-secrets
connect a paste-ready recipe for your own engine, carrying the
identity that matches your level (read holders get the
read-only one; snowflake does unless --write); with
one catalog visible no --catalog is needed; for duckdb
the DuckDB on this machine is checked against 1.5.5
duckdb open the `duckdb` shell already attached to the
catalog (the recipe goes in through a 0600 init file,
deleted at once; the credential is never on screen or
in argv); --persist instead writes DuckDB persistent
secrets so a bare ATTACH works in any session
sink ingest sinks: one HTTP intake per table for a device,
a webhook or an agent without an engine; JSON arrays
in, Iceberg rows out on a rolling policy
(https://lakehousebox.com/docs/ingest/)
index blob index rules: every file uploaded under a prefix
of a catalog's blob bucket becomes a row of a table
(https://lakehousebox.com/docs/ingest/)
slice table slices (enterprise): one copy of a table per
value of a column, each in its own namespace, kept up
to date; grant each audience its namespace
mcp the MCP server for Claude Desktop, Claude Code and
other hosts: install its configuration with the saved
key, or run it
options:
-h, --help show this help message and exit
--version show program's version number and exit
--url URL API base URL (env LHBOX_URL, default
https://api.lakehousebox.com)
--api-key API_KEY API key (env LHBOX_API_KEY). Kept for scripts, but a
key on a command line is visible to other local users
in the process list (ps) while the command runs:
prefer LHBOX_API_KEY or the login `lhbox login` saves
--profile PROFILE the saved login to use, one per account or
organisation on this machine (env LHBOX_PROFILE;
`lhbox profile use` sets the default; see `lhbox
profile list`)
--json force JSON output
--human force human-readable output
Output is JSON when piped and a table on a terminal. Piped, stdout is one JSON document or nothing;
every error is one JSON envelope on stderr {"error": {code, message, remedy, ...}}.
Anywhere a project_id or a catalog's warehouse_id is accepted, the name works too.
Exit codes, one meaning each:
0 ok
1 invalid input the values you sent are wrong; fix them, do not retry
2 limit/conflict a real quota or a real 409; change the plan
3 auth key missing, wrong or revoked
4 not found no such project, catalog, namespace or table
5 server or unreachable retry with backoff, unless the error's outcome is "unknown":
the request may have completed; check first (the remedy says how)
6 usage error the command line is malformed; fix it, do not retry
A bug in LakehouseBox itself: lhbox bug --title "..." (the public tracker's form, filled in; nothing sent).
lhbox signup
usage: lhbox signup [-h] --email EMAIL [--workspace WORKSPACE]
[--full-name FULL_NAME] [--organization ORGANIZATION]
[--handle HANDLE] [--code CODE]
[--engine {duckdb,pyiceberg,spark}] [--save] [--show-key]
[--accept-terms]
options:
-h, --help show this help message and exit
--email EMAIL
--workspace WORKSPACE
workspace name (default: the part before @)
--full-name FULL_NAME
your name, for the account
--organization ORGANIZATION
organisation name; with --handle the organisation is
reserved now
--handle HANDLE the organisation's handle
(lakehousebox.com/app/<handle>; lowercase letters,
digits, dashes; fixed after signup)
--code CODE the code you received; prompted for if omitted
--engine {duckdb,pyiceberg,spark}
--save store the API key in ~/.config/lhbox/credentials.json;
with it the key is NOT printed
--show-key print the API key even when it was saved (default: a
saved key stays out of the terminal)
--accept-terms accept the Terms of Service and Privacy Policy
lhbox terms
usage: lhbox terms [-h] [--accept]
options:
-h, --help show this help message and exit
--accept
lhbox account
usage: lhbox account [-h] {delete} ...
positional arguments:
{delete}
delete delete your account: keys revoked now, data purged within 30
days
options:
-h, --help show this help message and exit
lhbox account delete
usage: lhbox account delete [-h] --confirm CONFIRM [--password @FILE|@-]
[--password-env VAR] [--code CODE]
DELETE /v1/me. A person's account asks for its current password, and for a
code when two-factor authentication is on: give them with --password and
--code, or type them when asked at a terminal. A token confirms with its
principal_id and needs neither.
options:
-h, --help show this help message and exit
--confirm CONFIRM your email, typed out
--password @FILE|@- your current password from a file or stdin, never
inline; asked for at a terminal when needed
--password-env VAR the environment variable holding your current password
--code CODE a code from your authenticator app (or a backup code),
when two-factor is on
lhbox login
usage: lhbox login [-h] [--issuer [ISSUER]] [--sso EMAIL] [--label LABEL]
[--name NAME] [--no-browser]
[--campaign SOURCE[/MEDIUM[/CAMPAIGN[/CONTENT]]]] [--new]
[--no-wait] [--resume] [--wait] [--engine ENGINE] [--save]
[--show-key] [--accept-terms]
options:
-h, --help show this help message and exit
--issuer [ISSUER] log in through an external OpenID Connect issuer
instead of the LakehouseBox login (the first
configured one, or the one named); for self-hosted
deployments with their own identity provider
--sso EMAIL log in with your organisation's own identity provider
(e.g. Microsoft Entra ID), found from your work
address
--label LABEL API key label on the --issuer and --sso paths (the
LakehouseBox login labels it cli <hostname>)
--name NAME the token's name shown on the consent screen and in
Connections (default: this machine's hostname)
--no-browser sign in with a code instead of opening a browser here
(the browser is on another computer: an SSH session, a
server). Either way the key acts as you, for 30 days
--campaign SOURCE[/MEDIUM[/CAMPAIGN[/CONTENT]]]
the campaign tags of the link that brought you here
(the site writes them into the command it shows); an
account created on the approval page is attributed to
them
--new start a new authorisation even when the saved key
still works
--no-wait print the page and the code, keep the pending login in
the credentials file (pending_device) and exit 0;
finish it later with --resume
--resume poll the pending login left by --no-wait once:
connected (exit 0), or still pending (exit 3, the
credential is still missing); --resume --wait polls
until the person decides
--wait with --resume: wait for the approval instead of
polling once (the default `lhbox login` waits anyway,
up to the code's 15 minutes)
--engine ENGINE
--save kept for compatibility: the key is always saved to
~/.config/lhbox/credentials.json
--show-key print the API key as well (default: it is saved and
never shown; an agent should not need it)
--accept-terms accept the Terms of Service on first login (--issuer
path)
lhbox new
usage: lhbox new [-h] [--force]
[--campaign SOURCE[/MEDIUM[/CAMPAIGN[/CONTENT]]]]
no account yet: a catalog for this agent now, write access, the key saved
here; a person claims it later or it is deleted with its data after 72 h.
Running it accepts the Terms
options:
-h, --help show this help message and exit
--force replace the key already saved for this API (default:
refused)
--campaign SOURCE[/MEDIUM[/CAMPAIGN[/CONTENT]]]
the campaign tags of the link that brought you here
(the site writes them into the command it shows)
lhbox claim
usage: lhbox claim [-h] [--wait] [--resume]
a person keeps the catalog `lhbox new` made: prints a link and a code for
them; --wait waits for their decision and saves this machine's new key
options:
-h, --help show this help message and exit
--wait wait for the person's decision (default: print and exit;
--resume later)
--resume collect a claim started earlier (polls once; with --wait until
decided)
lhbox status
usage: lhbox status [-h]
whether this machine's catalog is still unclaimed (lhbox new), and the time it
has left
options:
-h, --help show this help message and exit
lhbox discard
usage: lhbox discard [-h] [--yes]
DELETE /v1/agent/identity: the unclaimed organisation `lhbox new` made, its
catalog with every table and file, and its keys, deleted now instead of at
expires_at. For a key that leaked, or a catalog nobody needs. A catalog a
person has claimed is theirs: refused.
options:
-h, --help show this help message and exit
--yes do not ask: discarding the catalog and its data cannot be
undone. Asked on a terminal; without one (or with --json) --yes
will be required from the next minor version of lhbox (a warning
today)
lhbox recover
usage: lhbox recover [-h]
options:
-h, --help show this help message and exit
lhbox api-key
usage: lhbox api-key [-h] {create,list,revoke} ...
positional arguments:
{create,list,revoke}
options:
-h, --help show this help message and exit
lhbox api-key create
usage: lhbox api-key create [-h] [--label LABEL]
options:
-h, --help show this help message and exit
--label LABEL
lhbox api-key list
usage: lhbox api-key list [-h]
options:
-h, --help show this help message and exit
lhbox api-key revoke
usage: lhbox api-key revoke [-h] --key-id KEY_ID [--yes [NAME]]
options:
-h, --help show this help message and exit
--key-id KEY_ID
--yes [NAME] do not ask: the revocation cannot be undone. Asked on a
terminal; without one (or with --json) --yes will be
required from the next minor version of lhbox (a warning
today)
lhbox org
usage: lhbox org [-h]
{list,members,invite,accept,domain,offers,join,set,role} ...
positional arguments:
{list,members,invite,accept,domain,offers,join,set,role}
domain claim your organisation's email domain (admin; it
takes effect only once a DNS TXT record verifies it),
--verify the claim, or --remove it
offers organisations you may join because they verified and
opened your address's domain
join accept a domain offer: join that organisation as a
member (nothing joins you otherwise)
policy what the organisation's members may do: publish
catalogs, connect agents, create hosted agents (read
it; an admin changes it with the flags)
role change a member's role: admin or member (an
organisation keeps at least one admin)
options:
-h, --help show this help message and exit
lhbox org list
usage: lhbox org list [-h]
options:
-h, --help show this help message and exit
lhbox org members
usage: lhbox org members [-h] [--org ORG]
options:
-h, --help show this help message and exit
--org ORG
lhbox org invite
usage: lhbox org invite [-h] [--org ORG] --email EMAIL [--role {member,admin}]
options:
-h, --help show this help message and exit
--org ORG
--email EMAIL
--role {member,admin}
lhbox org accept
usage: lhbox org accept [-h] --token TOKEN
options:
-h, --help show this help message and exit
--token TOKEN
lhbox org domain
usage: lhbox org domain [-h] [--org ORG] --domain DOMAIN [--verify | --remove]
options:
-h, --help show this help message and exit
--org ORG
--domain DOMAIN
--verify look up the TXT record _lakehousebox.<domain> and verify
the claim
--remove release the domain instead of claiming it (admin); domain
join switches off when no verified domain remains
lhbox org offers
usage: lhbox org offers [-h]
options:
-h, --help show this help message and exit
lhbox org join
usage: lhbox org join [-h] [--org ORG]
options:
-h, --help show this help message and exit
--org ORG the offering organisation's handle (optional when you have
exactly one offer)
lhbox org set
usage: lhbox org set [-h] [--org ORG] [--name NAME] [--domain-join {on,off}]
[--deprovision {tokens,everything}]
options:
-h, --help show this help message and exit
--org ORG
--name NAME
--domain-join {on,off}
offer membership to people on a verified domain (they
join only if they accept)
--deprovision {tokens,everything}
what removing a member stops: their tokens and
connections (tokens, the default), or also the sinks,
blob uploaders and hosted agents they made
(everything)
lhbox org policy
usage: lhbox org policy [-h] [--org ORG]
[--publish-catalogs {admins,members,nobody}]
[--connect-agents {members,admins}]
[--hosted-agents {members,admins}]
options:
-h, --help show this help message and exit
--org ORG org_id or the organisation's handle (lhbox org list
shows both); default: your only organisation
--publish-catalogs {admins,members,nobody}
who may make a catalog public: admins (the default),
members too (on a catalog they can write), or nobody;
catalogs public now stay public
--connect-agents {members,admins}
who may connect agents: tokens, `lhbox login`, MCP
clients, personal API keys (default members)
--hosted-agents {members,admins}
who may create hosted agents (default members)
lhbox org role
usage: lhbox org role [-h] [--org ORG] --principal PRINCIPAL
--role {admin,member}
options:
-h, --help show this help message and exit
--org ORG org_id or the organisation's handle (lhbox org list
shows both); default: your only organisation
--principal PRINCIPAL
--role {admin,member}
lhbox agent
usage: lhbox agent [-h]
{create,list,show,update,run,runs,subscribe,unsubscribe,delete} ...
Hosted agents: a prompt LakehouseBox runs for you next to your data, on the
provided model, reading the catalogs you grant it; runs count against your
organisation's monthly tokens and runs per day.
positional arguments:
{create,list,show,update,run,runs,subscribe,unsubscribe,delete}
create a new agent with its own key-less identity reading the
catalogs given
list your organisation's agents and its allowance (tokens
this month, runs today)
models the models LakehouseBox provides: where they run,
whether they see images, price
show one agent: its definition, catalogs and last run
update a new version of the agent (only what you pass
changes), or --pause / --resume
run run the agent now (one run at a time; counts against
today's runs and the month's tokens)
brief what needs you first (its flags, decisions due,
failures, missed runs), then its memory as it sees it
and its recent runs; no model
talk talk to the agent: it answers from its memory and
runs, and may propose a new prompt or items to mark
done, which change nothing until --apply
runs the agent's runs: state, tokens, model requests,
errors
subscribe email me this agent's notices (you need read on every
catalog it reads and a verified address)
unsubscribe stop emailing me this agent's notices
delete delete the agent for good: its identity is suspended,
its runs stay readable
options:
-h, --help show this help message and exit
lhbox agent create
usage: lhbox agent create [-h] --prompt TEXT [--catalog CATALOG]
[--state-key KEY] [--topic TOPIC] [--notify-owner |
--no-notify-owner] [--run-seconds S] [--model MODEL]
[--schedule CRON] [--tz ZONE]
[--integration NAME:TOOL[,TOOL]]
[--write CATALOG.NS.TABLE] [--allow-host HOST]
[--host-credential HOST=INTEGRATION] [--org ORG_ID]
NAME
positional arguments:
NAME the agent's name (lowercase letters, digits and _)
options:
-h, --help show this help message and exit
--prompt TEXT what the agent does: inline, @file or @- (stdin)
--catalog CATALOG a catalog the agent may read (repeat; its grants
become exactly these)
--state-key KEY a small value it may remember between runs (repeat)
--topic TOPIC a memory topic: the agent keeps notes about items in
it (note, resolve, flag_owner) and starts each run
from their view (repeat; up to 12)
--notify-owner it may email you (its creator's person) once per run
--no-notify-owner it may not email
--run-seconds S one run's wall time (at most the organisation's limit)
--model MODEL a provided model (default deepseek-v4-flash)
--schedule CRON when it runs by itself: five cron fields, e.g. "0 7 *
* *" (at most hourly and the organisation's runs per
day)
--tz ZONE the schedule's time zone, e.g. Europe/Madrid (default
UTC)
--integration NAME:TOOL[,TOOL]
an integration you connected and the tools the agent
may call there (repeat; the list becomes exactly
these), e.g. github:search_issues,get_file_contents.
Your own sign-in only: a token (an lhbox login
connection included) is refused
integration_needs_person; attach it on the account
page
--write CATALOG.NS.TABLE
a table the agent may append rows to (repeat; the list
becomes exactly these): in a catalog it reads, and you
need write there
--allow-host HOST a web host the agent may fetch (https GET; repeat):
its web hosts become exactly these, with the keys
given by --host-credential in the same command (no
other key is kept)
--host-credential HOST=INTEGRATION
send that web key (lhbox integration add NAME --web-
key) to HOST; the host is allowed too (alone on
update: added to the agent's hosts and keys). Like
--integration, your own sign-in only (a token is
refused)
--org ORG_ID the organisation (when you belong to several)
lhbox agent list
usage: lhbox agent list [-h] [--org ORG_ID]
options:
-h, --help show this help message and exit
--org ORG_ID the organisation (when you belong to several)
lhbox agent models
usage: lhbox agent models [-h]
options:
-h, --help show this help message and exit
lhbox agent show
usage: lhbox agent show [-h] NAME
positional arguments:
NAME the agent's name, or its agent_id (needed when you belong to two
organisations with an agent of that name)
options:
-h, --help show this help message and exit
lhbox agent update
usage: lhbox agent update [-h] [--prompt TEXT] [--catalog CATALOG]
[--state-key KEY] [--topic TOPIC] [--notify-owner |
--no-notify-owner] [--run-seconds S] [--model MODEL]
[--schedule CRON] [--tz ZONE]
[--integration NAME:TOOL[,TOOL]]
[--write CATALOG.NS.TABLE] [--allow-host HOST]
[--host-credential HOST=INTEGRATION] [--no-catalogs]
[--no-state-keys] [--no-topics] [--no-schedule]
[--no-integrations] [--no-writes] [--no-web]
[--confirm-definition] [--pause | --resume]
[--restore-version VERSION]
NAME
positional arguments:
NAME the agent's name, or its agent_id (needed when you
belong to two organisations with an agent of that
name)
options:
-h, --help show this help message and exit
--prompt TEXT what the agent does: inline, @file or @- (stdin)
--catalog CATALOG a catalog the agent may read (repeat; its grants
become exactly these)
--state-key KEY a small value it may remember between runs (repeat)
--topic TOPIC a memory topic: the agent keeps notes about items in
it (note, resolve, flag_owner) and starts each run
from their view (repeat; up to 12)
--notify-owner it may email you (its creator's person) once per run
--no-notify-owner it may not email
--run-seconds S one run's wall time (at most the organisation's limit)
--model MODEL a provided model (default deepseek-v4-flash)
--schedule CRON when it runs by itself: five cron fields, e.g. "0 7 *
* *" (at most hourly and the organisation's runs per
day)
--tz ZONE the schedule's time zone, e.g. Europe/Madrid (default
UTC)
--integration NAME:TOOL[,TOOL]
an integration you connected and the tools the agent
may call there (repeat; the list becomes exactly
these), e.g. github:search_issues,get_file_contents.
Your own sign-in only: a token (an lhbox login
connection included) is refused
integration_needs_person; attach it on the account
page
--write CATALOG.NS.TABLE
a table the agent may append rows to (repeat; the list
becomes exactly these): in a catalog it reads, and you
need write there
--allow-host HOST a web host the agent may fetch (https GET; repeat):
its web hosts become exactly these, with the keys
given by --host-credential in the same command (no
other key is kept)
--host-credential HOST=INTEGRATION
send that web key (lhbox integration add NAME --web-
key) to HOST; the host is allowed too (alone on
update: added to the agent's hosts and keys). Like
--integration, your own sign-in only (a token is
refused)
--no-catalogs take away every catalog grant (with no --catalog)
--no-state-keys forget every state key (with no --state-key)
--no-topics turn its memory topics off (its notes are kept)
--no-schedule remove the schedule: it runs on demand only
--no-integrations detach every integration (with no --integration)
--no-writes the agent appends to no table (with no --write)
--no-web the agent fetches no web host (with no --allow-host)
--confirm-definition approve the agent's current definition as yours, so it
may use your integrations (needed when another
credential, e.g. a token, wrote it; your own sign-in
only)
--pause no run starts until --resume
--resume runs may start again
--restore-version VERSION
put an earlier definition back whole (its id is in the
mail sent when a key changes the agent)
lhbox agent run
usage: lhbox agent run [-h] [--dry-run] [--wait] [--timeout S] NAME
positional arguments:
NAME the agent's name, or its agent_id (needed when you belong to
two organisations with an agent of that name)
options:
-h, --help show this help message and exit
--dry-run a test run: its tools read for real, its email and memory
writes are shown, not performed; outside today's runs (its own
daily cap), tokens still count
--wait wait for the run to end and print its final message
--timeout S how long --wait waits (default 600 s)
lhbox agent brief
usage: lhbox agent brief [-h] NAME
positional arguments:
NAME the agent's name, or its agent_id (needed when you belong to two
organisations with an agent of that name)
options:
-h, --help show this help message and exit
lhbox agent talk
usage: lhbox agent talk [-h] [--about TOPIC/ITEM] [--apply ID] [--discard ID]
[--limit N]
NAME [MESSAGE]
positional arguments:
NAME the agent's name, or its agent_id (needed when you
belong to two organisations with an agent of that name)
MESSAGE what you say (omit it to list the latest exchanges)
options:
-h, --help show this help message and exit
--about TOPIC/ITEM reply to an entry of its brief ([topic/item] there)
--apply ID apply what it proposed in that exchange (a new version
of its prompt)
--discard ID discard what it proposed; nothing changes
--limit N how many exchanges to list (default 5)
lhbox agent runs
usage: lhbox agent runs [-h] [--limit N] [--detail] NAME
positional arguments:
NAME the agent's name, or its agent_id (needed when you belong to two
organisations with an agent of that name)
options:
-h, --help show this help message and exit
--limit N
--detail each run's tool calls (with their SQL), emails and memory
changes
lhbox agent subscribe
usage: lhbox agent subscribe [-h] NAME
positional arguments:
NAME the agent's name, or its agent_id (needed when you belong to two
organisations with an agent of that name)
options:
-h, --help show this help message and exit
lhbox agent unsubscribe
usage: lhbox agent unsubscribe [-h] NAME
positional arguments:
NAME the agent's name, or its agent_id (needed when you belong to two
organisations with an agent of that name)
options:
-h, --help show this help message and exit
lhbox agent delete
usage: lhbox agent delete [-h] [--yes] NAME
positional arguments:
NAME the agent's name, or its agent_id (needed when you belong to two
organisations with an agent of that name)
options:
-h, --help show this help message and exit
--yes do not ask: the deletion cannot be undone. Asked on a terminal;
without one (or with --json) --yes will be required from the
next minor version of lhbox (a warning today)
lhbox agent revoke
usage: lhbox agent revoke [-h] [ref]
positional arguments:
ref
options:
-h, --help show this help message and exit
lhbox integration
usage: lhbox integration [-h] {add,list,show,reconnect,wait,delete} ...
Integrations connect any MCP server to your organisation: with its sign-in
(you approve in a browser where you are signed in to LakehouseBox), an API
key, or none; the CLI never receives a token. Attach one to an agent on the
account page, or with `lhbox agent update NAME --integration NAME:tool,...`
signed in as yourself (a token cannot attach your integrations; lhbox
integration show NAME lists its tools).
positional arguments:
{add,list,show,reconnect,wait,delete}
add connect an MCP server: prints the link to approve in
your browser
list your organisation's integrations and their state
show one integration: its server, tools, scopes and state
reconnect approve again (after `needs_reauth`, or to switch
accounts)
wait wait until the person approved (after add or
reconnect; their answer's wait_command)
delete delete the integration for good: its tokens are
revoked and wiped
options:
-h, --help show this help message and exit
lhbox integration add
usage: lhbox integration add [-h] [--mcp URL] [--api-key-stdin]
[--header NAME] [--web-key] [--host HOST]
[--key-param NAME] [--scope SCOPE] [--org ORG_ID]
[--wait] [--timeout S] [--open]
NAME
positional arguments:
NAME the integration's name (e.g. linear; lowercase
letters, digits and _)
options:
-h, --help show this help message and exit
--mcp URL the MCP server's URL (https; for the name github:
GitHub's MCP server by default)
--api-key-stdin the server takes an API key: read it from stdin (it is
stored sealed and never shown again)
--header, --header-name NAME
with --api-key-stdin: the header the key goes in
(default Authorization, sent as 'Bearer <key>')
--web-key a web API's key for an agent's fetch_url (with --host
and --api-key-stdin), not an MCP server
--host HOST with --web-key: the one host the key is sent to
--key-param NAME with --web-key: send the key as this query parameter
instead of a header
--scope SCOPE narrow the scopes asked for (repeat); default: the
server's own
--org ORG_ID the organisation (when you belong to several)
--wait wait until you approved in the browser (or it failed)
--timeout S how long --wait waits (default 600 s)
--open also open the link in this machine's browser
lhbox integration list
usage: lhbox integration list [-h] [--org ORG_ID]
options:
-h, --help show this help message and exit
--org ORG_ID the organisation (when you belong to several)
lhbox integration show
usage: lhbox integration show [-h] NAME
positional arguments:
NAME the integration's name or id
options:
-h, --help show this help message and exit
lhbox integration reconnect
usage: lhbox integration reconnect [-h] [--wait] [--timeout S] [--open] NAME
positional arguments:
NAME the integration's name or id
options:
-h, --help show this help message and exit
--wait wait until you approved in the browser (or it failed)
--timeout S how long --wait waits (default 600 s)
--open also open the link in this machine's browser
lhbox integration wait
usage: lhbox integration wait [-h] [--previous CONNECTED_AT] [--timeout S]
NAME
positional arguments:
NAME the integration's name or id
options:
-h, --help show this help message and exit
--previous CONNECTED_AT
its connected_at before the approval ('-' for never),
from the add or reconnect answer
--timeout S how long to wait (default 600 s)
lhbox integration delete
usage: lhbox integration delete [-h] [--yes] NAME
positional arguments:
NAME the integration's name or id
options:
-h, --help show this help message and exit
--yes do not ask: the deletion cannot be undone. Asked on a terminal;
without one (or with --json) --yes will be required from the
next minor version of lhbox (a warning today)
lhbox token
usage: lhbox token [-h] {create,update,list,revoke} ...
positional arguments:
{create,update,list,revoke}
create a new token; it holds exactly the grants given (none
until granted otherwise) and the role given (member
unless --role admin)
update rename a token, replace its grants and/or change its
role (admin, or the member who created it, within
their own role)
list the organisation's tokens; grants as [{catalog, level,
warehouse_id}] in JSON, catalog:level on a terminal
revoke revoke a token's key at once (admin, or the member who
created it): lhbox token revoke <name or id>
options:
-h, --help show this help message and exit
lhbox token create
usage: lhbox token create [-h] [--org ORG] --name NAME [--role {admin,member}]
[--grant CATALOG:LEVEL] [--send SINK]
options:
-h, --help show this help message and exit
--org ORG org_id or the organisation's handle (lhbox org list
shows both); default: your only organisation
--name NAME
--role {admin,member}
organisation role of the token (default member): what
it may do to the organisation, its people and its
tokens; capped by your own role. Data access is its
grants
--grant CATALOG:LEVEL
<catalog name or id>:<read|write>, repeatable (a name
is resolved here against lhbox catalog list); capped
by your own level on each catalog
--send SINK a sink (id or name) the token may send batches to with
its own key (`lhbox sink send` without a send key),
repeatable; needs your write on the sink's catalog,
and the token needs a --grant <catalog>:read on it
lhbox token update
usage: lhbox token update [-h] [--org ORG] [--token TOKEN] [--name NAME]
[--role {admin,member}] [--grant CATALOG:LEVEL]
[--send SINK]
[TOKEN]
positional arguments:
TOKEN the token's id or its name (lhbox token list shows
both)
options:
-h, --help show this help message and exit
--org ORG org_id or the organisation's handle (lhbox org list
shows both); default: your only organisation
--token, --token-id TOKEN
the token's id or its name (lhbox token list shows
both); the same as the positional
--name NAME the new name
--role {admin,member}
new organisation role; capped by your own
--grant CATALOG:LEVEL
<catalog name or id>:<read|write>, repeatable; the
list REPLACES the current grants
--send SINK a sink (id or name) the token may send to, repeatable;
the list REPLACES the current one (--send '' clears
it)
lhbox token list
usage: lhbox token list [-h] [--org ORG]
options:
-h, --help show this help message and exit
--org ORG org_id or the organisation's handle (lhbox org list shows both);
default: your only organisation
lhbox token revoke
usage: lhbox token revoke [-h] [--org ORG] [--token TOKEN] [--yes [NAME]]
[TOKEN]
positional arguments:
TOKEN the token's id or its name (lhbox token list shows
both)
options:
-h, --help show this help message and exit
--org ORG org_id or the organisation's handle (lhbox org list
shows both); default: your only organisation
--token, --token-id TOKEN
the token's id or its name (lhbox token list shows
both); the same as the positional
--yes [NAME] do not ask: the revocation cannot be undone. Asked on
a terminal; without one (or with --json) --yes will be
required from the next minor version of lhbox (a
warning today)
lhbox whoami
usage: lhbox whoami [-h] [--full]
options:
-h, --help show this help message and exit
--full the whole answer: every limit, project and organisation as
tables on a terminal; piped or --json, GET /v1/usage as it comes
plus the per-catalog levels (the default JSON is the brief
object)
lhbox doctor
usage: lhbox doctor [-h] [--catalog CATALOG] [--write-probe]
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list; default: your only catalog
(with several, name one)
--write-probe also write and delete a 0-byte
_lhbox/_probe/data/probe.parquet in the table bucket as
the recipe's identity: the one check that sees a store
refusing writes (the store's error is quoted verbatim on
FAIL)
lhbox bug
usage: lhbox bug [-h] [--title TITLE] [--what WHAT] [--command COMMAND]
[--no-open]
Builds the bug form's URL; you review and submit it. The tracker is public:
what you pass is scrubbed of key and secret shapes, and no organisation,
catalog or home directory is added. A security vulnerability goes to a private
report instead
(https://github.com/lakehousebox/lakehousebox/security/advisories/new).
options:
-h, --help show this help message and exit
--title TITLE one line: what is wrong
--what WHAT what happened: the error as printed (its `code` above
all)
--command COMMAND what you ran: the command, SQL or MCP tool call
--no-open print the link, do not open a browser
lhbox update
usage: lhbox update [-h] [--check] [--site URL]
options:
-h, --help show this help message and exit
--check say which version is published, install nothing
--site URL install from this site instead of https://lakehousebox.com (a
self-hosted deployment's own): https, or http on this machine
only; never taken from --url / LHBOX_URL, since its install
script runs here
lhbox logout
usage: lhbox logout [-h] [--local] [--org ORG]
options:
-h, --help show this help message and exit
--local remove the saved login without revoking its key at the API
--org ORG the organisation that holds the token (only needed when you
belong to several)
lhbox profile
usage: lhbox profile [-h] {list,use,delete} ...
positional arguments:
{list,use,delete}
list the profiles, the one in force marked
use make a profile the default for every command (--profile
and LHBOX_PROFILE still win)
delete remove a profile's saved login from this machine (logout
--profile NAME revokes its key first)
options:
-h, --help show this help message and exit
lhbox profile list
usage: lhbox profile list [-h]
options:
-h, --help show this help message and exit
lhbox profile use
usage: lhbox profile use [-h] name
positional arguments:
name
options:
-h, --help show this help message and exit
lhbox profile delete
usage: lhbox profile delete [-h] [--yes] name
positional arguments:
name
options:
-h, --help show this help message and exit
--yes do not ask: forgetting the saved login cannot be undone. Asked
on a terminal; without one (or with --json) --yes will be
required from the next minor version of lhbox (a warning today)
lhbox env
usage: lhbox env [-h] {check} ...
positional arguments:
{check}
check `set` or `missing` for LHBOX_API_KEY, LHBOX_URL, the saved login
and the rest; never a value
options:
-h, --help show this help message and exit
lhbox env check
usage: lhbox env check [-h]
options:
-h, --help show this help message and exit
lhbox query
usage: lhbox query [-h] [--max-rows MAX_ROWS] [--require-complete] [--dry-run]
[--engine {1.5,2.0}]
sql
A quick-query and preview interface: one statement on LakehouseBox's shared
engine, 20 seconds, and two independent caps on the answer, rows (--max-rows,
default 200, at most 2000) and characters (about 50,000 characters of JSON in
all, whatever --max-rows says). An answer cut by either still exits 0 and says
so: truncated: true, truncated_by: rows or characters, and local_compute, how
to get the whole result (aggregate or filter in SQL, or the same SQL in your
own DuckDB: lhbox duckdb --catalog <catalog>, no caps). --require-complete
makes a cut answer exit 2.
positional arguments:
sql the SQL (DuckDB dialect), tables named
catalog.namespace.table; '-' reads it from stdin
options:
-h, --help show this help message and exit
--max-rows MAX_ROWS rows to return (default 200, at most 2000); the
character cap (about 50,000) can still cut a wide
answer sooner
--require-complete exit 2 (code incomplete_result on stderr) when the
answer was cut by either cap; the answer is still
printed. Without it a cut answer exits 0
--dry-run run nothing: the tables it reads with their rows, files
and bytes (an upper bound), and whether the cache would
answer
--engine {1.5,2.0} DuckDB engine version: 2.0 (DuckDB 2.0.0, default) or
1.5 (DuckDB 1.5.6, the fallback, for claimed accounts)
lhbox usage
usage: lhbox usage [-h] [--catalog CATALOG] [--days DAYS] [--full]
options:
-h, --help show this help message and exit
--catalog CATALOG one catalog's view, day by day: stored bytes and objects,
S3 requests and bytes in/out; the catalog's warehouse_id
(UUID) or its name; list both with: lhbox catalog list
--days DAYS length of the daily history, 1..30 (default 7)
--full piped or --json: GET /v1/usage as it comes
(limits_detail, every project's warehouse_storage with
history[]) instead of the brief object
lhbox audit
usage: lhbox audit [-h] [--org ORG] [--since SINCE] [--action ACTION]
[--limit LIMIT]
options:
-h, --help show this help message and exit
--org ORG org_id: the organisation's events (owners and admins)
instead of only yours
--since SINCE 24h, 7d, 30m, or an ISO-8601 timestamp
--action ACTION one action, e.g. credentials.vend, or a prefix like org.*
--limit LIMIT at most this many events (max 1000)
lhbox admin
usage: lhbox admin [-h] {status,setup,plans,org,person,client,news} ...
positional arguments:
{status,setup,plans,org,person,client,news}
status the install: release, security settings and where each
is set, sign-in, SIEM export, backups, storage,
administrators (read-only)
setup first-run setup of a self-hosted install: the install
administrator and the first organisation, with the
one-time token `./al setup-link` printed on the host
plans every plan by name with its limits and features
org one organisation's plan and limits
person a person's access: suspend, reactivate, revoke every
session and key
client approved OAuth/MCP clients: they connect with dynamic
registration off
news the product-news mailing list: status, write an issue,
test it, send it
options:
-h, --help show this help message and exit
lhbox admin status
usage: lhbox admin status [-h] [--login EMAIL]
options:
-h, --help show this help message and exit
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin setup
usage: lhbox admin setup [-h] --token TOKEN --email EMAIL --name NAME
--org ORG [--handle HANDLE] [--password-stdin |
--no-password]
options:
-h, --help show this help message and exit
--token TOKEN the setup token (lhbsetup_...)
--email EMAIL
--name NAME your name
--org ORG the first organisation's name
--handle HANDLE its handle (default: from the name)
--password-stdin read the password from standard input
--no-password no password: you sign in with the install's identity
provider (AL_OIDC_ISSUERS)
lhbox admin plans
usage: lhbox admin plans [-h] [--login EMAIL]
options:
-h, --help show this help message and exit
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin org
usage: lhbox admin org [-h] {limits,set,create,list,admin} ...
positional arguments:
{limits,set,create,list,admin}
limits the organisation's plan, overrides, effective limits
and features, and last changes
set put the organisation on a plan and/or override limits;
a flag not passed leaves that limit as it is
create a new organisation and its first admin: an existing
person at once, any other address by an admin
invitation (its link printed once)
list every organisation, newest first: handle, members,
catalogs, stored
admin an organisation's admins
options:
-h, --help show this help message and exit
lhbox admin org limits
usage: lhbox admin org limits [-h] [--login EMAIL] org
positional arguments:
org org_id or handle
options:
-h, --help show this help message and exit
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin org set
usage: lhbox admin org set [-h] [--plan PLAN] [--storage-gb STORAGE_GB]
[--public-storage-gb PUBLIC_STORAGE_GB]
[--objects OBJECTS] [--catalogs CATALOGS]
[--sql-sinks SQL_SINKS] [--sql-min-every SECONDS]
[--sql-run-seconds SQL_RUN_SECONDS]
[--sql-requests-per-run SQL_REQUESTS_PER_RUN]
[--sql-download-mb-per-run SQL_DOWNLOAD_MB_PER_RUN]
[--projects PROJECTS] [--clear FIELD]
[--feature NAME=VALUE] [--note NOTE]
[--login EMAIL]
org
positional arguments:
org org_id or handle
options:
-h, --help show this help message and exit
--plan PLAN the plan name (lhbox admin plans)
--storage-gb STORAGE_GB
private storage limit in decimal GB (x 1000^3 bytes)
--public-storage-gb PUBLIC_STORAGE_GB
public storage limit in decimal GB
--objects OBJECTS objects per catalog (a guideline today)
--catalogs CATALOGS catalogs per project
--sql-sinks SQL_SINKS
scheduled SQL sinks of the organisation; 0 = not
enabled (the default), N enables the feature with at
most N sinks
--sql-min-every SECONDS
the shortest schedule (default 300)
--sql-run-seconds SQL_RUN_SECONDS
one run's wall time (default 60)
--sql-requests-per-run SQL_REQUESTS_PER_RUN
HTTP requests per run (default 1000)
--sql-download-mb-per-run SQL_DOWNLOAD_MB_PER_RUN
MiB downloaded per run (default 1024)
--projects PROJECTS projects per account
--clear FIELD remove one override by its API name (lhbox admin plans
lists them, e.g. storage_bytes); repeatable
--feature NAME=VALUE switch a plan feature or set any limit by its API
name: on/off or a number, e.g. public_images=on
(photos served from public folders) or
device_sinks=20; repeatable (lhbox admin plans lists
the names)
--note NOTE why (kept with the change in the audit log)
--login EMAIL as this install administrator: password and
authenticator code asked on the terminal, the session
ended after the command (default: LHBOX_OPERATOR_KEY)
lhbox admin org create
usage: lhbox admin org create [-h] --admin EMAIL [--name NAME] [--login EMAIL]
handle
positional arguments:
handle the organisation's handle (its address and the prefix of its
storage names)
options:
-h, --help show this help message and exit
--admin EMAIL the first admin's address
--name NAME its display name (default: the handle)
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin org list
usage: lhbox admin org list [-h] [--q Q] [--limit LIMIT] [--login EMAIL]
options:
-h, --help show this help message and exit
--q Q search handle or name
--limit LIMIT
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin org admin
usage: lhbox admin org admin [-h] {add,remove} ...
positional arguments:
{add,remove}
add make an address an admin (a member is promoted, an existing
person added, anyone else invited)
remove an admin becomes a member (the last admin is refused); an open
admin invitation is withdrawn
options:
-h, --help show this help message and exit
lhbox admin org admin add
usage: lhbox admin org admin add [-h] [--login EMAIL] org email
positional arguments:
org org_id or handle
email
options:
-h, --help show this help message and exit
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin org admin remove
usage: lhbox admin org admin remove [-h] [--login EMAIL] org member
positional arguments:
org org_id or handle
member email or principal_id
options:
-h, --help show this help message and exit
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin person
usage: lhbox admin person [-h] {suspend,reactivate,revoke-all} ...
positional arguments:
{suspend,reactivate,revoke-all}
suspend refuse their login and every credential of theirs and
of their tokens, until reactivated
reactivate lift a suspension (not one the organisation's
directory made)
revoke-all revoke every session, API key and token of the person,
in every organisation (the account stays; not
reversible)
options:
-h, --help show this help message and exit
lhbox admin person suspend
usage: lhbox admin person suspend [-h] [--reason REASON] [--login EMAIL]
person
positional arguments:
person email or principal_id
options:
-h, --help show this help message and exit
--reason REASON kept in the audit log
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin person reactivate
usage: lhbox admin person reactivate [-h] [--login EMAIL] person
positional arguments:
person email or principal_id
options:
-h, --help show this help message and exit
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin person revoke-all
usage: lhbox admin person revoke-all [-h] [--yes] [--login EMAIL] person
positional arguments:
person email or principal_id
options:
-h, --help show this help message and exit
--yes do not ask: revoking every credential of the person cannot be
undone. Asked on a terminal; without one (or with --json)
--yes will be required from the next minor version of lhbox
(a warning today)
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin client
usage: lhbox admin client [-h] {add,list,disable,enable,remove} ...
positional arguments:
{add,list,disable,enable,remove}
add register a client (public by default; --confidential
prints its secret once)
list the clients install administrators registered, and
whether dynamic registration is on
disable no new sign-in and no refresh (reversible)
enable undo disable
remove remove it and revoke every connection made through it
options:
-h, --help show this help message and exit
lhbox admin client add
usage: lhbox admin client add [-h] --name NAME --redirect-uri URI
[--client-id CLIENT_ID]
[--client-uri CLIENT_URI] [--confidential]
[--login EMAIL]
options:
-h, --help show this help message and exit
--name NAME
--redirect-uri URI https, loopback http or a native app's scheme;
repeatable
--client-id CLIENT_ID
the client's own fixed id (default: one is generated)
--client-uri CLIENT_URI
its home page (https), shown on the consent page
--confidential a client with a secret (HTTP Basic or
client_secret_post)
--login EMAIL as this install administrator: password and
authenticator code asked on the terminal, the session
ended after the command (default: LHBOX_OPERATOR_KEY)
lhbox admin client list
usage: lhbox admin client list [-h] [--all] [--login EMAIL]
options:
-h, --help show this help message and exit
--all the self-registered (dynamic) clients too
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin client disable
usage: lhbox admin client disable [-h] [--login EMAIL] client_id
positional arguments:
client_id
options:
-h, --help show this help message and exit
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin client enable
usage: lhbox admin client enable [-h] [--login EMAIL] client_id
positional arguments:
client_id
options:
-h, --help show this help message and exit
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin client remove
usage: lhbox admin client remove [-h] [--yes] [--login EMAIL] client_id
positional arguments:
client_id
options:
-h, --help show this help message and exit
--yes do not ask: removing the client cannot be undone. Asked on a
terminal; without one (or with --json) --yes will be required
from the next minor version of lhbox (a warning today)
--login EMAIL as this install administrator: password and authenticator
code asked on the terminal, the session ended after the
command (default: LHBOX_OPERATOR_KEY)
lhbox admin news
usage: lhbox admin news [-h] {status,create,show,edit,test,send} ...
positional arguments:
{status,create,show,edit,test,send}
status subscribers, withdrawals, whether sending is
configured, the issues
create a draft issue from a Markdown file
show one issue: state, delivery counts, text
edit change a draft's subject or text (needs a new test)
test send the issue to one address, subject prefixed [test]
send send the issue to every subscriber, once (needs a test
after the last edit); on an issue already sending,
resumes a send the relay paused
options:
-h, --help show this help message and exit
lhbox admin news status
usage: lhbox admin news status [-h]
options:
-h, --help show this help message and exit
lhbox admin news create
usage: lhbox admin news create [-h] --subject SUBJECT --file FILE
options:
-h, --help show this help message and exit
--subject SUBJECT
--file FILE Markdown file, or - for stdin
lhbox admin news show
usage: lhbox admin news show [-h] issue
positional arguments:
issue
options:
-h, --help show this help message and exit
lhbox admin news edit
usage: lhbox admin news edit [-h] [--subject SUBJECT] [--file FILE] issue
positional arguments:
issue
options:
-h, --help show this help message and exit
--subject SUBJECT
--file FILE Markdown file, or - for stdin
lhbox admin news test
usage: lhbox admin news test [-h] --to TO issue
positional arguments:
issue
options:
-h, --help show this help message and exit
--to TO
lhbox admin news send
usage: lhbox admin news send [-h] [--retry-failed] issue
positional arguments:
issue
options:
-h, --help show this help message and exit
--retry-failed also put the rows the relay refused for good back in line
lhbox project
usage: lhbox project [-h] {create,list} ...
positional arguments:
{create,list}
options:
-h, --help show this help message and exit
lhbox project create
usage: lhbox project create [-h] --name NAME [--provider PROVIDER]
[--region REGION]
[--idempotency-key IDEMPOTENCY_KEY]
options:
-h, --help show this help message and exit
--name NAME
--provider PROVIDER default: the deployment's own
--region REGION default: the deployment's own placement
--idempotency-key IDEMPOTENCY_KEY
any string; safe to reuse when retrying the same call
lhbox project list
usage: lhbox project list [-h]
options:
-h, --help show this help message and exit
lhbox catalog
usage: lhbox catalog [-h]
{create,list,get,measure,update,rotate,grants,grant,revoke,delete,restore,publish,unpublish,public-url,uploader,lifecycle,agents-md} ...
positional arguments:
{create,list,get,measure,update,rotate,grants,grant,revoke,delete,restore,publish,unpublish,public-url,uploader,lifecycle,agents-md}
create a new catalog: `lhbox catalog create <name>` puts it
in the organisation's default project; usually
seconds, up to ~2 minutes on a busy host, progress on
stderr
list every catalog you can see: name, id (warehouse_id in
the API), your level, project, status and the format
version its new tables get, side by side
get one catalog by name or id: the row of `catalog list`
plus its bucket, storage figure, default format
version and status (ready, or provisioning while its
storage has not accepted a write yet)
measure measure a catalog's storage now (bytes and objects of
its two buckets) instead of waiting for the next
reading (every 5 minutes); once a minute per catalog
update change a catalog's settings: the format version
LakehouseBox gives its new tables (admin, or a write
holder) and its tier policy, hot or cold (admin)
rotate replace the catalog's credentials (read/write and
read-only): `lhbox catalog rotate <name>`; the old
keys and their tokens are refused at once, vended
storage sessions run out within an hour; slow
(identity writes at the store), progress on stderr
grants who holds read or write on a catalog, and how (admin,
membership, creator, explicit): `lhbox catalog grants
<name>`
grant grant read or write on a catalog to a member or a
token (admin, or a write holder up to write): `lhbox
catalog grant <name> --principal <id> --level
read|write`
revoke revoke a grant (admin, or whoever made it): `lhbox
catalog revoke <name> --principal <id>`; revoking a
member's membership read records level none
delete delete a catalog (admin): `lhbox catalog delete <name>
--confirm <name>` (or --yes); it stays restorable for
the service's grace period (`lhbox catalog restore`),
then tables, buckets and identities are purged;
--purge deletes for good at once
restore restore a catalog in its deletion grace period
(admin): `lhbox catalog restore <id or name>`; same
tables and buckets, a new key pair if the old one was
revoked, comes back private
publish make a catalog PUBLIC (admin, the name typed out):
every data file and every retained metadata.json
becomes readable by anyone without credentials, and
anyone can attach it read-only by name
(<handle>/<catalog>), no account needed; its bytes
count against the public allowance (5 GB; 50 GB once
reviewed)
unpublish make a public catalog private again (admin): `lhbox
catalog unpublish <name>`; anonymous reads are refused
at once
public-url the public URL of a catalog and the no-credentials
DuckDB recipe: `lhbox catalog public-url <name>`; with
--table, that table's current metadata URL (the thing
to paste into iceberg_scan)
uploader write-only credentials for the catalog's blob bucket:
a relay, a camera, a field device or a partner
dropping files under one prefix, never able to read,
list (unless asked) or touch tables
(https://lakehousebox.com/docs/uploaders/)
lifecycle expiry rules on the catalog's blob bucket: delete
camera/ after 90 days, abort stale multipart uploads;
never on tables
(https://lakehousebox.com/docs/lifecycle/)
agents-md the catalog's AGENTS.md: context for agents at the
root of its file bucket (read, --set, --remove)
options:
-h, --help show this help message and exit
lhbox catalog create
usage: lhbox catalog create [-h] [--project PROJECT]
[--default-format-version {2,3}] [--no-wait]
[--idempotency-key IDEMPOTENCY_KEY]
[CATALOG]
POST /v1/catalogs. `lhbox catalog create <name>` (lowercase letters, digits,
_; a letter first). Usually done in a few seconds; on a busy host it can take
up to two minutes: a line every ~10 s says so; a timeout is exit 5 with the
remedy to check `catalog list` first. Should the server answer 202 status:
provisioning, the CLI polls until ready (--no-wait returns at once).
positional arguments:
CATALOG the catalog's name
options:
-h, --help show this help message and exit
--project PROJECT project_id (UUID) or project name; list both with:
lhbox project list (default: the organisation's
default project)
--default-format-version {2,3}
the Iceberg format version (2 or 3) LakehouseBox gives
tables IT creates in this catalog: table create
without --format-version and table import; an engine's
own CREATE TABLE is honoured as sent (default 2)
--no-wait do not poll a 202 provisioning answer until ready
--idempotency-key IDEMPOTENCY_KEY
any string; safe to reuse when retrying the same call
lhbox catalog list
usage: lhbox catalog list [-h] [--project PROJECT]
options:
-h, --help show this help message and exit
--project PROJECT project_id (UUID) or project name; list both with: lhbox
project list (default: every project you can see)
lhbox catalog get
usage: lhbox catalog get [-h] [CATALOG]
GET /v1/catalogs/{id}. `lhbox catalog get <name>`; without a name, your only
catalog.
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list shows both);
default: your only catalog (with several, name one)
options:
-h, --help show this help message and exit
lhbox catalog measure
usage: lhbox catalog measure [-h] [CATALOG]
POST /v1/catalogs/{id}/measure. `lhbox catalog measure <name>`. Reads the
store's own figure for the catalog's table bucket and blob bucket and stores
it; GET /v1/usage and `catalog list` show it until the next reading
(stale_after). Read level suffices.
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list shows both);
default: your only catalog (with several, name one)
options:
-h, --help show this help message and exit
lhbox catalog update
usage: lhbox catalog update [-h] [--default-format-version {2,3}]
[--tier {hot,cold}]
[--snapshot-retention-days N|default]
[--edge-reads {on,off}] [--virtual-host {on,off}]
[CATALOG]
PATCH /v1/catalogs/{id}. `lhbox catalog update <name> --default-format-version
2|3` and/or `--tier hot|cold` (the catalog is always named). the Iceberg
format version (2 or 3) LakehouseBox gives tables IT creates in this catalog:
table create without --format-version and table import; an engine's own CREATE
TABLE is honoured as sent. Tables an engine creates itself keep the version
the engine chose; existing tables are unchanged. GET /v1/config/formats (no
login) lists which engines write each version. Tier policy: cold (the default)
lets volumes untouched for the quiet period move to cold storage, where a
table's first read is slower; hot never moves them. The sample catalog and
public catalogs stay hot.
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list
shows both)
options:
-h, --help show this help message and exit
--default-format-version {2,3}
2 (written by every engine) or 3 (geometry/geography
columns; not every engine writes it yet)
--tier {hot,cold} hot: never moved to cold storage; cold: quiet volumes
move to cold storage (admin only)
--snapshot-retention-days N|default
keep every snapshot of the last N days (1-90; at most
500 per table) so tables can be read or rolled back to
any point in them; `default` returns to 20 snapshots /
7 days, whichever is tighter (admin only; retained
snapshots count as storage)
--edge-reads {on,off}
edge caching for this private catalog: its readers'
lhbox duckdb sessions read through our European CDN
(an admin; /docs/edge-caching/)
--virtual-host {on,off}
serve https://<bucket>.s3.<domain> for this catalog
(Snowflake needs it; an admin asking for its recipe
turns it on). Its TLS certificate lists the bucket
names in the public Certificate Transparency logs
(admin only; off by default, public catalogs included)
lhbox catalog rotate
usage: lhbox catalog rotate [-h] [--yes [NAME]] [--show-secrets] [CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list shows
both)
options:
-h, --help show this help message and exit
--yes [NAME] do not ask: a rotation cannot be undone. Asked on a
terminal; without one (or with --json) --yes will be
required from the next minor version of lhbox (a warning
today)
--show-secrets print the two new secrets; without it they are ********
(fetch one later with `lhbox connect --show-secrets`)
lhbox catalog grants
usage: lhbox catalog grants [-h] [CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list shows both);
default: your only catalog (with several, name one)
options:
-h, --help show this help message and exit
lhbox catalog grant
usage: lhbox catalog grant [-h] --principal PRINCIPAL --level {read,write}
[--namespace NS]
[CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list
shows both)
options:
-h, --help show this help message and exit
--principal PRINCIPAL
principal_id of the member or token (lhbox org members
/ token list)
--level {read,write}
--namespace NS grant only this namespace of the catalog, to a
principal holding nothing on the catalog itself (an
enterprise feature; admins only); it gets its own
credentials, confined to the namespace
lhbox catalog revoke
usage: lhbox catalog revoke [-h] --principal PRINCIPAL [--namespace NS]
[--yes [NAME]]
[CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list
shows both)
options:
-h, --help show this help message and exit
--principal PRINCIPAL
--namespace NS revoke the grant on this one namespace (the catalog-
level grant stays as it is)
--yes [NAME] do not ask: the revocation cannot be undone. Asked on
a terminal; without one (or with --json) --yes will be
required from the next minor version of lhbox (a
warning today)
lhbox catalog delete
usage: lhbox catalog delete [-h] [--confirm [NAME]] [--purge] [CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list
shows both)
options:
-h, --help show this help message and exit
--confirm, --yes [NAME]
the catalog's name, exactly (a bare --yes confirms the
NAME given positionally); this cannot be undone
--purge skip the grace period: purge tables, buckets and
identities now, irreversibly (also ends the grace
period of a catalog already deleted; name it with
--confirm <name>)
lhbox catalog restore
usage: lhbox catalog restore [-h] [CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list shows both)
options:
-h, --help show this help message and exit
lhbox catalog publish
usage: lhbox catalog publish [-h] [--confirm [NAME]] [--files PREFIX |
--no-files]
[CATALOG]
Puts one plain S3 bucket policy on the catalog's bucket (anyone may GET and
LIST its objects, unsigned; writes and deletes stay refused). Public means the
whole catalog: every table, every data file, every retained metadata.json
including the table's history, and the list of object names. Readers with no
account attach the whole catalog by name, read-only, through the public
catalog: ATTACH '<handle>/<catalog>' (TYPE iceberg, ENDPOINT <the public
catalog>, AUTHORIZATION_TYPE 'none'), its answers and files served from the
CDN; `lhbox catalog public-url <name>` prints that line, and a table's
metadata URL with --table. Any account also attaches it by name in lhbox
duckdb (a shared public-reader key: catalog reads only, replaced at
unpublish). The catalog's FILE bucket stays private unless --files <prefix>/
names one folder of it to publish too (GET, range requests and listing under
that folder only). Undo with: lhbox catalog unpublish <name> (tables and
files). `lhbox catalog publish <name> --confirm <name> [--files public/]` (or
--yes).
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list
shows both)
options:
-h, --help show this help message and exit
--confirm, --yes [NAME]
the catalog's name, exactly (a bare --yes confirms the
NAME given positionally); this cannot be undone
--files PREFIX also publish ONE folder of the catalog's file bucket,
e.g. public/ (ends in /): anyone may GET its data
files (range requests included; photos only for
organisations approved for it) and list the files
under it at public_files_url; the rest of the file
bucket stays private. Publishing again with another
folder replaces it; without --files the published
folder, if any, stays as it is
--no-files stop publishing the file bucket's folder; the
catalog's tables stay public
lhbox catalog unpublish
usage: lhbox catalog unpublish [-h] [CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list shows both)
options:
-h, --help show this help message and exit
lhbox catalog public-url
usage: lhbox catalog public-url [-h] [--table NS.TABLE] [CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list shows
both); default: your only catalog (with several, name one)
options:
-h, --help show this help message and exit
--table NS.TABLE <namespace>.<table>: also print its current metadata_url
and version_hint_url
lhbox catalog uploader
usage: lhbox catalog uploader [-h] {create,list,revoke} ...
positional arguments:
{create,list,revoke}
create a new uploader: a key that can only upload under its
prefix of the catalog's file bucket; the secret is
shown once (--show-key) or written to a 0600 file
(--save)
list the catalog's uploaders without their secrets (never
stored); --all includes revoked ones
revoke revoke an uploader by id or name: its key is deleted
at the store first and refused at once (revoke, then
create, to replace a lost or leaked secret)
options:
-h, --help show this help message and exit
lhbox catalog uploader create
usage: lhbox catalog uploader create [-h] --name NAME [--prefix PREFIX]
[--list] [--expires-at ISO8601]
[--protocols LIST] [--save FILE]
[--show-key]
[CATALOG]
POST /v1/catalogs/{id}/uploaders. Write level. Without --save or --show-key
the command stops before calling the API: no identity is created whose secret
nobody received. The uploader cannot read what it uploads; consume the files
from the catalog side with the catalog's own credential (lhbox connect).
`lhbox catalog uploader create <catalog> --name ...`.
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list
shows both); default: your only catalog (with several,
name one)
options:
-h, --help show this help message and exit
--name NAME who uploads: relay, camera-north, partner-acme (a
lowercase letter, then lowercase, digits, _ -)
--prefix PREFIX key prefix the credential is confined to, e.g.
photos/2026/ (default: the whole blob bucket)
--list also allow ListBucket under the prefix (an s3:prefix
condition: the device must list with ?prefix=<its
prefix>); without it the uploader cannot list at all
--expires-at ISO8601 shown as the status 'expired' after that time; within
the hour after it the key is removed and a drop-box
login refused, as if revoked
--protocols LIST also a drop-box login for devices that speak only FTP:
a comma list of ftps, sftp, ftp (plain, unencrypted);
the login and password are generated and shown once
like the secret
--save FILE write the pair, endpoint, bucket and prefix to FILE
(mode 0600) and do not print the secret
--show-key print the secret once (as well as saving it, with
--save)
lhbox catalog uploader list
usage: lhbox catalog uploader list [-h] [--all] [CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list shows both);
default: your only catalog (with several, name one)
options:
-h, --help show this help message and exit
--all include revoked uploaders
lhbox catalog uploader revoke
usage: lhbox catalog uploader revoke [-h] --uploader UPLOADER [--yes [NAME]]
[CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list shows
both); default: your only catalog (with several, name
one)
options:
-h, --help show this help message and exit
--uploader UPLOADER the uploader_id or the name (lhbox catalog uploader
list)
--yes [NAME] do not ask: the revocation cannot be undone. Asked on a
terminal; without one (or with --json) --yes will be
required from the next minor version of lhbox (a
warning today)
lhbox catalog lifecycle
usage: lhbox catalog lifecycle [-h] {set,get,clear} ...
positional arguments:
{set,get,clear}
set add or replace ONE rule (by id; the default id is the
prefix), keeping the others
get the rules configured on the blob bucket, including any set
with boto3
clear remove every lifecycle rule from the blob bucket
options:
-h, --help show this help message and exit
lhbox catalog lifecycle set
usage: lhbox catalog lifecycle set [-h] [--prefix PREFIX] [--expire-days N]
[--expire-date YYYY-MM-DD]
[--abort-multipart-days N] [--id ID]
[--disabled] [--replace-all]
[CATALOG]
PUT /v1/catalogs/{id}/lifecycle. Write level. Reads the current rules first
and re-puts them with this one (the S3 API replaces the whole document);
--replace-all keeps none. Objects are deleted at the store's next daily pass
after they are due (up to 24 h late), permanently. Transitions do not exist
here (no storage classes); --expire-days 0 is refused (the store would accept
it and never fire it).
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list
shows both); default: your only catalog (with several,
name one)
options:
-h, --help show this help message and exit
--prefix PREFIX key prefix the rule applies to, e.g. camera/ (default:
the whole blob bucket)
--expire-days N delete objects N days (24 h each) after their last
write; at least 1
--expire-date YYYY-MM-DD
delete every matching object at the first pass on or
after this date (a past date: the next pass)
--abort-multipart-days N
abort multipart uploads not completed N days after
they began
--id ID the rule's id (default: the prefix without slashes, or
'bucket'); a rule with the same id is replaced
--disabled store the rule disabled (kept, not evaluated)
--replace-all drop every other rule, this one alone remains
lhbox catalog lifecycle get
usage: lhbox catalog lifecycle get [-h] [--xml] [CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list shows both);
default: your only catalog (with several, name one)
options:
-h, --help show this help message and exit
--xml print the stored S3 LifecycleConfiguration document instead of
the table
lhbox catalog lifecycle clear
usage: lhbox catalog lifecycle clear [-h] [--yes [NAME]] [CATALOG]
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list shows both);
default: your only catalog (with several, name one)
options:
-h, --help show this help message and exit
--yes [NAME] do not ask: removing every rule cannot be undone. Asked on a
terminal; without one (or with --json) --yes will be required
from the next minor version of lhbox (a warning today)
lhbox catalog agents-md
usage: lhbox catalog agents-md [-h] [--set FILE] [--base-sha256 SHA256]
[--remove] [--raw]
[CATALOG]
GET, PUT or DELETE /v1/catalogs/{id}/agents-md. The object AGENTS.md at the
root of the catalog's file bucket; readable anonymously while the catalog is
public. Reading needs read level; --set and --remove need write level. At most
64 KB of UTF-8; every write is audited.
positional arguments:
CATALOG the catalog's name (or its id; lhbox catalog list
shows both); default: your only catalog (with several,
name one)
options:
-h, --help show this help message and exit
--set FILE write FILE (or - for stdin) as the catalog's AGENTS.md
--base-sha256 SHA256 with --set: write only if the current AGENTS.md has
this sha256 (none: only if there is none yet)
--remove delete the catalog's AGENTS.md
--raw print the text alone, no header
lhbox file
usage: lhbox file [-h] {cp,sync,put,get,ls,rm} ...
positional arguments:
{cp,sync,put,get,ls,rm}
cp copy files in or out, as aws s3 cp: `lhbox file cp
./tiles.pmtiles lhbox://garden/public/`, `lhbox file
cp lhbox://garden/data/a.csv .`, `lhbox file cp
./exports lhbox://garden/exports/ --recursive`
sync copy only what changed, as aws s3 sync: `lhbox file
sync ./exports lhbox://garden/exports/` (or
lhbox://garden/exports/ ./exports); never deletes
put upload a file, or a directory recursively, to the
catalog's files: `lhbox file put tiles.pmtiles public/
--cache-control "public, max-age=86400"` (the same as
`lhbox file cp tiles.pmtiles
lhbox://<catalog>/public/`)
get download one file: `lhbox file get data/a.csv [DEST]`
(-o PATH works too; - for stdout)
ls list the catalog's files: `lhbox file ls public/` or
`lhbox file ls lhbox://garden/public/` (folders first;
--recursive for every key)
rm delete files: `lhbox file rm data/a.csv` or `lhbox
file rm lhbox://garden/data/ --recursive` (asks only
on a terminal)
options:
-h, --help show this help message and exit
lhbox file cp
usage: lhbox file cp [-h] [--catalog CATALOG] [--recursive] [--exclude GLOB]
[--include GLOB] [--content-type CONTENT_TYPE]
[--cache-control CACHE_CONTROL]
[--content-disposition CONTENT_DISPOSITION]
[--content-language CONTENT_LANGUAGE] [--meta NAME=VALUE]
[--part-size MIB] [--parallel N] [--skip-refused]
[--skip-unchanged] [--dry-run] [--progress] [--no-edge]
SRC DST
Exactly one side is the catalog, written lhbox://<catalog>/<key or prefix>
(the catalog's name or id); the other is a local path. Up (local -> catalog)
is `file put`: one file goes to the key, or under it when the key ends with /
(or is empty); a directory's contents go under the prefix, relative paths
kept, with or without --recursive (hidden files skipped). Down (catalog ->
local) is `file get` for one key (DEST a file, a directory, or - for stdout);
a key ending with /, or --recursive, downloads everything under it as a folder
into the DEST directory. s3:// URLs are not taken: the bucket is the
platform's name. --exclude/--include filter by path relative to the source.
positional arguments:
SRC a local file or directory, or lhbox://<catalog>/<key
or prefix>
DST lhbox://<catalog>/<key or prefix>, or a local path
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one); a
lhbox://<catalog>/… address names it too
--recursive a folder: download everything under the prefix (an
upload of a directory is recursive either way)
--exclude GLOB leave out paths matching GLOB, relative to the source
folder or prefix (* crosses /); --exclude and
--include apply in order and the last match wins, as
in aws s3
--include GLOB take back paths matching GLOB after an --exclude (e.g.
--exclude '*' --include '*.csv')
--content-type CONTENT_TYPE
the media type (default: from the file's name; a
published file is served with the type its name says
either way)
--cache-control CACHE_CONTROL
the file's Cache-Control, kept and passed to readers
by the CDN of published files (default there: public,
max-age=3600)
--content-disposition CONTENT_DISPOSITION
e.g. attachment; filename="report.csv"
--content-language CONTENT_LANGUAGE
e.g. en, or es-ES
--meta, --metadata NAME=VALUE
custom metadata, sent as x-amz-meta-NAME: name=value,
several as a=1,b=2 (aws's shorthand) or JSON {"a":
"1"}; repeat the flag for more
--part-size MIB files larger than this go up in parts of this size
(MiB, at least 5; default 64)
--parallel N parts uploaded at once (default 4)
--skip-refused upload what the catalog's files accept and list the
rest as skipped (web pages, scripts, programs, and
files over 100 kB of a type not on the list); without
it such a file stops the upload before anything is
sent
--skip-unchanged skip files whose stored copy has the same size and
ETag (what sync does)
--dry-run, --dryrun list what would be copied and send nothing
--progress progress lines on stderr even when it is not a
terminal
--no-edge download from the storage even where the catalog has
edge caching (a network that cannot reach the CDN)
lhbox file sync
usage: lhbox file sync [-h] [--catalog CATALOG] [--exclude GLOB]
[--include GLOB] [--content-type CONTENT_TYPE]
[--cache-control CACHE_CONTROL]
[--content-disposition CONTENT_DISPOSITION]
[--content-language CONTENT_LANGUAGE]
[--meta NAME=VALUE] [--part-size MIB] [--parallel N]
[--skip-refused] [--dry-run] [--progress] [--no-edge]
SRC DST
Recursive cp that skips every file whose other copy has the same size and ETag
(its content: a second run sends nothing); any other file is copied over, so
the source wins (a file edited on the destination side is replaced, whatever
its age). Up: the directory's contents go under the prefix. Down: everything
under the prefix into the directory, relative paths kept. A key ending with /
or not, SRC/DST under lhbox:// is a folder. Nothing is ever deleted on either
side (no --delete): lhbox file rm --recursive removes files from the catalog.
positional arguments:
SRC a local directory, or lhbox://<catalog>/<prefix>
DST lhbox://<catalog>/<prefix>, or a local directory
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one); a
lhbox://<catalog>/… address names it too
--exclude GLOB leave out paths matching GLOB, relative to the source
folder or prefix (* crosses /); --exclude and
--include apply in order and the last match wins, as
in aws s3
--include GLOB take back paths matching GLOB after an --exclude (e.g.
--exclude '*' --include '*.csv')
--content-type CONTENT_TYPE
the media type (default: from the file's name; a
published file is served with the type its name says
either way)
--cache-control CACHE_CONTROL
the file's Cache-Control, kept and passed to readers
by the CDN of published files (default there: public,
max-age=3600)
--content-disposition CONTENT_DISPOSITION
e.g. attachment; filename="report.csv"
--content-language CONTENT_LANGUAGE
e.g. en, or es-ES
--meta, --metadata NAME=VALUE
custom metadata, sent as x-amz-meta-NAME: name=value,
several as a=1,b=2 (aws's shorthand) or JSON {"a":
"1"}; repeat the flag for more
--part-size MIB files larger than this go up in parts of this size
(MiB, at least 5; default 64)
--parallel N parts uploaded at once (default 4)
--skip-refused upload what the catalog's files accept and list the
rest as skipped (web pages, scripts, programs, and
files over 100 kB of a type not on the list); without
it such a file stops the upload before anything is
sent
--dry-run, --dryrun list what would be copied and send nothing
--progress progress lines on stderr even when it is not a
terminal
--no-edge download from the storage even where the catalog has
edge caching (a network that cannot reach the CDN)
lhbox file put
usage: lhbox file put [-h] [--catalog CATALOG] [--content-type CONTENT_TYPE]
[--cache-control CACHE_CONTROL]
[--content-disposition CONTENT_DISPOSITION]
[--content-language CONTENT_LANGUAGE]
[--meta NAME=VALUE] [--part-size MIB] [--parallel N]
[--skip-refused] [--skip-unchanged] [--recursive]
[--exclude GLOB] [--include GLOB] [--dry-run]
[--progress]
PATH [KEY]
Uploads through URLs the API signs (POST /v1/catalogs/{id}/blobs/upload; a
file over --part-size goes up in parts, in parallel, each retried on its own:
POST …/blobs/multipart). Write level. KEY: the key for one file (KEY ending in
/ = KEY + the file's name; default: its name), or the prefix for a directory
(default: the directory's name), under which relative paths are kept; hidden
files are skipped. KEY may be lhbox://<catalog>/<key>. Prints JSON {uploaded,
skipped, count, bytes, seconds} when piped. Keys starting with _ at the root
and AGENTS.md are the platform's.
positional arguments:
PATH a local file or directory
KEY the key (one file) or prefix (a directory) in the
catalog's files
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one); a
lhbox://<catalog>/… address names it too
--content-type CONTENT_TYPE
the media type (default: from the file's name; a
published file is served with the type its name says
either way)
--cache-control CACHE_CONTROL
the file's Cache-Control, kept and passed to readers
by the CDN of published files (default there: public,
max-age=3600)
--content-disposition CONTENT_DISPOSITION
e.g. attachment; filename="report.csv"
--content-language CONTENT_LANGUAGE
e.g. en, or es-ES
--meta, --metadata NAME=VALUE
custom metadata, sent as x-amz-meta-NAME: name=value,
several as a=1,b=2 (aws's shorthand) or JSON {"a":
"1"}; repeat the flag for more
--part-size MIB files larger than this go up in parts of this size
(MiB, at least 5; default 64)
--parallel N parts uploaded at once (default 4)
--skip-refused upload what the catalog's files accept and list the
rest as skipped (web pages, scripts, programs, and
files over 100 kB of a type not on the list); without
it such a file stops the upload before anything is
sent
--skip-unchanged skip files whose stored copy has the same size and
ETag
--recursive accepted as in aws s3; a directory is uploaded
recursively with or without it
--exclude GLOB leave out paths matching GLOB, relative to the source
folder or prefix (* crosses /); --exclude and
--include apply in order and the last match wins, as
in aws s3
--include GLOB take back paths matching GLOB after an --exclude (e.g.
--exclude '*' --include '*.csv')
--dry-run, --dryrun list what would be uploaded and send nothing
--progress progress lines on stderr even when it is not a
terminal
lhbox file get
usage: lhbox file get [-h] [--catalog CATALOG] [-o PATH] [--progress]
[--no-edge]
KEY [DEST]
positional arguments:
KEY the file's key, or lhbox://<catalog>/<key>
DEST where to write it (a directory keeps the name; - =
stdout; default: the file's name here)
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list; default: your only catalog
(with several, name one); a lhbox://<catalog>/… address
names it too
-o, --output PATH the same as DEST
--progress progress lines on stderr even when it is not a terminal
--no-edge download from the storage even where the catalog has edge
caching (a network that cannot reach the CDN)
lhbox file ls
usage: lhbox file ls [-h] [--catalog CATALOG] [--recursive] [PREFIX]
positional arguments:
PREFIX list under this prefix (end a folder with /);
lhbox://<catalog>/<prefix> works too
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list; default: your only catalog
(with several, name one); a lhbox://<catalog>/… address
names it too
--recursive every key under the prefix, no folders
lhbox file rm
usage: lhbox file rm [-h] [--catalog CATALOG] [--recursive] [--exclude GLOB]
[--include GLOB] [--dry-run] [--yes]
KEY [KEY ...]
positional arguments:
KEY the keys (or prefixes, with --recursive);
lhbox://<catalog>/<key> works too
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one); a lhbox://<catalog>/…
address names it too
--recursive delete every file under each KEY taken as a folder
--exclude GLOB leave out paths matching GLOB, relative to the source
folder or prefix (* crosses /); --exclude and --include
apply in order and the last match wins, as in aws s3
--include GLOB take back paths matching GLOB after an --exclude (e.g.
--exclude '*' --include '*.csv')
--dry-run, --dryrun list what would be deleted and delete nothing
--yes do not ask: the deletion cannot be undone. Asked [y/N]
only when a person is at a terminal; a script or an
agent (no terminal) deletes without it, as aws s3 rm
does
lhbox namespace
usage: lhbox namespace [-h] {list,delete} ...
positional arguments:
{list,delete}
delete drop one EMPTY namespace: `lhbox namespace delete <ns>
--catalog <name>`; asks unless --yes; a namespace with tables
is refused (drop them first with `table delete`), a public
catalog wants --force
options:
-h, --help show this help message and exit
lhbox namespace list
usage: lhbox namespace list [-h] [--catalog CATALOG]
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list; default: your only catalog
(with several, name one)
lhbox namespace delete
usage: lhbox namespace delete [-h] [--catalog CATALOG] [--yes] [--force]
NAMESPACE
positional arguments:
NAMESPACE
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list; default: your only catalog
(with several, name one)
--yes do not ask (required without a terminal or with --json);
the drop cannot be undone
--force drop it even in a PUBLIC catalog (an admin only), whose
public URL then stops serving it
lhbox table
usage: lhbox table [-h]
{list,get,delete,create,import,add-files,set-properties,document} ...
positional arguments:
{list,get,delete,create,import,add-files,set-properties,document}
list every table in a catalog, or in one namespace
get schema, real format-version, snapshot and row count:
`lhbox table get <catalog>.<ns>.<table>` (or
<ns>.<table> --catalog <name>)
delete drop one table and its data files: `lhbox table delete
<ns>.<table> --catalog <name>`; asks unless --yes; a
public catalog wants --force; cannot be undone
create a new table from --column specs or a schema file:
`lhbox table create <catalog>.<ns>.<table> --column
id:long:required …` (or <ns>.<table> --catalog <name>)
import import Parquet or CSV files into a table as ONE
commit: DuckDB on this machine runs one CREATE TABLE …
AS SELECT (or INSERT INTO … SELECT) over
read_parquet([all the files]) or read_csv([…]);
GeoParquet metadata is kept as geo.* table properties
add-files register Parquet files staged in the catalog's files
(lhbox file put) in a table AS THEY ARE: copied in the
store into the table, ONE commit (no read of the rows,
no sort, no rewrite)
set-properties set or remove table properties (one commit through the
catalog): --property key=value …, --remove key …
document describe a table and its columns for the people and
agents who read it (one commit): --doc TEXT, --column
name=TEXT …, --from FILE
options:
-h, --help show this help message and exit
lhbox table list
usage: lhbox table list [-h] [--catalog CATALOG] [--namespace NAMESPACE]
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one)
--namespace NAMESPACE
restrict to one namespace
lhbox table get
usage: lhbox table get [-h] [--catalog CATALOG] [[CATALOG.]NAMESPACE.TABLE]
positional arguments:
[CATALOG.]NAMESPACE.TABLE
the table: catalog.namespace.table, or namespace.table
with --catalog
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list
lhbox table delete
usage: lhbox table delete [-h] [--catalog CATALOG] [--yes] [--force]
[CATALOG.]NAMESPACE.TABLE
positional arguments:
[CATALOG.]NAMESPACE.TABLE
the table: catalog.namespace.table, or namespace.table
with --catalog
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one)
--yes do not ask (required without a terminal or with
--json); the drop cannot be undone
--force drop it even in a PUBLIC catalog (an admin only),
whose public URL then stops serving it
lhbox table create
usage: lhbox table create [-h] [--catalog CATALOG] [--column COLUMN]
[--schema-file SCHEMA_FILE] [--format-version {2,3}]
[--partition TRANSFORM(COLUMN)]
[--idempotency-key IDEMPOTENCY_KEY]
[[CATALOG.]NAMESPACE.TABLE]
positional arguments:
[CATALOG.]NAMESPACE.TABLE
the table: catalog.namespace.table, or namespace.table
with --catalog
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list
--column COLUMN name:type[:required][:identifier], repeatable
--schema-file SCHEMA_FILE
a full Iceberg schema as JSON, e.g.
{"type":"struct","schema-id":0,"identifier-field-ids":
[1],"fields":[{"id":1,"name":"id","required":true,"typ
e":"long"}]}
--format-version {2,3}
2 or 3; default: the catalog's default_format_version
(2 unless changed with lhbox catalog update). 3 holds
geometry/geography columns; GET /v1/config/formats
says which engines write each. The version actually
used is printed on stderr.
--partition TRANSFORM(COLUMN)
partition the table, repeatable: day(ts), hour(ts),
month(ts), year(ts), a column name (identity),
bucket[16](id), truncate[4](code). A sink feeds a
format-version 3 table partitioned by time (day(<time
column>) for positions: one day per partition, ordered
in space by the maintenance). Writing it from DuckDB
yourself: SET TimeZone = 'UTC' first (DuckDB 1.5.6
files a timestamptz under its day in the session's
zone)
--idempotency-key IDEMPOTENCY_KEY
any string; safe to reuse when retrying the same call
lhbox table import
usage: lhbox table import [-h] [--catalog CATALOG]
[--format {auto,csv,parquet}] [--python PATH]
[--full] [--mode {create,append}]
[--format-version {2,3}] [--order-by COLUMN |
--no-order] [--no-version-check]
[--row-group-rows N]
[--idempotency-key IDEMPOTENCY_KEY]
[[CATALOG.]NAMESPACE.TABLE]
FILE_OR_GLOB [FILE_OR_GLOB ...]
Imports many files in one statement, so one Iceberg commit however many files
(44 files one by one were 44 commits and 44 waits). Parquet (.parquet) or CSV
(.csv, .csv.gz, .tsv; header and types auto-detected by DuckDB's read_csv) by
extension, or --format; one format per run. Globs are expanded here; s3:// and
https:// sources are read by DuckDB's httpfs. DuckDB is the python module of
the interpreter running lhbox when importable (--python PATH or LHBOX_PYTHON
names another interpreter's), else the `duckdb` binary on PATH (1.5.5 or
newer). The catalog credential goes recipe -> DuckDB and is never printed. A
format-version 3 table (--format-version 3, or the catalog's default) is
created through the API with the schema DuckDB inferred (geometry columns
typed), then filled with one INSERT; a format-version 2 table is created empty
by DuckDB with its own column types (geometry as BLOB, written as WKB), then
filled with one INSERT. Every table gets the Iceberg property write.target-
file-size-bytes = 134217728 (128 MiB) before its first row, and the rows are
written ORDER BY the first DATE/TIMESTAMP column (--order-by names another,
--no-order keeps the files' order) with preserve_insertion_order on, so a
query on that column skips whole files (docs/performance). Files with a
geometry column are written in Hilbert order of the primary geometry by ONE
writer thread, with 8 MB row groups for that INSERT (the property is removed
after it); the thread and row-group settings are temporary DuckDB 1.5
workarounds (the report's spatial_layout says so). Query such a table with
`geometry && <shape> AND ST_Intersects(geometry, <shape>)`: DuckDB skips files
and row groups for && only. When a file carries GeoParquet metadata, the table
gets geo.encoding, geo.crs, geo.columns, geo.primary_column (docs/engines).
positional arguments:
[CATALOG.]NAMESPACE.TABLE
the table, [catalog.]namespace.table (the namespace is
created on demand); then the files
FILE_OR_GLOB Parquet or CSV files, globs (quote them:
'data/**/*.parquet') or URLs (s3://…, https://…)
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one)
--format {auto,csv,parquet}
what the files are (default auto: by extension; an
extension nobody recognises is a usage error)
--python PATH the interpreter whose duckdb module runs the import
(env LHBOX_PYTHON; default: the python running lhbox,
else the duckdb binary on PATH)
--full include the generated SQL statement (every column and
file) in the JSON
--mode {create,append}
create (default; the table must not exist: 409 -> exit
2 otherwise) or append into an existing table (columns
matched by name; missing ones become NULL)
--format-version {2,3}
for --mode create: 2 or 3; default the catalog's
default_format_version. 3 goes through the API (POST
/v1/tables with the inferred schema, then INSERT); 2
is DuckDB's own CREATE TABLE, then INSERT
--order-by COLUMN write the rows sorted by this column (ORDER BY,
insertion order preserved), so a query filtering on it
skips the files outside its range; default: files with
a geometry column in Hilbert order of their primary
geometry, others by their first DATE/TIMESTAMP column
--no-order keep the files' own row order (no ORDER BY): for files
already sorted, e.g. by gpio sort hilbert
--no-version-check import with a DuckDB older than 1.5.5 anyway (its
manifests are not maintained by the catalog)
--row-group-rows N write N rows per Parquet row group, through PyIceberg
(0.12+, with pyarrow and duckdb, in the --python
interpreter): for tables of few, large rows (e.g. an
image tile per row: use 1), where DuckDB's row groups
hold the whole file and a point query reads all of it.
Rows in --order-by order; the table's creation (or the
property) and the rows are ONE commit; format-version
2, no geometry columns. Known limitation: memory grows
with the source file, not bounded yet (a 988 MB file
written as one row group peaked at 3.5 GB); import a
large set in parts (--mode append) until it is, or lay
the files out yourself and use table add-files
--idempotency-key IDEMPOTENCY_KEY
any string; safe to reuse when retrying the same call
lhbox table add-files
usage: lhbox table add-files [-h] [--catalog CATALOG] [--mode {create,append}]
[--sorted-by COLUMN] [--keep-staged]
[--python PATH] [--full]
[[CATALOG.]NAMESPACE.TABLE] KEY [KEY ...]
For data whose producer lays it out itself (row groups, order, compression;
files written with your own tools): stage the files with lhbox file put, then
add-files copies them in the store into the table's data/ (no byte passes
through this machine) and registers them with ONE PyIceberg add_files, so one
commit and one manifest, and readers skip files by the column bounds in their
footers. Then the staged files are deleted (--keep-staged keeps them). Only
the footers are read: every file must have the same schema (and, with --mode
append, fit the table's); files carrying Iceberg field ids are refused; layout
warnings (row groups over 64 MB or of wide rows, unsigned columns, --sorted-by
ranges that overlap) are reported, never acted on. Format-version 2 tables;
needs pyiceberg 0.12+ with pyarrow in the --python interpreter, no DuckDB. A
failure before the commit deletes what it copied and keeps the staged files.
To rewrite rows (local files, CSV, URLs, sorting, small row groups) use table
import.
positional arguments:
[CATALOG.]NAMESPACE.TABLE
the table, [catalog.]namespace.table (the namespace is
created on demand); then the staged files
KEY files in the catalog's files: a key
(staging/a.parquet), a folder (staging/iberia/: every
.parquet under it) or a glob (quoted:
'staging/iberia/*.parquet'; ** crosses folders)
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one)
--mode {create,append}
create (default; the table must not exist, its schema
is the first file's) or append into an existing
format-version 2 table whose schema the files fit
--sorted-by COLUMN the column the files are sorted by: the report warns
when files' ranges of it overlap (or lack statistics),
since a filter on it then reads more files; nothing is
reordered
--keep-staged keep the staged files after the commit (default:
deleted once the table has its copies)
--python PATH the interpreter with pyiceberg 0.12+ and pyarrow (env
LHBOX_PYTHON; default: the python running lhbox)
--full include every staged file's footer summary (key, rows,
row groups, --sorted-by range) in the JSON
lhbox table set-properties
usage: lhbox table set-properties [-h] [--catalog CATALOG]
[--property KEY=VALUE] [--remove KEY]
[[CATALOG.]NAMESPACE.TABLE]
positional arguments:
[CATALOG.]NAMESPACE.TABLE
the table: catalog.namespace.table, or namespace.table
with --catalog
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list
--property KEY=VALUE repeatable; values are strings
--remove KEY repeatable
lhbox table document
usage: lhbox table document [-h] [--catalog CATALOG] [--doc TEXT]
[--column NAME=TEXT] [--from FILE]
[[CATALOG.]NAMESPACE.TABLE]
positional arguments:
[CATALOG.]NAMESPACE.TABLE
the table: catalog.namespace.table, or namespace.table
with --catalog
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list
--doc TEXT the table's description: its grain, where it comes
from, how fresh it is
--column NAME=TEXT a column's description; repeatable
--from FILE JSON {"doc": "...", "columns": {"name": "..."}}, or -
for stdin
lhbox credentials
usage: lhbox credentials [-h] [--catalog CATALOG] [--show-secrets]
[[CATALOG.]NAMESPACE.TABLE]
positional arguments:
[CATALOG.]NAMESPACE.TABLE
the table: catalog.namespace.table, or namespace.table
with --catalog
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list
--show-secrets print the secret key and the session token (for boto3
or the aws CLI); without it they are ********
lhbox connect
usage: lhbox connect [-h] [--catalog CATALOG]
[--engine {duckdb,pyiceberg,spark,snowflake,trino,polars,clickhouse}]
[--write | --readonly] [--no-version-check]
[--show-secrets]
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one); or
<handle>/<catalog> for another organisation's PUBLIC
catalog, attached read-only as <handle>_<catalog>
(--engine duckdb only)
--engine {duckdb,pyiceberg,spark,snowflake,trino,polars,clickhouse}
trino prints a catalog properties file; polars a
PyIceberg-backed Python snippet; clickhouse reads only
(icebergS3, one table at a time); snowflake reads
unless --write
--write the WRITING recipe, asked for explicitly (you must
hold write on the catalog). For snowflake: the
read/write key pair in an external volume with
ALLOW_WRITES = TRUE plus a catalog-linked database, so
Snowflake creates and writes Iceberg tables here; the
pair is static and stays in Snowflake
--readonly the catalog's READ-ONLY recipe whatever level you hold
(identity t-<uuid>-ro: the catalog refuses its
commits, the store its PUT/DELETE) -- with --readonly
--show-secrets, the recipe to send a colleague who
should read your catalog, or to paste under 'Add
catalog' in the account page's explorer (the secret is
masked otherwise)
--no-version-check do not run `duckdb --version` / `python3 -c 'import
duckdb'` (writes need DuckDB 1.5.5 or newer: older
versions write manifests the catalog's maintenance
cannot read)
--show-secrets print the catalog credential's secret; without it the
secret is ******** wherever the recipe repeats it (the
key id is shown) and a DuckDB recipe starts with an
error() naming this flag. Pipe it rather than show it:
`lhbox connect --show-secrets --human | duckdb`;
`lhbox duckdb` attaches without showing it; the sample
code reads LHBOX_<ALIAS>_CLIENT_ID / _CLIENT_SECRET
lhbox duckdb
usage: lhbox duckdb [-h] [--catalog CATALOG] [--no-edge] [--persist]
[--python PATH] [--no-version-check]
...
Without --persist: runs the duckdb binary with -init pointing at a private
0600 file holding the connect recipe (secrets and ATTACH), removes the file a
second later, and hands you the shell with the catalog attached under its
alias. Arguments after -- go to duckdb. With --persist: runs CREATE OR REPLACE
PERSISTENT SECRET lhbox_<alias> (TYPE ICEBERG) and lhbox_<alias>_s3 (TYPE S3,
scoped to the catalog's bucket) in DuckDB, so any later session -- the shell
or python -- needs only the printed ATTACH line. Trade-off: the persistent
secrets keep the catalog credential on disk in DuckDB's secret directory
(~/.duckdb/stored_secrets, 0600), readable by any DuckDB this user runs;
revoke with lhbox catalog rotate (for a catalog lhbox new made, once a person
has claimed it) or DROP PERSISTENT SECRET.
positional arguments:
-- DUCKDB_ARGS passed to the duckdb binary, e.g. -- -readonly or --
mydb.duckdb
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list; default: your only catalog
(with several, name one); or <handle>/<catalog> for
another organisation's PUBLIC catalog, attached read-
only as <handle>_<catalog> (not with --persist)
--no-edge read from the storage even where the catalog has edge
caching on (a network that reaches the storage but not
the CDN)
--persist write persistent DuckDB secrets and print the ATTACH
line instead of opening a shell
--python PATH with --persist: the interpreter whose duckdb module
writes the secrets (env LHBOX_PYTHON)
--no-version-check do not warn about a duckdb older than 1.5.5
lhbox sink
usage: lhbox sink [-h]
{create,list,get,delete,send,device-token,mapping,test,preview,run,runs,pause,resume,connection,dedupe,update} ...
positional arguments:
{create,list,get,delete,send,device-token,mapping,test,preview,run,runs,pause,resume,connection,dedupe,update}
create a new sink on an existing table (unpartitioned, or a
format-version 3 table partitioned by time); the send
key is shown once (--show-key) or written to a 0600
file (--save)
list the sinks of every catalog you can see (or of one),
with what is waiting and the last roll; never a key
get one sink by id or name: state, roll policy, lag, last
roll, rejects, totals
delete delete a sink: its send key is refused from now on;
batches still waiting are discarded (--force when
there are any); the table is untouched
send POST one batch (a JSON array of objects in a file, or
- for stdin) to a sink with its send key, or with your
own key when it holds the sink's send grant
device-token give a sink a device route (a new URL; the old one is
refused at once), or --revoke it
mapping show a sink's mapping, or set it (--preset / --mapping
FILE) or remove it (--clear)
test dry-run a device body through a sink (the pinned
secret, the strip list, the mapping) with your own
key; nothing is stored
preview run a scheduled-SQL query once, now, and write
nothing: rows, columns and their Iceberg types, and
whether they fit a table
run run a SQL sink's query now, outside its schedule (a
paused sink too)
runs a SQL sink's runs, newest first: state, rows,
requests, bytes, time, errors
pause stop a SQL sink's schedule (lhbox sink run still runs
it by hand)
resume restart a paused SQL sink's schedule
connection credentials a scheduled-SQL query sends to an
authenticated source (bearer, header, basic): write-
only, bound to a URL prefix
dedupe keep only the latest row per key in a keyed sink's
whole table, once (rows stored before the key was set
keep their duplicates otherwise)
update change a SQL sink's query or schedule (a new query is
previewed against the table first), or any sink's key
options:
-h, --help show this help message and exit
lhbox sink create
usage: lhbox sink create [-h] [--catalog CATALOG] --table NS.TABLE --name NAME
[--roll-seconds N] [--roll-bytes N]
[--inactivity-seconds N] [--key COL[,COL]]
[--save FILE] [--show-key] [--device]
[--require-field FIELD=VALUE] [--strip FIELD]
[--preset NAME] [--param NAME=VALUE] [--mapping FILE]
[--sql SQL] [--every DURATION] [--offset DURATION]
[--create-table] [--format-version {2,3}]
[--partition TRANSFORM(COLUMN)] [--paused]
[--engine {2.0,1.5}] [--mode {append,replace}]
[--min-rows N] [--replace-existing]
POST /v1/sinks. Write level on the catalog. Nothing is committed until the
first roll, which adds the optional __ingest_ts column and the metadata-
retention properties. One sink per table. Without --save or --show-key the
command stops before calling the API: no sink is created whose key nobody
received. `lhbox sink create --catalog <catalog> --table <ns>.<table> --name
<name> --save sink.json`.
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one)
--table NS.TABLE the table the sink feeds, <namespace>.<table>; it must
exist (lhbox table create)
--name NAME the sink's name (the catalog rule: lowercase letter,
then lowercase, digits, _)
--roll-seconds N commit what is waiting every N seconds (default 300,
floor 60); a roll is a commit, 300-900 suits a small
producer
--roll-bytes N also roll when N bytes wait (32-64 MiB; default 64
MiB)
--inactivity-seconds N
also roll when the sink has been quiet for N seconds
--key COL[,COL] replace the rows that share these columns at each roll
instead of appending them again (a format-version 2
table; e.g. --key observation_id): the latest row per
key wins
--save FILE write the send key, the send URL and the sink id to
FILE (mode 0600) and do not print the key
--show-key print the send key once (as well as saving it, with
--save)
device intake:
--device also give the sink a device route: a URL a device
posts to with no header (form, JSON, NDJSON or CSV);
saved with the send key
--require-field FIELD=VALUE
a body field the device must send with this value
(e.g. PASSKEY=...); hashed here, stripped before
storage
--strip FIELD a body field never stored (repeatable)
--preset NAME a mapping shipped with the platform: ecowitt,
ispindel, ttn (The Things Stack webhooks), sensor-
community
--param NAME=VALUE a preset's parameter, e.g. WS=ws-01 (repeatable)
--mapping FILE a mapping document (JSON), validated against the table
scheduled SQL: the sink runs a DuckDB query on a schedule; no send key:
--sql SQL one DuckDB SELECT reading public http(s) URLs, inline,
@file or @- (stdin)
--every DURATION how often it runs: 1m, 20m, 1h, 300 (at least the
plan's minimum, 1 min)
--offset DURATION where in the interval it runs (every 20m offset 2m:
:02, :22, :42)
--create-table create the table from the query's columns when it does
not exist, in --format-version (default: the catalog's
default format version)
--format-version {2,3}
with --create-table: the new table's Iceberg format
version; 3 for a geometry column or --partition, 2 for
--key. A query that does not fit it is refused before
the table is made
--partition TRANSFORM(COLUMN)
with --create-table and format version 3: partition
the new table by time, repeatable: day(ts), hour(ts),
month(ts), year(ts) of a date or timestamp column
--paused create it paused (run it by hand with lhbox sink run)
--engine {2.0,1.5} the DuckDB the query runs on: 2.0 (DuckDB 2.0.0, the
default) or 1.5 (DuckDB 1.5.6); a sink keeps its
engine until you change it, whatever the default
becomes
--mode {append,replace}
what each run does to the table: append its rows (the
default) or replace every row with them, in one
snapshot per run (a format-version 2 table; time
travel reads the versions the snapshot retention
keeps). Fixed at creation
--min-rows N with --mode replace: a run with fewer than N rows
(default 1) fails and leaves the table as it is, so an
empty source never empties it
--replace-existing with --mode replace on a table that already holds
rows: replace them at the first run
lhbox sink list
usage: lhbox sink list [-h] [--catalog CATALOG] [--all]
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list
--all include deleted sinks
lhbox sink get
usage: lhbox sink get [-h] SINK
positional arguments:
SINK the sink_id or the name
options:
-h, --help show this help message and exit
lhbox sink delete
usage: lhbox sink delete [-h] [--force] [--yes] SINK
positional arguments:
SINK the sink_id or the name
options:
-h, --help show this help message and exit
--force, --discard-pending
delete even if batches are waiting for a roll (they
are discarded)
--yes do not ask: the deletion cannot be undone. Asked on a
terminal; without one (or with --json) --yes will be
required from the next minor version of lhbox (a
warning today)
lhbox sink send
usage: lhbox sink send [-h] [--send-key-file FILE] [--send-key SEND_KEY]
[--batch-id ID]
[SINK] FILE
The send route, POST /v1/sinks/{id}: the key from --send-key-file (what `sink
create --save` wrote), --send-key or LHBOX_SEND_KEY; without one, this
machine's own key (a token given the sink with `lhbox token create --send`, or
write on its catalog); the batch id from --batch-id or a fresh UUID. The same
batch id again answers duplicate without storing twice. 202 accepted; 400 not
an array; 413 over 16 MiB.
positional arguments:
SINK the sink_id (omit it when --send-key-file names one; a
name works with your own key)
FILE a file holding a JSON array of objects, or - for stdin
options:
-h, --help show this help message and exit
--send-key-file FILE the file `sink create --save` wrote
--send-key SEND_KEY the send key itself (prefer --send-key-file or
LHBOX_SEND_KEY)
--batch-id ID the Idempotency-Key: 1-128 chars of A-Z a-z 0-9 . _ :
- (default: a UUID)
lhbox sink device-token
usage: lhbox sink device-token [-h] [--save FILE] [--show-key] [--revoke] SINK
POST /v1/sinks/{id}/device-token: the URL a device that sets no header posts
to. Shown once: --save FILE (0600) or --show-key. Counts against the plan's
device_sinks when the sink had no route.
positional arguments:
SINK the sink_id or the name
options:
-h, --help show this help message and exit
--save FILE write the device URL and token to FILE (mode 0600)
--show-key print the device URL once
--revoke remove the device route instead (DELETE)
lhbox sink mapping
usage: lhbox sink mapping [-h] [--preset NAME] [--param NAME=VALUE]
[--mapping FILE] [--clear]
SINK
GET /v1/sinks/{id}?include_mapping=true, or PATCH /v1/sinks/{id} {mapping}. A
mapping turns what a device sends into the table's rows at each roll: rename,
drop, constants, a timestamp, and melt (one wide record into many long rows
with unit conversions). Validated against the table.
positional arguments:
SINK the sink_id or the name
options:
-h, --help show this help message and exit
--preset NAME a mapping shipped with the platform: ecowitt, ispindel,
ttn (The Things Stack webhooks), sensor-community
--param NAME=VALUE a preset's parameter, e.g. WS=ws-01 (repeatable)
--mapping FILE a mapping document (JSON)
--clear remove the mapping
lhbox sink test
usage: lhbox sink test [-h] [--content-type TYPE] [--limit LIMIT] SINK FILE
POST /v1/sinks/{id}/test. The content type follows the file's extension (.csv,
.ndjson, .json) and defaults to form-urlencoded (what an Ecowitt gateway
posts); --content-type overrides it.
positional arguments:
SINK the sink_id or the name
FILE a captured body, or - for stdin
options:
-h, --help show this help message and exit
--content-type TYPE the body's media type
--limit LIMIT rows to print (default 20)
lhbox sink preview
usage: lhbox sink preview [-h] [--catalog CATALOG] --sql SQL
[--table NS.TABLE] [--limit LIMIT]
[--engine {2.0,1.5}] [--format-version {2,3}]
POST /v1/sql/preview. The query runs on LakehouseBox's executor: public
http(s) URLs only (read_json, read_csv, read_parquet, read_text, FROM
'https://...'), one SELECT. Errors name the column, the host or the budget,
with a remedy.
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one)
--sql SQL the SELECT, inline, @file or @- (stdin)
--table NS.TABLE also check that the rows fit this table
--limit LIMIT rows to print (default 20); the preview itself carries
at most 50 rows and 50,000 characters per value, and
says when it cut (truncated)
--engine {2.0,1.5} the DuckDB to preview on: 2.0 (DuckDB 2.0.0, the
default) or 1.5 (DuckDB 1.5.6)
--format-version {2,3}
without --table: check the query against the format
version --create-table would use (default: the
catalog's default); a geometry under 2 is listed as a
problem
lhbox sink run
usage: lhbox sink run [-h] [--wait] [--timeout TIMEOUT] SINK
positional arguments:
SINK the sink_id or the name
options:
-h, --help show this help message and exit
--wait wait until the run is committed, empty or failed
--timeout TIMEOUT seconds --wait waits (default 300)
lhbox sink runs
usage: lhbox sink runs [-h] [--limit LIMIT] SINK
positional arguments:
SINK the sink_id or the name
options:
-h, --help show this help message and exit
--limit LIMIT runs to show (default 20, at most 500)
lhbox sink pause
usage: lhbox sink pause [-h] SINK
positional arguments:
SINK the sink_id or the name
options:
-h, --help show this help message and exit
lhbox sink resume
usage: lhbox sink resume [-h] SINK
positional arguments:
SINK the sink_id or the name
options:
-h, --help show this help message and exit
lhbox sink connection
usage: lhbox sink connection [-h] {create,list,replace,delete} ...
A connection is a named credential of one catalog, sent only to URLs that
start with its https:// prefix; the SQL names the URL, never the credential.
Values come from @FILE, @- (stdin) or an environment variable (--value-env),
never inline, and no command shows them again. A person can also add one on
the account page (the catalog's Sinks tab).
positional arguments:
{create,list,replace,delete}
create a new connection: NAME, --url-prefix, one of --bearer
/ --header NAME / --basic, and --value @FILE, --value-
env VAR or a hidden prompt
list a catalog's connections (never their values)
replace replace a connection's value (a rotated key)
delete delete a connection; SQL sinks whose query names its
prefix are paused
options:
-h, --help show this help message and exit
lhbox sink connection create
usage: lhbox sink connection create [-h] [--catalog CATALOG] --url-prefix URL
[--bearer] [--header HEADER] [--basic]
[--value @FILE] [--value-env VAR]
NAME
POST /v1/sql/connections. --basic takes user:password as its value.
positional arguments:
NAME the connection's name (lower case letters, digits, _)
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list
--url-prefix URL https://host/path/ : the credential is sent only to URLs
that start with it (ends in /)
--bearer send Authorization: Bearer <value>
--header HEADER send the value in this header, e.g. X-API-Key
--basic send Authorization: Basic, the value being user:password
--value @FILE the credential: @FILE or @- (stdin); never inline
--value-env VAR read the credential from this environment variable (an
agent that holds the key never writes it to a file); with
neither, a terminal asks, hidden
lhbox sink connection list
usage: lhbox sink connection list [-h] [--catalog CATALOG]
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list
lhbox sink connection replace
usage: lhbox sink connection replace [-h] [--catalog CATALOG] [--value @FILE]
[--value-env VAR] [--header HEADER]
NAME
positional arguments:
NAME the connection's name
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list
--value @FILE the new credential: @FILE or @- (stdin)
--value-env VAR read it from this environment variable
--header HEADER a new header name (kind header only)
lhbox sink connection delete
usage: lhbox sink connection delete [-h] [--catalog CATALOG] [--yes] NAME
positional arguments:
NAME the connection's name
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list
--yes do not ask: deleting a connection cannot be undone. Asked
on a terminal; without one (or with --json) --yes will be
required from the next minor version of lhbox (a warning
today)
lhbox sink dedupe
usage: lhbox sink dedupe [-h] [--wait] [--timeout SECONDS] [--yes] SINK
positional arguments:
SINK the sink_id or the name
options:
-h, --help show this help message and exit
--wait wait until the roller has done it, and print what it
removed
--timeout SECONDS how long --wait waits (default 1800)
--yes do not ask: a dedupe removes the older rows of every key
cannot be undone. Asked on a terminal; without one (or
with --json) --yes will be required from the next minor
version of lhbox (a warning today)
lhbox sink update
usage: lhbox sink update [-h] [--sql SQL] [--every DURATION]
[--offset DURATION] [--engine {2.0,1.5}]
[--min-rows N] [--force] [--key COL[,COL] | --no-key]
SINK
positional arguments:
SINK the sink_id or the name
options:
-h, --help show this help message and exit
--sql SQL the new SELECT, inline, @file or @- (stdin)
--every DURATION the new interval
--offset DURATION the new offset
--engine {2.0,1.5} move the sink to this DuckDB: 2.0 (DuckDB 2.0.0) or 1.5
(DuckDB 1.5.6); its query is previewed on it against the
table first
--min-rows N a replace sink's guard: a run with fewer than N rows
fails and leaves the table as it is
--force accept a new query or engine whose preview returns 0
rows although the sink's recent runs had rows (refused
otherwise: preview_empty)
--key COL[,COL] from the next roll, replace the rows that share these
columns instead of appending them again (a format-
version 2 table)
--no-key drop the key: each roll appends again
lhbox index
usage: lhbox index [-h] {create,list,get,delete} ...
positional arguments:
{create,list,get,delete}
create a new index rule on an existing table
list the index rules of every catalog you can see (or of
one)
get one index rule by id or name: its last poll, what it
indexed, its lag
delete stop indexing (the table, its rows and the files stay)
options:
-h, --help show this help message and exit
lhbox index create
usage: lhbox index create [-h] [--catalog CATALOG] --name NAME
[--prefix PREFIX] --table NS.TABLE --extractors JSON
[--rename EXTRACTED=COLUMN] [--poll-seconds N]
[--ordered-keys] [--roll-seconds N]
[--inactivity-seconds N]
POST /v1/indexes. Extractors, a JSON list (inline or @file) from: "basic"
(bucket, object_key, filename, size_bytes, etag, content_type, last_modified,
indexed_at, uploader), "image" (width, height, exif_taken_at), {"type":
"filename_time", "column": …, "pattern": "(\\d{14})", "format":
"%%Y%%m%%d%%H%%M%%S", "tz": "Europe/Madrid", "fallback": "last_modified"},
{"type": "path_parts", "pattern": "^camera/(?P<camera_id>[^/]+)/"}, {"type":
"constant", "values": {…}}.
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list
both with: lhbox catalog list; default: your only
catalog (with several, name one)
--name NAME the rule's name (lowercase letter, then lowercase,
digits, _)
--prefix PREFIX the prefix of the blob bucket to watch, e.g. camera/
(default: all of it)
--table NS.TABLE the table the rows go to; it must exist
--extractors JSON the extractor list, inline JSON or @file
--rename EXTRACTED=COLUMN
store an extracted column under the table's name
(repeatable)
--poll-seconds N how often the prefix is listed (default 60, floor 30)
--ordered-keys the keys sort by time (dated folders such as
camera/2026/11/06/): each poll lists from the newest
folder indexed on, not the whole prefix; a file
uploaded into an older folder is then never indexed
--roll-seconds N commit the found files every N seconds (default 300,
floor 60)
--inactivity-seconds N
also commit once nothing new was found for N seconds
lhbox index list
usage: lhbox index list [-h] [--catalog CATALOG]
options:
-h, --help show this help message and exit
--catalog CATALOG the catalog's warehouse_id (UUID) or its name; list both
with: lhbox catalog list
lhbox index get
usage: lhbox index get [-h] INDEX
positional arguments:
INDEX the index_id or the name
options:
-h, --help show this help message and exit
lhbox index delete
usage: lhbox index delete [-h] [--yes] INDEX
positional arguments:
INDEX the index_id or the name
options:
-h, --help show this help message and exit
--yes do not ask: the deletion cannot be undone. Asked on a terminal;
without one (or with --json) --yes will be required from the
next minor version of lhbox (a warning today)
lhbox mcp
usage: lhbox mcp [-h] {install,headers,run} ...
positional arguments:
{install,headers,run}
install write the mcpServers entry into a host's
configuration; the key comes from LHBOX_API_KEY or the
credentials file and is never printed nor put on a
command line
headers for programs: print {"Authorization": "Bearer <key>"}
(LHBOX_API_KEY, else the saved login), the
headersHelper Claude Code runs for the entry `mcp
install` writes; refused on a terminal without --show-
key
run start the MCP server over stdio with the saved key in
its environment (for a host configured by hand:
command lhbox, args mcp run); until the package is on
PyPI it runs from a checkout (--from
<checkout>/platform/mcp): most hosts want `lhbox mcp
install`, the hosted server, with nothing to run here
options:
-h, --help show this help message and exit
lhbox mcp install
usage: lhbox mcp install [-h] [--client {claude-code,claude-desktop}]
[--transport {http,stdio}] [--mcp-url MCP_URL]
[--url URL] [--from SOURCE] [--name NAME]
[--scope {local,user,project}] [--print]
Claude Code (default): adds the remote server {"type": "http", "url":
"https://mcp.lakehousebox.com/mcp", "headersHelper": "<this lhbox> --url <API>
--profile <profile> mcp headers"} with `claude mcp add-json` when `claude` is
on PATH, else prints the entry and the command to run. The entry holds no key:
Claude Code runs the helper when it connects, which reads LHBOX_API_KEY from
Claude Code's environment, else the saved login, so a key given only with
--api-key is refused. Claude Desktop: its config file takes only local
servers, so the hosted server is a custom connector added in the app (Settings
> Connectors > Add custom connector, signed in with OAuth); this prints those
steps and writes nothing. --transport stdio (developers: a checkout's
platform/mcp against a local stack) writes a local server instead, only when
uv is installed and SOURCE resolves, else nothing is written: for Claude Code
{"command": "<this lhbox>", "args": ["--url", API, "--profile", PROFILE,
"mcp", "run", "--from", SOURCE]} (no key in it); for Claude Desktop
{"command": "uvx", "args": ["--from", SOURCE, "lakehousebox-mcp"], "env":
{LHBOX_URL, LHBOX_API_KEY}}, merged into its claude_desktop_config.json (other
servers kept) after a .bak-<timestamp> copy, both written 0600. The key comes
from the credentials file (or --api-key / LHBOX_API_KEY); the five tools only
read, so a token's key with read grants confines the server to those catalogs
(and to public ones) (docs/mcp.md).
options:
-h, --help show this help message and exit
--client {claude-code,claude-desktop}
which host's configuration to write (default: claude-
code)
--transport {http,stdio}
http (default): the hosted server, nothing runs
locally; stdio: a local server started with uvx from a
checkout, for developers
--mcp-url MCP_URL the remote server (default: mcp.<domain>/mcp next to
the API's api.<domain>)
--url URL API base URL the server will talk to and whose saved
key is used (default: the global --url / LHBOX_URL,
i.e. https://api.lakehousebox.com)
--from SOURCE developers: what uvx runs the local server from,
normally a checkout's platform/mcp directory (default:
the checkout this CLI runs from; the package
lakehousebox-mcp is not published). Checked first: a
source that cannot start the server is refused and
nothing is written
--name NAME the server's name in the host (default: lhbox)
--scope {local,user,project}
Claude Code only: user (default; every project on this
machine), local (this project, this machine), or
project (.mcp.json, shared through version control: it
holds no key, but names this machine's lhbox)
--print print the mcpServers snippet instead of writing
anything (for another host's configuration; a Claude
Desktop entry's key is MASKED: put the real one in
from the credentials file)
lhbox mcp headers
usage: lhbox mcp headers [-h] [--url URL] [--show-key]
options:
-h, --help show this help message and exit
--url URL API base URL whose saved key is used (default: the global --url
/ LHBOX_URL, i.e. https://api.lakehousebox.com)
--show-key print it on a terminal too
lhbox mcp run
usage: lhbox mcp run [-h] [--url URL] [--from SOURCE] ...
positional arguments:
-- SERVER_ARGS passed to the server, e.g. -- --list-tools
options:
-h, --help show this help message and exit
--url URL API base URL (default: the global --url / LHBOX_URL, i.e.
https://api.lakehousebox.com)
--from SOURCE developers: what uvx runs the local server from, normally a
checkout's platform/mcp directory (default: the checkout
this CLI runs from; the package lakehousebox-mcp is not
published). Checked first: a source that cannot start the
server is refused and nothing is written