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

whathow
LHBOX_URL / --urlAPI 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-keyThe key. Otherwise read from the credentials file.
LHBOX_CREDENTIALSPath 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

codemeaningwhat to do
0ok
1invalid inputthe values you sent are wrong; fix them, never retry unchanged
2limit or conflicta real quota or a real 409; stop and change the plan
3authkey missing, wrong or revoked
4not foundno such project, catalog, namespace or table
5server or unreachableours or the network's; safe to retry with backoff
6usage errorthe 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