Indonesia is building a national health data exchange called SATUSEHAT, and every hospital in the country needs to integrate with it. If you're a developer tasked with this integration, this guide covers the architecture, FHIR resource types, common gotchas, and a realistic timeline for going live.
What is SATUSEHAT?
SATUSEHAT is Indonesia's national health data platform, managed by the Ministry of Health (Kemenkes). Think of it as Indonesia's answer to nationwide health interoperability — a centralized FHIR R4 server where hospitals push clinical encounter data.
The mandate is clear: hospitals that fail to integrate face sanctions from the Directorate General of Health Services (Dirjen Yankes). As of 2026, over 1,200 hospitals have been flagged for non-compliance, with a June 2026 deadline looming.
The platform runs on HL7 FHIR R4 and exposes RESTful APIs for data submission. The developer portal is at satusehat.kemkes.go.id.
FHIR Resources You Need to Implement
SATUSEHAT doesn't require every FHIR resource — just a specific subset relevant to Indonesian clinical workflows. Here's what you'll need:
| Resource | Purpose | Required? |
|---|---|---|
Organization |
Your hospital entity | Yes |
Location |
Departments, rooms, beds | Yes |
Practitioner |
Doctors, nurses | Yes |
Patient |
Patient demographics (linked to NIK) | Yes |
Encounter |
Clinical visits/admissions | Yes |
Condition |
Diagnoses (ICD-10) | Yes |
MedicationRequest |
Prescriptions | Yes |
MedicationDispense |
Pharmacy dispensing | Yes |
Observation |
Vital signs, lab results | Conditional |
Specimen |
Lab specimens (SNOMED-CT) | Conditional |
ImagingStudy |
Radiology (DICOM + NIDR) | Conditional |
The "conditional" resources depend on your hospital's service scope. A hospital with a radiology department needs ImagingStudy; a small hospital without a lab may skip Specimen initially.
Authentication Flow
SATUSEHAT uses OAuth 2.0 Client Credentials flow. You register your application in the developer portal to get a client_id and client_secret, then exchange them for an access token:
# Get access token
curl -X POST https://api-satusehat.kemkes.go.id/oauth2/v1/accesstoken?grant_type=client_credentials \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
{
"access_token": "eyJ...",
"token_type": "BearerToken",
"expires_in": "3599",
"scope": ""
}
Tokens expire in 1 hour. Build token refresh into your integration layer — don't fetch a new token per request.
The Integration Architecture
Here's the pattern we've found works well for Indonesian hospitals:
┌──────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ SIMRS / │ │ Integration │ │ SATUSEHAT │
│ RME │────▶│ Gateway │────▶│ FHIR Server │
│ Database │ │ │ │ │
└──────────────┘ │ - FHIR mapper │ └─────────────────┘
│ - Queue system │
│ - Retry logic │
│ - Audit log │
└──────────────────┘
The Integration Gateway sits between your hospital's SIMRS and SATUSEHAT. It handles:
- Data mapping: Transform SIMRS-specific data models to FHIR R4 resources
- Queueing: Buffer submissions during network issues (hospital internet in Indonesia can be unreliable)
- Retry with backoff: SATUSEHAT API has rate limits and occasional downtime
- Audit logging: Track every submission for compliance reporting
Example: Mapping an Encounter
// Transform SIMRS visit data to FHIR Encounter
function mapToFHIREncounter(visit) {
return {
resourceType: "Encounter",
status: mapVisitStatus(visit.status),
class: {
system: "http://terminology.hl7.org/CodeSystem/v3-ActCode",
code: visit.type === "inpatient" ? "IMP" : "AMB",
display:
visit.type === "inpatient" ? "inpatient encounter" : "ambulatory",
},
subject: {
reference: `Patient/${visit.patient_satusehat_id}`,
display: visit.patient_name,
},
participant: [
{
individual: {
reference: `Practitioner/${visit.doctor_satusehat_id}`,
display: visit.doctor_name,
},
},
],
period: {
start: formatToFHIRDateTime(visit.admission_date),
end: visit.discharge_date
? formatToFHIRDateTime(visit.discharge_date)
: undefined,
},
location: [
{
location: {
reference: `Location/${visit.department_satusehat_id}`,
display: visit.department_name,
},
},
],
serviceProvider: {
reference: `Organization/${HOSPITAL_ORG_ID}`,
},
};
}
Common Gotchas
After integrating multiple hospitals, here are the pitfalls that catch most teams:
1. Patient NIK Matching
SATUSEHAT links patients via NIK (national ID number). The problem: many hospital databases have inconsistent NIK data — missing digits, typos, or placeholder values. You'll need a data cleanup step before integration.
def validate_nik(nik: str) -> bool:
"""Indonesian NIK is exactly 16 digits."""
if not nik or not nik.isdigit():
return False
if len(nik) != 16:
return False
# First 6 digits = region code (validate against known codes)
region = nik[:6]
return region in VALID_REGION_CODES
2. SATUSEHAT ID Mapping
Every entity (patient, practitioner, location) needs a SATUSEHAT-assigned ID. You need to:
- Search SATUSEHAT first to find existing records
- Create only if not found
- Store the mapping in your local database
Never hardcode SATUSEHAT IDs. They can change during data migrations.
3. Sandbox vs. Production Differences
The sandbox environment at api-satusehat-stg.kemkes.go.id doesn't perfectly mirror production. Some things we've seen:
- Sandbox accepts malformed resources that production rejects
- Rate limits are more generous in sandbox
- Some code systems have different validation rules
Always do a pilot run in production with real (consented) data before declaring integration complete.
4. FHIR DateTime Formatting
SATUSEHAT is strict about datetime formats. Use ISO 8601 with timezone:
// Correct
"2026-04-04T10:30:00+07:00"
// Wrong (will be rejected)
"2026-04-04 10:30:00"
"04/04/2026"
5. ICD-10 Code Validation
SATUSEHAT validates ICD-10 codes against the ICD-10 WHO version, not ICD-10-CM. Some codes that exist in ICD-10-CM don't exist in the WHO version. Make sure your mapping uses the correct codeset.
Realistic Timeline
Based on our experience across multiple hospital integrations:
| Phase | Duration | Activities |
|---|---|---|
| Registration | 1-2 weeks | Portal registration, credential setup, team onboarding |
| Data Audit | 2-3 weeks | NIK cleanup, master data mapping, gap analysis |
| Development | 4-6 weeks | FHIR mapper, gateway, queue system, retry logic |
| Sandbox Testing | 2-3 weeks | Submit test data, fix validation errors, load testing |
| Production Pilot | 2-3 weeks | Limited department rollout, monitor success rates |
| Full Go-Live | 1-2 weeks | All departments, monitoring, staff training |
Total: 12-19 weeks for a typical hospital. Smaller hospitals with simpler SIMRS can be faster; large hospitals with complex legacy systems take longer.
Monitoring Post-Integration
Once live, track these metrics:
- Submission success rate — aim for >98%. Below that, investigate validation errors.
- Retry rate — high retries suggest network or rate limit issues.
- Data completeness — are all departments submitting? Some may quietly drop off.
- Latency — SATUSEHAT API response times can spike during peak hours (morning clinic times).
# Simple monitoring check
def check_submission_health(last_24h_stats):
success_rate = stats.success / stats.total
if success_rate < 0.98:
alert(f"SATUSEHAT submission rate dropped to {success_rate:.1%}")
if stats.avg_retry > 2:
alert(f"High retry rate: {stats.avg_retry:.1f} per submission")
Resources
- SATUSEHAT Developer Portal — official API docs, sandbox access, code system references
- Cara Integrasi SATUSEHAT dengan SIMRS: Panduan Teknis — step-by-step guide in Indonesian
- HL7 FHIR R4 Specification — the standard SATUSEHAT is built on
- MedMinutes — health IT tools for Indonesian hospitals, including managed SATUSEHAT integration
MedMinutes provides managed SATUSEHAT integration for hospitals that want to comply without building the entire pipeline in-house. We handle the FHIR mapping, queue management, and ongoing API change monitoring. Currently serving 50+ hospitals across Indonesia.
Top comments (0)