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

# API endpoints

> Complete QWED API endpoint reference covering health checks, verification, batch operations, agent endpoints, observability, and admin routes.

## Base URL

```text theme={null}
https://api.qwedai.com/v1
```

## Health check

### GET /health

Check API status. No authentication required.

```bash theme={null}
curl https://api.qwedai.com/v1/health
```

**Response:**

```json theme={null}
{
  "status": "healthy",
  "service": "QWED Platform",
  "version": "5.1.0",
  "timestamp": "2024-12-20T12:00:00Z"
}
```

***

## Verification endpoints

### POST /verify/natural\_language

Main entry point for verifying natural language queries. Routes through the QWED Control Plane with multi-tenancy support.

**Request:**

```json theme={null}
{
  "query": "What is 15% of 200?",
  "provider": "openai"
}
```

| Parameter  | Type   | Required | Description                                          |
| ---------- | ------ | -------- | ---------------------------------------------------- |
| `query`    | string | Yes      | Natural language claim to verify                     |
| `provider` | string | No       | Preferred LLM provider (e.g., `openai`, `anthropic`) |

**Response:**

The response follows the `VerificationResult` schema. For math queries, the top-level `status` is `INCONCLUSIVE` because the LLM translation step is not formally verified — see [Trust boundary](/engines/math#trust-boundary) for details.

```json theme={null}
{
  "status": "INCONCLUSIVE",
  "user_query": "What is 15% of 200?",
  "translation": {
    "expression": "0.15 * 200",
    "claimed_answer": 30.0,
    "reasoning": "15% as decimal is 0.15, multiply by 200",
    "confidence": 0.95
  },
  "verification": {
    "calculated_value": 30.0,
    "is_correct": true,
    "diff": 0.0
  },
  "trust_boundary": {
    "query_interpretation_source": "llm_translation",
    "query_semantics_verified": false,
    "verification_scope": "translated_expression_only",
    "deterministic_expression_evaluation": true,
    "formal_proof": false,
    "translation_claim_self_consistent": true,
    "provider_used": "openai_compat",
    "overall_status": "INCONCLUSIVE"
  },
  "final_answer": 30.0,
  "provider_used": "openai_compat",
  "latency_ms": 245.3
}
```

**Status values for natural language math verification:**

| Status           | Meaning                                                                                                                                                           |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INCONCLUSIVE`   | Expression evaluation succeeded, but the LLM translation step is non-deterministic, so you cannot treat the result as a proven verdict on the original user query |
| `ERROR`          | The translated expression had a syntax error or the engine could not evaluate it                                                                                  |
| `NOT_MATH_QUERY` | The query was not recognized as a mathematical question                                                                                                           |
| `BLOCKED`        | Request blocked by security policy                                                                                                                                |

<Note>
  Previous versions set the `status` field to the inner engine result (e.g., `VERIFIED`). It now returns `INCONCLUSIVE` when the inner engine returns `VERIFIED` or `CORRECTION_NEEDED`, because the natural language pipeline involves a non-deterministic LLM translation step. Use the `trust_boundary` object to inspect the detailed verification breakdown. For fully deterministic results, use `POST /verify/math` directly.
</Note>

**`trust_boundary` fields:**

| Field                                 | Type    | Description                                                                              |
| ------------------------------------- | ------- | ---------------------------------------------------------------------------------------- |
| `query_interpretation_source`         | string  | Always `"llm_translation"`                                                               |
| `query_semantics_verified`            | boolean | Always `false` — QWED cannot verify that the LLM correctly interpreted the user's intent |
| `verification_scope`                  | string  | Always `"translated_expression_only"`                                                    |
| `deterministic_expression_evaluation` | boolean | `true` when the inner engine status was `VERIFIED` or `CORRECTION_NEEDED`                |
| `formal_proof`                        | boolean | Always `false`                                                                           |
| `translation_claim_self_consistent`   | boolean | Whether the translated expression matched its own claimed answer                         |
| `provider_used`                       | string  | LLM provider used for translation                                                        |
| `overall_status`                      | string  | Mirrors the top-level `status` field                                                     |

***

### POST /verify/math

<Info>Updated in v5.1.0: `verify_identity()` numerical sampling fallback now returns `BLOCKED` with `is_equivalent: false` instead of `UNKNOWN`. This fail-closed behavior ensures that sampling-only agreement is never mistaken for a verified identity.</Info>

Verify mathematical expressions or equations using SymPy symbolic computation.

**Request:**

```json theme={null}
{
  "expression": "x**2 + 2*x + 1 = (x+1)**2",
  "context": {
    "domain": "real"
  }
}
```

| Parameter    | Type   | Required | Description                                                               |
| ------------ | ------ | -------- | ------------------------------------------------------------------------- |
| `expression` | string | Yes      | Mathematical expression or equation. Use `=` for equations                |
| `context`    | object | No       | Optional context (e.g., `{"domain": "real"}` to restrict to real numbers) |

**Response (equation):**

```json theme={null}
{
  "is_valid": true,
  "result": true,
  "left_side": "x**2 + 2*x + 1",
  "right_side": "(x + 1)**2",
  "simplified_difference": "0",
  "message": "Identity is true"
}
```

**Response (expression):**

```json theme={null}
{
  "is_valid": true,
  "value": 4.0,
  "simplified": "4",
  "original": "2 + 2"
}
```

Symbolic expressions that cannot be evaluated to a numeric value include `is_symbolic: true`. Expressions involving complex numbers include `is_complex: true`.

**Ambiguous expressions:** Expressions with implicit multiplication after division (e.g., `1/2(3+1)`) are rejected with `is_valid: false`, `result: false`, and `status: "BLOCKED"`. The response includes `warning: "ambiguous"` and a `message` explaining why. Rewrite the expression with explicit parentheses or a `*` operator to remove the ambiguity.

```json theme={null}
{
  "is_valid": false,
  "result": false,
  "status": "BLOCKED",
  "warning": "ambiguous",
  "message": "Expression may be ambiguous due to implicit multiplication after division",
  "simplified": "..."
}
```

**Error cases:** Division by zero, `log(0)`, and square root of negative numbers in the real domain return `is_valid: false` with a descriptive `error` and `message`.

**Tolerance bounding:** When verifying an expression against an expected value with a `tolerance` parameter, the engine enforces a deterministic upper bound on the tolerance. The maximum allowed tolerance is `max(0.01, abs(calculated_value) * 0.01)`. If the requested tolerance exceeds this bound, the request returns a `BLOCKED` status. Invalid tolerance values (negative, `NaN`, `Infinity`, or non-numeric) are also rejected with `BLOCKED`.

**Response (tolerance exceeded):**

```json theme={null}
{
  "is_correct": false,
  "error": "Tolerance exceeds deterministic verification bound",
  "requested_tolerance": "1000",
  "max_allowed_tolerance": "0.02000000",
  "calculated_value": "2.000000",
  "precision_mode": "decimal",
  "status": "BLOCKED"
}
```

| Field                   | Type   | Description                                                     |
| ----------------------- | ------ | --------------------------------------------------------------- |
| `requested_tolerance`   | string | The tolerance value that was submitted                          |
| `max_allowed_tolerance` | string | The maximum tolerance the engine allows for the computed result |
| `calculated_value`      | string | The engine's computed result                                    |
| `precision_mode`        | string | `"decimal"` or `"float"` depending on the computation path      |

***

### POST /verify/logic

Verify logical constraints. Routes through the QWED Control Plane.

**Request:**

```json theme={null}
{
  "query": "(AND (GT x 5) (LT y 10))",
  "provider": "openai"
}
```

| Parameter  | Type   | Required | Description                                   |
| ---------- | ------ | -------- | --------------------------------------------- |
| `query`    | string | Yes      | Logical constraint in DSL or natural language |
| `provider` | string | No       | Preferred LLM provider                        |

**Response:**

```json theme={null}
{
  "status": "SAT",
  "model": {"x": "6", "y": "9"},
  "provider_used": "openai_compat"
}
```

The `provider_used` field indicates which LLM provider handled the translation step. This field is included in both success and error responses, which helps with debugging provider routing issues.

| Status    | Meaning                                               |
| --------- | ----------------------------------------------------- |
| `SAT`     | Satisfiable — a solution exists                       |
| `UNSAT`   | Unsatisfiable — no solution possible                  |
| `BLOCKED` | Request blocked by security policy (returns HTTP 403) |
| `ERROR`   | Internal verification error                           |

**Error response:**

```json theme={null}
{
  "status": "ERROR",
  "error": "Internal verification error",
  "provider_used": "openai_compat"
}
```

***

### POST /verify/code

Check code for security vulnerabilities using AST analysis.

**Request:**

```json theme={null}
{
  "code": "import os\nos.system('rm -rf /')",
  "language": "python"
}
```

| Parameter  | Type   | Required | Description                              |
| ---------- | ------ | -------- | ---------------------------------------- |
| `code`     | string | Yes      | Source code to analyze                   |
| `language` | string | No       | Programming language (default: `python`) |

**Response:**

```json theme={null}
{
  "is_safe": false,
  "vulnerabilities": [
    {
      "type": "os.system",
      "severity": "critical",
      "line": 2,
      "message": "Shell command execution"
    }
  ]
}
```

| Field             | Type    | Description                                                                    |
| ----------------- | ------- | ------------------------------------------------------------------------------ |
| `is_safe`         | boolean | Whether the code passed all security checks                                    |
| `vulnerabilities` | array   | List of detected vulnerabilities with type, severity, line number, and message |

***

### POST /verify/sql

Validate SQL queries against a provided schema.

**Request:**

```json theme={null}
{
  "query": "SELECT * FROM users WHERE id = 1",
  "schema_ddl": "CREATE TABLE users (id INT, name TEXT)",
  "dialect": "sqlite"
}
```

| Parameter    | Type   | Required | Description                                             |
| ------------ | ------ | -------- | ------------------------------------------------------- |
| `query`      | string | Yes      | SQL query to validate                                   |
| `schema_ddl` | string | Yes      | DDL schema definition (e.g., `CREATE TABLE` statements) |
| `dialect`    | string | No       | SQL dialect (default: `sqlite`)                         |

**Response:**

```json theme={null}
{
  "is_valid": true,
  "message": "Query is valid against the provided schema"
}
```

***

### POST /verify/fact

Verify a factual claim against a provided context.

**Request:**

```json theme={null}
{
  "claim": "Paris is the capital of France",
  "context": "France is a country in Western Europe. Its capital is Paris.",
  "provider": "anthropic"
}
```

| Parameter  | Type   | Required | Description                                |
| ---------- | ------ | -------- | ------------------------------------------ |
| `claim`    | string | Yes      | The factual statement to verify            |
| `context`  | string | Yes      | Reference text to verify the claim against |
| `provider` | string | No       | Preferred LLM provider                     |

**Response:**

```json theme={null}
{
  "verdict": "SUPPORTED",
  "confidence": 0.98,
  "reasoning": "The context explicitly states that the capital of France is Paris"
}
```

| Verdict        | Meaning                            |
| -------------- | ---------------------------------- |
| `SUPPORTED`    | Claim is supported by the context  |
| `REFUTED`      | Claim contradicts the context      |
| `INCONCLUSIVE` | Not enough evidence in the context |

***

### POST /verify/consensus

<Info>New in v4.0.0 · Updated in v5.0.0</Info>

Multi-engine consensus verification. Runs the query through multiple verification engines and requires agreement above a confidence threshold. This endpoint is rate-limited per API key.

Mathematical expressions are parsed and compared using `Decimal` arithmetic internally, which avoids floating-point drift when engines cross-check results.

<Note>The fact engine is excluded from automatic engine selection during consensus verification. Fact-based verification requires external context and will return an error if invoked without it, preventing self-referential consensus loops.</Note>

**Request:**

```json theme={null}
{
  "query": "The square root of 144 is 12",
  "verification_mode": "high",
  "min_confidence": 0.95
}
```

| Parameter           | Type   | Required | Default  | Description                       |
| ------------------- | ------ | -------- | -------- | --------------------------------- |
| `query`             | string | Yes      | —        | The claim to verify               |
| `verification_mode` | string | No       | `single` | `single`, `high`, or `maximum`    |
| `min_confidence`    | float  | No       | `0.95`   | Minimum confidence threshold, 0–1 |

**Verification modes:**

| Mode      | Engines | Use case                               |
| --------- | ------- | -------------------------------------- |
| `single`  | 1       | Fast, single engine verification       |
| `high`    | 2       | Higher confidence for important claims |
| `maximum` | 3+      | Critical domains (medical, financial)  |

**Response:**

```json theme={null}
{
  "final_answer": "12",
  "confidence": 98.5,
  "engines_used": 2,
  "agreement_status": "UNANIMOUS",
  "verification_chain": [
    {
      "engine": "math",
      "method": "symbolic",
      "result": "12",
      "confidence": 99.0,
      "latency_ms": 12.5,
      "success": true
    }
  ],
  "total_latency_ms": 45.2,
  "meets_requirement": true
}
```

| Field                | Type    | Description                               |
| -------------------- | ------- | ----------------------------------------- |
| `confidence`         | float   | Confidence as a percentage (0–100)        |
| `verification_chain` | array   | Detailed results from each engine         |
| `meets_requirement`  | boolean | Whether confidence meets `min_confidence` |

| Status | Description                                                                             |
| ------ | --------------------------------------------------------------------------------------- |
| 400    | Invalid verification mode                                                               |
| 422    | Consensus confidence below the requested `min_confidence` threshold                     |
| 503    | Secure Docker sandbox unavailable — required for Python engine in `high`/`maximum` mode |

***

### POST /verify/image

Verify claims about image content. Accepts multipart form data with an image file (max 10 MB). Supported formats: PNG, JPEG, GIF, WebP.

**Request:**

```bash theme={null}
curl -X POST https://api.qwedai.com/v1/verify/image \
  -H "X-API-Key: qwed_your_key" \
  -F "image=@photo.jpg" \
  -F "claim=The image is 800x600 pixels"
```

| Parameter | Type   | Required | Description                                  |
| --------- | ------ | -------- | -------------------------------------------- |
| `image`   | file   | Yes      | Image file (max 10 MB; PNG, JPEG, GIF, WebP) |
| `claim`   | string | Yes      | The claim about the image to verify          |

**Response:**

```json theme={null}
{
  "verdict": "SUPPORTED",
  "confidence": 0.95,
  "reasoning": "Image dimensions match the claimed resolution",
  "methods_used": ["metadata_analysis", "pixel_inspection"]
}
```

| Verdict        | Meaning                                          |
| -------------- | ------------------------------------------------ |
| `SUPPORTED`    | Claim is confirmed by image analysis             |
| `REFUTED`      | Claim contradicts image analysis                 |
| `INCONCLUSIVE` | Cannot determine from available methods          |
| `VLM_REQUIRED` | Visual Language Model needed for deeper analysis |

***

### POST /verify/stats

Verify statistical claims against CSV data. Accepts multipart form data with a CSV file.

Statistical code execution requires the secure Docker sandbox. If Docker is unavailable, the endpoint returns HTTP 503. If the verification is blocked by a security policy, it returns HTTP 403.

**Request:**

```bash theme={null}
curl -X POST https://api.qwedai.com/v1/verify/stats \
  -H "X-API-Key: qwed_your_key" \
  -F "file=@data.csv" \
  -F "query=The average salary is above 50000"
```

| Parameter | Type   | Required | Description                 |
| --------- | ------ | -------- | --------------------------- |
| `file`    | file   | Yes      | CSV data file               |
| `query`   | string | Yes      | Statistical claim to verify |

**Response:**

```json theme={null}
{
  "status": "SUCCESS",
  "computed_value": 52340.5,
  "message": "Statistical claim verified against dataset"
}
```

| Status | Description                                                                             |
| ------ | --------------------------------------------------------------------------------------- |
| 403    | Verification blocked by security policy (e.g., generated code failed AST safety checks) |
| 503    | Secure Docker sandbox unavailable — statistical verification requires Docker            |

<Note>
  Statistical verification requires a running Docker daemon. If Docker is unavailable, the endpoint returns HTTP 503 instead of falling back to in-process execution. See the [Stats engine](/engines/stats) page for details.
</Note>

***

### POST /verify/process

<Info>New in v4.0.1</Info>

Verify the structural integrity of LLM reasoning traces. Supports IRAC structural compliance checking and custom milestone validation with decimal scoring.

**Request (IRAC mode):**

```json theme={null}
{
  "trace": "The issue is whether the contract was breached. The rule is Article 2 of the UCC. Applying this rule, the defendant failed to deliver on time. In conclusion, breach occurred.",
  "mode": "irac"
}
```

**Request (milestones mode):**

```json theme={null}
{
  "trace": "Risk assessment complete. Compliance check passed. Implementation timeline defined.",
  "mode": "milestones",
  "milestones": ["risk assessment", "compliance check", "implementation"]
}
```

| Parameter    | Type      | Required    | Default | Description                          |
| ------------ | --------- | ----------- | ------- | ------------------------------------ |
| `trace`      | string    | Yes         | —       | The LLM reasoning trace to validate  |
| `mode`       | string    | No          | `irac`  | `irac` or `milestones`               |
| `milestones` | string\[] | Conditional | —       | Required when `mode` is `milestones` |

**Response (IRAC mode):**

```json theme={null}
{
  "verified": true,
  "score": 1.0,
  "missing_steps": [],
  "mechanism": "Regex Pattern Matching (Deterministic)"
}
```

**Response (milestones mode):**

```json theme={null}
{
  "verified": true,
  "process_rate": 1.0,
  "missed_milestones": []
}
```

| Status | Description                                                      |
| ------ | ---------------------------------------------------------------- |
| 400    | Invalid mode or missing `milestones` when `mode` is `milestones` |

***

### POST /verify/rag

<Info>New in v4.0.1</Info>

Verify that retrieved RAG chunks originate from the expected source document. Prevents Document-Level Retrieval Mismatch (DRM) hallucinations in RAG pipelines.

**Request:**

```json theme={null}
{
  "target_document_id": "contract_nda_v2",
  "chunks": [
    { "id": "c1", "metadata": { "document_id": "contract_nda_v2" } },
    { "id": "c2", "metadata": { "document_id": "contract_nda_v1" } }
  ],
  "max_drm_rate": "0"
}
```

| Parameter            | Type      | Required | Default | Description                                                                                  |
| -------------------- | --------- | -------- | ------- | -------------------------------------------------------------------------------------------- |
| `target_document_id` | string    | Yes      | —       | Expected source document ID                                                                  |
| `chunks`             | object\[] | Yes      | —       | Array of chunk objects with metadata                                                         |
| `max_drm_rate`       | string    | No       | `"0"`   | Maximum tolerable mismatch fraction as a `Fraction`-compatible string (e.g. `"0"`, `"1/10"`) |

<Note>`max_drm_rate` accepts only string values for symbolic precision. Use fraction notation like `"1/10"` instead of `0.1`.</Note>

**Response:**

```json theme={null}
{
  "verified": false,
  "risk": "DOCUMENT_RETRIEVAL_MISMATCH",
  "drm_rate": 0.5,
  "chunks_checked": 2,
  "mismatched_count": 1
}
```

| Status | Description                                                                                           |
| ------ | ----------------------------------------------------------------------------------------------------- |
| 400    | Invalid request payload (empty `target_document_id`, empty `chunks`, or invalid `max_drm_rate` value) |

***

### POST /verify/batch

Verify multiple items in a single request. Processes all items concurrently and returns aggregated results. Maximum 100 items per batch.

**Request:**

```json theme={null}
{
  "items": [
    {"query": "What is 2+2?", "type": "natural_language"},
    {"query": "(AND (GT x 5) (LT y 10))", "type": "logic"},
    {"query": "x**2 + 2*x + 1 = (x+1)**2", "type": "math"}
  ]
}
```

| Parameter        | Type   | Required | Description                                                                                                 |
| ---------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| `items`          | array  | Yes      | Array of verification items (max 100)                                                                       |
| `items[].query`  | string | Yes      | The claim to verify                                                                                         |
| `items[].type`   | string | No       | Verification type: `natural_language`, `logic`, `math`, `code`, `fact`, `sql` (default: `natural_language`) |
| `items[].params` | object | No       | Additional parameters for the verification                                                                  |

**Response:**

```json theme={null}
{
  "job_id": "batch_abc123",
  "status": "completed",
  "results": [...],
  "summary": {
    "total": 3,
    "verified": 3,
    "failed": 0,
    "success_rate": 100.0
  }
}
```

#### Math items: proof vs. simplification

Math items are split into two distinct outcomes so that symbolic transformation is never mistaken for a verified claim:

* **Equality or proof claims** (e.g., `x**2 + 2*x + 1 = (x+1)**2`) take the proof path. Verified identities return `is_valid: true` with `message: "Identity verified"`. Non-identities return `is_valid: false` with `message: "Not equal"`.
* **Non-equality expressions** (e.g., `x + x`) are simplified only. They are returned as explicit simplification results, not as valid proofs.

Simplification-only results include:

| Field        | Value                                                                  |
| ------------ | ---------------------------------------------------------------------- |
| `is_valid`   | `false`                                                                |
| `status`     | `"SIMPLIFIED"`                                                         |
| `simplified` | The simplified expression as a string                                  |
| `type`       | `"math"`                                                               |
| `message`    | `"Expression simplified, but no equality or proof claim was provided"` |

**Example response for `"x + x"`:**

```json theme={null}
{
  "is_valid": false,
  "status": "SIMPLIFIED",
  "simplified": "2*x",
  "type": "math",
  "message": "Expression simplified, but no equality or proof claim was provided"
}
```

<Warning>
  A successfully simplified expression is not a verified claim. Treat `status: "SIMPLIFIED"` results as transformed input, not as proof. To get a `VERIFIED` outcome from a math item, submit an equality (e.g., `x + x = 2*x`).
</Warning>

### GET /verify/batch/{job_id}

Get the status and results of a batch verification job. Use this to poll results when processing large batches.

**Response:**

```json theme={null}
{
  "job_id": "batch_abc123",
  "status": "completed",
  "results": [...]
}
```

| Status | Description                                             |
| ------ | ------------------------------------------------------- |
| 403    | Access denied — job belongs to a different organization |
| 404    | Job not found                                           |

***

## Agent endpoints

### POST /agents/register

Register a new AI agent with QWED for verified agentic workflows.

**Request:**

```json theme={null}
{
  "name": "FinanceBot",
  "agent_type": "semi_autonomous",
  "description": "Financial analysis agent",
  "permissions": ["math", "logic", "code"],
  "max_cost_per_day": 50.0
}
```

| Parameter          | Type   | Required | Default      | Description                                                       |
| ------------------ | ------ | -------- | ------------ | ----------------------------------------------------------------- |
| `name`             | string | Yes      | —            | Agent display name                                                |
| `agent_type`       | string | No       | `autonomous` | `autonomous`, `semi_autonomous`, or `assistant`                   |
| `description`      | string | No       | —            | Agent description                                                 |
| `permissions`      | array  | No       | `[]`         | Allowed verification engine names (e.g., `math`, `logic`, `code`) |
| `max_cost_per_day` | float  | No       | `100.0`      | Daily budget cap in USD                                           |

**Agent types:**

| Type              | Description                            |
| ----------------- | -------------------------------------- |
| `autonomous`      | Fully autonomous agent (AutoGPT-style) |
| `semi_autonomous` | Requires approval for critical actions |
| `assistant`       | Human-in-the-loop                      |

**Response:**

```json theme={null}
{
  "agent_id": 42,
  "agent_token": "qwed_agent_...",
  "name": "FinanceBot",
  "type": "semi_autonomous",
  "status": "active",
  "max_cost_per_day": 50.0,
  "message": "Agent registered successfully. Store the agent_token securely."
}
```

<Warning>Store the `agent_token` immediately — it cannot be retrieved again after registration.</Warning>

***

### POST /agents/{agent_id}/verify

<Info>Updated in v5.0.0</Info>

Verify a claim using an agent token. Creates an auditable record tied to the agent. Security checks are enforced server-side — exfiltration detection always runs, and MCP poisoning detection runs automatically when a `tool_schema` is provided.

<Warning>**Breaking change (v5.0.0):** The `security_checks` request field has been removed. Security checks are now mandatory and enforced server-side. You no longer need to (or can) opt in to exfiltration or MCP poison checks.</Warning>

<Warning>**Breaking change (v5.0.0):** The `context` field with `conversation_id` and `step_number` is now required for all agent action verification requests. Requests without these fields are rejected with error code `QWED-AGENT-CTX-001`. See [conversation controls](/advanced/agent-verification#conversation-controls) for details.</Warning>

**Request:**

```json theme={null}
{
  "query": "What is 15% of 200?",
  "provider": "openai",
  "context": {
    "conversation_id": "conv_abc123",
    "step_number": 1,
    "pre_action_state_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "state_source": "db_snapshot"
  },
  "tool_schema": {
    "name": "fetch_report",
    "description": "Fetch quarterly report data"
  }
}
```

| Parameter                       | Type    | Required    | Description                                                                                                                                                                                                                     |
| ------------------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `query`                         | string  | Yes         | The claim to verify                                                                                                                                                                                                             |
| `provider`                      | string  | No          | LLM provider preference                                                                                                                                                                                                         |
| `context`                       | object  | Yes         | Action context — see below                                                                                                                                                                                                      |
| `context.conversation_id`       | string  | Yes         | Unique identifier for the conversation/session                                                                                                                                                                                  |
| `context.step_number`           | integer | Yes         | Monotonically increasing step counter (>= 1)                                                                                                                                                                                    |
| `context.pre_action_state_hash` | string  | Conditional | SHA-256 hex digest (64 lowercase hex characters) of the world state before the action. Must be provided together with `state_source`. Enables [LOOP-004](/specs/agent#94-progress-aware-doom-loop-detection-loop-004) detection |
| `context.state_source`          | string  | Conditional | Declares how `pre_action_state_hash` was derived. One of: `file_tree`, `db_snapshot`, `conversation_digest`, `git_tree`, `custom`. Must be provided together with `pre_action_state_hash`                                       |
| `tool_schema`                   | object  | No          | MCP tool definition to scan. When provided, `MCPPoisonGuard` runs automatically                                                                                                                                                 |

**Headers:**

```text theme={null}
X-Agent-Token: qwed_agent_...
```

**Security behavior:**

* **Exfiltration check:** Always runs on every agent verification request. If the query payload is flagged, the request is rejected with a `403`.
* **MCP poison check:** Runs automatically when `tool_schema` is present. If the tool definition is flagged, the request is rejected with a `403`.

**Error responses:**

| Status | Description                                                 |
| ------ | ----------------------------------------------------------- |
| 401    | Invalid agent token                                         |
| 403    | Agent budget exceeded or request blocked by security checks |
| 500    | Internal agent verification error                           |

***

### POST /agents/{agent_id}/tools/{tool_name}

Submit an agent tool call for risk evaluation before execution. Unknown tools (not on the safe or dangerous operations list) are denied by default, regardless of risk score. See [tool approval policy](/advanced/agent-verification#tool-approval-policy) for details.

**Request:**

```json theme={null}
{
  "tool_params": {
    "query": "SELECT * FROM users",
    "dialect": "postgresql"
  }
}
```

**Possible outcomes:**

| Outcome                            | When                                     |
| ---------------------------------- | ---------------------------------------- |
| Approved                           | Tool is on the safe operations list      |
| Blocked (manual approval required) | Tool is on the dangerous operations list |
| Blocked (default-deny)             | Tool is not on either list               |

***

### GET /agents/{agent_id}/activity

Retrieve the audit log for a specific agent. Provides a full audit trail of all agent actions.

**Headers:**

```text theme={null}
X-Agent-Token: qwed_agent_...
```

**Query params:**

| Parameter | Type    | Default | Description                                  |
| --------- | ------- | ------- | -------------------------------------------- |
| `limit`   | integer | `20`    | Maximum number of activity records to return |

**Response:**

```json theme={null}
{
  "agent_id": 42,
  "agent_name": "FinanceBot",
  "total_activities": 5,
  "current_cost_today": 0.05,
  "max_cost_per_day": 50.0,
  "activities": [
    {
      "type": "verification_request",
      "description": "Query: What is 15% of 200?",
      "status": "success",
      "cost": 0.01,
      "timestamp": "2026-03-20T12:00:00Z"
    }
  ]
}
```

| Status | Description         |
| ------ | ------------------- |
| 401    | Invalid agent token |

***

## Attestation endpoints

### GET /attestation/{id}

Get an attestation by ID.

### POST /attestation/verify

Verify an attestation JWT.

**Request:**

```json theme={null}
{
  "jwt": "eyJhbGciOiJFUzI1NiIs..."
}
```

***

## Observability endpoints

### GET /metrics

Returns global system metrics and per-tenant breakdowns. Requires admin authentication — you must provide either a JWT token for an active user with the `owner` or `admin` role, or an API key linked to an active `owner` or `admin` user.

**Headers (one of):**

```text theme={null}
Authorization: Bearer <jwt_token>
X-API-Key: <api_key>
```

**Response:**

```json theme={null}
{
  "global": { "total_requests": 1250, "avg_latency_ms": 82.3 },
  "tenants": { "1": { "requests": 500 }, "2": { "requests": 750 } }
}
```

| Status | Description                                    |
| ------ | ---------------------------------------------- |
| 401    | No authentication provided                     |
| 403    | Authenticated but not an active admin or owner |

### GET /metrics/{organization_id}

Returns metrics scoped to a specific organization. Tenants can only view their own metrics.

| Status | Description                                         |
| ------ | --------------------------------------------------- |
| 403    | You can only view metrics for your own organization |

### GET /metrics/prometheus

Returns metrics in Prometheus text format for scraping by monitoring infrastructure. Requires the same admin authentication as `GET /metrics`.

**Headers (one of):**

```text theme={null}
Authorization: Bearer <jwt_token>
X-API-Key: <api_key>
```

| Status | Description                                    |
| ------ | ---------------------------------------------- |
| 401    | No authentication provided                     |
| 403    | Authenticated but not an active admin or owner |

### GET /logs

Returns verification logs for the authenticated tenant, ordered by most recent first.

**Query params:**

| Parameter | Type    | Default | Description                      |
| --------- | ------- | ------- | -------------------------------- |
| `limit`   | integer | `10`    | Maximum number of logs to return |

**Response:**

```json theme={null}
{
  "organization_id": 1,
  "organization_name": "Acme Corp",
  "total_logs": 3,
  "logs": [
    {
      "id": 42,
      "query": "What is 2+2?",
      "is_verified": true,
      "domain": "MATH",
      "timestamp": "2026-03-20T12:00:00Z"
    }
  ]
}
```

***

## Admin endpoints

<Info>New in v4.0.0</Info>

These endpoints require the `admin:all` API key scope.

### GET /admin/compliance/export/csv

Export the full audit trail as a CSV file.

### GET /admin/compliance/verify/{log_id}

Cryptographically verify a specific audit log entry using HMAC-SHA256 and hash-chain validation.

The response reports three independent checks: payload hash, HMAC signature (compared in constant time), and chain linkage to the prior entry within the same organization. Genesis entries must have a `null` `previous_hash`; non-genesis entries must link to a non-empty prior entry hash. Entries with malformed stored result payloads cause verification to fail closed with `SecurityError` instead of returning `"valid": true`.

Hash matching accepts either the current canonical payload (which includes `raw_llm_output`) or the legacy canonical payload, so entries written before that field was covered remain verifiable after the upgrade.

**Response:**

```json theme={null}
{
  "valid": true,
  "checks": {
    "hash_valid": true,
    "signature_valid": true,
    "chain_valid": true
  },
  "errors": [],
  "log_id": 42,
  "timestamp": "2026-03-20T12:00:00Z"
}
```

<Note>
  `AuditLogger` requires `QWED_AUDIT_SECRET_KEY` to be set. If the key is missing or persisted chain continuity cannot be loaded, initialization raises `SecurityError` and verification endpoints fail closed.
</Note>

### GET /admin/compliance/report/soc2/{org_id}

Generate a SOC 2 Type II compliance report for an organization.

### GET /admin/security/threats/{org_id}

Returns a real-time threat summary for an organization, including blocked injection attempts and anomalous patterns.

### POST /admin/keys/rotate

Rotate an API key. Available to admin and member roles.

**Request:**

```json theme={null}
{
  "key_id": "key_abc123"
}
```

***

## Badge endpoints

All badge endpoints return SVG images (`image/svg+xml`). You can embed them directly in Markdown or HTML.

### GET /badge/verified

Get a verified or failed badge SVG.

| Parameter  | Type    | Default | Description                                |
| ---------- | ------- | ------- | ------------------------------------------ |
| `verified` | boolean | `true`  | Whether to show a verified or failed badge |

### GET /badge/status/{status}

Get a badge for any verification status (e.g., `VERIFIED`, `FAILED`, `CORRECTED`, `BLOCKED`, `PENDING`, `ERROR`).

### GET /badge/attestation/{attestation_id}

Get a badge for a specific attestation by ID.

### GET /badge/engine/{engine}

Get a badge for a specific verification engine.

| Parameter  | Type    | Default | Description                                |
| ---------- | ------- | ------- | ------------------------------------------ |
| `verified` | boolean | `true`  | Whether to show a verified or failed badge |

### GET /badge/custom

Generate a custom badge with configurable label, message, color, and logo.

| Parameter | Type    | Default    | Description                 |
| --------- | ------- | ---------- | --------------------------- |
| `label`   | string  | `QWED`     | Left side label             |
| `message` | string  | `verified` | Right side message          |
| `color`   | string  | —          | Hex color (e.g., `#00C853`) |
| `logo`    | boolean | `true`     | Include QWED logo           |
