Compliance Reports — EU AI Act Article 12 / 9 / 6
Same data as a bundle, rendered as a regulator-ready PDF — what your compliance officer hands the regulator.
Report vs Bundle. A report is the human-readable PDF a regulator opens. A Compliance Bundle is the machine-readable JSON evidence behind it. Most teams produce both. See The Five Aira Sub-Products for the full taxonomy.
What it is
A compliance report is a regulator-facing PDF generated by Aira from your action receipts, signed with the same Ed25519 key that signs every receipt and optionally timestamped by an RFC 3161 trusted authority. It complements compliance bundles: bundles are the cryptographic evidence; reports are the human-readable document a regulator can open in any PDF viewer.
Three frameworks are supported in this release:
| Framework | Identifier | Output |
|---|---|---|
| EU AI Act, Article 12 | eu_ai_act_art12 | Annex VII-style technical file: every action grouped by agent, mapped to the Article 12 fields the regulation requires (period of use, input/output hashes, system + model identifiers, instruction hash, natural persons in the loop, policy decision chain, tamper evidence). |
| EU AI Act, Article 9 | eu_ai_act_art9 | Risk management register: actions classified into the eight Annex III high-risk categories (biometric, critical infrastructure, education, employment, essential services, law enforcement, migration, justice + a fallback "other"), with sample IDs and the control statements Aira enforces for each category. |
| EU AI Act, Article 6 | eu_ai_act_art6 | Right-to-explanation document for a single action: the policy decision chain (rules, AI, consensus modes), the human approval chain, and the cryptographic receipt. |
Generating a report
Article 12 — period-based
curl -X POST https://api.airaproof.com/api/v1/compliance/reports \
-H "Authorization: Bearer aira_live_..." \
-H "Content-Type: application/json" \
-d '{
"framework": "eu_ai_act_art12",
"period_start": "2026-04-01T00:00:00Z",
"period_end": "2026-04-30T23:59:59Z"
}'The endpoint is synchronous. For periods with thousands of actions the call returns in a few seconds; if you need to generate against year-long periods, queue the work via the dashboard.
The response is the report metadata (status will be ready once generation
completes). The PDF is not in the response — fetch it from the download
endpoint.
Article 9 — risk register
Same payload as Article 12, just change the framework:
curl -X POST https://api.airaproof.com/api/v1/compliance/reports \
-H "Authorization: Bearer aira_live_..." \
-H "Content-Type: application/json" \
-d '{
"framework": "eu_ai_act_art9",
"period_start": "2026-04-01T00:00:00Z",
"period_end": "2026-04-30T23:59:59Z"
}'Article 6 — single action
curl -X POST https://api.airaproof.com/api/v1/compliance/reports \
-H "Authorization: Bearer aira_live_..." \
-H "Content-Type: application/json" \
-d '{
"framework": "eu_ai_act_art6",
"action_uuid": "00000000-0000-0000-0000-000000000001"
}'A read-only JSON view of the same explanation is exposed at
GET /api/v1/actions/{action_uuid}/explanation. The explanation can also be
streamed directly as a PDF via
GET /api/v1/actions/{action_uuid}/explanation/pdf.
Downloading the PDF
curl https://api.airaproof.com/api/v1/compliance/reports/{report_uuid}/download \
-H "Authorization: Bearer aira_live_..." \
-o aira-report.pdfThe download response carries three custom headers that mirror the row metadata:
X-Aira-Content-Hash— SHA-256 of the PDF bytesX-Aira-Signature— Ed25519 signature over the signed descriptorX-Aira-Signing-Key— key identifier (look up in/.well-known/jwks.json)
Article 6 evidence quality (linked vs inferred)
Each entry in the policy_chain of an Article 6 explanation carries an
evidence_quality flag so a reader can tell signed evidence apart from
lifecycle-derived inferences:
linked— the entry was loaded from a signedPolicyEvaluationrow whoseaction_uuiddirectly references this action. Cryptographic evidence; independently verifiable.inferred— the entry was derived from the action's lifecycle (thestatuscolumn and anyHumanAuthorizationrows). Used when noPolicyEvaluationrow is linked to the action. The action's outcome is recoverable from the signed receipt + status — but the specific policy that produced the outcome can't be pinpointed.
The Article 6 service derives one of three lifecycle outcomes:
| Lifecycle signal | Inferred decision |
|---|---|
status == 'denied_by_policy' | deny |
Any signed HumanAuthorization exists | require_approval |
| Otherwise (notarized / authorized cleanly) | allow |
This mirrors what the dashboard shows in the action lifecycle timeline, so the PDF and the dashboard never disagree.
In the PDF, inferred entries are tagged Evidence: derived from action lifecycle. The receipt and (when present) the approval chain remain
the cryptographic record.
Verifying a report
curl https://api.airaproof.com/api/v1/compliance/reports/{report_uuid}/verify \
-H "Authorization: Bearer aira_live_..."Returns:
{
"report_uuid": "...",
"valid": true,
"checks": {
"content_hash_matches": true,
"signature_valid": true
},
"descriptor": { ... },
"request_id": "req_..."
}The verifier recomputes the SHA-256 of the stored PDF bytes and re-derives the
canonical descriptor that was signed at generation time, then verifies the
signature against the active public key. Both checks must succeed for valid
to be true.
SDK usage
Python
from aira import Aira
aira = Aira(api_key="aira_live_...")
report = aira.create_compliance_report(
framework="eu_ai_act_art12",
period_start="2026-04-01T00:00:00Z",
period_end="2026-04-30T23:59:59Z",
)
with open("article12.pdf", "wb") as fh:
fh.write(aira.download_compliance_report(report.id))
verification = aira.verify_compliance_report(report.id)
assert verification.validTypeScript
import { Aira } from "aira-sdk";
import { writeFileSync } from "node:fs";
const aira = new Aira({ apiKey: "aira_live_..." });
const report = await aira.createComplianceReport({
framework: "eu_ai_act_art12",
periodStart: "2026-04-01T00:00:00Z",
periodEnd: "2026-04-30T23:59:59Z",
});
writeFileSync("article12.pdf", await aira.downloadComplianceReport(report.id));
const verification = await aira.verifyComplianceReport(report.id);
console.log("valid:", verification.valid);Disabling the endpoints
The route group sits behind a feature flag, ENABLE_COMPLIANCE_REPORTS, which
defaults to True. Set the env var to false to make every endpoint return
404. Useful for self-hosted operators who don't need PDF reports.
Compliance Bundles (EU AI Act Art 12)
Cryptographic snapshot of every receipt in a period — what an auditor verifies offline. Merkle-rooted, signed, framework-mapped, self-contained JSON.
EU AI Act — Article 12 (automatic event logs)
How Aira satisfies the Article 12 automatic logging obligation — what the report covers, what the retention window is, and how to generate, verify, and share it.