dqc CLI reference
PLACEHOLDER Cloud ships an operator CLI named dqc. It's installed by pip install -e '.[api]' (or uv sync) and lives on your PATH. Run dqc --help for the in-tree summary; this page is the prose version.
Subcommands
dqc export-schema
Dump the FastAPI OpenAPI schema as JSON.
dqc export-schema # to stdout
dqc export-schema --out api.json # to file
dqc export-schema -o api.json # short form
Use cases:
- Regenerate the agent client (
make generate-clientcalls this). - Pin the API contract for a downstream SDK build (FEATURE-32, FEATURE-33).
- Diff the schema between releases.
The FastAPI app is imported lazily inside this command, so dqc export-schema --help doesn't pay the SQLAlchemy / DB-URL import cost.
dqc provision-tenant ORG_ID
Create the tenant_<org_id> schema and stamp the current tenant model into it.
dqc provision-tenant 8d4f7c8e-3b2a-4a1e-9c5d-3e2f8b1a4c5e
What it does:
- Validates
ORG_IDis a valid UUID. - Connects to Postgres using
DB_URL. - Creates the schema (
CREATE SCHEMA IF NOT EXISTS tenant_<id>). CREATE TABLEs every tenant model fromdb/models/tenant.pyinto it.
This is the initial-provisioning path only. Once tables exist, run dqc migrate-tenant (single org) or dqc migrate-all-tenants (every org) to roll forward.
dqc migrate-tenant ORG_ID
Runs incremental Alembic revisions against the tenant branch for one org. Each tenant schema carries its own alembic_version_tenant row so Alembic computes the per-tenant diff.
dqc migrate-tenant 8d4f7c8e-3b2a-4a1e-9c5d-3e2f8b1a4c5e
dqc migrate-all-tenants
Iterates every Organisation row and runs migrate-tenant for each. Per-tenant failures are caught and reported in the summary; they don't stop the loop. Exits non-zero if any tenant failed — the CI / ops path.
dqc migrate-all-tenants
dqc rotate-encryption-key ORG_ID [--new-dek]
Two modes:
- Default (rewrap). Re-wrap one tenant's Data Encryption Key (DEK) under the current
ENCRYPTION_KEY— unwrapping it under the old key supplied viaENCRYPTION_KEY_PREVIOUS. The customer ciphertexts (datasource credentials, alert-destination secrets, LLM API keys) are not touched, so this is the safe recovery path after the env key was rotated in place. One column write per org; nothing can fail to decrypt. --new-dek(full rotation). Generate a fresh DEK and re-encrypt every secret the tenant owns under it — datasource credentials, alert-destination secrets (Slack webhook, generic-webhook signing secret, PagerDuty routing key), and the org's LLM API keys (HARDEN-16). Operationally offline-tolerable: drain the API + worker for the tenant before running. The CLI does the rotation in one transaction per tenant — a crash mid-rotation rolls back to the old DEK and leaves the existing ciphertexts intact. A concurrent writer holding the old DEK could persist a secret that the new DEK can't decrypt, so you do need the drain.
# Recover after an ENCRYPTION_KEY env rotation (rewrap each DEK under the new key):
ENCRYPTION_KEY_PREVIOUS="<old-key>" dqc rotate-encryption-key 8d4f7c8e-3b2a-4a1e-9c5d-3e2f8b1a4c5e
# Full DEK rotation (re-encrypt every secret):
dqc rotate-encryption-key 8d4f7c8e-3b2a-4a1e-9c5d-3e2f8b1a4c5e --new-dek
dqc rotate-all-encryption-keys [--new-dek]
Iterate every org and rewrap (default) or fully rotate (--new-dek) its DEK. The default rewrap mode is the fleet-wide fix after an ENCRYPTION_KEY env rotation. Per-tenant failures are reported in the summary but don't stop the loop — rotation is independent per tenant. Exits non-zero if any tenant failed.
ENCRYPTION_KEY_PREVIOUS="<old-key>" dqc rotate-all-encryption-keys
dqc set-role EMAIL ROLE [--org SLUG] [--no-workspace]
Promote or demote a user's role in an org. ROLE is one of owner, admin, editor, viewer, billing (token-only pseudo-roles are rejected). Updates the user's org_memberships row and — unless --no-workspace is passed — every workspace_memberships row the user holds in that org, to the same role. Idempotent.
Pass --org SLUG to scope the change when the user belongs to more than one org (required in that case; a single-org user can omit it).
The intended admin@dqcloud.dev seed account is born admin. But an account that first signed in via IAP JIT-provisioning is minted at IAP_DEFAULT_JIT_ROLE (historically a hardcoded editor), so an IAP-provisioned admin ends up an editor and fails every admin-gated check (#336/#340/#343). This is the explicit one-time fix:
dqc set-role admin@dqcloud.dev admin
dqc seed
Non-interactively provision the canonical bootstrap state (#48). Everything is read from env — no flags, no prompts — so a stage reseed or CI job can run it as a one-shot container. Idempotent: each step skips itself when its resource already exists.
FIRST_ORG_NAME="Acme Inc" \
FIRST_ADMIN_EMAIL="admin@acme.example" \
FIRST_ADMIN_PASSWORD="<12+ chars>" \
FIRST_LLM_PROVIDER=anthropic \
FIRST_LLM_MODEL=claude-sonnet-4-5 \
FIRST_LLM_API_KEY="<provider key>" \
dqc seed > agent-token.txt
What it creates:
- Org + default workspace + admin user with the canonical Phase-1 roles: the org membership is
super_adminand the workspace membership is the derivedworkspace_admin— never the legacyadminstring. The tenant schema is created with RLS applied. Keyed onFIRST_ADMIN_EMAIL: when the user already exists, nothing is written. - One
is_defaultLLM config — only whenFIRST_LLM_PROVIDER+FIRST_LLM_MODELare set. The API key is AES-256-GCM-encrypted under the org DEK before it is stored; the plaintext never touches a column and is never echoed back. Skipped when the org already has a default config. - One
dqa_agent token — freshly generated (random, never a fixed literal), stored only as a SHA-256 hash. The plaintext is printed to stdout exactly once (all status lines go to stderr, sodqc seed > agent-token.txtcaptures just the token). It is not recoverable afterwards — revoke the token and re-run to mint a new one.
Environment variables:
| Variable | Required | Meaning |
|---|---|---|
FIRST_ORG_NAME | yes | Organisation name. |
FIRST_ADMIN_EMAIL | yes | Admin login email (the idempotency key). |
FIRST_ADMIN_PASSWORD | yes | Admin password, 12+ chars. Rotate after first login. |
FIRST_ORG_SLUG | no | Slug override; derived from the name when empty. |
FIRST_LLM_PROVIDER | no | openai / anthropic / azure_openai / ollama / custom. Set together with FIRST_LLM_MODEL. |
FIRST_LLM_MODEL | no | Model id for the default LLM config. |
FIRST_LLM_API_KEY | no | Provider API key; encrypted under the org DEK at rest. Omit for Ollama. |
FIRST_LLM_BASE_URL | no | Base URL for ollama / azure_openai / custom. |
FIRST_AGENT_TOKEN_NAME | no | Agent-token name (default seed-agent); also the re-run idempotency key. |
The same FIRST_ORG_NAME / FIRST_ADMIN_EMAIL / FIRST_ADMIN_PASSWORD / FIRST_ORG_SLUG vars drive the compose one-shot scripts/seed_first_admin.py init container, which shares the same implementation (org + admin only — no LLM config, no agent token).
Planned subcommands
dqc seed-dev
Status: planned (HARDEN-5).
Wraps python -m scripts.seed_dev for parity with the other operator subcommands. Until it ships, use python -m scripts.seed_dev directly.
How dqc is installed
The binary is declared in api/pyproject.toml via [project.scripts]. After uv sync or pip install -e '.[api]' it sits on $PATH. From a checked-out source tree without an install, run python -m dq_cloud_api.cli <subcommand>.
Exit codes
0— success.2— invalid argument (e.g. non-UUID passed toprovision-tenant, an unknown role passed toset-role, or an unresolvable email / ambiguous multi-org user).- Anything non-zero from inner Python exceptions — the traceback is printed; check the stack.