SATU SEHAT (Kemenkes) Platform SDK for Node.js — TypeScript, ESM, zero runtime dependencies.
npm install github:harizinside/satusehatFull step-by-step integration guide (real order of operations, gotchas, confirmed-working examples): see wiki/.
Covers the platform's public surface: OAuth2 client-credentials, 39+ FHIR R4 resources (including Appointment/HealthcareService/AppointmentResponse/ PractitionerRole/Slot scheduling and FamilyMemberHistory), Master Data (wilayah + sarana), KFA (farmasi/alkes formulary & pricing), KYC (faskes), national RME consent/link, the Rawat Jalan and Gigi outpatient workflows, the private-insurance (Asuransi Swasta) + BPJS-Kesehatan claim modules, ~50 FHIR terminology/coding constants, and full ICD-10/ICD-9-CM catalogs.
This SDK has been exercised end-to-end against the real SATU SEHAT staging
sandbox — not just compiled. Rawat Jalan (16 steps), Gigi (11 steps), Master
Data, KYC, and RME have all been run with real API calls, with several real
server-side requirements discovered and fixed along the way (see
wiki/Known-Issues.md).
Mirrors the sibling doku package:
SatuSehatClient— config + low-levelrequest(); injectablefetchImpl- Standalone functions, one module per collection folder:
fn(client, ...args) createSatuSehatClient(config)binds every module onto the instance and is also exported standalone for tree-shaking:import { search } from "@harizinside/satusehat"; // standalone satusehat.search.searchPatient(params); // bound to a client
- Auth is caller-managed. The SDK never caches or auto-refreshes tokens —
getAccessToken()fetches and returns{ accessToken, expiresAt, ... }; you store it (Redis, DB, memory) and pass it back vianew SatuSehatClient({ token })orclient.setToken().
Four API families, each with its own host/path shape — confirmed against the
live server, not just the source collections (which had a stale Master Data
host). Full detail: wiki/Setup-and-Auth.md.
| Family | production |
staging |
|---|---|---|
FHIR (fhir.*) |
api-satusehat.kemkes.go.id/fhir-r4/v1 |
api-satusehat-stg.dto.kemkes.go.id/fhir-r4/v1 |
| Master Data + KFA | api-satusehat.kemkes.go.id |
api-satusehat-stg.dto.kemkes.go.id |
| OAuth2 | api-satusehat.kemkes.go.id/oauth2/v1 |
api-satusehat-stg.dto.kemkes.go.id/oauth2/v1 |
| KYC | api-satusehat.kemkes.go.id/kyc/v1 |
api-satusehat-stg.dto.kemkes.go.id/kyc/v1 |
| RME | api-satusehat.kemkes.go.id |
api-satusehat-stg.dto.kemkes.go.id |
All four staging hosts are the same .dto. domain, just different path
prefixes (baseUrl, masterDataBaseUrl, authBaseUrl, kycBaseUrl,
rmeBaseUrl on SatuSehatClientConfig — override any of them per client).
import { createSatuSehatClient, nikIdentifier } from "@harizinside/satusehat";
const satusehat = createSatuSehatClient({
environment: "staging", // or "production"
clientId: process.env.SATUSEHAT_CLIENT_ID,
clientSecret: process.env.SATUSEHAT_CLIENT_SECRET,
// token: cachedAccessToken, // optional — skips an explicit auth step
});
// 1. Mint a token (caller owns storage/refresh)
const { accessToken, expiresAt } = await satusehat.auth.getAccessToken();
satusehat.setToken(accessToken); // or keep it and pass `token` on next construction
// 2. Find the patient (FHIR search)
const patients = await satusehat.search.searchPatient({
identifier: nikIdentifier("9271060312000001"), // helper builds "https://fhir.kemkes.go.id/id/nik|<nik>"
});
const patient = patients.entry?.[0]?.resource;
// 3. Outpatient visit (Rawat Jalan) — see wiki/Rawat-Jalan-Flow.md for the full
// 16-step sequence with every required field this session found by testing
// live (Medication.extension, PATCH content-type, identifier namespaces, ...)
const encounter = await satusehat.fhir.encounter.encounterCreate({
resourceType: "Encounter",
status: "arrived",
class: satusehat.coding.ENCOUNTER_CLASS.ambulatory,
subject: { reference: `Patient/${patient.id}` },
// ...
});- Next.js — construct the client inside route handlers/server actions with
tokenread from your cache; never exposeclientSecretto the browser. - Hono — the client is plain ESM with no runtime deps, so it runs on
nodejs_compatworkers/middleware as-is. - TanStack Start — same; use server functions for any call touching secrets.
satusehat.coding.* — ~50 FHIR/Kemkes CodeSystem constants (ENCOUNTER_CLASS,
OBSERVATION_CATEGORY, CONDITION_CLINICAL_STATUS, KEMKES_MEDICATION_TYPE,
dental (DENTAL_*) vocabulary, identifier-namespace helpers, etc.) so you
rarely have to hand-type a {system, code, display} triple. Built from the
official FHIR R4 spec plus SATU SEHAT's docs, and the ones exercised in this
session's live testing are confirmed working — see
wiki/Known-Issues.md for which constants are still
unconfirmed guesses vs. proven against the real server.
Full Kemkes e-klaim catalogs (18,542 / 4,626 codes) are bundled but not in the main import — they're on separate subpaths so consumers who don't need them never pay for the extra bundle size:
import { icd10Coding, searchIcd10 } from "@harizinside/satusehat/icd10";
import { icd9cmCoding, searchIcd9cm } from "@harizinside/satusehat/icd9cm";
searchIcd10("cholera", 5); // -> Coding[] matched by display substringModules for the big collections were generated from the official SATU SEHAT
Postman collections (deleted from this repo after generation — they're ~30MB
of source data, not needed at runtime, and the compiled src/*.ts is what
ships). To regenerate or extend: re-export the relevant collection(s) from
your own Postman workspace into the repo root, then:
npm run generate # writes any missing src/<module>/ files
npm run generate -- --force # overwrite everything, including hand-written files
npm run generate:icd # regenerate ICD-10/9-CM from CSV (also deleted; same deal)- One function per unique method+path per module file (the BPJS collection's 610 saved example scenarios collapse to their ~50 real endpoints).
- Files that already exist are skipped unless
--force, so hand-refined modules (rawat-jalan/bundle.ts,klaim-swasta/webhook.ts,common/*,fhir/coding/*) survive re-runs.
See wiki/Known-Issues.md for the full list, confirmed
live. Highlights:
- KFA v1 ATC/tag endpoints are dead (
getAtcMetadata,getProductsByAtc,getTagMetadata,getProductsByTag) — real404s, removed from the current official docs too. Marked@deprecated; usegetAllProductsV2instead. - KFA Alkes V3 Basic auth —
POST /kfa-v3/alkes/templateand/kfa-v3/alkes/productsship with a hardcodedAuthorization: Basic ...header (not the platform OAuth2 Bearer) and an internal-only host. Implemented askfaAlkes.getAlkesTemplates/Products(client, body, { authorization, baseUrl }); may not be reachable from outside Kemkes' network. - Tablet/countable dosage quantities use
v3-orderableDrugForm(code: "TAB"), not UCUM — confirmed live after UCUM's own codes ({tbl},1) were both rejected by the server.coding.UCUM_COUNTis marked@deprecatedpointing at the right constant instead. - KYC (
client.kyc.*) — envelope encryption (RSA-OAEP-sha256 + AES-256-GCM, matching SATU SEHAT's official PHP client) is built in vianode:crypto(zero new dependency); you only supplysatuSehatPublicKey(their published key, not one you generate).generateChallengeCodeneeds theframeTokenprivateKeyreturned by a precedinggenerateKycUrlcall — seewiki/KYC-and-RME.md.
- Webhook examples —
Katalog Webhookrequests target{{webhook_url}}(your own endpoint).klaimSwasta.webhookprovides typed parsers (parseWebhookPayloadAs(body, "chargeItemSubmission")) plussendWebhookCallback()for the acknowledgement call.
| Script | Purpose |
|---|---|
npm run build |
tsc → dist/ |
npm run generate |
regenerate FHIR modules (needs source collections re-supplied, see Codegen) |
npm run generate:icd |
regenerate ICD-10/9-CM data (needs source CSVs re-supplied) |
npm run smoke |
staging smoke test (see below) |
SATUSEHAT_CLIENT_ID=... SATUSEHAT_CLIENT_SECRET=... npm run smokeMints a token, searches master-data provinces, and (optionally, with
SATUSEHAT_TEST_NIK set) searches a patient by NIK — all non-destructive reads.
npm run build(tsc, strict): clean.- Live-tested against SATU SEHAT staging, not just compiled: full Rawat
Jalan flow (23/23 steps, incl. MedicationRequest/MedicationDispense), full
Gigi flow (14/14 steps), Immunization, Master Wilayah + Sarana + most KFA,
full KYC flow (
generateKycUrl+generateChallengeCode, encrypted end-to-end), RME (routing confirmed, needs real registered patient/practitioner data + eligible practitioner role to fully exercise). No open issues at the moment — seewiki/Known-Issues.mdfor the fixes that got each one there and the gotchas worth knowing before you hit them yourself.