FHIR RESTful API: cách FHIR dùng HTTP để trao đổi dữ liệu y tế
FHIR RESTful API là API tài nguyên (resource-oriented) trên nền HTTPS, sử dụng JSON làm định dạng mặc định cho ứng dụng web và di động.
Mọi tương tác — đọc, tìm kiếm, tạo mới, cập nhật, xóa — được ánh xạ vào các phương thức HTTP chuẩn (GET, POST, PUT, DELETE) trên các endpoint cấp instance, cấp type, cấp toàn hệ thống và operation đặt tên với tiền tố $.
Trang này dành cho lập trình viên web/mobile đã quen REST nhưng chưa từng code FHIR, muốn hiểu vì sao API y tế có tính chất riêng và cách tích hợp đúng chuẩn vào hệ thống bệnh viện Việt Nam theo khung pháp lý hiện hành (Luật 91/2025, NĐ 356/2025, NĐ 137/2024).
Tóm tắt nhanh
- Năm tương tác cốt lõi (read, search, create, update, delete) cộng thêm vread, history, patch, Bundle transaction, Operation và Bulk Data đủ phục vụ mọi use case y tế.
- Định danh tài nguyên theo cú pháp
[base]/[type]/[id]; cấp type cho search và create; cấp toàn hệ thống cho transaction và$export. - Định dạng chuẩn FHIR R4:
application/fhir+jsonvàapplication/fhir+xmlđều normative; trang này khuyến nghị JSON cho web/mobile. - Bảo mật mặc định: HTTPS bắt buộc, xác thực qua SMART on FHIR (OAuth 2.0 + OpenID Connect) và lưu vết AuditEvent cho mọi truy cập.
- Khung pháp lý Việt Nam (Luật 91/2025 BVDLCN, NĐ 356/2025, NĐ 137/2024) đặt nghĩa vụ bảo vệ dữ liệu cá nhân và chữ ký số; FHIR + SMART là cách hợp lý để hiện thực hóa.
Nội dung trang
- Cấu trúc endpoint FHIR
- Năm tương tác cốt lõi và họ hàng mở rộng
- Search — truy vấn dữ liệu lâm sàng
- Bundle transaction — gộp nhiều thao tác nguyên tử
- CapabilityStatement — discovery của server
- Operations: $expand, $validate, $everything…
- Bulk Data API và $export bất đồng bộ
- Xác thực: SMART on FHIR + OAuth 2.0
- Mã trạng thái và OperationOutcome
- Versioning, conditional, paging
- Best practices cho bối cảnh Việt Nam
- Đọc tiếp
1. Cấu trúc endpoint FHIR
FHIR là REST hướng tài nguyên (resource-oriented). Mọi URL được dựng từ một service base URL kết hợp với loại tài nguyên, định danh tài nguyên, lịch sử phiên bản và truy vấn tìm kiếm. Cú pháp tổng quát theo §3.1 của FHIR R4 là:
[base]/[type]/[id]{/_history/[vid]}{?[search]} [base] là URL gốc của server (ví dụ https://hapi.fhir.org/baseR4); [type] là tên Resource viết hoa đúng (Patient, Encounter, Observation…); [id] là chuỗi định danh logic do server cấp; [vid] là số phiên bản tăng đơn điệu sau mỗi lần update. Có bốn cấp endpoint cần phân biệt:
- Instance level (
/Patient/123): áp dụng cho read, vread, update, delete, patch. - Type level (
/Patient): áp dụng cho search và create. - Whole-system level (
/): dùng cho batch/transaction Bundle và search toàn server. - Operation (
$name): áp dụng ở cả ba cấp trên, ví dụ/Patient/$matchhay/Patient/123/$everything.
Ví dụ thực tế trên HAPI sandbox công khai (ghi chú: chỉ dùng cho dev/test, không gửi dữ liệu thật):
curl -H "Accept: application/fhir+json" \
https://hapi.fhir.org/baseR4/Patient/example
curl -H "Accept: application/fhir+json" \
"https://hapi.fhir.org/baseR4/Patient?name=Smith&_count=5" 2. Năm tương tác cốt lõi và họ hàng mở rộng
Đặc tả FHIR R4 định nghĩa năm tương tác bắt buộc — read, search, create, update, delete — và một số tương tác mở rộng phổ biến. Bảng sau ánh xạ phương thức HTTP, URL mẫu và tình huống dùng:
| Tương tác | HTTP | URL | Use case |
|---|---|---|---|
| read | GET | /Patient/123 | Đọc một Resource theo id |
| vread | GET | /Patient/123/_history/2 | Đọc một phiên bản lịch sử cụ thể |
| search | GET | /Patient?name=Nguyễn | Tìm theo tham số |
| create | POST | /Patient | Tạo mới, server tự cấp id |
| update | PUT | /Patient/123 | Thay thế toàn bộ Resource |
| patch | PATCH | /Patient/123 | Cập nhật một phần (JSON Patch / FHIRPath Patch) |
| delete | DELETE | /Patient/123 | Xóa logic tài nguyên |
| history | GET | /Patient/123/_history | Liệt kê lịch sử thay đổi |
| capabilities | GET | /metadata | Discovery năng lực server |
Server có quyền chọn nhóm tương tác hỗ trợ; CapabilityStatement (mục 5) liệt kê chính xác từng Resource. Trong VN Core, chúng tôi khuyến nghị tối thiểu read, search, create, update cho Patient, Encounter, Condition, Observation và Coverage để đảm bảo bệnh án điện tử (TT 13/2025/TT-BYT) liên thông được với hồ sơ BHYT và Sổ Sức khỏe điện tử trên VNeID.
3. Search — truy vấn dữ liệu lâm sàng
Search là tương tác phức tạp nhất của FHIR. Mỗi Resource có một bộ search parameter chuẩn (như name, identifier, birthdate, code, subject) cùng nhiều modifier (:exact, :contains, :missing, :not, :above, :below) và prefix cho số/ngày (eq, ne, gt, lt, ge, le).
Khi tìm theo identifier có system rõ ràng, dùng cú pháp token system|value. Trong VN Core, system định danh cá nhân (CCCD) là http://fhir.hl7.org.vn/core/sid/cccd:
curl -H "Accept: application/fhir+json" \
"https://hapi.fhir.org/baseR4/Patient?identifier=http://fhir.hl7.org.vn/core/sid/cccd|001234567890"
curl -H "Accept: application/fhir+json" \
"https://hapi.fhir.org/baseR4/Patient?birthdate=ge1980-01-01&birthdate=lt1990-01-01"
curl -H "Accept: application/fhir+json" \
"https://hapi.fhir.org/baseR4/Observation?subject=Patient/example&code=http://loinc.org|2093-3"
Hai chế độ tham chiếu hữu ích là _include (kéo thêm Resource được tham chiếu từ kết quả) và _revinclude (kéo thêm Resource trỏ ngược về kết quả). Lưu ý quan trọng: cả hai là tham số tìm kiếm, chỉ áp dụng cho tương tác search ở cấp type, không áp dụng cho read instance:
# Lấy Patient kèm mọi Observation tham chiếu tới Patient đó
curl -H "Accept: application/fhir+json" \
"https://hapi.fhir.org/baseR4/Patient?_id=example&_revinclude=Observation:subject"
# Hoặc đảo chiều: lấy Observation và kéo theo Patient liên quan
curl -H "Accept: application/fhir+json" \
"https://hapi.fhir.org/baseR4/Observation?subject=Patient/example&_include=Observation:subject" Chained search cho phép đi qua tham chiếu để lọc Resource cha. Ví dụ tìm Observation thuộc về Patient có họ tên chứa "Nguyễn":
curl -H "Accept: application/fhir+json" \
"https://hapi.fhir.org/baseR4/Observation?subject:Patient.name=Nguy%E1%BB%85n" 4. Bundle transaction — gộp nhiều thao tác nguyên tử
Bundle là Resource đặc biệt cho phép đóng gói nhiều thao tác trong một HTTP request gửi tới endpoint cấp toàn hệ thống. Hai chế độ phổ biến: type=batch (mỗi entry độc lập, lỗi cục bộ) và type=transaction (atomic — nếu một entry thất bại, toàn bộ rollback). Transaction cũng hỗ trợ tham chiếu chéo qua fullUrl dạng urn:uuid: và header ifNoneExist cho conditional create.
Ví dụ chạy được trên HAPI sandbox (lưu vào bundle.json, gửi bằng curl --data-binary @bundle.json):
POST / HTTP/1.1
Host: hapi.fhir.org
Content-Type: application/fhir+json
Accept: application/fhir+json
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:patient-001",
"resource": {
"resourceType": "Patient",
"identifier": [{
"system": "http://fhir.hl7.org.vn/core/sid/cccd",
"value": "001234567890"
}],
"name": [{ "family": "Nguyễn", "given": ["Văn An"] }],
"gender": "male",
"birthDate": "1985-04-12"
},
"request": {
"method": "POST",
"url": "Patient",
"ifNoneExist": "identifier=http://fhir.hl7.org.vn/core/sid/cccd|001234567890"
}
},
{
"resource": {
"resourceType": "Encounter",
"status": "in-progress",
"class": {
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
"code": "AMB",
"display": "Ambulatory"
},
"subject": { "reference": "urn:uuid:patient-001" }
},
"request": { "method": "POST", "url": "Encounter" }
}
]
}
Server trả về Bundle type=transaction-response, mỗi entry mang response.status (ví dụ 201 Created) và response.location chỉ tới Resource vừa được tạo. Đây là cách thanh lịch để đẩy một ca khám trọn gói (Patient + Encounter + Condition + Observation) lên FHIR server từ HIS.
5. CapabilityStatement — discovery của server
Bất kỳ FHIR server nào cũng phải đáp lời tại GET [base]/metadata bằng một CapabilityStatement, mô tả: phiên bản FHIR (4.0.1 cho R4), các Resource hỗ trợ, danh sách tương tác cho từng Resource, search parameter đã implement, các Operation và phương thức bảo mật. Đây là tài liệu máy đọc được giúp client biết "server này làm được gì" trước khi gọi tiếp.
curl -H "Accept: application/fhir+json" \
https://hapi.fhir.org/baseR4/metadata | jq '.fhirVersion, .rest[0].resource[].type' Một trích đoạn CapabilityStatement tối giản cho VN Core:
{
"resourceType": "CapabilityStatement",
"status": "active",
"date": "2026-05-02",
"kind": "instance",
"fhirVersion": "4.0.1",
"format": ["application/fhir+json", "application/fhir+xml"],
"rest": [{
"mode": "server",
"security": {
"service": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/restful-security-service",
"code": "SMART-on-FHIR"
}]
}]
},
"resource": [{
"type": "Patient",
"interaction": [
{ "code": "read" },
{ "code": "search-type" },
{ "code": "create" },
{ "code": "update" }
],
"searchParam": [
{ "name": "identifier", "type": "token" },
{ "name": "name", "type": "string" },
{ "name": "birthdate", "type": "date" }
]
}]
}]
} Khi triển khai cho bệnh viện, nên publish CapabilityStatement riêng cho production và staging, nêu rõ profile VN Core đang ràng buộc và scope SMART on FHIR đang hỗ trợ. Đây cũng là tạo thuận lợi cho audit và đánh giá theo Luật 91/2025 về Bảo vệ dữ liệu cá nhân.
6. Operations: $expand, $validate, $everything…
Operation là RPC thuần được FHIR chuẩn hóa, gọi bằng POST (hoặc GET nếu thuần đọc) với tên bắt đầu bằng $. Một số Operation thường gặp:
$validate— kiểm tra Resource có hợp lệ với một profile không, trước khi POST chính thức.$expand— mở rộng ValueSet thành danh sách code (dùng cho dropdown UI).$lookup— tra một code trong CodeSystem, lấy display, designation, tính chất.$everything— gọi/Patient/123/$everythingđể lấy toàn bộ dữ liệu liên quan tới một bệnh nhân, hữu ích cho hồ sơ tóm tắt.$translate— ánh xạ code qua ConceptMap (ví dụ ICD-10 VN sang SNOMED CT VN theo QĐ 2427/QĐ-BYT đợt 1).
Validate trước khi commit là thói quen quan trọng cho hệ thống Việt Nam, nơi nhiều profile VN Core đặt ràng buộc Must Support trên CCCD, dân tộc và phường/xã:
POST /Patient/$validate?profile=http://fhir.hl7.org.vn/core/StructureDefinition/vn-core-patient HTTP/1.1
Host: hapi.fhir.org
Content-Type: application/fhir+json
{
"resourceType": "Patient",
"identifier": [{
"system": "http://fhir.hl7.org.vn/core/sid/cccd",
"value": "001234567890"
}],
"name": [{ "family": "Trần", "given": ["Thị Bình"] }],
"gender": "female",
"birthDate": "1990-08-21"
} 7. Bulk Data API và $export bất đồng bộ
Khi cần xuất dữ liệu khối lớn (cohort cho nghiên cứu, huấn luyện AI y tế, đối soát BHYT), $export theo Bulk Data Access IG là cách chuẩn. Quy trình bất đồng bộ ba bước:
- Client gửi yêu cầu kèm header
Prefer: respond-async; server đáp202 Acceptedkèm headerContent-Locationtrỏ tới URL polling. - Client poll URL đó; khi xong, server trả manifest JSON liệt kê các tệp NDJSON theo từng loại Resource.
- Client tải từng tệp NDJSON, mỗi dòng là một Resource độc lập.
Yêu cầu mẫu cho Group-level export (cohort thí điểm 2026):
GET /Group/vn-pilot-cohort-2026/$export?_type=Patient,Observation,Condition&_since=2026-01-01T00:00:00Z HTTP/1.1
Host: hapi.fhir.org
Accept: application/fhir+json
Prefer: respond-async
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR... Lưu ý pháp lý
Dữ liệu y tế là dữ liệu cá nhân nhạy cảm theo Điều 3 NĐ 356/2025/NĐ-CP. Mọi $export phải có cơ sở pháp lý rõ ràng (sự đồng ý của chủ thể dữ liệu hoặc cơ sở khác theo Luật 91/2025), DPIA cập nhật và lưu vết AuditEvent đầy đủ; nếu xuất xuyên biên giới, hồ sơ đánh giá theo Mẫu số 09 NĐ 356/2025 là bắt buộc.
8. Xác thực: SMART on FHIR + OAuth 2.0
SMART on FHIR là profile của OAuth 2.0 và OpenID Connect dành riêng cho FHIR. Spec định nghĩa ba mô hình launch:
- EHR launch — ứng dụng được EMR mở trong ngữ cảnh bệnh nhân/bác sĩ đang đăng nhập.
- Standalone launch — ứng dụng (ví dụ portal cho bệnh nhân) khởi chạy độc lập, người dùng đăng nhập trực tiếp.
- Backend services — máy với máy, dùng JWT client assertion thay vì người dùng.
Scope tuân theo cú pháp <ngữ-cảnh>/<Resource>.<quyền>, ví dụ:
patient/Patient.read # đọc hồ sơ của bệnh nhân hiện tại
patient/Observation.rs # read + search Observation
user/Encounter.cruds # mọi quyền với Encounter
system/*.read # backend đọc tất cả
Luồng PKCE chuẩn: client gọi /authorize, người dùng đăng nhập, đồng ý scope; server trả mã ủy quyền; client đổi mã lấy access token tại /token:
POST /oauth2/token HTTP/1.1
Host: auth.example-ehr.vn
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=Aa1Bb2Cc3...&
redirect_uri=https%3A%2F%2Fapp.omihealth.vn%2Fcallback&
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk&
client_id=app-omihealth-vn
Token trả về thường là JWT có thời hạn ngắn cộng refresh token. Mọi request FHIR phải kèm header Authorization: Bearer <token> trên kết nối HTTPS. Cấu hình SMART discovery nằm tại .well-known/smart-configuration.
9. Mã trạng thái và OperationOutcome
FHIR R4 quy định khá chặt cách dùng HTTP status code:
- 200 OK — read/search/update thành công.
- 201 Created — create thành công, kèm header
LocationvàETag. - 204 No Content — delete thành công không trả body.
- 304 Not Modified — phản hồi cho conditional read khi dữ liệu chưa đổi.
- 400 Bad Request — Resource không hợp lệ về cú pháp.
- 401 Unauthorized / 403 Forbidden — thiếu hoặc thiếu quyền access token.
- 404 Not Found — không có Resource khớp.
- 409 Conflict — version conflict (kết hợp
If-Match). - 410 Gone — Resource đã bị delete logic.
- 412 Precondition Failed — header điều kiện không khớp.
- 422 Unprocessable Entity — Resource hợp lệ về cú pháp nhưng không thoả invariant/profile.
- 5xx — lỗi phía server.
FHIR khuyến nghị (SHOULD) trả OperationOutcome trong body cho các lỗi cấp FHIR (4xx/5xx liên quan tới ngữ nghĩa Resource). Tuy nhiên client phải chấp nhận trường hợp body rỗng hoặc không phải FHIR ở tầng hạ tầng (ví dụ proxy trả về HTML). Một OperationOutcome điển hình:
{
"resourceType": "OperationOutcome",
"issue": [{
"severity": "error",
"code": "invariant",
"details": {
"text": "Patient.identifier yêu cầu hệ thống CCCD theo VNCorePatient profile"
},
"diagnostics": "Identifier with system=http://fhir.hl7.org.vn/core/sid/cccd is required.",
"expression": ["Patient.identifier"]
}]
}
Trong VN Core, các invariant ràng buộc CCCD theo profile VNCorePatient sẽ trả 422 với issue.code = "invariant" và expression chỉ chính xác phần tử vi phạm — tiện cho UI hiển thị lỗi tới người nhập liệu.
10. Versioning, conditional, paging
FHIR đặc biệt mạnh ở ba khía cạnh thường bị làm dối trong các API tự chế:
- Optimistic concurrency — mọi update phải kèm header
If-Match: W/"2"để tránh ghi đè lẫn nhau; server trả 409 nếu phiên bản đã thay đổi. - Conditional create / update / delete — sử dụng header
If-None-Existhoặc tham số tìm kiếm trên URL để "chỉ tạo nếu chưa có", lý tưởng cho ingest từ XML 4210 vào FHIR. - Paging — search trả Bundle
type=searchsetvớilink.relation = "next"; client chỉ cần đi theo link, không tự dựng URL trang sau. Tham số_countđề xuất kích thước trang (mặc định khuyến nghị 20).
curl -X PUT \
-H "Content-Type: application/fhir+json" \
-H "If-Match: W/\"2\"" \
--data-binary @patient-v3.json \
https://hapi.fhir.org/baseR4/Patient/123
curl -X POST \
-H "Content-Type: application/fhir+json" \
-H "If-None-Exist: identifier=http://fhir.hl7.org.vn/core/sid/cccd|001234567890" \
--data-binary @patient-new.json \
https://hapi.fhir.org/baseR4/Patient 11. Best practices cho bối cảnh Việt Nam
FHIR là tiêu chuẩn kỹ thuật trung lập, nhưng triển khai thực tế phải bám sát khung pháp lý. Tách bạch giữa nghĩa vụ pháp lý và lựa chọn kỹ thuật để hiện thực hoá:
- Bảo vệ dữ liệu cá nhân: Luật 91/2025/QH15 và NĐ 356/2025/NĐ-CP đặt nghĩa vụ bảo mật, lưu vết, DPIA, DPO, thông báo vi phạm trong 72 giờ. Hiện thực hóa qua HTTPS bắt buộc trên mọi endpoint, mã hóa at-rest, phân quyền theo SMART scope, lưu AuditEvent cho từng request, và workflow Consent cấp Resource.
- Giao dịch điện tử và chữ ký số: NĐ 137/2024/NĐ-CP yêu cầu chữ ký số cho hồ sơ điện tử. Trong FHIR, dùng Resource Provenance kèm
signatuređể gắn chữ ký vào mỗi bản ghi quan trọng (đơn thuốc, kết quả xét nghiệm, phiếu chuyển viện). - Bệnh án điện tử: TT 13/2025/TT-BYT đặt deadline 31/12/2026 cho cơ sở KCB. FHIR REST + VN Core profiles là cách hợp lý để đáp ứng yêu cầu liên thông giữa bệnh án và VNeID.
- Hiệu năng và độ ổn định: paging mặc định 20, rate limit 100 req/phút cho client app (có thể nới cho backend), cache CapabilityStatement và
$expand, dùng ETag cho mọi update. - Kiểm thử: dùng HAPI sandbox công khai cho dev/test, không bao giờ gửi dữ liệu thật; chạy
$validatetrong CI/CD trước khi merge profile mới.
Khuyến nghị từ OmiGroup
Khi tích hợp FHIR vào HIS Việt Nam, tách rõ tầng FHIR Façade (HTTPS + SMART + AuditEvent) với tầng nghiệp vụ phía sau. Façade tuân thủ pháp lý, nghiệp vụ tập trung vào quy tắc lâm sàng và BHYT. Hỏi đáp chi tiết, liên hệ Phan Mạnh Hùng (HungPM, [email protected]).
Tham chiếu
- FHIR R4 RESTful API §3.1 — hl7.org/fhir/R4/http.html
- FHIR Search — hl7.org/fhir/R4/search.html
- FHIR Bundle — hl7.org/fhir/R4/bundle.html
- FHIR CapabilityStatement — hl7.org/fhir/R4/capabilitystatement.html
- FHIR OperationOutcome — hl7.org/fhir/R4/operationoutcome.html
- SMART App Launch IG — hl7.org/fhir/smart-app-launch
- Bulk Data Access IG — hl7.org/fhir/uv/bulkdata
- HAPI FHIR public sandbox — hapi.fhir.org
- Luật 91/2025/QH15 — Bảo vệ dữ liệu cá nhân (xem legal-corpus mã
L-91-2025) - Nghị định 356/2025/NĐ-CP — Hướng dẫn thi hành Luật BVDLCN (xem legal-corpus mã
ND-356-2025) - Nghị định 137/2024/NĐ-CP — Giao dịch điện tử (xem legal-corpus mã
ND-137-2024) - Thông tư 13/2025/TT-BYT — Bệnh án điện tử (xem legal-corpus mã
TT-13-2025)