Base URL
https://api.qwedai.com/v1
Health check
GET /health
Check API status. No authentication required.
curl https://api.qwedai.com/v1/health
Response:
{
"status": "healthy",
"service": "QWED Platform",
"version": "5.0.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:
{
"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 for details.
{
"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 the result cannot be treated 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 |
The status field was previously set 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.
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
New in v5.0.0: verify_identity() numerical sampling fallback now returns UNKNOWN instead of LIKELY_EQUIVALENT.
Verify mathematical expressions or equations using SymPy symbolic computation.
Request:
{
"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):
{
"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):
{
"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 (e.g., implicit multiplication after division) return a warning: "ambiguous" field.
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.
POST /verify/logic
Verify logical constraints. Routes through the QWED Control Plane.
Request:
{
"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:
{
"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:
{
"status": "ERROR",
"error": "Internal verification error",
"provider_used": "openai_compat"
}
POST /verify/code
Check code for security vulnerabilities using AST analysis.
Request:
{
"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:
{
"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:
{
"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:
{
"is_valid": true,
"message": "Query is valid against the provided schema"
}
POST /verify/fact
Verify a factual claim against a provided context.
Request:
{
"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:
{
"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
New in v4.0.0 · Updated in v5.0.0
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.
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.
Request:
{
"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:
{
"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:
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:
{
"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:
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:
{
"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 |
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 page for details.
POST /verify/process
Verify the structural integrity of AI reasoning traces. Supports IRAC structural compliance checking and custom milestone validation with decimal scoring.
Request (IRAC mode):
{
"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):
{
"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 AI reasoning trace to validate |
mode | string | No | irac | irac or milestones |
milestones | string[] | Conditional | — | Required when mode is milestones |
Response (IRAC mode):
{
"verified": true,
"score": 1.0,
"missing_steps": [],
"mechanism": "Regex Pattern Matching (Deterministic)"
}
Response (milestones mode):
{
"verified": true,
"process_rate": 1.0,
"missed_milestones": []
}
| Status | Description |
|---|
| 400 | Invalid mode or missing milestones when mode is milestones |
POST /verify/rag
Verify that retrieved RAG chunks originate from the expected source document. Prevents Document-Level Retrieval Mismatch (DRM) hallucinations in RAG pipelines.
Request:
{
"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") |
max_drm_rate accepts only string values for symbolic precision. Use fraction notation like "1/10" instead of 0.1.
Response:
{
"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:
{
"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:
{
"job_id": "batch_abc123",
"status": "completed",
"results": [...],
"summary": {
"total": 3,
"verified": 3,
"failed": 0,
"success_rate": 100.0
}
}
GET /verify/batch/
Get the status and results of a batch verification job. Use this to poll results when processing large batches.
Response:
{
"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:
{
"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:
{
"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."
}
Store the agent_token immediately — it cannot be retrieved again after registration.
POST /agents//verify
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.
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.
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 for details.
Request:
{
"query": "What is 15% of 200?",
"provider": "openai",
"context": {
"conversation_id": "conv_abc123",
"step_number": 1
},
"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) |
tool_schema | object | No | MCP tool definition to scan. When provided, MCPPoisonGuard runs automatically |
Headers:
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//tools/
Submit an agent tool call for risk evaluation before execution.
Request:
{
"tool_params": {
"query": "SELECT * FROM users",
"dialect": "postgresql"
}
}
GET /agents//activity
Retrieve the audit log for a specific agent. Provides a full audit trail of all agent actions.
Headers:
X-Agent-Token: qwed_agent_...
Query params:
| Parameter | Type | Default | Description |
|---|
limit | integer | 20 | Maximum number of activity records to return |
Response:
{
"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/
Get an attestation by ID.
POST /attestation/verify
Verify an attestation JWT.
Request:
{
"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):
Authorization: Bearer <jwt_token>
X-API-Key: <api_key>
Response:
{
"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/
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):
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:
{
"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
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/
Cryptographically verify a specific audit log entry using HMAC and hash chain validation.
GET /admin/compliance/report/soc2/
Generate a SOC 2 Type II compliance report for an organization.
GET /admin/security/threats/
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:
{
"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/
Get a badge for any verification status (e.g., VERIFIED, FAILED, CORRECTED, BLOCKED, PENDING, ERROR).
GET /badge/attestation/
Get a badge for a specific attestation by ID.
GET /badge/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 |