FRIA
A FRIA is a Fundamental Rights Impact Assessment under EU AI Act
Article 27 — the artefact a deployer of a high-risk
(Annex III) AI system must complete before putting the system into
use. The FRIA carries its own SHA-256 chain anchor (chainHash)
that is separate from the Decision Hash Chain.
One FRIA per high-risk AI system; wizard-driven on the dashboard,
API-callable for deployer-side automation. Tested against API
version v1. JWT auth on every endpoint; plan-gated by
friaWizard (Governance and above).
The FRIA object
| Field | Type | Required | Description |
|---|---|---|---|
_id / id | string | yes | MongoDB ObjectId |
organizationId | string | yes | Owning org |
systemName | string | yes | The high-risk AI system this FRIA covers |
deployerProfileId | string | no | Reference to the org's Deployer Profile |
purpose | string | no | Wizard section — intended purpose of the system |
affectedPersons | string | no | Categories of natural persons affected (Art. 27(1)(a)) |
usageContext | string | no | Operational context for the deployment |
durationOfUse | string | no | Expected operational period |
identifiedRisks | Risk[] | no | Structured array of risk rows; see schema below |
humanOversightSummary | string | no | Art. 14 oversight measures |
complaintsMechanism | string | no | Affected-person complaint channel |
status | enum | no | draft (default), review, approved, archived |
approvedBy | string | no | User ObjectId of the approver |
approvedAt | string (ISO 8601) | no | Set on approval |
chainHash | string | no | SHA-256 anchor over canonical FRIA contents at submission |
createdAt / updatedAt | string (ISO 8601) | no | Standard timestamps |
Risk sub-document
| Field | Type | Required | Description |
|---|---|---|---|
title | string | yes | Short risk label |
description | string | no | Risk narrative |
likelihood | enum | no | low, medium (default), high |
severity | enum | no | low, medium (default), high |
affectedRights | string[] | no | Charter / fundamental-rights references |
mitigation | string | no | Counter-measure narrative |
residualRisk | enum | no | low, medium (default), high after mitigation |
The dashboard's wizard collapses two free-text fields
(risksIdentified, mitigations) into a synthetic single-row
risk array on save; structured API consumers should send
identifiedRisks directly to keep the per-row severity and
likelihood signals.
State machine
┌───── ───────────────────────────────────────┐
│ draft ── /submit ──▶ review │
│ ▲ │ │
│ │ ▼ │
│ └───── /transition ── approved │
│ │ │
│ ▼ │
│ archived │
└────────────────────────────────────────────┘
The wizard's submit step writes the FRIA's chainHash — a
SHA-256 over the canonicalised assessment contents at the moment of
submission. The hash is the reviewer's signature anchor and persists
across subsequent edits in review state (the chain detects
post-submission tampering).
Endpoints
GET /api/v1/fria
POST /api/v1/fria
GET /api/v1/fria/:id
PATCH /api/v1/fria/:id
POST /api/v1/fria/:id/transition
POST /api/v1/fria/:id/submit
POST /api/v1/fria/:id/approve
DELETE /api/v1/fria/:id
/submit and /approve are aliases that call the unified
/transition handler with a fixed target status (review and
approved respectively). Use them when integrating with the wizard;
use /transition for state changes that don't fit the
draft→review→approved wizard path (for example, rolling an
approved FRIA back to draft for a re-assessment).
List FRIAs
GET /api/v1/fria
| Query parameter | Description |
|---|---|
status | Filter by draft / review / approved / archived |
search | Substring on systemName |
limit | Page size; capped server-side |
curl "https://api.adjudon.com/api/v1/fria?status=approved" \
-H "Authorization: Bearer $ADJUDON_API_KEY"
import os, requests
r = requests.get(
"https://api.adjudon.com/api/v1/fria",
params={"status": "approved"},
headers={"Authorization": f"Bearer {os.environ['ADJUDON_API_KEY']}"},
)
Errors: 401, 403 UPGRADE_REQUIRED, 500 FRIA_LIST_FAILED.
Create a FRIA
POST /api/v1/fria
Creates a new FRIA in draft status. systemName is required;
every other field is optional and can be filled in via subsequent
PATCH calls as the wizard progresses.
curl -X POST https://api.adjudon.com/api/v1/fria \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"systemName": "Underwriter v1 — credit screening",
"purpose": "Screen retail loan applications under €50,000",
"affectedPersons": "Retail credit applicants in DACH region"
}'
fria = {
"systemName": "Underwriter v1 — credit screening",
"purpose": "Screen retail loan applications under €50,000",
"affectedPersons": "Retail credit applicants in DACH region",
"usageContext": "Online application form; downstream of KYC",
"durationOfUse": "Continuous; reviewed annually",
"identifiedRisks": [
{
"title": "Disparate-impact risk",
"description": "Model under-approves applicants from low-income postal codes",
"likelihood": "medium",
"severity": "high",
"affectedRights": ["non-discrimination", "right to good administration"],
"mitigation": "Quarterly fairness audit + Review Queue thresholds",
"residualRisk": "low"
}
],
"humanOversightSummary": "All denials below confidence 0.7 land in Review Queue",
"complaintsMechanism": "[email protected] — 30-day SLA"
}
import os, requests
r = requests.post(
"https://api.adjudon.com/api/v1/fria",
json=fria,
headers={"Authorization": f"Bearer {os.environ['ADJUDON_API_KEY']}"},
)
Errors: 401, 403, 500 FRIA_CREATE_FAILED.
Get one FRIA
GET /api/v1/fria/:id
Returns the FRIA with a completeness score (the controller's
projection helper computes a 0–100 percentage of populated
wizard fields). Errors: 401, 404 NOT_FOUND, 500
FRIA_GET_FAILED.
Update a FRIA
PATCH /api/v1/fria/:id
Partial update of any wizard section. Permitted in draft and
review states; an approved FRIA must transition back to
draft before further edits.
curl -X PATCH https://api.adjudon.com/api/v1/fria/65b1f2c4 \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "humanOversightSummary": "Updated to add SLA on Review Queue: 24h." }'
Errors: 401, 404, 500 FRIA_UPDATE_FAILED.
Submit (draft → review)
POST /api/v1/fria/:id/submit
Transitions the FRIA from draft to review and writes
chainHash. Equivalent to
POST /transition with body: { status: "review" }.
curl -X POST https://api.adjudon.com/api/v1/fria/65b1f2c4/submit \
-H "Authorization: Bearer $ADJUDON_API_KEY"
Errors: 400 FRIA_TRANSITION_FAILED (illegal current state),
401, 404, 500.
Approve (review → approved)
POST /api/v1/fria/:id/approve
Transitions the FRIA to approved and stamps approvedBy and
approvedAt. Equivalent to POST /transition with
body: { status: "approved" }. The chain hash is re-anchored on
approval so the approved artefact is independently verifiable.
Errors: 400 FRIA_TRANSITION_FAILED, 401, 404, 500.
Generic transition
POST /api/v1/fria/:id/transition
| Body field | Required | Description |
|---|---|---|
status | yes | Target status: draft, review, approved, or archived |
curl -X POST https://api.adjudon.com/api/v1/fria/65b1f2c4/transition \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "archived" }'
Errors: 400 VALIDATION_ERROR (missing status), 400
FRIA_TRANSITION_FAILED (state-machine rejected), 401, 404,
500.
Delete a FRIA
DELETE /api/v1/fria/:id
Hard-deletes a FRIA — permitted only on draft records that
have never been submitted. Approved or archived FRIAs cannot be
deleted; the operations audit-log entry on fria.delete would
remain by Cardinal Rule 5 in any case.
Errors: 401, 404 NOT_FOUND (also returned when the FRIA
exists but is not deletable), 500 FRIA_DELETE_FAILED.
Common gotchas
- Plan-gate. The whole resource requires the
friaWizardfeature. Sandbox and Scale plans receive403 UPGRADE_REQUIRED. - Two chains. The FRIA
chainHashis separate from the Decision Hash Chain. Verifying a FRIA does not require pulling the trace chain export. See Audit Log & Security for the two-chain split. - Dashboard vs structured payload. The wizard sends
risksIdentified+mitigationstext fields; the API consumer contract isidentifiedRisks[]with structured rows. The controller collapses the former into a single syntheticRisk Summaryrow. SendidentifiedRisksdirectly to preserve per-row likelihood / severity / affected-rights data. - State-machine transitions. Submitting requires
draft; approving requiresreview; archive can be reached from any state. A rejected attempt returns400 FRIA_TRANSITION_FAILED. - Approved FRIA edits. Direct
PATCHagainst anapprovedrecord is rejected; transition back todraftfirst, edit, re-submit. The chain hash is re-anchored on each approval.
See also
- EU AI Act Compliance — the Article 27 mapping in narrative form
- FRIA Wizard — the concept page covering the chain anchor mechanics
- Audit Log & Security — the two-chain split (Decision + FRIA chains)
- Deployer Profile API —
the
deployerProfileIdlinkage target - Audit Log API — where
fria.create,fria.transition, andfria.deleteevents land - Error Codes — the full error taxonomy