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
pipx install lakehousebox # once published on PyPI; the same file
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, default ~/.config/lhbox/credentials.json (0600, keyed by API URL; written by signup --save, recover --save, login --save). |
AGENTLAKE_* | The pre-rename names are still read as fallbacks, and ~/.config/agentlake/credentials.json is read (never written). |
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.
Commands
Rendered from the CLI's own --help at build time.
lhbox
usage: lhbox [-h] [--version] [--url URL] [--api-key API_KEY] [--json]
[--human]
{signup,terms,account,login,recover,api-key,org,agent,whoami,doctor,usage,audit,project,catalog,namespace,table,credentials,connect,duckdb,sink,mcp} ...
LakehouseBox: an open lakehouse you control.
positional arguments:
{signup,terms,account,login,recover,api-key,org,agent,whoami,doctor,usage,audit,project,catalog,namespace,table,credentials,connect,duckdb,sink,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 connect this machine to your catalog: approve a code
in the browser; an agent named after this machine is
created with the access you approve, and its key is
saved here, never printed
recover lost API key: a code to your email gives you a new one
api-key API keys of the caller
org organisations: members, domains, invitations
agent agents (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; PASS/FAIL/SKIP per line, exit 1 on any
FAIL. Writes nothing unless --write-probe
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)
project projects (placement is immutable)
catalog catalogs: one Iceberg catalog per lakehouse, with its
own storage and identity (the API names one by
warehouse_id, under /v1/warehouses)
namespace namespaces (created on demand by table create)
table tables
credentials short-lived, table-scoped storage credentials
connect a paste-ready recipe for your own engine, carrying the
identity that matches your level (read holders get the
read-only one; snowflake always does); 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/)
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)
--json force JSON output
--human force human-readable output
Output is JSON when piped and a table on a terminal.
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 (safe to retry)
6 usage error the command line is malformed; fix it, do not retry
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
options:
-h, --help show this help message and exit
--confirm CONFIRM your email, typed out
lhbox login
usage: lhbox login [-h] [--issuer [ISSUER]] [--label LABEL] [--name NAME]
[--level {read,write}] [--catalog CATALOG[:LEVEL]] [--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
--label LABEL API key label on the --issuer path (the LakehouseBox
login labels it cli <hostname>)
--name NAME the agent's name shown on the consent screen and in
Connections (default: this machine's hostname)
--level {read,write} the access to ask for (default write); the person
approving may lower it to read
--catalog CATALOG[:LEVEL]
connect to this catalog, by name or id, with the level
to ask for on it (read or write; default: --level),
e.g. --catalog sales:read (repeatable; default: the
organisation's default catalog). The person approving
sees them preselected and may change the set
--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 recover
usage: lhbox recover [-h] --email EMAIL [--code CODE] [--save] [--show-key]
options:
-h, --help show this help message and exit
--email EMAIL
--code CODE
--save store the key in ~/.config/lhbox/credentials.json; with it
the key is NOT printed
--show-key print the key even when it was saved
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
options:
-h, --help show this help message and exit
--key-id KEY_ID
lhbox org
usage: lhbox org [-h] {list,members,invite,accept,domain,set,role} ...
positional arguments:
{list,members,invite,accept,domain,set,role}
domain claim your organisation's email domain (yours must be
on it), or --remove it
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 [--remove]
options:
-h, --help show this help message and exit
--org ORG
--domain DOMAIN
--remove release the domain instead of claiming it (admin); domain
join switches off when no domain remains
lhbox org set
usage: lhbox org set [-h] [--org ORG] [--name NAME] [--domain-join {on,off}]
options:
-h, --help show this help message and exit
--org ORG
--name NAME
--domain-join {on,off}
let verified-domain users join
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,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)
options:
-h, --help show this help message and exit
lhbox agent create
usage: lhbox agent create [-h] [--org ORG] --name NAME [--role {admin,member}]
[--grant CATALOG:LEVEL]
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
lhbox agent update
usage: lhbox agent update [-h] [--org ORG] --agent AGENT [--name NAME]
[--role {admin,member}] [--grant CATALOG:LEVEL]
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
--agent, --agent-id AGENT
the agent's id or its name (lhbox agent list shows
both)
--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
lhbox agent list
usage: lhbox agent 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 agent revoke
usage: lhbox agent revoke [-h] [--org ORG] --agent AGENT
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
--agent, --agent-id AGENT
the agent's id or its name (lhbox agent list shows
both)
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 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 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,publish,unpublish,public-url,uploader,lifecycle} ...
positional arguments:
{create,list,get,measure,update,rotate,grants,grant,revoke,delete,publish,unpublish,public-url,uploader,lifecycle}
create a new catalog: `lhbox catalog create <name>` puts it
in the organisation's default project; slow
(identities at the store, up to ~2 minutes), 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 hourly
sweep; 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 900s; 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 an
agent (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 for good (admin): `lhbox catalog
delete <name> --confirm <name>` (or --yes); tables,
buckets, identities, grants; slow, progress on stderr
publish make a catalog PUBLIC (admin, the name typed out):
every data file and every retained metadata.json
becomes readable by anyone without credentials at its
public URL; the REST catalog endpoint stays private;
its bytes count against the 50 GB public allowance
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/)
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/warehouses. `lhbox catalog create <name>` (lowercase letters, digits,
_; a letter first). The call writes identities and policies at the store and
can take up to two minutes on a busy host: 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, the MCP create_table tool,
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/warehouses/{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/warehouses/{id}/measure. `lhbox catalog measure <name>`. Lists the
catalog's table bucket and blob bucket as the tenant and stores the figure GET
/v1/usage and `catalog list` show until the sweep's next sample (stale_after).
Read level suffices. Quota enforcement still follows the sweep.
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}]
[CATALOG]
PATCH /v1/warehouses/{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, the MCP create_table tool,
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)
lhbox catalog rotate
usage: lhbox catalog rotate [-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 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}
[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 agent (lhbox org members
/ agent list)
--level {read,write}
lhbox catalog revoke
usage: lhbox catalog revoke [-h] --principal PRINCIPAL [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
lhbox catalog delete
usage: lhbox catalog delete [-h] [--confirm [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
--confirm, --yes [NAME]
the catalog's name, exactly (a bare --yes confirms the
NAME given positionally); this cannot be undone
lhbox catalog publish
usage: lhbox catalog publish [-h] [--confirm [NAME]] [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. The Iceberg REST
endpoint is NOT opened: readers use the metadata file's URL (lhbox catalog
public-url --table <ns>.<t>) with DuckDB's iceberg_scan and no credentials.
Undo with: lhbox catalog unpublish <name>. `lhbox catalog publish <name>
--confirm <name>` (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
lhbox catalog unpublish
usage: lhbox catalog unpublish [-h] [--confirm [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
--confirm, --yes [NAME]
accepted for symmetry with publish; not required
lhbox catalog public-url
usage: lhbox catalog public-url [-h] [--table 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 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: one IAM identity confined to
s3:PutObject on s3://b-<uuid>/<prefix>*; 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 (the store's
IAM writes: key and identity; the prefix grant is a
bucket policy)
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]
[--save FILE] [--show-key]
[CATALOG]
POST /v1/warehouses/{id}/uploaders. Write level. Three IAM writes at the store
(each up to 10 s at the host's current identity count); the policy is written
once, never re-put. 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 recorded and shown as the status 'expired' after that
time; the key itself stays valid until revoked
--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 [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)
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/warehouses/{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] [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 namespace
usage: lhbox namespace [-h] {list} ...
positional arguments:
{list}
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 table
usage: lhbox table [-h] {list,get,create,import,set-properties} ...
positional arguments:
{list,get,create,import,set-properties}
list every table in a catalog, or in one namespace
get schema, real format-version, snapshot and row count
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
set-properties set or remove table properties (one commit through the
catalog): --property key=value …, --remove key …
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 --namespace NAMESPACE
--name 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
--namespace NAMESPACE
--name NAME
lhbox table create
usage: lhbox table create [-h] --catalog CATALOG --namespace NAMESPACE
--name NAME [--column COLUMN]
[--schema-file SCHEMA_FILE] [--format-version {2,3}]
[--idempotency-key IDEMPOTENCY_KEY]
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
--namespace NAMESPACE
--name NAME
--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.
--idempotency-key IDEMPOTENCY_KEY
any string; safe to reuse when retrying the same call
lhbox table import
usage: lhbox table import [-h] [--catalog CATALOG] --namespace NAMESPACE
--name NAME [--format {auto,csv,parquet}]
[--python PATH] [--full] [--mode {create,append}]
[--format-version {2,3}] [--no-version-check]
[--idempotency-key IDEMPOTENCY_KEY]
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 DuckDB's own
CREATE TABLE AS SELECT, geometry written as WKB. When a file carries
GeoParquet metadata, the table gets geo.encoding, geo.crs, geo.columns,
geo.primary_column (docs/engines).
positional arguments:
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)
--namespace NAMESPACE
created on demand
--name NAME the table
--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 AS SELECT
--no-version-check import with a DuckDB older than 1.5.5 anyway (its
manifests are not maintained by the catalog)
--idempotency-key IDEMPOTENCY_KEY
any string; safe to reuse when retrying the same call
lhbox table set-properties
usage: lhbox table set-properties [-h] --catalog CATALOG --namespace NAMESPACE
--name NAME [--property KEY=VALUE]
[--remove KEY]
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
--namespace NAMESPACE
--name NAME
--property KEY=VALUE repeatable; values are strings
--remove KEY repeatable
lhbox credentials
usage: lhbox credentials [-h] --catalog CATALOG --namespace NAMESPACE
--table TABLE
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
--namespace NAMESPACE
--table TABLE
lhbox connect
usage: lhbox connect [-h] [--catalog CATALOG]
[--engine {duckdb,pyiceberg,spark,snowflake}]
[--no-version-check]
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)
--engine {duckdb,pyiceberg,spark,snowflake}
--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)
lhbox duckdb
usage: lhbox duckdb [-h] [--catalog CATALOG] [--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 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)
--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} ...
positional arguments:
{create,list,get,delete,send}
create a new sink on an existing, unpartitioned table; 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 (--discard-pending
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
options:
-h, --help show this help message and exit
lhbox sink create
usage: lhbox sink create [-h] --table NS.TABLE --name NAME [--roll-seconds N]
[--roll-bytes N] [--inactivity-seconds N]
[--save FILE] [--show-key]
[CATALOG]
POST /v1/sinks. Write level on the catalog. No IAM write and no commit: the
first roll 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> --table <ns>.<table> --name <name> --save sink.json`.
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 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
--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)
lhbox sink list
usage: lhbox sink list [-h] [--all] [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
--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] [--discard-pending] SINK
positional arguments:
SINK the sink_id or the name
options:
-h, --help show this help message and exit
--discard-pending delete even if batches are waiting for a roll
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; 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)
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 mcp
usage: lhbox mcp [-h] {install,run} ...
positional arguments:
{install,run}
install write the mcpServers entry into a host's configuration; the
key comes from --api-key, LHBOX_API_KEY or the credentials
file and is never printed
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)
options:
-h, --help show this help message and exit
lhbox mcp install
usage: lhbox mcp install [-h] [--client {claude-code,claude-desktop}]
[--url URL] [--from SOURCE] [--name NAME]
[--scope {local,user,project}] [--print]
Writes {"lhbox": {"command": "uvx", "args": ["--from", SOURCE, "lakehousebox-
mcp"], "env": {LHBOX_URL, LHBOX_API_KEY}}} into the host's mcpServers, with
the key read from the credentials file (or --api-key / LHBOX_API_KEY). Claude
Code: `claude mcp add-json` when `claude` is on PATH, else the entry is
printed with the key masked and the command to run. Claude Desktop: its
claude_desktop_config.json is merged into (other servers kept), after a
.bak-<timestamp> copy; the file is written 0600. The saved key is a person's
(lhbox signup --save / login --save), which today is what the account tools
need; pass --api-key with a token's key to confine the server to its grants
instead (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)
--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 what uvx installs the server from: the platform/mcp
directory of a checkout, or the package name
lakehousebox-mcp (once published); default: the
checkout this CLI runs from, else lakehousebox-mcp
--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:
the key would be too)
--print print the mcpServers snippet with the key MASKED
instead of writing anything (for another host's
configuration; put the real key in from the
credentials file)
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 what uvx installs the server from: the platform/mcp
directory of a checkout, or the package name lakehousebox-
mcp (once published); default: the checkout this CLI runs
from, else lakehousebox-mcp