Skip to main content

Deployer Profile

The Deployer Profile API captures the EU AI Act Article 26 deployer-compliance pack: the human-oversight assignment under § 2, the monitoring arrangements under § 5, the DPIA cross-link under § 6, the inbound provider instructions-of- use intake, and the cross-link to every FRIA the deployer has authored. One profile per organisation; the deployer attests each section and the audit log records every change. Tested against API version v1. Mounted at /api/v1/deployer-profile (with a dashboard alias at /api/v1/deployer). JWT auth; plan-gated by art26DeployerPack (Governance+).

Article 26 mapping

The schema mirrors Article 26 paragraph-by-paragraph — the auditor's expected reading order is the order the JSON document unfolds:

Art. 26 paragraphDeployerProfile field
§ 2 Human oversightoversight.{assignedTeam, trainedUserIds[], sopReference}
§ 5 Monitoring of operationmonitoring.{sopReference, cadence, alertChannels[]}
§ 6 Data Protection Impact Assessmentdpia.{completed, completedAt, documentRef}
Receipt of instructions-of-use from providerinstructionsOfUse[]
Cross-link to FRIA (Art. 27)friaIds[]

The profile is a singleton per org — unique index on organizationId. Initial fetch lazily creates the document with empty fields so the dashboard's first-load pattern works without a bootstrap call.

The DeployerProfile object

FieldTypeDescription
organizationIdstringOwning org (singleton)
legalNamestring | nullLegal entity name as it appears on filings
contactEmailstring | nullDesignated Article 26 contact
oversight.assignedTeamstring | nullFree-text team or function name
oversight.trainedUserIds[]string[]Adjudon User IDs of operators with documented Art. 14 training
oversight.sopReferencestring | nullURL or document reference to the oversight SOP
monitoring.sopReferencestring | nullMonitoring SOP reference
monitoring.cadenceenumcontinuous, daily, weekly, monthly, quarterly
monitoring.alertChannels[]string[]Channels where monitoring alerts route (Slack, email, PagerDuty, etc.)
dpia.completedbooleanDPIA completion flag
dpia.completedAtstring (ISO 8601)Completion timestamp
dpia.documentRefstring | nullReference to the DPIA artefact (out-of-platform)
instructionsOfUse[]arrayEach entry { providerName, systemName, documentRef, receivedAt, acknowledgedAt }
friaIds[]string[]Cross-link to FRIA documents authored under Art. 27
completenessnumberSelf-assessed 0-100 posture
createdAt / updatedAtstring (ISO 8601)Standard timestamps

Endpoints

GET    /api/v1/deployer-profile
PATCH /api/v1/deployer-profile
POST /api/v1/deployer-profile/instructions
POST /api/v1/deployer-profile/instructions/:id/acknowledge
POST /api/v1/deployer-profile/fria-link
POST /api/v1/deployer-profile/fria-links ── alias

The /fria-link and /fria-links paths share one handler; the dashboard uses the plural alias, the legacy schema uses the singular. The same is true of /instructions/:id/acknowledge: :id accepts both a numeric index (0, 1, …) and the dashboard's ins-<index> form (ins-0).

Read or initialise the profile

GET /api/v1/deployer-profile
curl
curl https://api.adjudon.com/api/v1/deployer-profile \
-H "Authorization: Bearer $ADJUDON_JWT"

A first-time call lazily creates the singleton with empty fields (no error if the org has nothing yet) and returns the freshly-initialised document. Errors: 401, 403 UPGRADE_REQUIRED (plan), 500.

Update the profile

PATCH /api/v1/deployer-profile

Partial-update against any nested field. The handler does deep-merge on the three sub-documents (oversight, monitoring, dpia) so a request that only updates monitoring.cadence does not clear the rest of monitoring. Re-computes completeness server-side after every PATCH and returns the updated value.

curl — set monitoring SOP + cadence
curl -X PATCH https://api.adjudon.com/api/v1/deployer-profile \
-H "Authorization: Bearer $ADJUDON_JWT" \
-H "Content-Type: application/json" \
-d '{
"monitoring": {
"sopReference": "https://intranet.example.com/sops/ai-monitoring-v3",
"cadence": "daily",
"alertChannels": ["slack:#ai-ops", "pagerduty:ai-oncall"]
}
}'

Writes one compliance.deployer.updated audit entry per call, with the changed-keys diff in metadata. Errors: 400 VALIDATION_ERROR (cadence outside enum), 401, 403, 500.

Ingest a provider instruction-of-use

POST /api/v1/deployer-profile/instructions

Records receipt of a provider's instruction-of-use document. Body: { providerName, systemName, documentRef? }. The handler stamps receivedAt = now and appends one entry to instructionsOfUse[]. The deployer later posts an acknowledgement once the operator has reviewed and integrated the instruction.

Acknowledge an instruction

POST /api/v1/deployer-profile/instructions/:id/acknowledge

Stamps acknowledgedAt = now on the matching entry. The :id parameter accepts both a numeric index and the dashboard's ins-<index> form — the controller normalises before lookup. Acknowledging an already-acknowledged instruction is a no-op (idempotent at the resource level). Errors: 404 INSTRUCTION_NOT_FOUND, 401, 403, 500.

POST /api/v1/deployer-profile/fria-link
POST /api/v1/deployer-profile/fria-links

Body: { friaId }. Adds the FRIA ObjectId to friaIds[], de-duplicates against existing entries, and returns the updated profile. Used to cross-reference an Article 27 FRIA from the Article 26 deployer pack — the auditor reading either artefact reaches the other in one hop.

Completeness scoring

The completeness field is recomputed server-side after every PATCH and after every instructions / acknowledge / fria-link mutation. The score is the proportion of the following nine slots that are populated:

SlotCounted populated when
legalNamenon-empty string
contactEmailnon-empty string
oversight.assignedTeamnon-empty string
oversight.trainedUserIdsarray length ≥ 1
oversight.sopReferencenon-empty string
monitoring.sopReferencenon-empty string
monitoring.cadencealways counts (default continuous)
dpia.completedtrue
friaIdsarray length ≥ 1

Each slot contributes ~11.1%. A profile with all nine populated reports completeness: 100; the dashboard's Compliance Posture card reads this directly. The score is designed to trend over time, not gate behaviour — an 80% profile is not auditor-ready by definition, and a 100% profile still requires the auditor's own review of the linked artefacts.

How an auditor consumes this surface

A typical Article 26 audit walks the profile in four steps:

  1. GET /deployer-profile to pull the full state.
  2. Cross-reference oversight.trainedUserIds against the operator's training-attestation records (off-platform).
  3. Open each entry in instructionsOfUse[] and confirm acknowledgedAt is set; an unacknowledged instruction is audit-evidence the deployer received provider documentation they have not integrated.
  4. Walk every FRIA ID in friaIds[] via the FRIA API and verify the chain anchor on each approved attestation.

The Operations Audit Log records every compliance.deployer.* mutation so the auditor can confirm the profile state was not retroactively shaped right before the audit window opened.

Common gotchas

  • Plan-gated. The whole resource requires art26DeployerPack (Governance+). Sandbox and Scale receive 403 UPGRADE_REQUIRED on every endpoint.
  • One profile per org. The unique index on organizationId enforces it. A POST to create a second profile is not exposed; the singleton is implicit.
  • Lazy initialisation. First GET creates an empty profile; do not pre-create. Frontends should treat a 200 with empty fields as the legitimate first-load state.
  • Two valid :id shapes on acknowledge. Numeric index (0, 1, …) and dashboard ins-<index> form both work. Either shape resolves to the same entry.
  • Idempotency. The Idempotency-Key middleware is wired only on POST /traces. Acknowledging an already-acknowledged instruction is idempotent at the resource level; PATCH is itself idempotent (re-PATCH with the same body produces the same end state); FRIA-link de-duplicates server-side.

See also