Base URL
Health check
GET /health
Check API status. No authentication required.GET /health/identity
Challenge-response proof that the server holds a given secret set.qwed init calls this endpoint before bootstrapping an API key to confirm it’s talking to the right local server. See qwed init. No authentication required.
Query parameters:
To verify, compute the same HMACs with your local secrets and compare them in constant time. The secrets themselves are never returned.
Error responses:
Servers released before this endpoint existed return
404.
Request limits
Every/verify route enforces a request body cap. QWED rejects an oversized body before the handler reads or parses it.
The server also bounds slow work so one request can’t stall the API for other callers. Self-hosters can tune these bounds with environment variables. See Availability limits.
Treat
503 responses from these limits as retryable. Back off and resend the request.
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:
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.
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.trust_boundary fields:
Mandatory attestation admission
The control plane treats attestation as an admission gate, not a decoration:- The final
statusandtrust_boundary.overall_statusare set byenforce_trust_decision(..., require_attestation=True)after signing, not by the raw verifier verdict. AVERIFIEDmath result is only surfaced oncecreate_verification_attestation()returnsISSUED. - If attestation signing fails for any reason, the response is downgraded to
BLOCKED(fail-closed). The attestation error code is echoed undertrust_boundary.attestation_errorand the top-level response omits the attestation token. - The attestation
qwed.query_hashbinds to the translated expression that the deterministic engine actually evaluated — not to the natural-languageuser_query. This matches the disclosedverification_scope: "translated_expression_only": the natural-language query still appears inresponse.user_queryfor display, but downstream consumers verifying the attestation should hash the translated expression, not the user prose.
AttestationResult contract and Trust boundary for the math-engine discussion.
The LLM provider call runs off the server’s event loop under
QWED_PROVIDER_TIMEOUT (default 30 seconds). If the provider doesn’t respond in time, the request fails closed with an ERROR status instead of stalling other requests.POST /verify/math
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.
Response (equation):
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.
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):
POST /verify/logic
Verify logical constraints. Routes through the QWED Control Plane. Request:
Response:
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.
Error response:
QWED_PROVIDER_TIMEOUT (default 30 seconds) and the Z3 solve under QWED_Z3_TIMEOUT (default 15 seconds). A timeout returns the ERROR response above.
POST /verify/code
Updated in v7.0.0 (breaking)
DiagnosticResult shape plus a separate admission decision.
Request:
Response (unsafe code — proven unsafe, not admitted):
POST /verify/sql
Updated in v7.0.0 (breaking)
DiagnosticResult shape plus a separate admission decision.
Request:
Response (safe query):
status: "VERIFIED" with developer_fields.is_valid: false, developer_fields.malicious_classification: true, and admission: "BLOCKED" — proving malice is a successful proof, so the truth verdict is preserved while admission is denied. BLOCKED status is reserved for verification failures: sql_verifier.parse_error, sql_verifier.schema_parse_error, sql_verifier.complexity_limit_exceeded, and sql_verifier.execution_error. See the SQL engine page for the full contract.
POST /verify/fact
Verify a factual claim against a provided context. Request:
Response:
POST /verify/consensus
New in v4.0.0 · Updated in v5.0.0
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.
Verification modes:
Response:
Only unanimous
VERIFIED engine outcomes can produce a VERIFIED consensus. Advisory-only sub-engines (statistical computation, code execution) always return UNVERIFIABLE, so a query answered solely by those paths never satisfies meets_requirement. See the Consensus engine page for the full sub-engine contract.
Each server process runs at most
QWED_SANDBOX_MAX_CONCURRENT sandbox containers at once (default 4). If a Python engine run can’t get a sandbox slot within QWED_SANDBOX_ACQUIRE_TIMEOUT (default 10 seconds), QWED rejects the run with Sandbox capacity reached — retry shortly. instead of starting another container. A capacity rejection never produces a VERIFIED result.
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:
Response:
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:
Response (execution succeeded):
status: "BLOCKED" with a constraint_id of stats_verifier.validation_error (translation or security validation failed), stats_verifier.execution_failure (sandbox execution failed), or stats_verifier.runtime_unavailable (Docker unavailable). Blocked results carry no proof_ref.
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.When the sandbox is at capacity (
QWED_SANDBOX_MAX_CONCURRENT containers already running) and no slot frees within QWED_SANDBOX_ACQUIRE_TIMEOUT, the execution is rejected with Sandbox capacity reached — retry shortly. and the result is BLOCKED.POST /verify/process
New in v4.0.1
Response (IRAC mode):
POST /verify/rag
New in v4.0.1
max_drm_rate accepts only string values for symbolic precision. Use fraction notation like "1/10" instead of 0.1.POST /verify/batch
Verify multiple items in a single request. Processes all items concurrently and returns aggregated results. Maximum 100 items per batch. Request:
Response:
Each entry in
items is a per-item response. For math items the verdict is the DiagnosticResult record nested under the item’s result — the same three-layer verdict shape used by POST /verify/math — so batch and single-item math verifications share one contract:
result.status—VERIFIED,UNVERIFIABLE, orBLOCKED.result.agent_message— short human-readable summary safe to surface to the caller.result.developer_fields— engine detail (including the legacyis_validflag for backward compatibility).result.proof_ref— cryptographic hash of the retained proof artifact. Present only whenresult.status == VERIFIED;nullotherwise. This is the authority bit — downstream gates must reject any item whoseproof_refisnull.
Math items: proof vs. simplification
Math items are routed through the trust boundary (enforce_trust_decision(require_attestation=True) on VERIFIED, with a signed attestation issued via create_verification_attestation). Three outcomes are possible:
If attestation signing fails on an otherwise-verified identity, the item is downgraded to
UNVERIFIABLE with developer_fields.constraint_id = "api.attestation.signing_error" — a math result is never returned as VERIFIED without a signed proof.
The pre-v6.0 top-level
is_valid field is preserved inside developer_fields.is_valid for backward compatibility, but new code should gate on result.status == "VERIFIED" and the presence of result.proof_ref. The batch response does not include a pass/fail aggregate because a boolean cannot represent the three-status verdict — count items[].result.status == "VERIFIED" entries directly if you need a success ratio.QWED_BATCH_ITEM_TIMEOUT (default 60 seconds). An item that times out is marked failed with its error recorded, and the rest of the job still completes.
GET /verify/batch/
Get the status and results of a batch verification job. Use this to poll results when processing large batches. Response:Verification Context endpoints
New in v7.1.0
POST /verification-context/from-diagnostic
Convert aDiagnosticResult into a schema-valid Verification Context document. Use this when you have an engine verdict and need the standardized VC record for auditing or downstream gating.
Request:
Response: the full Verification Context document (
spec_version, object, context, verdict).
Fail-closed behavior:
- A malformed
diagnosticpayload is converted to aBLOCKEDdocument instead of being rejected, so the audit trail records the failure. - A
VERIFIEDdiagnostic without anattestation_tokenfails closed toBLOCKED(trust_gate.mandatory_attestation_missing). A supplied invalid token fails closed toBLOCKED(trust_gate.invalid_attestation_token); a valid token with mismatched claims uses the correspondingtrust_gate.claims_*code. Attestation is enforced through the same trust boundary as/verify/*.
POST /verification-context/validate
Validate a Verification Context document against the v1.0 JSON Schema, including the verdict/proof_ref invariants.
Request:
{"valid": false, "error": "validation_failed"} with HTTP 200. Gate on the valid field.
POST /verification-context/resolve
Resolve theproof_ref evidence commitment of a Verification Context document. The server removes the stored proof_ref, re-derives the SHA-256 commitment over the canonical (RFC 8785) bound payload, and compares it to the stored value.
Request:
resolved is true only when the document has verdict: "VERIFIED", validates against the schema, and the stored proof_ref matches the re-derived commitment. Any other outcome (non-VERIFIED verdict, schema failure, mismatched or missing commitment) returns {"resolved": false} — a proof_ref that cannot be resolved confers no authority.
Agent endpoints
POST /agents/register
Register a new AI agent with QWED for verified agentic workflows. Request:
Agent types:
Response:
POST /agents//verify
Updated in v5.0.0
tool_schema is provided.
Request:
Headers:
- 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_schemais present. If the tool definition is flagged, the request is rejected with a403.
POST /agents//tools/
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 for details. Request:GET /agents//activity
Retrieve the audit log for a specific agent. Provides a full audit trail of all agent actions. Headers:
Response:
Attestation endpoints
GET /attestation/
Get an attestation by ID.POST /attestation/verify
Verify an attestation JWT. Request:Observability endpoints
GET /metrics
Returns global system metrics and per-tenant breakdowns. This is a cross-tenant, platform-operator capability: the caller must be a user ID listed inQWED_METRICS_OPERATOR_USER_IDS (comma-separated, fail-closed when unset). Organization roles (owner / admin) do not grant access — they are scoped to a single organization.
Headers (one of):
GET /metrics/
Returns metrics scoped to a specific organization. Tenants can only view their own metrics.GET /metrics/prometheus
Returns metrics in Prometheus text format for scraping by monitoring infrastructure. Requires the same platform-operator authentication asGET /metrics.
Headers (one of):
GET /logs
Returns verification logs for the authenticated tenant, ordered by most recent first. Query params:
Response:
Admin endpoints
New in v4.0.0
X-API-Key header, then authorize the request by the role of the user who owns the key. Every admin endpoint acts only on your own organization, which is the organization the API key belongs to.
Before QWED checks the role, the request must meet all of these conditions. Otherwise QWED returns 403 Forbidden:
- The API key is active, not revoked, and not expired.
- The key’s owning user is active and belongs to the key’s organization.
- The organization is active.
A caller whose role isn’t allowed gets
403 Forbidden with "Insufficient permissions for this operation". A missing or invalid API key returns 401 Unauthorized.
Before this change, every admin endpoint returned
401 Unauthorized, even for valid owner and admin keys. Admin endpoints now authorize by role and are scoped to the caller’s organization.GET /admin/compliance/export/csv
Export your organization’s audit trail as a CSV file (text/csv).
Passing another organization’s ID returns
403 Forbidden with "Cannot access another organization's data".
GET /admin/compliance/verify/
Cryptographically verify a specific audit log entry using HMAC-SHA256 and hash-chain validation. The entry must belong to your organization. QWED returns404 Not Found for a log ID in another organization, so the response doesn’t reveal whether that entry exists.
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:
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.GET /admin/compliance/report/soc2/
Generate a SOC 2 Type II compliance report for your organization.org_id must be your own organization ID. Another organization’s ID returns 403 Forbidden.
GET /admin/security/threats/
Returns a real-time threat summary for your organization, including blocked injection attempts and anomalous patterns.org_id must be your own organization ID. Another organization’s ID returns 403 Forbidden.
POST /admin/keys/rotate
Rotate an API key and return the new secret once. Owners, admins, and members can call this endpoint, subject to these rules:- Your own key: any allowed role can rotate a key it owns.
- Another user’s key: only an
owneroradmincan rotate it, and only when the key’s owner doesn’t hold a higher role. An admin can’t rotate an owner’s key. A member can’t rotate anyone else’s key. These cases return403 Forbidden. - Organization scope: the key must belong to your organization. A key ID in another organization returns
404 Not Found, without revealing that the key exists. - Live keys only: QWED treats a revoked or inactive key as absent and returns
404 Not Found. You can’t rotate a dead credential into a new working secret.
new_key_secret immediately. QWED returns it only in this response.
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.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.GET /badge/custom
Generate a custom badge with configurable label, message, color, and logo.Webhook endpoints
POST /webhooks/secret-scanning
Receive signed secret-leak notifications from GitHub secret scanning (or any GitHub-compatible partner scanner). The endpoint is the intake side of the leaked-QWED-key revocation pipeline: authenticated reports of leaked tokens are handed to the revocation sink after signature verification succeeds. The endpoint is intentionally unauthenticated — it must be reachable by external scanners that have no QWED credentials. The ECDSA signature over the raw request body is the only credential; nothing in the payload is parsed, logged, or persisted until the signature is verified against GitHub’s published public keys. Request headers:
Request body:
A non-empty JSON array of match objects. Extra fields on each item are ignored so scanners can add informational fields over time.
Envelope caps:
- Request body: 1 MiB. The stream is bounded incrementally — an over-cap body is rejected the moment the cap is exceeded and never fully buffered.
- Matches per request: 500.
- Signature header: 1024 characters before base64 decode.
- Active key: revoked immediately (
is_active=False,revoked_atset). QWED writes aSECRET_LEAK_AUTO_REVOKEDsecurity event that carries only thekey_preview, never the token. - Owner email: sent to the key’s user, or to the organization’s first active owner or admin as a fallback. SMTP has a 10-second timeout. A failed notification never rolls back the revocation.
- Already-revoked key or unknown token: no revocation or owner notification. Redeliveries are idempotent and never send a second email. When false-positive feedback is enabled (next bullet), QWED still reports a label for the match — for an unknown token,
false_positive. - Test keys: registered scanner patterns cover
qwed_live_keys only, soqwed_test_keys are not reported. The revocation sink itself does not filter by prefix. - False-positive feedback (opt-in): when
QWED_LEAK_FEEDBACK_ENABLED=true, QWED sends atrue_positiveorfalse_positivelabel per match toQWED_LEAK_FEEDBACK_URLafter the revocation attempt. Items carry SHA-256 token hashes unlessQWED_LEAK_FEEDBACK_SEND_RAW=trueis set with an HTTPS URL.
All signature-failure responses share a single generic detail (
"invalid webhook signature") so the failing verification step is not observable to the caller.
Configure your scanner (or a GitHub partner-program registration) to POST to
https://<your-qwed-host>/webhooks/secret-scanning. No API key or bearer token is provisioned on QWED’s side — the signature over the raw body is the sole credential, and QWED fetches the public keys from GitHub on demand. See Security hardening — Secret-scanning webhook intake for the trust model and operator configuration.