Bắt đầu nhanh
Ba bước để có dữ liệu đầu tiên.
-
Nhờ quản trị viên phòng khám tạo API key
Vào Quản trị → Kết nối ngoài (API) → Tạo key, đặt một tên gợi nhớ (ví dụ
Tele-sale). Key dạngick_…chỉ hiện đúng một lần — sao chép và lưu vào nơi an toàn ngay lúc đó. -
Gọi thử
Gửi key trong header
Authorization: Bearer. Dùng khung “Thử gọi ngay” ở đầu trang, hoặc chạy thẳng lệnh bên cạnh. -
Đối chiếu và lưu theo
encounterUuidMỗi lượt khám có một
encounterUuidcố định, không bao giờ đổi — dùng nó làm khoá chống trùng khi lưu vào hệ thống của bạn.
curl -sS "https://iclinic.phongkhamhieploi.vn/api/integration/v1/encounters?from=2026-09-14&to=2026-09-19&status=all" \
-H "Authorization: Bearer $ICLINIC_API_KEY"
Xác thực
Mọi yêu cầu cần một API key trong header Authorization, dùng kiểu Bearer. Không cookie, không OAuth — chỉ cần đúng key là đủ.
- Không tự hết hạn. Key dùng được tới khi quản trị viên thu hồi.
- Chỉ đọc. Key này không sửa hay xoá được dữ liệu.
- Theo từng phòng khám. Mỗi key chỉ thấy dữ liệu của phòng khám đã tạo ra nó; tối đa 10 key hoạt động cùng lúc mỗi phòng khám.
- Chỉ lưu dấu vân tay. iClinic chỉ giữ giá trị băm sha256 của key nên không xem lại được — mất key thì tạo key mới, không khôi phục được key cũ.
Đổi key không làm gián đoạn dịch vụ
Tạo key mới → cập nhật hệ thống của bạn → thu hồi key cũ. Trong lúc chuyển, cả hai key vẫn hoạt động song song.
Authorization: Bearer ick_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Danh sách lượt khám
GET/api/integration/v1/encounters
Trả về các lượt khám có visitDate trong khoảng [from, to], mới nhất trước.
status=all (mọi trạng thái) hoặc status=CANCELLED.
Tham số truy vấn
| Tham số | Mô tả |
|---|---|
fromstring · datebắt buộc |
Ngày khám bắt đầu, YYYY-MM-DD, theo giờ phòng khám. |
tostring · datetuỳ chọn |
Ngày khám kết thúc, gồm cả ngày này. Mặc định: hôm nay. Khoảng from–to tối đa 31 ngày. |
statusstring · enumtuỳ chọn |
Bỏ trống: mọi trạng thái trừ CANCELLED. all: tất cả, gồm cả đã huỷ. Hoặc một trong NEW ONGOING FINISHED REOPENED LOCKED CANCELLED. Không phân biệt hoa thường. |
pageintegertuỳ chọn |
Số trang, bắt đầu từ 0. Mặc định 0. |
pageSizeintegertuỳ chọn |
Từ 1 đến 500. Mặc định 200. |
Phản hồi 200
Rê chuột hoặc dùng Tab trên một dòng để xem trường đó nằm ở đâu trong JSON.
| Trường | Ý nghĩa |
|---|---|
requestIdstring | Mã yêu cầu, trùng header X-Request-Id. Gửi kèm khi cần hỗ trợ. |
totalinteger | Tổng số lượt khám khớp bộ lọc, trên mọi trang. |
encountersarray | Các lượt khám của trang hiện tại. Cùng from, to, page, pageSize được trả lại nguyên như đã gửi (sau khi áp mặc định). |
Một lượt khám
| Trường | Ý nghĩa |
|---|---|
encounterUuidstring | Khoá chống trùng. Mã ổn định của lượt khám, không bao giờ đổi. |
idstring | Mã hiển thị (mã hồ sơ). Chỉ để tham chiếu; có thể được cấp lại nên đừng dùng làm khoá chống trùng. |
patientNamestring | Tên bệnh nhân. |
phonestring | null | Số điện thoại; null nếu phòng khám chưa có số. |
visitDatedate | Ngày khám YYYY-MM-DD theo giờ phòng khám. Hệ thống lưu ngày, không lưu giờ khám. |
checkedInAtdate-time | null | Thời điểm tiếp nhận (UTC) khi biết được, ngược lại null. Đừng coi null là chưa đến — hãy dựa vào status. |
completedAtdate-time | null | Thời điểm kết thúc khám (UTC) hoặc null. |
statusenum |
|
patientTypeenum |
|
diagnosisstring | Chẩn đoán; chuỗi rỗng nếu chưa có. |
icd10Codestring | Mã ICD-10; chuỗi rỗng nếu chưa có. |
curl -sS "https://iclinic.phongkhamhieploi.vn/api/integration/v1/encounters?from=2026-09-14&to=2026-09-19&status=all&page=0&pageSize=200" \
-H "Authorization: Bearer $ICLINIC_API_KEY"
const BASE = 'https://iclinic.phongkhamhieploi.vn/api/integration/v1';
// Node 18+ (fetch có sẵn). Duyệt hết mọi trang.
export async function listEncounters({ from, to, status = 'all' }) {
const pageSize = 200;
const encounters = [];
for (let page = 0; ; page += 1) {
const url = new URL(`${BASE}/encounters`);
url.search = new URLSearchParams({ from, to, status, page, pageSize });
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.ICLINIC_API_KEY}` },
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${res.status} ${error.code}: ${error.message}`);
}
const body = await res.json();
encounters.push(...body.encounters);
if (body.encounters.length === 0 || encounters.length >= body.total) return encounters;
}
}
import os
import requests
BASE = "https://iclinic.phongkhamhieploi.vn/api/integration/v1"
def list_encounters(from_date, to_date, status="all"):
"""Duyệt hết mọi trang."""
headers = {"Authorization": f"Bearer {os.environ['ICLINIC_API_KEY']}"}
encounters, page = [], 0
while True:
res = requests.get(
f"{BASE}/encounters",
headers=headers,
params={"from": from_date, "to": to_date, "status": status,
"page": page, "pageSize": 200},
timeout=30,
)
res.raise_for_status()
body = res.json()
encounters += body["encounters"]
if not body["encounters"] or len(encounters) >= body["total"]:
return encounters
page += 1
{
"requestId": "req_7c1f3a",
"from": "2026-09-14",
"to": "2026-09-19",
"page": 0,
"pageSize": 200,
"total": 1,
"encounters": [
{
"id": "BA-000123",
"encounterUuid": "3f0c9a52-6d1b-4e7a-9b1e-2a4c8d5e7f10",
"patientName": "Nguyễn Văn A",
"phone": "0900000001",
"visitDate": "2026-09-19",
"checkedInAt": "2026-09-19T02:14:05.000Z",
"completedAt": null,
"status": "ONGOING",
"patientType": "OLD",
"diagnosis": "Tăng huyết áp",
"icd10Code": "I10"
}
]
}
Phân trang
Phân trang theo page (từ 0) và pageSize (tối đa 500). Tăng page cho tới khi đã nhận đủ total lượt khám, hoặc gặp một trang rỗng.
Kết quả xếp mới nhất trước. Nếu có lượt khám mới được tạo trong lúc bạn đang duyệt, các trang có thể dịch chuyển — vì vậy hãy khử trùng theo encounterUuid khi gộp kết quả.
from là vài ngày gần nhất là đủ cho việc đối chiếu lịch hẹn.
GET …/encounters?from=2026-09-01&to=2026-09-19&pageSize=200&page=0 → total 340, 200 lượt
GET …/encounters?from=2026-09-01&to=2026-09-19&pageSize=200&page=1 → total 340, 140 lượt (đủ 340, dừng)
Mã lỗi
Lỗi luôn có dạng { requestId, error: { code, message } }. Xử lý theo code, không theo message — nội dung message dành cho người đọc và có thể được chỉnh lại.
| HTTP | error.code | Khi nào | Thử lại? |
|---|---|---|---|
| 400 | INVALID_DATE | Thiếu hoặc sai định dạng from, to. | Không — sửa yêu cầu |
| 400 | INVALID_RANGE | to trước from, hoặc khoảng quá 31 ngày. | Không — sửa yêu cầu |
| 400 | INVALID_STATUS | status không hợp lệ. | Không — sửa yêu cầu |
| 400 | INVALID_PAGE | page hoặc pageSize sai. | Không — sửa yêu cầu |
| 401 | UNAUTHORIZED | Thiếu, sai hoặc key đã bị thu hồi; hoặc phòng khám đang bị tạm ngưng. | Không — kiểm tra lại key |
| 405 | METHOD_NOT_ALLOWED | Chỉ hỗ trợ GET. | Không |
| 429 | RATE_LIMITED | Vượt giới hạn tốc độ hoặc IP đang bị khoá tạm. | Có — sau Retry-After |
| 5xx | — | Lỗi phía máy chủ. | Có — chờ tăng dần |
{
"requestId": "req_7c1f3a",
"error": {
"code": "INVALID_RANGE",
"message": "`to` phải sau `from` và khoảng tối đa 31 ngày."
}
}
Giới hạn tốc độ (rate limit)
Cả hai giới hạn trả về 429 RATE_LIMITED kèm header Retry-After (số giây). Hãy chờ đúng khoảng đó rồi thử lại, đừng gọi lặp liên tục.
401, dừng lại và báo cho người phụ trách kiểm tra key.
async function callWithRetry(url, options, attempts = 4) {
for (let i = 0; i < attempts; i += 1) {
const res = await fetch(url, options);
if (res.status === 429) {
const seconds = Number(res.headers.get('Retry-After')) || 30;
await new Promise((r) => setTimeout(r, seconds * 1000));
continue;
}
if (res.status >= 500) {
await new Promise((r) => setTimeout(r, 2 ** i * 1000));
continue;
}
return res; // 2xx, 4xx: không thử lại
}
throw new Error('Hết số lần thử');
}
Công thức
Đánh dấu lịch hẹn “Đã đến” / “Đã huỷ”
- Lấy lượt khám gần đây với
status=allđể có cả lượt đã huỷ. - Ghép theo số điện thoại và
visitDatetrong vòng ±5 ngày so với ngày hẹn. Bỏ qua lượt khám cóphone = null. - Có ít nhất một lượt không phải
CANCELLED→ Đã đến. Chỉ có lượtCANCELLED→ Đã huỷ. Không có lượt nào → giữ nguyên.
Danh sách gọi chăm sóc sau khám (ngày 0 / 3 / 6 / 30)
- Lấy
status=FINISHEDvớifrom= hôm nay − 30 ngày (đúng 31 ngày, đủ cho mốc ngày 30). - Tính ngày gọi =
visitDate+ 0, 3, 6, 30 ngày; lấy các lượt có ngày gọi trùng hôm nay. - Hiển thị
patientName,phone,diagnosis.patientType = NEWlà khách mới;OLDvàRE_EXAMINATIONlà tái khám.
const DAY = 86_400_000;
const dayOf = (date) => Date.parse(`${date}T00:00:00Z`);
// appointment: { phone, date: 'YYYY-MM-DD' }; encounters: từ status=all
export function appointmentOutcome(appointment, encounters) {
const near = encounters.filter(
(e) =>
e.phone &&
e.phone === appointment.phone &&
Math.abs(dayOf(e.visitDate) - dayOf(appointment.date)) <= 5 * DAY,
);
if (near.length === 0) return null; // giữ nguyên
return near.some((e) => e.status !== 'CANCELLED') ? 'Đã đến' : 'Đã huỷ';
}
const OFFSETS = [0, 3, 6, 30];
const addDays = (date, n) =>
new Date(Date.parse(`${date}T00:00:00Z`) + n * 86_400_000).toISOString().slice(0, 10);
// encounters: status=FINISHED, from = today - 30 ngày
export function callsDueOn(today, encounters) {
return encounters.flatMap((e) =>
OFFSETS.filter((n) => addDays(e.visitDate, n) === today).map((n) => ({
day: n,
uuid: e.encounterUuid,
name: e.patientName,
phone: e.phone,
kind: e.patientType === 'NEW' ? 'Khách mới' : 'Tái khám',
diagnosis: e.diagnosis,
})),
);
}
Ghi chú dữ liệu
- Ngày, không phải giờ.
visitDatelà ngày khám theo giờ phòng khám (UTC+7). Mọi thời điểm dạngdate-timelà UTC. checkedInAtcó thể lànull. Nó chỉ có khi ngày tiếp nhận trùngvisitDate; với hồ sơ nhập ngược ngày thì không có giờ đến thật. Dùngstatusđể biết đã đến hay chưa.- Hồ sơ chuyển từ Medi (2026-09-19). Có
visitDateđúng ngày khám gốc nhưng không có giờ tiếp nhận thật. Với các ca ngày 2026-09-19 được nhập trong ngày,checkedInAtchỉ là thời điểm nhập — đừng dùng làm giờ đến khám. - Không có số điện thoại. Một số bệnh nhân không có SĐT →
phonelànull. - Trường mới không phá vỡ. Hãy bỏ qua các trường bạn chưa biết khi đọc phản hồi.
Phiên bản & thay đổi
Phiên bản nằm trong đường dẫn (/v1). Trong cùng một phiên bản, thay đổi chỉ theo hướng bổ sung: thêm trường trong phản hồi, thêm giá trị enum hoặc tham số tuỳ chọn. Thay đổi làm hỏng tương thích sẽ ra phiên bản mới (/v2).
Vì vậy client nên: bỏ qua trường lạ, và không giả định danh sách giá trị của status là cố định.
Nhật ký thay đổi
- 1.0.0
- Phát hành đầu tiên:
GET /encountersxác thực bằng API key cố định, có phân trang và rate limit. Thay thế cho API lượt khám của Medi (đã ngừng hoạt động).
https://iclinic.phongkhamhieploi.vn/developers/openapi.json
# Nhập vào Postman, Insomnia hoặc trình sinh mã:
npx @openapitools/openapi-generator-cli generate \
-i https://iclinic.phongkhamhieploi.vn/developers/openapi.json \
-g typescript-fetch -o ./iclinic-client
Hỗ trợ
Khi cần hỗ trợ, hãy gửi kèm requestId của yêu cầu lỗi (có trong thân phản hồi và header X-Request-Id), thời điểm gọi và tham số bạn đã dùng. Đừng gửi kèm API key — nếu lỡ gửi, hãy thu hồi key đó ngay.