Command-line quickstart¶
dh is DuckHaven's command-line interface. It gives analysts, CI pipelines and operators scriptable
access to everything the API offers: running SQL, browsing the catalog, publishing dbt lineage and
semantic models, managing grants, and operating service accounts and agents.
Install¶
Or, for an isolated tool install that does not touch a project's environment:
It runs on Python 3.10 and newer. Check it landed:
Sign in¶
dh asks for your email and password, mints a personal access token for you, and writes it to
~/.config/duckhaven/config.toml with mode 0600. The sign-in session itself is never stored — it
exists for the three requests it takes to issue the token, then is discarded.
Single sign-on deployments
If your DuckHaven authenticates only through an identity provider, there is no password for dh
to collect. Ask an administrator for a service-account token and
run dh auth login --host https://... --token dh_pat_... instead.
Confirm it worked, and see which credential is in play:
dh auth describe is the one to reach for when something is not behaving: it prints every setting
and where it came from — a flag, an environment variable, or which profile.
Your tokens¶
dh auth status warns when the token you are using is within a fortnight of expiring, so a scheduled
job does not discover it with a 401. To see and manage them:
dh auth tokens # what you hold, and which one is in use
dh auth revoke <id> # retire one, including the one you are using
The listing never shows a token's value, and cannot: only a hash of it is stored. A token is shown
once, when it is issued — if you lose it, issue a new one with dh auth login and revoke the old.
Set a default workspace¶
Most commands act on a workspace. Set one once rather than passing --workspace every time:
Or edit the profile directly:
default_profile = "default"
[profile.default]
host = "https://duckhaven.example.com"
token = "dh_pat_..."
workspace = "analytics"
catalog = "main"
Run a query¶
dh sql submits the query, waits for it to finish, and prints every page of results. From a file, or
from a pipe:
Run dh sql with no arguments in a terminal and you get an interactive shell.
Browse the catalog¶
dh catalog list
dh schema list
dh table list sales
dh table get sales.orders
dh table sample sales.orders
A table is schema.table, or catalog.schema.table when you want a catalog other than your default
for one command. dh table sample returns a fixed 20-row preview; for anything else, write the query.
Use it from a script¶
Two things make dh safe to automate.
One output shape. --format json wraps every response the same way, so jq works the same
against every command:
Output defaults to a table on a terminal and to JSON when piped or redirected, so this works whether
or not you remember the flag. --all walks every page rather than returning the first one.
Exit codes that mean something. In particular, a query that runs and fails exits 6, so a
pipeline can tell bad SQL from a broken CLI or an unreachable server:
| Code | Meaning |
|---|---|
| 0 | Success |
| 2 | Bad flags or arguments |
| 3 | Authentication or permission |
| 4 | Not found |
| 5 | Rejected input, or a conflict |
| 6 | The query ran and failed |
| 7 | Timed out |
| 8 | Server unavailable |
| 9 | A destructive command was not confirmed, so nothing happened |
Destructive commands ask first¶
Commands that destroy data — workspace delete, catalog drop, catalog detach, schema drop,
table drop, lineage purge and semantic purge — will not run unattended. At a terminal they ask,
and the default answer is no:
Anywhere else — a pipeline, a cron job, a CI step — there is nobody to ask, so dh refuses and
exits 9 without sending anything. Pass --yes (or -y) to say so up front:
Why it refuses instead of prompting
Prompting in a job with no terminal would hang it on a question nobody will answer, and
proceeding anyway would make --yes decorative. Exit 9 is its own code so a script can tell
"the server rejected this" (5) from "I never sent it".
Credentials in CI¶
dh reads DH_HOST, DH_TOKEN and DH_WORKSPACE from the environment, so a pipeline needs no
config file at all:
export DH_HOST=https://duckhaven.example.com
export DH_TOKEN=dh_pat_...
export DH_WORKSPACE=analytics
dh sql -q "select 1"
Do not use your personal token for CI
dh auth login mints a token tied to you: it expires, and it stops working the day you
leave. Have an administrator create a service account and issue
a token for that instead.
Where to go next¶
- CLI reference — every command and flag
- Import lineage from dbt — publishing from CI
- Import semantics from dbt
- Service accounts & tokens — credentials for automation