OJK Compliance API Reference
This reference documents the OJK endpoints registered in the current enterprise build. The module covers Indonesian regulatory frameworks: OJK AI Governance, UU PDP (Law 27/2022), and BI payment system (PBI 23/6 and 23/7).
This reference is engineering documentation for teams building governed AI on AxonFlow. It is not legal advice. Validate the regulatory mapping with your legal and compliance teams before relying on it for OJK, BI, or UU PDP compliance.
Authentication
Every OJK route is scoped to the caller's organization:
X-Org-IDis the identifier. Behind the AxonFlow agent it is set from the cryptographically validated client credential, so a caller cannot choose it.X-Tenant-IDis accepted only whenX-Org-IDis absent. That fallback covers single-identifier deployments, where the two values are the same. When both headers are present the organization wins; the fallback never widens scope.- A request with neither header, or with a whitespace-only value, is refused with
HTTP 400 and code
missing_org. No query is issued with a blank scope.
Use X-User-ID when you want explicit attribution on export flows, breach notifications, and kill-switch actions.
Route overview
| Route | Method |
|---|---|
/api/v1/ojk/audit/export | POST |
/api/v1/ojk/audit/export/{id} | GET |
/api/v1/ojk/audit/retention | GET |
/api/v1/ojk/audit/readiness | GET |
/api/v1/ojk/breach/notify | POST |
/api/v1/ojk/breach/acknowledge | POST |
/api/v1/ojk/breach/evaluate-deadlines | POST |
/api/v1/ojk/dashboard | GET |
Export API
Create export
POST /api/v1/ojk/audit/export
Supported request fields:
{
"start_date": "2026-01-01",
"end_date": "2026-03-31",
"data_types": ["policy_violations", "llm_calls", "decision_chain", "pii_redactions", "cross_border_transfers"],
"format": "json",
"framework": "OJK_AI_GOVERNANCE",
"include_pii": false,
"filters": {
"agent_ids": ["customer-copilot"],
"severity": ["high"],
"policy_types": ["pii"]
}
}
Request field details:
-
start_dateandend_date-YYYY-MM-DDdate strings defining the export window (RFC3339 timestamps are rejected withvalidation_error); the range cannot exceed 5 years -
data_types- typed enum:policy_violations,llm_calls,decision_chain,hitl_oversight,pii_redactions,cross_border_transfers,breach_notifications, orall -
format-json,csvorxmlare all ACCEPTED, but this endpoint produces only JSON. A csv or xml request returnsformat: json(what the body is), plusrequested_formatand aformat_noteexplaining it. The response never labels a JSON body as csv or xml. -
framework-OJK_AI_GOVERNANCE,UU_PDP,BI_PJP, orOJK_BI_COMBINED. The label selects the report sections whendata_typesis omitted:Framework Sections, in report order OJK_AI_GOVERNANCEpolicy_violations,llm_calls,decision_chain,hitl_oversightUU_PDPpii_redactions,cross_border_transfers,breach_notifications,policy_violationsBI_PJPdecision_chain,hitl_oversight,pii_redactions,cross_border_transfers,breach_notificationsOJK_BI_COMBINEDall seven sections -
include_pii- boolean; PII is excluded unless explicitly requested -
filters- optional object withagent_ids,user_ids,severity, andpolicy_types(all string arrays)
An explicitly requested data_types entry is always served, even when the
selected framework does not consider it in scope; the section is returned with
in_framework_scope: false and a note rather than being dropped. A data type the
platform does not recognise produces an explicit per-section error, never a
silent empty section.
Response summary shape
{
"summary": {
"total_records": 12,
"records_by_type": { "pii_redactions": 4, "decision_chain": 8 },
"report_state": "populated",
"sections": [
{
"data_type": "pii_redactions",
"report_state": "populated",
"record_count": 4,
"in_framework_scope": true,
"note": "Processing record for Indonesian personal data (NIK, NPWP, contact, bank account) with the action taken."
},
{
"data_type": "breach_notifications",
"report_state": "enabled_empty",
"record_count": 0,
"in_framework_scope": true
}
],
"framework_summary": {
"framework": "UU_PDP",
"citation": "UU PDP Law 27/2022, Pasal 56 (cross-border transfer) and Art. 46 (3x24h breach notification)",
"pillars": []
}
}
}
report_state is carried per section and rolled up on the summary:
| Value | Meaning |
|---|---|
populated | Served, at least one record |
enabled_empty | Served, and the honest answer is zero records for this organization and window |
not_available | Could not be served at all. Always paired with an error and an error_kind of section_not_implemented, store_absent or query_failed |
Read report_state, not the presence of a JSON key: every data array is
omitempty, so absence alone is ambiguous by construction.
Sections are capped at 100,000 records. A section that reaches the cap says so in
its note.
metadata.export_version is 2.0.0 for this contract. A 1.0.0 consumer that
inferred "module not enabled" from an empty body must move to report_state.
PII redaction records
{
"id": "…",
"timestamp": "2026-07-15T10:00:00Z",
"pii_type": "nik",
"ojk_category": "national_identity",
"severity": "critical",
"masked_value": "31**********0001",
"redaction_method": "indonesia_detector_mask",
"confidence": 0.7,
"action": "blocked",
"plane": "gateway",
"decision_id": "…",
"correlation_id": "…"
}
ojk_category-national_identity,tax_identifier,contact_information,financial_accountaction-blocked(request refused),redacted(this plane masked the value),redaction_required(a policy decision point required redaction and told the PEP; the gateway pre-check and/api/v1/decideare PDPs and the calling SDK performs the mask),detected(observed under a warn/log posture, content unmodified)plane-gateway,decision,mcp
Raw detected values are never stored and therefore can never be exported. This
section requires enterprise migration 137; without it the section reports
not_available with an error naming the migration.
Get export status
GET /api/v1/ojk/audit/export/{id}
The response includes:
export_idstatusframeworksummarydownload_urlexpires_atmetadata
Retention API
Get retention status
GET /api/v1/ojk/audit/retention
The retention response includes:
- overall
compliance_status frameworkretention_days(the configured retention period) andmin_retention_days(the enforced floor)- a
data_typesarray (the shape reserves one entry per audit data type -data_type,status, optionaloldest_record/newest_record,total_records- but the current implementation always returns it empty) - optional
next_cleanup
The OJK module enforces a 5-year minimum retention floor when AXONFLOW_COMPLIANCE_REGION includes ID.
Readiness API
Get readiness
GET /api/v1/ojk/audit/readiness
The readiness response shape:
{
"ready": false,
"score": 70,
"framework": "OJK_BI_COMBINED",
"measured_checks": 5,
"unknown_checks": 0,
"checks": [
{
"name": "Data Retention",
"description": "OJK requires minimum 5-year retention of AI decision records",
"status": "pass",
"details": "Retention configured at 3650 days (minimum 1825).",
"observed": 3650
},
{
"name": "PII Detection",
"description": "NIK, NPWP, and bank-account detection must be active per UU PDP",
"status": "warning",
"details": "1 enabled Indonesia PII policies are visible, but no detection event was recorded in the last 90 days. The control is configured; this window contains no evidence that it fired.",
"observed": 0
},
{
"name": "Human Oversight",
"description": "OJK AI Governance requires human oversight for material decisions",
"status": "fail",
"details": "3 oversight requests were queued in the last 90 days and NONE was reviewed. Gated decisions are not receiving human review.",
"observed": 0
}
],
"recommendations": ["Human Oversight: 3 oversight requests were queued …"]
}
Every check queries the state it names, or reports unknown. A dimension the
platform cannot observe from this deployment is never reported as a pass.
| Check | What is measured |
|---|---|
| Data Retention | Effective retention configuration against the 1825-day Indonesian floor |
| PII Detection | Enabled pii-indonesia policy rows visible to the organization, plus detection events in the last 90 days |
| Human Oversight | Oversight requests queued in the last 90 days, and how many were reviewed |
| Audit Logging | Audit records for the organization in the last 90 days |
| Breach Notification | Recorded breaches past the 3x24 hour window without a timely submission |
Each check contains:
namedescriptionstatus-pass,warning,fail, orunknowndetails- what was OBSERVED, not what is assumedobserved- the raw measurement behind the verdict, when there is one
Scoring: a pass counts 1, a warning 0.5, a failure 0; an unknown check scores
zero while still counting in the denominator, so an unobservable dimension
drags the score down rather than inflating it. measured_checks /
unknown_checks expose the split. ready requires no failures, no
unknowns, and a score of at least 80.
- optional
details
Breach Notification API
Generate breach notification (UU PDP Art. 46)
POST /api/v1/ojk/breach/notify
Request fields (all required):
{
"incident_timestamp": "2026-03-15T09:10:00Z",
"discovery_time": "2026-03-15T14:30:00Z",
"data_subjects_affected": 1200,
"data_types_involved": ["nik", "npwp", "bank_account"],
"description": "Credential leak exposed customer identity records",
"remediation_steps": [
"Revoked compromised API credentials",
"Enabled additional PII redaction policies",
"Initiated forensic audit of affected tenant"
]
}
Request field details:
incident_timestamp- RFC3339 timestamp of the breach event itselfdiscovery_time- RFC3339 timestamp when the breach was discovered (the notification deadline is calculated as 72 hours from this)data_subjects_affected- positive integer count of affected data subjectsdata_types_involved- array of personal data types that were breacheddescription- free-text incident description (UU PDP Art. 46)remediation_steps- array of strings describing actions taken or planned
The handler returns 201 Created with the stored notification:
id- unique tracking identifiernotification_deadline- 72 hours fromdiscovery_timenotified_authority- defaults to"MOCDA"(current notification recipient, pending DPA constitution)status- the effective lifecycle status: submitting via this endpoint yieldssubmitted, oroverdueif the 72-hour window had already lapsed at submissionsubmitted_at,created_at- server timestamps- the request fields echoed back
Acknowledge breach notification
POST /api/v1/ojk/breach/acknowledge
Records authority receipt for a previously submitted breach. Body: {"id": "<notification-id>"}. Returns the updated notification; 404 not_found for an unknown id and 409 invalid_transition when the notification is not in an acknowledgeable state.
Evaluate breach deadlines
POST /api/v1/ojk/breach/evaluate-deadlines
Durably flips never-submitted draft notifications whose 72-hour window has lapsed to overdue. Returns {"flipped_overdue": <count>}.
Lifecycle statuses across the breach endpoints: draft → submitted → acknowledged, with overdue for a lapsed deadline and failed as the terminal error state.
Dashboard API
Get dashboard
GET /api/v1/ojk/dashboard
The dashboard returns organization-scoped counts, each derived from a query:
| Field | Derivation |
|---|---|
compliance_score | The readiness score, so this and /audit/readiness cannot disagree. readiness_unknown_checks reports unread dimensions; non-zero adds compliance_score:partial to unavailable |
total_audit_records | Audit records for the organization, all time |
active_policies | Enabled Indonesia-PII policy rows the organization can see (global system tier plus its own) |
recent_violations | Blocked, redacted or needs-approval decisions in the last 30 days |
indonesia_pii_events | Indonesia PII detection events in the last 30 days |
breach_notifications / overdue_breach_notifications | Recorded breaches, and how many are past the 3x24 hour window |
retention_status | Derived from the Data Retention readiness check |
A count that cannot be derived on this deployment is returned as -1 and named
in an unavailable array. "We could not measure this" and "there were none" are
different answers, so they are different values.
Error responses
All OJK endpoints return a structured error body:
{
"code": "validation_error",
"message": "end_date must be after start_date",
"details": "optional additional context"
}
Common error codes:
| Status | code | When |
|---|---|---|
| 400 | invalid_request / validation_error | Malformed body or invalid request parameters |
| 400 | missing_tenant | No tenant context resolved for the request |
| 404 | not_found | Export or breach-notification ID does not exist |
| 409 | invalid_transition | Breach notification cannot move to the requested lifecycle state |
| 500 | internal_error | Server-side failure |
