Skip to main content

Compliance Mapping

The Compliance Mapping API stores per-clause attestations for the audit-frameworks Adjudon supports today: ISO/IEC 42001 (18 clauses), EU AI Act Annex IV, and NIST AI RMF. The static clause catalogue (clause text, default status, auto-satisfaction hints) ships with the dashboard; this resource is the per-org overlay that records the deployer's actual status, evidence narrative, and reviewer attribution. Tested against API version v1. Mounted at /api/v1/compliance. JWT auth on every endpoint; plan-gated by complianceMapping (Governance+).

What lives where

LayerWhat it storesWhere
Static catalogueClause text, default status, auto-satisfaction hintsfrontend/src/data/iso42001-mapping.ts (and equivalent files for other frameworks)
Per-org attestationStatus override, evidence narrative, reviewer attributionComplianceMappingNote collection — this resource
Legacy free-text mapPlain string per clause; no status, no reviewerOrganization.complianceNotes (kept for migration; new writes go to ComplianceMappingNote)

The frameworks enum is iso-42001, eu-ai-act, nist-rmf. The statuses enum is covered, partial, gap, not-applicable. Defaults come from the static catalogue; this API records the deployer's overrides only.

The ComplianceMappingNote object

FieldTypeRequiredDescription
_id / idstringyesMongoDB ObjectId
organizationIdstringyesOwning org
frameworkenumyesiso-42001, eu-ai-act, nist-rmf
clauseIdstringyesFramework-specific identifier (e.g. A.6.2.4); max 32 chars
statusenumnocovered, partial, gap, not-applicable; default gap
evidencestringnoFree-text narrative; max 4,000 chars
reviewedAtstring (ISO 8601)noSet on each PATCH
reviewedBystringnoUser ObjectId of the reviewer
reviewedByEmailstringnoEmail snapshot at PATCH time
createdAt / updatedAtstring (ISO 8601)noStandard timestamps

The compound unique index (organizationId, framework, clauseId) makes every attestation upsert-safe — the same clause cannot have two competing entries.

Endpoints

GET   /api/v1/compliance/mapping?framework=iso-42001
PATCH /api/v1/compliance/mapping
GET /api/v1/compliance/mapping-notes
PUT /api/v1/compliance/mapping-notes

The first two are the structured path (current) and the recommended integration. The second two are the legacy free-text path kept for any caller still using the prior shape; reads merge both sources.

Get the per-framework rollup

GET /api/v1/compliance/mapping?framework=iso-42001

Returns the deployer's full attestation set for the requested framework, plus a coverage rollup the dashboard renders directly.

curl
curl "https://api.adjudon.com/api/v1/compliance/mapping?framework=iso-42001" \
-H "Authorization: Bearer $ADJUDON_JWT"
Query parameterRequiredDescription
frameworknoOne of iso-42001 (default), eu-ai-act, nist-rmf

Response (200 OK):

{
"success": true,
"data": {
"framework": "iso-42001",
"totalClauses": 18,
"notes": [
{
"clauseId": "A.6.2.4",
"status": "covered",
"evidence": "FRIA wizard records intended use; Hash Chain anchors every decision.",
"reviewedAt": "2026-04-29T14:02:11.000Z",
"reviewedByEmail": "[email protected]"
},
{ "clauseId": "A.7.2", "status": "partial", "evidence": "Drift dashboards live; data-quality KPI not yet wired." }
]
}
}

totalClauses is the static-catalogue total for the requested framework; the dashboard divides notes by total to render the coverage percentage. Errors: 400 VALIDATION_ERROR (unknown framework), 401, 403 UPGRADE_REQUIRED (plan), 500.

Upsert one clause attestation

PATCH /api/v1/compliance/mapping
Body fieldRequiredDescription
frameworkyesOne of iso-42001, eu-ai-act, nist-rmf
clauseIdyesMax 32 chars; framework-specific (e.g. A.7.2)
statusnoOne of covered, partial, gap, not-applicable
evidencenoUp to 4,000 chars
curl — downgrade A.7.2 with justification
curl -X PATCH https://api.adjudon.com/api/v1/compliance/mapping \
-H "Authorization: Bearer $ADJUDON_JWT" \
-H "Content-Type: application/json" \
-d '{
"framework": "iso-42001",
"clauseId": "A.7.2",
"status": "partial",
"evidence": "Anomalies dashboard live; full data-quality KPI scheduled Q4 2026."
}'
Python — bulk-attest from a CSV
import csv, os, requests

JWT = os.environ["ADJUDON_JWT"]
url = "https://api.adjudon.com/api/v1/compliance/mapping"

with open("clause-attestations.csv") as f:
for row in csv.DictReader(f):
r = requests.patch(url,
headers={"Authorization": f"Bearer {JWT}"},
json={
"framework": row["framework"],
"clauseId": row["clauseId"],
"status": row["status"],
"evidence": row["evidence"],
},
)
r.raise_for_status()

The handler does five things atomically: upserts the ComplianceMappingNote document, stamps reviewedAt, reviewedBy, and reviewedByEmail from the caller's JWT, writes a compliance.mapping.updated entry to the Operations Audit Log, appends an entry to the SHA-256 Hash Chain, and returns the resulting note plus the new coverage rollup. An auditor reading the chain six months later can reconstruct exactly who attested what when.

Multi-framework orgs (an entity certifying ISO 42001 and attesting against EU AI Act Annex IV simultaneously) PATCH each framework independently — the unique index is per (org, framework, clauseId), so the same clauseId string can exist on different frameworks without collision.

Errors: 400 VALIDATION_ERROR (missing or out-of-enum field; clauseId longer than 32 chars; evidence longer than 4,000 chars), 401, 403, 500.

Legacy free-text endpoints

GET /api/v1/compliance/mapping-notes
PUT /api/v1/compliance/mapping-notes

The legacy shape stored a single string per clause keyed by clauseId, with no status, no reviewer, no evidence-vs-note distinction:

{ "A.6.2.4": "Covered by FRIA + Hash Chain.", "A.7.2": "Partial." }

These endpoints are retained for any caller that still uses the prior shape. Reads merge both sources (legacy strings overlay the structured ComplianceMappingNote collection); writes update only the legacy Organization.complianceNotes Mixed map. New integrations should use the structured /mapping endpoints; the legacy surface will be deprecated per the Versioning policy with at least 90 days notice.

How an auditor consumes this surface

A typical pre-audit gap analysis reads this surface in three passes. First, GET /mapping?framework=iso-42001 to pull the current attestation state and the coverage percentage. Second, the auditor cross-references each partial and gap row against the operator's planned remediation; the evidence narrative explains where each gap sits and what the operator intends to do. Third, the auditor pulls the matching Audit Log slice filtered to compliance.mapping.updated entries to confirm the attestation history has not been retroactively rewritten — the Hash Chain anchors prove it.

Common gotchas

  • Static catalogue vs per-org overlay. The clause text and default status are not editable via this API — they ship with the dashboard's data file. This API only records the deployer's overrides. Adding a new framework or new clauses ships in a Adjudon release.
  • Evidence has a 4,000-character ceiling. Long evidence narratives should link out to a separate document (Confluence, Notion, GRC tool); the field is for the audit- readable summary, not the full procedure.
  • Reviewer attribution survives user deletion. The reviewedByEmail snapshot is captured at PATCH time so an auditor reading the attestation a year later can attribute the change without joining against the live User collection.
  • Default status is the floor, not the ceiling. A covered default in the static catalogue can be downgraded to partial or gap via PATCH; the audit log records the change and the reviewer email. The ISO 42001 page explains the framing.
  • Idempotency. The Idempotency-Key middleware is wired only on POST /traces; mapping mutations do not auto-receive replay protection. The compound unique index on (organizationId, framework, clauseId) defends against duplicate notes; PATCH is itself idempotent (re-PATCH with the same body produces the same end state).

See also