Skip to main content

Incidents (Multi-Clock Hub)

An Incident is a compliance-relevant event that triggers up to five regulatory deadline clocks in parallel: GDPR Art. 33, EU AI Act Art. 73, DORA Art. 19, NIS2 Art. 23, and CRA Art. 11. The Multi-Clock Hub anchors all five clocks to the same incident record so a single detection event flows into one workflow, not five. Tested against API version v1. JWT auth on every endpoint; plan-gated by multiClockIncidents (Governance and above).

The Incident object

FieldTypeRequiredDescription
_id / idstringyesMongoDB ObjectId
organizationId / workspaceIdstringyesTenancy scope
externalRefstringnoSIEM ticket / Jira / Linear identifier
titlestringyesOperator-readable summary
summarystringnoFree-form detail
severityenumnoinfo, low, medium (default), high, critical
categoryenumnodata_breach, ai_serious_incident, operational, cyber, product_safety, other
statusenumnoopen (default), investigating, reported, mitigated, resolved, closed, archived
triggerTraceIdsstring[]noTrace IDs that opened the incident
relatedAgentIdsstring[]noAgent IDs implicated
reportedTostring[]noPer-regulator codes the report has reached (gdpr-dpa, aiact-ms, dora-na, nis2-csirt, cra-enisa)
regulatorsSubmission[]noPer-regulator submission record (code, name, contactEmail, submittedAt, submissionRef)
createdBy / createdByEmailstringnoReporter; email is a snapshot that survives user deletion
detectedAtstring (ISO 8601)noWhen the event happened (defaults to createdAt); SIEM ingests can pass an earlier value
closedAtstring (ISO 8601)noSet when status transitions to closed
rootCause / remediationstringnoClosing-narrative fields
createdAt / updatedAtstring (ISO 8601)noStandard timestamps

The status enum is a 7-value union spanning both the academic vocabulary (openinvestigatingreportedresolvedarchived) and the dashboard vocabulary (openinvestigatingmitigatedclosed). The controller translates between the two so both call paths see the names they expect.

The IncidentClock object

FieldTypeRequiredDescription
regulatorenumyesgdpr, aiact, dora, nis2, cra
articleRefstringnoFree string (e.g., Art. 33)
clockStartedAtstring (ISO 8601)yesWhen the deadline countdown began
applicabilityReasonstringnoWhy this regulator applies to this incident
checkpointsCheckpoint[]noEach: label, deadlineAt, completedAt, completedBy, evidenceTraceId, notes
nextCheckpointAtstring (ISO 8601)noLive deadline for the dashboard badge
finalDeadlineAtstring (ISO 8601)noFinal regulatory deadline
statusenumnoactive (default), paused, completed, breached

A breached checkpoint is not deleted. The clock's status flips to breached and nextCheckpointAt stays in the past so the post-incident audit can replay which deadline was missed and when.

Standard clock cadence

RegulatorArticleEarlyIntermediateFinal
GDPRArt. 3372 h
EU AI ActArt. 732 d10 d15 d
DORAArt. 194 h72 h30 d
NIS2Art. 2324 h72 h30 d
CRAArt. 1124 h72 h14 d

Endpoints

GET    /api/v1/incidents
POST /api/v1/incidents
GET /api/v1/incidents/:id
PATCH /api/v1/incidents/:id
POST /api/v1/incidents/:id/close
GET /api/v1/incidents/:id/clocks
POST /api/v1/incidents/:id/clocks/:clockId/checkpoint
POST /api/v1/incidents/:id/checkpoints/:checkpointId/complete

The two checkpoint-completion endpoints share one handler — the dashboard prefers the second path (cp-N synthetic ids), the SDK prefers the first (numeric checkpointIndex).

List incidents

GET /api/v1/incidents
Query parameterDefaultDescription
statusFilter by status enum
severityFilter by severity enum
regulatorFilter by attached clock's regulator code
searchSubstring on title and summary
limit50Page size; capped server-side
curl
curl "https://api.adjudon.com/api/v1/incidents?status=open&severity=critical" \
-H "Authorization: Bearer $ADJUDON_API_KEY"
Python
import os, requests
r = requests.get(
"https://api.adjudon.com/api/v1/incidents",
params={"status": "open", "severity": "critical"},
headers={"Authorization": f"Bearer {os.environ['ADJUDON_API_KEY']}"},
)
Node
const url = new URL("https://api.adjudon.com/api/v1/incidents");
url.searchParams.set("status", "open");
url.searchParams.set("severity", "critical");
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.ADJUDON_API_KEY}` },
});

Errors: 401, 403 UPGRADE_REQUIRED, 500 INCIDENT_LIST_FAILED.

Create an incident

POST /api/v1/incidents
Body fieldRequiredDescription
titleyesRequired; non-empty string
summarynoDetail
severitynoDefaults to medium
categorynoDefaults to other
triggerTraceIdsnoTrace IDs that opened the incident
relatedAgentIdsnoAgents implicated
regulatorsnoArray of regulator codes; clocks attach automatically
detectedAtnoOverride; defaults to createdAt
externalRefnoSIEM/Jira/Linear identifier
curl
curl -X POST https://api.adjudon.com/api/v1/incidents \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Suspicious cross-org trace pattern detected",
"severity": "high",
"category": "ai_serious_incident",
"triggerTraceIds": ["trace_aBcD1234"],
"regulators": [{"code": "aiact"}, {"code": "gdpr"}, {"code": "dora"}]
}'

The response is the full Incident document with embedded clocks array. Each clock arrives populated with checkpoints for its regulator's standard cadence (table above). Errors: 400 VALIDATION_ERROR (missing title), 401, 403, 500 INCIDENT_CREATE_FAILED.

Get one incident

GET /api/v1/incidents/:id

Returns the incident with embedded clocks array (each clock including its checkpoints, computed nextCheckpointAt, and status). Errors: 401, 403, 404 NOT_FOUND, 500 INCIDENT_GET_FAILED.

Update an incident

PATCH /api/v1/incidents/:id

Partial update; any of title, summary, severity, category, status, triggerTraceIds, relatedAgentIds, detectedAt, externalRef may be supplied. Errors: 401, 404, 500 INCIDENT_UPDATE_FAILED.

Close an incident

POST /api/v1/incidents/:id/close

Transitions status to closed and sets closedAt. Body fields populate the closing narrative the dashboard surfaces:

Body fieldRequiredDescription
rootCausenoShort narrative of root cause
remediationnoSteps taken
notesnoFree-form addendum
curl
curl -X POST https://api.adjudon.com/api/v1/incidents/65b1f2c4/close \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "rootCause": "Misconfigured policy", "remediation": "Rolled out fix in agents/v1.4.2" }'

A breached clock attached to a closed incident is preserved — the post-incident audit needs to read which deadlines were missed. Errors: 401, 404, 500 INCIDENT_CLOSE_FAILED.

List clocks for an incident

GET /api/v1/incidents/:id/clocks

Returns the array of IncidentClock documents attached to the incident. Useful for a dashboard panel rendering all five regulator clocks side-by-side. Errors: 401, 404, 500 INCIDENT_CLOCKS_FAILED.

Complete a checkpoint

POST  /api/v1/incidents/:id/clocks/:clockId/checkpoint
POST /api/v1/incidents/:id/checkpoints/:checkpointId/complete
Body fieldRequiredDescription
checkpointIndexyes (legacy path)0-indexed checkpoint number
evidenceTraceIdnoDecision Hash Chain trace anchoring the report
evidenceUrlno (dashboard path)External URL (Jira ticket, regulator portal receipt)
notesnoFree-form addendum

The dashboard path uses synthetic checkpoint identifiers (cp-0, cp-1, …); the legacy path takes the numeric index directly. Both write the same record. Errors: 400 VALIDATION_ERROR (missing index), 401, 404 NOT_FOUND (incident / clock / checkpoint), 500 CHECKPOINT_COMPLETE_FAILED.

Common gotchas

  • Plan-gate. The whole resource is gated by multiClockIncidents — Sandbox and Scale plans receive 403 UPGRADE_REQUIRED.
  • Two checkpoint paths. Both work, both call the same handler. New integrations should prefer the dashboard path (/checkpoints/:checkpointId/complete); the legacy path is kept for SDK consumers that already integrated against checkpointIndex.
  • Status enum is a union. Seven values cover both the academic vocabulary and the dashboard vocabulary; the controller translates between the two. Don't filter on dashboard-only values when scripting against an academic-vocab system.
  • Breached clocks are preserved. A status: 'breached' clock stays in the index with its missed deadline. Audit replays depend on this.

See also