Lewati ke isi

Pengembang

API publik dan webhook

Integrasikan Medivana dengan HIS, aplikasi pasien, atau gudang data Anda secara aman. Tersedia di paket Klinik Pro dan Enterprise, atau add-on API dan webhook di paket Klinik.

127 operasi · 30 event webhook · 7 scope

Contoh permintaan token
POST /oauth/token
Authorization: Basic ‹client_id:client_secret›
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&scope=patients:read encounters:read

→ 200 { "token_type": "Bearer", "expires_in": 3600 }

Autentikasi

Dari klien API ke panggilan pertama

  1. 01

    Buat klien API

    Admin klinik membuka Pengaturan, API dan webhook, lalu memilih scope, cabang, dan batas laju. Client secret hanya ditampilkan sekali.

  2. 02

    Tukar menjadi token

    POST /oauth/token dengan Basic auth dan grant_type=client_credentials. Token opaque berlaku 3.600 detik; minta token baru sebelum kedaluwarsa.

  3. 03

    Panggil endpoint

    Kirim Authorization: Bearer ‹token›. Pilih cabang dengan X-Branch-ID bila klien berlaku di beberapa cabang.

  4. 04

    Cabut kapan saja

    Mengubah scope, merotasi secret, atau mencabut klien langsung membatalkan semua token aktifnya. Setiap akses tercatat di audit log klinik.

Autentikasi dan tanda tangan

  • OAuth2 client credentials

    Admin klinik membuat klien API; integrator menukar kredensial menjadi token Bearer berumur 1 jam.

  • Scope = izin

    Scope dipetakan ke izin RBAC, jadi endpoint menegakkan hak akses yang sama dengan pengguna aplikasi.

  • Webhook bertanda tangan

    Header X-Medivana-Signature: t=‹unix›,v1=HMAC-SHA256(secret, "‹t›." + body); tolak bila selisih waktu > 5 menit.

Mulai cepat

Tanpa SDK: curl, JavaScript, Python

Dua permintaan cukup untuk membaca data pertama. Simpan kredensial di variabel lingkungan dan jangan pernah menaruh client secret di aplikasi peramban atau ponsel.

1. Minta access token

Bahasa contoh kode
curl -X POST "https://api.medivana.cloud/api/v1/oauth/token" \
  -u "$MEDIVANA_CLIENT_ID:$MEDIVANA_CLIENT_SECRET" \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'scope=patients:read encounters:read'
const basic = Buffer.from(`${process.env.MEDIVANA_CLIENT_ID}:${process.env.MEDIVANA_CLIENT_SECRET}`).toString("base64");
const res = await fetch("https://api.medivana.cloud/api/v1/oauth/token", {
  method: "POST",
  headers: { Authorization: `Basic ${basic}` },
  body: new URLSearchParams({
    grant_type: "client_credentials",
    scope: "patients:read encounters:read",
  }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const data = await res.json();
import os
import requests

resp = requests.post(
    "https://api.medivana.cloud/api/v1/oauth/token",
    auth=(os.environ["MEDIVANA_CLIENT_ID"], os.environ["MEDIVANA_CLIENT_SECRET"]),
    data={
        "grant_type": "client_credentials",
        "scope": "patients:read encounters:read",
    },
    timeout=30,
)
resp.raise_for_status()
data = resp.json()

2. Cari pasien dengan token

Bahasa contoh kode
curl -X GET "https://api.medivana.cloud/api/v1/patients?q=RM-000123" \
  -H "Authorization: Bearer $MEDIVANA_TOKEN"
const res = await fetch("https://api.medivana.cloud/api/v1/patients?q=RM-000123", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.MEDIVANA_TOKEN}`,
  },
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
const data = await res.json();
import os
import requests

resp = requests.get(
    "https://api.medivana.cloud/api/v1/patients?q=RM-000123",
    headers={"Authorization": f"Bearer {os.environ['MEDIVANA_TOKEN']}"},
    timeout=30,
)
resp.raise_for_status()
data = resp.json()

3. Ambil halaman berikutnya

Daftar berhalaman mengembalikan next_cursor. Kirim kembali sebagai ?cursor= sampai next_cursor tidak ada.

Bahasa contoh kode
curl "https://api.medivana.cloud/api/v1/patients?limit=100&cursor=$NEXT_CURSOR" \
  -H "Authorization: Bearer $MEDIVANA_TOKEN"
let cursor;
do {
  const url = new URL("https://api.medivana.cloud/api/v1/patients");
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("cursor", cursor);
  const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.MEDIVANA_TOKEN}` } });
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  const page = await res.json();
  for (const patient of page.items) console.log(patient.mrn, patient.name);
  cursor = page.next_cursor;
} while (cursor);
import os
import requests

session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['MEDIVANA_TOKEN']}"
params = {"limit": 100}
while True:
    resp = session.get("https://api.medivana.cloud/api/v1/patients", params=params, timeout=30)
    resp.raise_for_status()
    page = resp.json()
    for patient in page["items"]:
        print(patient["mrn"], patient["name"])
    if not page.get("next_cursor"):
        break
    params["cursor"] = page["next_cursor"]

Otorisasi

Scope dan izin RBAC

Minta scope sesedikit mungkin. Operasi yang tidak diizinkan scope token menjawab 403 forbidden; endpoint konteks (cabang, unit, tenaga kesehatan, terminologi) bisa dipanggil token apa pun.

Scope API publik dan izin RBAC yang dibukanya
ScopeIzin RBACOperasi
patients:readpatient.read9 operasi
patients:writepatient.read, patient.write20 operasi
encounters:readencounter.read62 operasi
appointments:readappointment.read5 operasi
appointments:writeappointment.read, appointment.manage16 operasi
invoices:readinvoice.read19 operasi
webhooks:managewebhook.manage9 operasi

Konvensi

Aturan yang berlaku di semua endpoint

  • Cabang aktif

    Header X-Branch-ID memilih cabang. Operasi tulis per cabang menolak permintaan tanpa cabang aktif (400); klien satu cabang memakai cabangnya otomatis.

  • Paginasi cursor

    Daftar berbentuk items dan next_cursor. Ukuran halaman diatur limit; cursor bersifat opaque dan tidak boleh disusun sendiri.

  • Batas laju

    Per klien sesuai rate_limit_per_min (default 120 per menit) dan 60 permintaan per menit per IP di endpoint OAuth. Melebihi batas menjawab 429 rate-limited.

  • Konkurensi

    Detail membawa ETag berisi versi. Kirim If-Match saat PATCH agar perubahan ditolak 412 bila data sudah diubah pihak lain.

  • Pengulangan aman

    Duplikat ditolak 409 (NIK pasien yang sama, slot booking yang sama), jadi permintaan yang gagal di jaringan aman diulang lalu dibaca kembali.

  • Versi dan bahasa

    Versi mayor ada di path /api/v1. Field, enum, event, dan endpoint baru ditambahkan tanpa versi baru, jadi abaikan field yang tidak dikenal. Galat mengikuti Accept-Language (id atau en).

Galat

Format galat RFC 9457

Galat berformat application/problem+json. Gunakan code dan errors[].field untuk logika program; title dan detail diterjemahkan sesuai Accept-Language. Sertakan request_id saat menghubungi dukungan.

Kode galat API publik
HTTPcodeArti
400bad-requestFormat atau parameter salah, atau cabang aktif belum dipilih
401unauthorizedToken tidak ada, salah, kedaluwarsa, atau dicabut
402module-lockedModul yang melayani endpoint tidak ada di paket klinik
403forbiddenScope token tidak memberi izin untuk aksi ini
403confidential-recordRekam medis ditandai rahasia; klien API tidak bisa membukanya
404not-foundData tidak ada atau di luar cakupan klien
409conflictDuplikat atau status data tidak mengizinkan aksi
412staleIf-Match tidak cocok dengan versi terbaru
422validationValidasi gagal; rincian per field di errors
429rate-limitedBatas laju terlampaui; coba lagi pada menit berikutnya

Endpoint OAuth memakai format RFC 6749, misalnya error invalid_client atau invalid_scope.

application/problem+json

{
  "type": "https://medivana.cloud/errors/forbidden",
  "title": "Akses ditolak",
  "status": 403,
  "code": "forbidden",
  "detail": "Peran Anda tidak memiliki izin untuk aksi ini.",
  "instance": "/api/v1/billing/invoices",
  "request_id": "01JC9Z3Y8M4N2P5Q7R9S0T1V2W"
}

Webhook

Event bertanda tangan HMAC-SHA256

Langgan event dengan POST /webhooks. Medivana mengirim POST JSON ke URL https Anda dengan header X-Medivana-Event, X-Medivana-Event-ID, X-Medivana-Delivery, dan X-Medivana-Signature.

Verifikasi setiap pengiriman

  1. Baca body mentah sebelum di-parse, lalu ambil t dan v1 dari X-Medivana-Signature.
  2. Tolak bila selisih waktu sekarang dan t lebih dari 300 detik (mencegah replay).
  3. Hitung HEX(HMAC-SHA256(secret, t + "." + body)) dan bandingkan dengan v1 secara konstan-waktu.
  4. Deduplikasi memakai id event; pengiriman ulang membawa id yang sama.
  5. Balas 2xx dalam 10 detik, lalu proses secara asinkron.

Pengiriman ulang

Selain 2xx atau timeout dicoba ulang 1, 5, 15, dan 60 menit, lalu tiap 6 jam selama 3 hari. Jawaban 410 Gone menonaktifkan langganan. Riwayat dan kirim ulang manual tersedia lewat API.

Lihat katalog event

Contoh verifikasi

Bahasa contoh kode
import crypto from "node:crypto";

export function verifyMedivanaSignature(rawBody, header, secret, toleranceSec = 300) {
  let t = 0;
  const sigs = [];
  for (const part of header.split(",")) {
    const [k, v] = part.trim().split("=", 2);
    if (k === "t") t = Number(v);
    if (k === "v1") sigs.push(v);
  }
  if (!t || sigs.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
  return sigs.some((s) => {
    const got = Buffer.from(s, "hex");
    return got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
}
import hashlib
import hmac
import time


def verify_medivana_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    t, sigs = 0, []
    for part in header.split(","):
        k, _, v = part.strip().partition("=")
        if k == "t" and v.isdigit():
            t = int(v)
        elif k == "v1":
            sigs.append(v)
    if not t or not sigs or abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, s) for s in sigs)

Spesifikasi OpenAPI 3.1

Impor ke Postman, Insomnia, atau generator klien. Spesifikasi diuji otomatis terhadap router dan peta scope Medivana di setiap perubahan.

Langkah berikutnya

Lihat Medivana bekerja untuk klinik Anda

Coba demo mandiri dengan data contoh, atau minta tim kami menunjukkan alur yang paling relevan untuk klinik Anda.

Demo mandiri berisi data contoh sesuai jenis klinik Anda dan berlaku 7 hari.