Skip to main content

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-client calls 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:

  1. Validates ORG_ID is a valid UUID.
  2. Connects to Postgres using DB_URL.
  3. Creates the schema (CREATE SCHEMA IF NOT EXISTS tenant_<id>).
  4. CREATE TABLEs every tenant model from db/models/tenant.py into 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 via ENCRYPTION_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:

  1. Org + default workspace + admin user with the canonical Phase-1 roles: the org membership is super_admin and the workspace membership is the derived workspace_admin — never the legacy admin string. The tenant schema is created with RLS applied. Keyed on FIRST_ADMIN_EMAIL: when the user already exists, nothing is written.
  2. One is_default LLM config — only when FIRST_LLM_PROVIDER + FIRST_LLM_MODEL are 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.
  3. 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, so dqc seed > agent-token.txt captures just the token). It is not recoverable afterwards — revoke the token and re-run to mint a new one.

Environment variables:

VariableRequiredMeaning
FIRST_ORG_NAMEyesOrganisation name.
FIRST_ADMIN_EMAILyesAdmin login email (the idempotency key).
FIRST_ADMIN_PASSWORDyesAdmin password, 12+ chars. Rotate after first login.
FIRST_ORG_SLUGnoSlug override; derived from the name when empty.
FIRST_LLM_PROVIDERnoopenai / anthropic / azure_openai / ollama / custom. Set together with FIRST_LLM_MODEL.
FIRST_LLM_MODELnoModel id for the default LLM config.
FIRST_LLM_API_KEYnoProvider API key; encrypted under the org DEK at rest. Omit for Ollama.
FIRST_LLM_BASE_URLnoBase URL for ollama / azure_openai / custom.
FIRST_AGENT_TOKEN_NAMEnoAgent-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 to provision-tenant, an unknown role passed to set-role, or an unresolvable email / ambiguous multi-org user).
  • Anything non-zero from inner Python exceptions — the traceback is printed; check the stack.