Audit Service — API Contracts
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · API Path Conventions
Base path: /api/v1/audit
Auth: Bearer JWT (Keycloak RS256). All endpoints require authentication.
Tenant isolation: Tenant Admins see only entries where tenant_id = their tenant. Super Admins see all.
1. Audit Entries
GET /api/v1/audit/entries
Query audit entries with filters and pagination.
| Auth scope | TENANT_ADMIN or SUPER_ADMIN |
|---|---|
| Rate limit | 60 req/min per user |
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
tenantId | UUID | No | Super Admin only; Tenant Admin's tenant auto-applied |
actorId | UUID | No | Filter by actor |
eventType | string | No | e.g. USER_SUSPENDED |
resourceType | string | No | e.g. USER, PATIENT |
resourceId | string | No | Filter by resource ID |
outcome | string | No | SUCCESS | FAILURE | PARTIAL |
dateFrom | ISO8601 | No | Start of range |
dateTo | ISO8601 | No | End of range; max 90 days from dateFrom |
limit | integer | No | Default 100; max 1000 |
offset | integer | No | Default 0 |
Response 200
{
"data": [
{
"id": "aud_01HXYZ",
"tenantId": "ten_def456",
"eventType": "USER_SUSPENDED",
"actorId": "usr_admin001",
"actorType": "USER",
"resourceType": "USER",
"resourceId": "usr_abc123",
"action": "UPDATE",
"outcome": "SUCCESS",
"sourceService": "identity-service",
"sourceEventId": "evt_iam_sus_001",
"nodeId": null,
"metadata": { "reason": "Policy violation", "previousStatus": "active" },
"chainHash": "a3f1...e9d7",
"occurredAt": "2026-02-26T11:00:00Z",
"recordedAt": "2026-02-26T11:00:03Z"
}
],
"total": 1284,
"limit": 100,
"offset": 0
}
Error Codes
| Code | HTTP | Condition |
|---|---|---|
AUD_CROSS_TENANT | 403 | Tenant Admin accessing foreign tenant |
AUD_DATE_RANGE_TOO_WIDE | 400 | Range > 90 days; use export instead |
GET /api/v1/audit/entries/:id
Get a single audit entry by ID.
| Auth scope | TENANT_ADMIN or SUPER_ADMIN |
|---|
Path params: id — AuditEntryId (ULID aud_...)
Response 200 — Single AuditEntry object (same schema as list item above).
Error Codes
| Code | HTTP | Condition |
|---|---|---|
AUD_ENTRY_NOT_FOUND | 404 | Entry not found |
AUD_CROSS_TENANT | 403 | Entry belongs to another tenant |
2. Accounting of Disclosures
GET /api/v1/audit/disclosures
Returns all events where a patient's clinical data was accessed (HIPAA accounting-of-disclosures).
| Auth scope | TENANT_ADMIN or SUPER_ADMIN |
|---|
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
patientId | UUID | Yes | Patient's resource ID |
dateFrom | ISO8601 | No | Start date |
dateTo | ISO8601 | No | End date |
limit | integer | No | Default 100 |
offset | integer | No | Default 0 |
Response 200
{
"patientId": "pat_xyz789",
"data": [
{
"id": "aud_dis_001",
"eventType": "PATIENT_RECORD_READ",
"actorId": "usr_abc123",
"actorType": "USER",
"resourceType": "PATIENT_RECORD",
"resourceId": "pat_xyz789",
"action": "READ",
"outcome": "SUCCESS",
"sourceService": "patient-chart-service",
"metadata": { "purpose": "Clinical care", "nodeId": "node_dept_emergency" },
"occurredAt": "2026-02-26T10:30:00Z"
}
],
"total": 42,
"limit": 100,
"offset": 0
}
3. Exports
POST /api/v1/audit/exports
Request an asynchronous export. The export is queued and processed in the background.
| Auth scope | SUPER_ADMIN |
|---|
Request Body
{
"filters": {
"tenantId": "ten_def456",
"dateFrom": "2026-01-01T00:00:00Z",
"dateTo": "2026-02-01T00:00:00Z"
},
"format": "ndjson"
}
Response 202
{
"exportId": "exp_01HXYZ",
"status": "queued",
"createdAt": "2026-02-26T12:00:00Z"
}
The export request is itself audited with
eventType: BULK_EXPORT(FR-AUD-EXP-002).
GET /api/v1/audit/exports/:id
Check export status and retrieve download URL when complete.
| Auth scope | SUPER_ADMIN |
|---|
Response 200 (queued or processing)
{
"exportId": "exp_01HXYZ",
"status": "processing",
"createdAt": "2026-02-26T12:00:00Z",
"completedAt": null,
"fileUrl": null,
"recordCount": null
}
Response 200 (completed)
{
"exportId": "exp_01HXYZ",
"status": "completed",
"createdAt": "2026-02-26T12:00:00Z",
"completedAt": "2026-02-26T12:02:34Z",
"fileUrl": "https://exports.ghasi-ehr.com/exp_01HXYZ.ndjson?token=...",
"recordCount": 15432
}
Error Codes
| Code | HTTP | Condition |
|---|---|---|
AUD_EXPORT_NOT_FOUND | 404 | Export job not found |
4. Error Code Registry
| Code | HTTP | Description |
|---|---|---|
AUD_ENTRY_NOT_FOUND | 404 | Audit entry not found |
AUD_EXPORT_NOT_FOUND | 404 | Export job not found |
AUD_CROSS_TENANT | 403 | Tenant Admin accessing foreign tenant data |
AUD_DATE_RANGE_TOO_WIDE | 400 | Query range > 90 days — use export endpoint |
All error responses follow the platform envelope:
{
"error": { "code": "AUD_CROSS_TENANT", "message": "..." },
"correlationId": "req_xxx",
"timestamp": "2026-04-18T..."
}
5. FHIR Mapping
| Platform endpoint | FHIR equivalent | Notes |
|---|---|---|
GET /api/v1/audit/entries | GET /fhir/AuditEvent | Read-only projection via interop-service |
GET /api/v1/audit/disclosures | GET /fhir/AuditEvent?type=patient-record-read | Filtered by patient |