Audience
- System Administrators: Use the API endpoints described below to manage compliance tasks.
- Developers: Integrate these features into your applications using the QWED Python SDK.
1. Security activity summary
GET /admin/compliance/report/soc2/{org_id} returns a security activity summary for your organization over the last 90 days. QWED computes every value from its own records: verification and security event counts, active users and API keys, and a signature and hash-chain check of the audit trail. Use it as input when you prepare for an audit or review security activity.
Prerequisites:
- An API key whose owner has the
owneroradminrole, or a signed-in session (Authorization: Bearer <jwt_token>) for anowneroradmin. See Session sign-in for admin endpoints. QWED_AUDIT_SECRET_KEYset on the server. Without it, the audit trail check can’t run andaudit_trail.statusisNOT_VERIFIED.
org_id must be your own organization ID. Another organization’s ID returns 403 Forbidden.
Response fields
Audit trail status
For every status other than
VERIFIED, proof_ref and evidence are null. total_entries, verified_entries, failed_entries, and legacy_entries count the entries checked, and first_failed_entry_id names the lowest failing entry, or null. errors lists at most 20 errors. total_errors gives the full count, and errors_truncated is true when the list was cut.
The check covers the audit entries that were recorded. It doesn’t show that every request wrote an entry, as the
scope field states.Audit trail verification
Every/verify/* endpoint, the Verification Context routes, POST /agents/{agent_id}/verify, and POST /audit/seed append their audit rows through AuditLogger. POST /verify/batch appends one row per item, including failed items. Each new row is HMAC-signed and hash-chained to the previous row for the same organization. If QWED can’t append the row, the request returns 500 with audit persistence failed instead of a result with no audit record. For a batch, the affected item is marked failed with that error and no result.
GET /audit/logs and GET /audit/logs/{log_id} return each row’s stored verdict and its chain fields (entry_hash, plus previous_hash and hmac_signature on the detail route). Each row also records:
admission: the admission decision its route made (ADMITorBLOCKED, orADMITorDENYforPOST /verification-context/from-diagnostic).nullfor routes that decide no admission and for older rows.api_key_id: the API key that authenticated the request.nullfor signed-in sessions, the audit seed, the agent route, and older rows.verifier_version: the QWED server version that wrote the row.latency_ms: whole milliseconds from the start of the HTTP request to when QWED began writing the audit row, before it waits for the append lock.nullfor rows written outside a request and for older rows.
GET /audit/logs with the before cursor until next_before is null. See Audit endpoints.
Verify the cryptographic integrity of your audit logs. Verification detects edited entries, reordered entries, and removals that break a link between the entries that remain. It does not prove the trail is complete: deleting the newest entries leaves a valid chain, so detecting that needs a separately stored checkpoint of the latest hash.
Per-entry verification covers three checks:
hash_valid: the entry’s SHA-256 hash matches a recomputation of its canonical payload. For backwards compatibility, entries hashed beforeraw_llm_outputwas covered are also accepted against the legacy canonical form.signature_valid: the entry’s HMAC-SHA256 signature is constant-time-equal to the expected signature.chain_valid: the entry’sprevious_hashcorrectly references the prior entry’s hash within the same organization. Genesis entries (the first entry for an organization) must have anullprevious_hash; non-genesis entries must reference a non-empty prior hash.
Audit chains are isolated per organization. Hash linkage is verified only against prior entries belonging to the same
organization_id, so cross-tenant activity cannot affect another organization’s chain validity.Legacy entries
Rows written before the API used the hash chain have no hash or signature. QWED treats an unchained row with no chained row before it as a legacy entry:verify_log_entryreturns"legacy": trueand"valid": falsefor it. A legacy entry is never reported as valid.- The first chained row after the legacy rows opens a new chain segment with a
nullprevious_hash. - An unchained row after a chained row is not legacy. QWED stops appending for that organization and verification fails.
result payload cannot be decoded as JSON, verify_log_entry fails closed with a SecurityError rather than reporting the entry as valid.
Verify a Single Log Entry:
start and end are optional ISO 8601 datetimes. Omit both to check the whole chain. The response’s scope names what QWED checked:
- With neither
startnorend:"Signature and hash-chain check of every audit entry recorded for the organisation. It does not show that every request was recorded." - With
start,end, or both:"Signature and hash-chain check of the audit entries recorded in the period. It does not show that every request was recorded."
status (VERIFIED, NO_ENTRIES, or NOT_VERIFIED), the entry counts, and first_failed_entry_id, so you can see where the chain stops checking out. See GET /admin/compliance/verify-trail.
verify_audit_trail directly.
verify_audit_trail lists legacy entries under legacy_entries, separately from failed_entries. The trail stays "valid": false while any legacy entry is in range. To verify only the rows written since your upgrade, pass start_date:
AuditLogger() requires QWED_AUDIT_SECRET_KEY to be set in the environment, or the constructor must receive an explicit secret_key argument. Initialization fails closed otherwise.Evidence collection
Export your organization’s verification logs for an audit period as a CSV file to provide to your auditor. API endpoint:start and end are ISO 8601 times. A time without a zone is UTC. Without end, the period ends now. Without start, it starts 30 days before end. A start after end, including a future start with no end, returns 400 Bad Request.
Each row carries the stored verdict and admission, so the file shows the decision QWED made for each request:
VerdictandAdmissionare the stored values, asGET /audit/logsreturns them. A value QWED didn’t record is an empty cell.Domainnames the verification engine. Natural-language requests useNATURAL_LANGUAGE. Rows written before that fix readMATH, because the domain is part of the signed entry.- Nothing is truncated. QWED prefixes an apostrophe to any cell that starts with
=,+,-,@, a tab, a carriage return, or a line feed, so a spreadsheet doesn’t run it as a formula. - The file holds at most the newest 1,000 rows of the period, newest first. When more rows matched, the response sets
X-Export-Truncated: true, otherwisefalse. Split a long audit period into shorterstart/endwindows to collect every row.
Chain Status column shows Chained for rows that carry a hash and Legacy for rows written before the hash chain. Chained only means a hash is present. The export does not re-verify the chain. Use GET /admin/compliance/verify/{log_id} or verify_audit_trail to verify rows.
See GET /admin/compliance/export/csv for every column.
2. GDPR compliance
Data export (Article 15 - right of access)
QWED supports the Right of Access by allowing you to export all data associated with an organization or user. API endpoint: Use the CSV export endpoint to retrieve verification data. Setstart and end to cover the period you need, because the default is the last 30 days and each file holds at most 1,000 rows.
Data deletion (Article 17 - right to erasure)
Note: Currently, an administrator must delete users directly in the database. A self-service deletion API is planned. To comply with a deletion request:- Delete the user: Remove the user record from the database.
- Prune logs: For full GDPR Article 17 compliance, ensure all verification logs associated with the user are also deleted or anonymized.
Consent management
QWED does not have a built-in “Consent Management Platform” (CMP), but you can use the hash-chained audit log to track consent events. Implementing consent tracking: Log a specific “verification” event when a user grants consent. The entry is HMAC-signed and hash-chained to the previous entry for the same organization, so a later edit to it is detectable when you verify the trail. It is not tamper-proof: anyone holdingQWED_AUDIT_SECRET_KEY and database write access can rewrite the chain.
3. Audit trail setup
Enable cryptographic logging
Cryptographic logging is enabled by default, but you must configure the signing key before the audit logger will start. The audit logger fails closed when the signing key is missing or when chain continuity cannot be established.-
Set the signing key:
Set
QWED_AUDIT_SECRET_KEYto a secure, random string (at least 32 characters). The API server refuses to start if this variable is not set, and the audit logger raises aSecurityError.qwed initgenerates and persists a key in.envfor local projects and never rotates an existing one. - Secure storage: Store the key in a secrets manager (for example, AWS Secrets Manager or HashiCorp Vault) and inject it into the application environment.
- Rotation: Rotating the signing key invalidates the HMAC signatures on existing entries. Verify and archive the existing trail before rotating, then start a new trail with the new key.
Chain continuity across restarts
On startup,AuditLogger loads the most recent persisted entry hash and uses it as the new chain head. New entries are bound to the persisted hash rather than a fresh in-memory root, so restarts do not produce a discontinuous trail.
If the in-memory chain head and the persisted chain head disagree at write time, log_verification raises a SecurityError instead of writing a misleading entry. Treat this as a tampering or replication-lag signal and investigate before restarting writes.
Upgrading to v8.0.0
On startup, v8.0.0 adds theapi_key_id, verifier_version, and admission columns to an existing audit log table. Existing rows read these fields as null. Stop every process on the old release before you start v8.0.0. Once v8.0.0 has written a row, don’t roll back to an older release.
Per-organization chain isolation
Each organization has its own append-only hash chain. When verifying or appending entries, the logger:- Locks the chain head per organization to prevent concurrent appends from forking the chain.
- Re-verifies the persisted chain head before appending a new entry and refuses to append if the head fails integrity checks.
- Accepts both the current canonical payload (which covers
raw_llm_output) and the legacy payload during verification, so historical entries written before that field was hashed remain verifiable. - Fails closed if a stored
resultpayload is malformed JSON or if a chain entry is missing itsentry_hash.
Verify log integrity
Regularly run the verification process (see “Audit trail verification” above) to detect database tampering. Verification now flags:- A genesis entry that references a non-null
previous_hash. - A previous entry that is missing its hash.
- A
previous_hashthat does not match the prior entry’s hash. - An entry whose recomputed hash or HMAC signature does not match the stored value.
Sample data
POST /audit/seed writes 10 sample rows, marked {"sample": true}, through the signed chain for development. It returns 404 and writes nothing unless the server runs with QWED_ENABLE_AUDIT_SEED=1. Leave it unset in production so sample rows never mix with a real trail.
Long-term storage
QWED uses the configured database (SQLite/PostgreSQL) for log storage. For high-volume compliance requirements:- Configure database backups to a WORM (Write Once, Read Many) compatible storage (e.g., AWS S3 Object Lock) for archival.
4. Compliance checklist
Use this checklist to prepare for an enterprise audit.- Audit Logs Verified: Run
verify_audit_trailon all historical logs. - API Keys Rotated: Rotate any API keys older than 90 days.
- Security Activity Summary Reviewed: Generate the security activity summary via API and confirm
audit_trail.statusisVERIFIED. - Secret Key Secured: Confirm
QWED_AUDIT_SECRET_KEYis set to a production-grade value and stored in a secrets manager. - GDPR Export Tested: Verify that data export works for a sample organization.
- Security Events Reviewed: Check
metrics.security_events_by_typein the security activity summary for any anomalies. - Rate Limiting Active: Confirm rate limits are enforced to prevent abuse (DoS protection).