> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwedai.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# QWED CLI reference

> QWED command-line interface reference. Run qwed verify with provider options, model selection, and caching controls for terminal-based LLM verification.

Command-line interface for QWED verification.

## Installation

```bash theme={null}
pip install qwed
```

The `qwed` CLI is automatically available after installation.

***

## Commands

### `qwed init` - onboarding and bootstrap

Onboarding wizard that sets up verification engines, configures your LLM provider, and bootstraps a local API key — all in a single command. Run this once after installing QWED.

```bash theme={null}
qwed init
```

The wizard runs three steps:

1. **Engine readiness** — checks that core verification engines (SymPy, Z3, AST, SQLGlot) are installed, then runs deterministic smoke tests to confirm they work correctly
2. **LLM provider setup** — select a provider, enter credentials with masked input, validate the key format, and test the connection with retry support
3. **API key bootstrap** — asks for the organization name and owner, starts a local QWED server, verifies the server's identity, creates an organization, and generates a one-time API key you can use immediately

**Supported providers:**

| Provider | Slug | Credentials | Key format |
| - | - | - | - |
| NVIDIA NIM | `nvidia` | API key | Any key format |
| OpenAI | `openai` | API key | `sk-...` or `sk-proj-...` |
| Anthropic | `anthropic` | API key | `sk-ant-...` |
| Google Gemini | `gemini` | API key | Google API key |
| Custom (OpenAI-compatible) | `custom` | Endpoint + key | Any bearer token |

**Example session:**

```
$ qwed init

[QWED] Initializing verification engines...
  [ok] SymPy    math engine ready
  [ok] Z3       logic engine ready
  [ok] AST      code engine ready
  [ok] SQLGlot  sql engine ready

Running verification suite...
  [ok] 2+2=5                    -> VERIFIED (false) | DENY
  [ok] x>5 AND x<3              -> UNVERIFIABLE | DENY
  [ok] SELECT * WHERE 1=1       -> VERIFIED (unsafe) | DENY
  [ok] eval(user_input)         -> VERIFIED (unsafe) | DENY

All engines ready.

-----------------------------------------
Step 1/3: LLM Provider Setup
-----------------------------------------
QWED uses an LLM for natural language translation.
The LLM is treated as an untrusted translator.
Its outputs are checked by deterministic engines before use.

Select provider:
  1. NVIDIA NIM
  2. OpenAI
  3. Anthropic Claude
  4. Google Gemini
  5. Custom Provider  (any OpenAI-compatible API)

Provider: 2

-----------------------------------------
Step 2/3: API Key
-----------------------------------------
OpenAI API key: ****
  Testing connection...
  [ok] Provider connected
  [ok] Model responding
  [ok] Credentials stored (.env, mode 0600)

-----------------------------------------
Step 3/3: Generate QWED API Key
-----------------------------------------
Organization name: my-team
Owner email (to sign in later, e.g. to qwed-dashboard): owner@example.com
Owner password:
Repeat for confirmation:
  Starting local server...
  [ok] Local server initialized
  [ok] Server identity verified
  [ok] Organization created
  [ok] Owner: owner@example.com (sign in with the password you set)

  Your API key: qwed_live_********************************wDHo
  Warning: Save this key. It is shown only once.

-----------------------------------------
QWED is ready.

Verify an output:
  curl -X POST http://localhost:8000/verify/math \
    -H "x-api-key: qwed_live_********************************wDHo" \
    -H "Content-Type: application/json" \
    -d '{"expression": "2+2=4"}'

Documentation: https://docs.qwedai.tech
-----------------------------------------
```

The key above is shown in QWED's documented [masked form](https://docs.qwedai.tech/api/authentication.md#masking-keys) — a real `qwed init` run prints the full v2 key (`qwed_live_` + 36 characters) once, at that moment.

#### Organization owner

`qwed init` signs up the organization it creates with an owner email and password. Use them to sign in to the organization later, for example to read its records in qwed-dashboard.

* **Email.** Pass `--owner-email`, or set `QWED_OWNER_EMAIL`. `qwed init` reads `QWED_OWNER_EMAIL` only when `--owner-email` is not given. An explicit empty or blank `--owner-email` is refused (or asked again interactively), never replaced by the environment variable. In interactive mode without either, `qwed init` asks for the email right after the organization name.
* **Password.** `qwed init` prompts for the password with hidden input and asks you to confirm it. There is no password flag, so the password stays out of your shell history. `qwed init` never prints the password or saves it to `.env`.

`qwed init` checks both values before it starts the server. The server runs the full email validation.

| Check | Rule |
| - | - |
| Email | A name before exactly one `@`, a domain with a dot inside it (`name@domain.tld`), and no whitespace |
| Password | 1 to 72 bytes in UTF-8 (the most bcrypt reads) |

If the email is already registered, signup fails with `Email already registered`. `qwed init` then tells you how to continue: sign in with that owner (for example to qwed-dashboard) and create a key there, or re-run with another email.

```bash theme={null}
qwed init --owner-email owner@example.com
```

```text theme={null}
  [ok] Organization created
  [ok] Owner: owner@example.com (sign in with the password you set)
```

#### Non-interactive mode (CI/CD)

Use `--non-interactive` to run `qwed init` without prompts. This is useful in CI pipelines, Docker builds, or automated provisioning scripts.

```bash theme={null}
qwed init \
  --non-interactive \
  --provider openai \
  --api-key "$OPENAI_API_KEY" \
  --model gpt-4o-mini \
  --organization-name my-team \
  --owner-email owner@example.com \
  --skip-tests
```

In non-interactive mode, `qwed init` reads the owner password from `QWED_OWNER_PASSWORD`. Set it from your CI secret store before you run the command:

```bash theme={null}
export QWED_OWNER_PASSWORD="$CI_QWED_OWNER_PASSWORD"
```

* **Owner email without a password.** If you pass an owner email (flag or `QWED_OWNER_EMAIL`) and `QWED_OWNER_PASSWORD` is unset or empty, `qwed init` prints `--owner-email with --non-interactive needs QWED_OWNER_PASSWORD set.`, exits with status `1`, and stops before the server starts.
* **Password isolation.** `qwed init` removes `QWED_OWNER_PASSWORD` from the environment of the local server it starts. The server never sees it outside the signup request.
* **No owner email.** `qwed init` creates the organization with a generated owner whose credentials are never shown or saved, and prints a warning that nobody can sign in to the organization. Its records cannot be read in qwed-dashboard.

> [!WARNING]
>
> An owner who can sign in is named only when the organization is created. Running `qwed init` again with `--owner-email` creates a new organization. It does not recover the records of the earlier one.

**All flags:**

| Flag | Description | Default |
| - | - | - |
| `--provider` | Provider to configure (`nvidia`, `openai`, `anthropic`, `gemini`, `custom`) | Interactive prompt (or `nvidia` in non-interactive mode) |
| `--api-key` | Provider API key (placeholder values are ignored) | Read from environment |
| `--base-url` | Base URL for custom/OpenAI-compatible providers | Provider default |
| `--model` | Model for the provider, saved to its model variable (such as `OPENAI_MODEL`) | None. Interactive mode asks for one. With `--non-interactive`, pass `--model` or set the provider's model variable, or `qwed init` stops |
| `--organization-name` | Organization name for API key bootstrap | Interactive prompt (or auto-generated) |
| `--owner-email` | Email of the organization owner, used to sign in later. The password is prompted for, or read from `QWED_OWNER_PASSWORD` with `--non-interactive` | `QWED_OWNER_EMAIL`, then interactive prompt (or a generated owner nobody can sign in as in non-interactive mode) |
| `--server-url` | Local QWED server URL | `http://localhost:8000` |
| `--non-interactive` | Run without prompts (CI-friendly) | `false` |
| `--skip-tests` | Skip the engine smoke tests | `false` |

> [!NOTE]
>
> API keys are never logged or displayed in full. The `.env` file is written atomically with owner-only permissions (`0600` on Unix) and symlink protection. `qwed init` auto-generates three server secrets on first run. `QWED_JWT_SECRET_KEY` signs JWTs. `QWED_API_KEY_LOOKUP_SECRET` derives the HMAC-SHA256 digests the server uses to look up API keys. `QWED_AUDIT_SECRET_KEY` signs the audit hash chain, and the server refuses to start without it. All three are passed into the local server it starts. If the project-root `.env` already sets `QWED_AUDIT_SECRET_KEY`, `qwed init` reuses that value. The fail-closed import check at server boot no longer trips on a fresh install.

**Local server runtime directory:**

During Step 3, `qwed init` starts a local API server and needs a writable location for the SQLite bootstrap database (`qwed.db`). The CLI resolves the runtime directory as follows:

1. **Current working directory** — used if it is writable
2. **`~/qwed-demo/`** — created and used as a fallback when the current directory is read-only (e.g., inside a container, a mounted volume, or a system-managed path)

The server process starts with its working directory set to this runtime directory, and `DATABASE_URL` is explicitly set to `sqlite:///<runtime_dir>/qwed.db`. This ensures bootstrap writes never target a read-only path.

> [!NOTE]
>
> If you previously encountered `sqlite3.OperationalError: attempt to write a readonly database` during `qwed init`, upgrade to v4.0.0 or later. The fix automatically resolves a writable directory so Step 3 succeeds regardless of where you run the command.

**Server identity handshake:**

Before bootstrapping an API key, `qwed init` proves that the server answering on `--server-url` holds this project's secrets. A healthy `GET /health` only shows that something answers on the port. A foreign process, or a stale QWED server started with older secret values, would otherwise receive the bootstrap request and issue a key that the intended server cannot resolve after a restart.

The handshake runs on both paths: when `qwed init` reuses an already-running server, and when it spawns a fresh one.

1. `qwed init` generates a fresh random nonce for each run (24 random bytes, URL-safe Base64).
2. It calls [`GET /health/identity`](https://docs.qwedai.tech/api/endpoints#get-%2Fhealth%2Fidentity) with that nonce.
3. The server returns HMAC-SHA256 proofs over the nonce under `QWED_JWT_SECRET_KEY`, `QWED_API_KEY_LOOKUP_SECRET`, and `QWED_AUDIT_SECRET_KEY` (`audit_hmac`). A server on a different audit key would write audit rows this project's server can't verify after a restart.
4. `qwed init` computes the expected proofs from its local secrets and compares them in constant time.

The secrets themselves never leave the server. Because each proof is bound to a one-time nonce, a captured response cannot be replayed in a later handshake. A narrow residual race is accepted by design: a local process that binds the port after it is released between the proof and the bootstrap could receive the key without holding either secret. Defending against that active local attacker is out of scope — the handshake addresses accidental staleness — so bootstrap only on hosts you control.

The handshake fails closed. `qwed init` prints `[x] Server identity check failed: <reason>`, exits with status `1`, and does not bootstrap a key when:

| Condition | Remediation |
| - | - |
| Local `QWED_JWT_SECRET_KEY`, `QWED_API_KEY_LOOKUP_SECRET`, or `QWED_AUDIT_SECRET_KEY` is empty | Re-run `qwed init` so fresh secrets are generated and persisted |
| The identity endpoint is unreachable | Start (or restart) the local server, then re-run `qwed init` |
| The server returns `404` — an older server without the endpoint | Upgrade the server to a version that exposes `GET /health/identity`, then re-run `qwed init` |
| The server returns `503` — it is running with no secrets configured | Set `QWED_JWT_SECRET_KEY`, `QWED_API_KEY_LOOKUP_SECRET`, and `QWED_AUDIT_SECRET_KEY` in the server's environment, restart the server so it loads them, then re-run `qwed init` |
| The server returns any other non-`200` status | Restart the local server, then re-run `qwed init` |
| The response body is malformed | Restart the local server, then re-run `qwed init` |
| Any proof doesn't match, including a server running an older release that returns no `audit_hmac` | Restart the server on the current release so it loads this project's `.env` secrets, then re-run `qwed init` |

> [!WARNING]
>
> Never bootstrap API keys against a server you don't recognize. If the identity check fails, stop whatever process holds the port before you re-run `qwed init`.

**Security guarantees:**

* Keys are entered with masked input and never displayed in full
* Keys are never written to logs
* `.env` is written with `0600` permissions (owner-read/write only on Unix)
* `.gitignore` protection is enforced before writing secrets — the command aborts if `.env` cannot be gitignored
* Atomic file writes prevent partial credential exposure
* Symlink targets are refused to prevent path traversal
* `.gitignore` protection is enforced before writing secrets

**Placeholder API key detection:** During onboarding, `qwed init` automatically detects and ignores common placeholder or dummy API key values. If your environment variable or `--api-key` argument contains a placeholder value, QWED treats it as empty and prompts you for a real key. Recognized placeholders include `your-api-key`, `changeme`, `placeholder`, `xxx`, and short NVIDIA keys such as `nvapi-xxxx`. This prevents silent failures caused by example values left in `.env` files or CI templates.

The following patterns are rejected:

* Generic placeholders: `test`, `dummy`, `sample`, `example`, `placeholder`, `changeme`, `null`, `none`
* Wildcard-style values: strings made entirely of `x`, `*`, or `.` characters (e.g. `xxx`, `****`)
* Template values: strings starting with `your-` or `replace-` that contain `key`
* Short NVIDIA keys: `nvapi-` prefixed values shorter than 20 characters

In non-interactive mode, the CLI checks three key sources: CLI argument, environment variable, and NVIDIA fallback. If all three resolve to a placeholder, the command reports that the key is required and exits. You get a clear error instead of a cryptic provider failure downstream.

**Re-running `qwed init`:** Running the command again merges new values into your existing `.env` file. Existing variables that you don't reconfigure are preserved. All three server secrets (`QWED_JWT_SECRET_KEY`, `QWED_API_KEY_LOOKUP_SECRET`, and `QWED_AUDIT_SECRET_KEY`) are retained across re-runs and never rotated automatically. Rotating the lookup secret invalidates every previously issued API key's lookup digest. Rotating the audit key invalidates the signature on every audit row already written. Projects onboarded before the audit key was required have no `QWED_AUDIT_SECRET_KEY` in `.env`. Re-run `qwed init` to add one; your existing secrets are kept. If your `.env` already sets `QWED_API_KEY_LOOKUP_SECRET` equal to `QWED_JWT_SECRET_KEY`, `qwed init` refuses to continue: reusing the JWT secret is a value the server will not boot with. Remove the offending line (a fresh, distinct value is generated) or set a distinct value manually. See [API key storage and migration](https://docs.qwedai.tech/api/authentication.md#api-key-storage-and-migration-v7-2) for why the lookup secret must stay stable.

***

### `qwed doctor` - system health check

Run a local health check that reports the status of verification engines, LLM provider connectivity, the QWED server, and the database. Use this after installation or when verification calls fail unexpectedly.

```bash theme={null}
qwed doctor
```

**What it checks:**

| Check | Description |
| - | - |
| Required engines | SymPy (math), Z3 (logic), AST (code), SQLGlot (SQL) |
| Optional engines | OpenCV (image verification) |
| Provider | Connectivity to the configured LLM provider |
| Server | Whether the QWED API server is reachable |
| Database | Health of the configured database |

The command exits with code `0` when all required checks pass (status `OPERATIONAL`) and code `1` when any required check fails (status `DEGRADED`). Optional engines that are missing do not cause a degraded status.

#### `.env` override behavior

The `doctor` command loads your `.env` file with **override mode enabled**. This means values in your `.env` file take precedence over variables already set in the current shell environment.

For example, if your shell has `ACTIVE_PROVIDER=ollama` but your `.env` file contains `ACTIVE_PROVIDER=openai_compat`, the doctor report reflects the `.env` value (`openai_compat`). This ensures the health report matches the configuration your application actually uses at runtime.

#### Database URL resolution

When checking database health, `doctor` prefers the `DATABASE_URL` environment variable (including values loaded from `.env`) over the application settings default. The resolution order is:

1. **`DATABASE_URL` from environment / `.env`** — used if present and non-empty
2. **Application settings** — the `DATABASE_URL` from your QWED configuration
3. **Default** — falls back to `sqlite:///./qwed.db`

This ensures the doctor report checks the database your deployment actually connects to, even when `DATABASE_URL` is set via environment variables rather than application config.

**Example output:**

```
$ qwed doctor

[QWED] System Health Check

Engines:
  [ok] SymPy    1.13.3   math engine ready
  [ok] Z3       4.13.4   logic engine ready
  [ok] AST      built-in code engine ready
  [ok] SQLGlot  26.30.0  sql engine ready
  [x]  OpenCV   missing  image verification
    -> pip install qwed[vision]

Provider:
  [ok] OpenAI - Connected (gpt-4o-mini)

Server:
  [ok] Running on http://localhost:8000

Database:
  [ok] src/qwed.db (healthy)

Status: OPERATIONAL (1 optional engine missing)
```

#### JSON output (CI/CD)

Use `--json` for machine-readable output. Useful in CI pipelines or monitoring scripts.

```bash theme={null}
qwed doctor --json
```

```json theme={null}
{
  "status": "OPERATIONAL",
  "optional_missing_count": 1,
  "engines": [
    { "name": "SymPy", "ready": true, "detail": "math engine ready", "version": "1.13.3" },
    { "name": "Z3", "ready": true, "detail": "logic engine ready", "version": "4.13.4" }
  ],
  "provider": { "ok": true, "label": "OpenAI", "message": "Connected (gpt-4o-mini)" },
  "server": { "running": true, "url": "http://localhost:8000" },
  "database": { "healthy": true, "location": "src/qwed.db" }
}
```

**Options:**

| Flag | Description | Default |
| - | - | - |
| `--json` | Print the full report as JSON instead of the human-readable table | `false` |

> [!TIP]
>
> Run `qwed doctor --json` in CI to gate deployments on system health. A non-zero exit code means at least one required component is degraded.

***

### `qwed test` - deterministic verification tests

Run the built-in deterministic test suite across the Math, Logic, SQL, and Code engines. Every test case has a known expected result and does not depend on an LLM, so outcomes are fully reproducible.

```bash theme={null}
qwed test
```

Use this command to confirm that verification engines are working correctly after installation, upgrades, or environment changes.

**What it tests:**

| Engine | Test cases |
| - | - |
| Math | Valid expression (`2+2=4`), invalid expression (`2+2=5`), large computation |
| Logic | Contradictions (`x>5 AND x<3`), satisfiable constraints, conflicting assignments |
| SQL | Valid SELECT, `OR 1=1` injection detection, stacked `DROP TABLE` detection |
| Code | Safe function, `eval(input())` detection, `curl \| bash` detection |

The command exits with code `0` when all tests pass and code `1` when any test fails.

**Example output:**

```
$ qwed test

[QWED] Running verification test suite...

Math:
  [ok] 2+2=4                  -> VERIFIED | ADMIT
  [ok] 2+2=5                  -> VERIFIED (false) | DENY
  [ok] 997*998*999            -> VERIFIED (994010994) | ADMIT

Logic:
  [ok] x>5 AND x<3            -> UNVERIFIABLE (contradiction) | DENY
  [ok] x>3 AND x<10           -> VERIFIED (x=4) | ADMIT
  [ok] approval=1 AND approval=0 -> UNVERIFIABLE (contradiction) | DENY

SQL:
  [ok] Valid SELECT            -> VERIFIED (safe) | ADMIT
  [ok] OR 1=1 injection       -> VERIFIED (unsafe) | DENY
  [ok] DROP TABLE stacked     -> VERIFIED (unsafe) | DENY

Code:
  [ok] Safe function           -> VERIFIED (safe) | ADMIT
  [ok] eval(input)             -> VERIFIED (unsafe, critical) | DENY
  [ok] curl | bash             -> VERIFIED (unsafe, critical) | DENY

12/12 tests passed. All engines ready.
```

Each line prints the verdict together with its admission decision, so `VERIFIED` never appears alone. `VERIFIED` means the engine decided the claim, not that the claim is safe to use. A claim proven false, such as `2+2=5`, is `VERIFIED (false) | DENY`. A test case whose self-check fails prints `FAILED` instead of a verdict, and an engine that raises prints `ERROR`.

#### Verbose mode

Use `--verbose` to show additional detail per test case, such as computed values and internal status codes.

```bash theme={null}
qwed test --verbose
```

```
Math:
  [ok] 2+2=4                  -> VERIFIED | ADMIT
    computed=4
  [ok] 2+2=5                  -> VERIFIED (false) | DENY
    computed=4
```

**Options:**

| Flag | Description | Default |
| - | - | - |
| `--verbose` | Show per-case detail (computed values, status codes) | `false` |

***

### `qwed verify` - one-shot verification

Verify a query and exit.

```bash theme={null}
qwed verify "What is 2+2?"
qwed verify "derivative of x^2" --provider openai
qwed verify "Is (p AND q) satisfiable?" --model llama3
```

**Options:**

* `--provider, -p` - LLM provider (`openai`, `anthropic`, `gemini`)
* `--model, -m` - Model name (e.g., `gpt-4o-mini`, `llama3`). Without it, the CLI reads the selected provider's model variable (see [Provider-specific variables](#provider-specific-variables)). With an explicit `--base-url`, it reads no model variable, so pass `--model`
* `--base-url` - Custom API endpoint (for Ollama: `http://localhost:11434/v1`)
* `--api-key` - API key (or use `QWED_API_KEY` env var). The provider `qwed init` saved gets a key only from `--api-key` on the command line or from its own key variable, never from `QWED_API_KEY`
* `--no-cache` - Disable caching
* `--quiet, -q` - Minimal output (for scripts)
* `--mask-pii` - Mask PII (emails, phone numbers, etc.) before sending queries to the LLM

QWED names no default model. When neither `--model` nor the provider's model variable is set, `qwed verify` stops with an error that names the variable to set. With an explicit `--base-url`, `qwed verify` reads no model variable, not even `CUSTOM_MODEL`, and the error asks for `--model`.

**Examples:**

```bash theme={null}
# Ollama (used when ACTIVE_PROVIDER is unset; reads OLLAMA_MODEL)
qwed verify "2+2"

# OpenAI
qwed verify "factorial of 5" --provider openai --api-key sk-...

# Custom endpoint
qwed verify "x^2 derivative" --base-url http://localhost:11434/v1 --model mistral

# Quiet mode
qwed verify "2+2" --quiet  # Just outputs: ✅ VERIFIED: 4
```

***

### `qwed interactive` - interactive mode

Start an interactive REPL session.

```bash theme={null}
qwed interactive
qwed interactive --provider openai
qwed interactive --model llama3
```

Without `--provider`, `qwed interactive` runs the provider `qwed init` saved in `ACTIVE_PROVIDER`, with its endpoint and key, as `qwed verify` does. It takes the model from `--model`, else from that provider's model variable, and stops with an error naming the variable when neither is set.

**Usage:**

```bash theme={null}
$ qwed interactive

🔬 QWED Interactive Mode
Type 'exit' or 'quit' to quit

> What is 2+2?
🔬 QWED Verification | Math Engine
📝 LLM Response: 4
✅ VERIFIED → 4

> stats
📊 Cache Statistics
Hits: 0
Misses: 1
Hit Rate: 0.0%

> exit
```

**Special commands:**

* `stats` - Show cache statistics
* `exit`, `quit`, `q` - Exit interactive mode

***

### `qwed cache` - cache management

Manage verification result cache.

#### `qwed cache stats`

Show cache statistics.

```bash theme={null}
$ qwed cache stats

📊 Cache Statistics
Hits: 156
Misses: 44
Hit Rate: 78.0%
Total Entries: 42/1000
Cache Size: 12.3 KB
```

#### `qwed cache clear`

Clear all cached results.

```bash theme={null}
$ qwed cache clear
Are you sure you want to clear the cache? [y/N]: y
✅ Cache cleared!
```

***

### `qwed pii` - PII detection

Test PII detection on arbitrary text. Requires the `qwed[pii]` extra.

```bash theme={null}
qwed pii "My email is john@example.com"
qwed pii "Card: 4532-1234-5678-9010"
```

**Example output:**

```
Original: My email is john@example.com
Masked:   My email is [EMAIL_REDACTED]

Detected: 1 entities
  - EMAIL: 1
```

> [!NOTE]
>
> Install PII support with `pip install 'qwed[pii]'` and download the spaCy model: `python -m spacy download en_core_web_lg`.

### `qwed context` - Verification Context utilities

> [!NOTE]
>
> New in v7.1.0

Work with [Verification Context](https://docs.qwedai.tech/specs/verification-context.md) documents from the command line. All three commands print JSON. `validate` and `from-diagnostic` exit with code 1 on any failure. `resolve` exits with code 1 when it can't read the file or the attestation check fails, but exits with code 0 when the commitment doesn't resolve. In CI, gate on the `resolved` field, not only the exit code.

`validate` and `resolve` check a document's [attestation](https://docs.qwedai.tech/specs/verification-context.md#version-1-1-and-the-issuer-attestation) by default: it must verify with the issuing server's keys and sign this document. Pass the keys with one of these options:

| Option | Description |
| - | - |
| `--server URL` | Fetch the keys from the server's `/.well-known/jwks.json` |
| `--jwks-file PATH` | Read the keys from a JWKS file, to verify offline or against keys you pinned |
| `--no-require-attestation` | Skip the attestation check. Use it for a v1.0 document, such as one `qwed context from-diagnostic` builds locally |

Pass `--server` or `--jwks-file`, not both. The attestation errors are:

| `error` | Cause |
| - | - |
| `attestation_required` | The document carries no `attestation` |
| `attestation_keys_required` | The document is signed, but you passed neither `--server` nor `--jwks-file` |
| `attestation_keys_unavailable` | The JWKS file could not be read, or the server's keys could not be fetched |
| `attestation_invalid` | The attestation does not verify with those keys, or does not sign this document |

#### `qwed context validate`

Validate a Verification Context document file against the JSON Schema of its `spec_version` (v1.0 or v1.1), including the verdict/`proof_ref` invariants, after the attestation check.

```bash theme={null}
qwed context validate --server http://localhost:8000 document.json
```

**Example output:**

```json theme={null}
{
  "valid": true
}
```

An invalid or unreadable file prints `{"valid": false, "error": "..."}` and exits with code 1. Error values are the attestation errors above, `invalid_document_file` (file could not be read or parsed as JSON) and `validation_failed` (schema or invariant violation).

#### `qwed context resolve`

Resolve the `proof_ref` evidence commitment of a document file. The command re-derives the SHA-256 commitment over the canonical bound payload and compares it to the stored value, after the attestation check.

```bash theme={null}
qwed context resolve --jwks-file jwks.json document.json
```

**Example output:**

```json theme={null}
{
  "resolved": true
}
```

`resolved` is `true` only for a schema-valid `VERIFIED` document whose attestation verifies and whose stored `proof_ref` matches the re-derived commitment. An attestation failure prints `{"resolved": false, "error": "..."}` and exits with code 1. Any other outcome prints `{"resolved": false}` and exits with code 0, so check the `resolved` field: a `proof_ref` that doesn't resolve confers no authority.

#### `qwed context from-diagnostic`

Create a Verification Context document from a [`DiagnosticResult`](https://docs.qwedai.tech/advanced/diagnostics) JSON file and print it. The command runs locally and has no signing key, so it prints an unsigned v1.0 document. Validate it with `--no-require-attestation`. For a signed document, use the server's [`POST /verification-context/from-diagnostic`](https://docs.qwedai.tech/api/endpoints#post-%2Fverification-context%2Ffrom-diagnostic).

```bash theme={null}
qwed context from-diagnostic \
  --diagnostic-file diag.json \
  --query "x + x = 2*x" \
  --verifier MathVerifier \
  --verifier-version 7.1.0 \
  --attestation-token "$QWED_ATTESTATION"
```

| Option | Required | Description |
| - | - | - |
| `--diagnostic-file` | Yes | Path to the `DiagnosticResult` JSON file |
| `--query` | Yes | The formal statement that was verified |
| `--verifier` | Yes | Name of the engine that produced the diagnostic |
| `--verifier-version` | No | Engine version. Defaults to the installed `qwed` version |
| `--attestation-token` | No | Signed attestation. Required to keep a `VERIFIED` status |

A malformed diagnostic file produces a `BLOCKED` document rather than a crash. A `VERIFIED` diagnostic without a valid attestation token fails closed to `BLOCKED` (fail-closed).

***

## Environment variables

### `ACTIVE_PROVIDER`

Set by `qwed init`. Tells the CLI which provider to use when no `--provider` or `--base-url` flag is given.

```bash theme={null}
# Set automatically by qwed init, or manually:
export ACTIVE_PROVIDER=openai
```

Values the CLI runs: `ollama`, `openai-compatible` (or `openai_compat`), `openai`, `openai_direct` (run as `openai`), `anthropic`, and `gemini`. The CLI reads the value as the server does: in any case, with `-`, `_`, or a space. When `ACTIVE_PROVIDER` is unset, the CLI runs Ollama.

A provider only the server runs, such as `azure_openai` or `auto`, stops `qwed verify` and `qwed interactive` with an error naming `ACTIVE_PROVIDER`. So does `anthropic` with `ANTHROPIC_ENDPOINT` set, because the CLI calls the Anthropic API directly.

### Provider-specific variables

These are written to `.env` by `qwed init` and loaded automatically via `python-dotenv`:

| Variable | Provider | Description |
| - | - | - |
| `OPENAI_API_KEY` | OpenAI | Your OpenAI API key |
| `OPENAI_MODEL` | OpenAI | Model name. Required: QWED names no default |
| `ANTHROPIC_API_KEY` | Anthropic | Your Anthropic API key |
| `ANTHROPIC_MODEL` | Anthropic | Model name. Required: QWED names no default |
| `GOOGLE_API_KEY` | Google Gemini | Your Google API key (`GEMINI_API_KEY` is read when it is unset) |
| `GEMINI_MODEL` | Google Gemini | Model name. Required: QWED names no default |
| `CUSTOM_BASE_URL` | NVIDIA NIM / Custom | Endpoint URL (required for custom; default `https://integrate.api.nvidia.com/v1` for NVIDIA) |
| `CUSTOM_API_KEY` | NVIDIA NIM / Custom | API key for the endpoint |
| `CUSTOM_MODEL` | NVIDIA NIM / Custom | Model name. Required: QWED names no default |
| `OLLAMA_BASE_URL` | Ollama | Ollama endpoint (default: `http://localhost:11434/v1`) |
| `OLLAMA_MODEL` | Ollama | Model name. Required: QWED names no default |
| `QWED_JWT_SECRET_KEY` | All | JWT secret for local server authentication (auto-generated by `qwed init`, retained on re-run) |
| `QWED_API_KEY_LOOKUP_SECRET` | All | HMAC-SHA256 key used to derive API-key lookup digests (auto-generated by `qwed init`, retained on re-run, must differ from `QWED_JWT_SECRET_KEY`). Never rotated automatically — see [API key storage and migration](https://docs.qwedai.tech/api/authentication.md#api-key-storage-and-migration-v7-2) |
| `QWED_AUDIT_SECRET_KEY` | All | HMAC key that signs the audit hash chain. Required: the server refuses to start without it (auto-generated by `qwed init`, retained on re-run, never rotated automatically). See [Audit trail setup](https://docs.qwedai.tech/advanced/compliance.md#3-audit-trail-setup) |

The recommended way to set these is via `qwed init`, which writes them to a `.env` file automatically. You can also export them manually.

### `DATABASE_URL`

Override the default database connection used by QWED. When set, `qwed doctor` uses this value for its health check instead of the application settings default.

```bash theme={null}
export DATABASE_URL="postgresql://user:pass@db.example.com:5432/qwed"
```

Supported schemes include `sqlite`, `postgresql`, `mysql`, and `mariadb`. For SQLite, use the `sqlite:///` prefix (e.g., `sqlite:///./qwed.db` for a relative path or `sqlite:////absolute/path/qwed.db` for an absolute path).

### `QWED_API_KEY`

API key that `qwed verify --api-key` reads when the flag is not given. It is used with an explicit `--provider` or `--base-url`. It is never sent to the provider `qwed init` saved in `ACTIVE_PROVIDER`, because it may be your QWED server's key: set that provider's own key variable (such as `OPENAI_API_KEY`) instead.

```bash theme={null}
export QWED_API_KEY="sk-proj-..."
export OPENAI_MODEL="gpt-4o-mini"
qwed verify "2+2" --provider openai
```

### `QWED_QUIET`

Disable colorful branding output.

```bash theme={null}
export QWED_QUIET=1
qwed verify "2+2"  # Minimal output
```

***

## Configuration

### Provider priority

When you run `qwed verify` without explicit flags, the CLI resolves the provider in this order:

1. **Command-line flags** — `--provider` or `--base-url` take highest precedence
2. **`ACTIVE_PROVIDER` env var** — set by `qwed init` or manually in `.env`
3. **Ollama**: when `ACTIVE_PROVIDER` is unset, the CLI runs Ollama at `OLLAMA_BASE_URL` (default `http://localhost:11434/v1`)

If you have run `qwed init`, the `ACTIVE_PROVIDER` value in your `.env` determines which provider and credentials are used automatically.

### Models

QWED names no default model. Each provider runs the model you configure, from `--model` or the provider's model variable (`OPENAI_MODEL`, `ANTHROPIC_MODEL`, `GEMINI_MODEL`, `OLLAMA_MODEL`, or `CUSTOM_MODEL`). The value is sent to the provider unchanged, so any model ID it serves works. When no model is set, `qwed verify` and `qwed interactive` stop with an error that names the variable to set. Commands that call no model, such as `qwed test`, need none.

***

## Output formats

### Colorful output (default)

```
🔬 QWED Verification | Math Engine
📝 LLM Response: 4
✅ VERIFIED → 4

────────────────────────────────────────────────────────
✨ Verified by QWED | Model Agnostic AI Verification
💚 If QWED saved you time, give us a ⭐ on GitHub!
👉 https://github.com/QWED-AI/qwed-verification
────────────────────────────────────────────────────────
```

### Quiet output (`--quiet`)

```
✅ VERIFIED: 4
```

### Error output

```
❌ Error: Ollama not running. Either:
1. Start Ollama: ollama serve
2. Or specify provider explicitly
```

***

### `qwed provider` - custom provider management

Manage custom LLM providers defined in `~/.qwed/providers.yaml`. See [Custom providers](https://docs.qwedai.tech/getting-started/custom-providers.md) for the full guide.

#### `qwed provider import <url>`

Import a community provider definition from a URL. The YAML file is downloaded, validated, and saved locally.

```bash theme={null}
qwed provider import https://raw.githubusercontent.com/my-org/qwed-providers/main/groq.yaml
```

**Example output:**

```
ℹ️  Downloading provider from https://raw.githubusercontent.com/my-org/qwed-providers/main/groq.yaml...
✅ Successfully imported provider 'groq'!
   You can now run 'qwed init' and select it from the interactive menu.
```

After importing, the new provider appears as a selectable option when you run `qwed init`.

> [!NOTE]
>
> Only `https` URLs are accepted, including across redirects. `http://` URLs are rejected before fetching. The imported YAML must contain `base_url` and `api_key_env` fields. Downloads time out after 10 seconds.

***

## Use cases

### 1. Quick verification

```bash theme={null}
qwed verify "Is 17 a prime number?"
```

### 2. Scripting

```bash theme={null}
#!/bin/bash
export QWED_QUIET=1
export QWED_API_KEY="sk-..."
export OPENAI_MODEL="gpt-4o-mini"

result=$(qwed verify "2+2" --provider openai)
if [[ $result == *"VERIFIED"* ]]; then
    echo "Math checks out!"
fi
```

### 3. Local development

```bash theme={null}
# Start Ollama
ollama serve

# Verify without API costs
export ACTIVE_PROVIDER=ollama
export OLLAMA_MODEL=llama3
qwed interactive

> What is the derivative of x^3?
✅ VERIFIED → 3*x**2
```

### 4. Cache performance testing

```bash theme={null}
# First run (cache miss)
time qwed verify "complex calculation"  # ~2 seconds

# Second run (cache hit)
time qwed verify "complex calculation"  # ~0.1 seconds!
```

***

## Troubleshooting

### "Ollama not running"

```bash theme={null}
# Check Ollama status
curl http://localhost:11434/v1/models

# Start Ollama
ollama serve
```

### "API key required"

```bash theme={null}
# Set env var
export QWED_API_KEY="sk-..."

# Or pass directly
qwed verify "query" --api-key sk-...
```

### "Module not found"

```bash theme={null}
# Install missing LLM clients
pip install openai anthropic google-generativeai

# Install verification engines
pip install sympy z3-solver
```

***

## Advanced features

### Caching behavior

* **Default:** Enabled (24h TTL)
* **Disable:** `--no-cache` flag
* **Clear:** `qwed cache clear`
* **Stats:** `qwed cache stats`

### Multiple providers

```bash theme={null}
# Compare results across providers
qwed verify "2+2" --provider openai
qwed verify "2+2" --base-url http://localhost:11434/v1 --model "$OLLAMA_MODEL"
qwed verify "2+2" --provider anthropic
```

***

## Related docs

* [QWEDLocal guide](https://docs.qwedai.tech/advanced/qwed-local.md) — Python API for local verification
* [Ollama integration](https://docs.qwedai.tech/advanced/ollama.md) — use free local LLMs
* [LLM configuration](https://docs.qwedai.tech/getting-started/llm-configuration.md) — provider setup details


## Related topics

- [QWED-Logic DSL reference](https://docs.qwedai.tech/api/dsl-reference.md)
- [QWED and Guardrails AI](https://docs.qwedai.tech/advanced/qwed-vs-guardrails.md)
- [QWEDLocal — client-side verification](https://docs.qwedai.tech/advanced/qwed-local.md)
- [Custom providers](https://docs.qwedai.tech/getting-started/custom-providers.md)
- [Python SDK](https://docs.qwedai.tech/sdks/python.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.