🔌 API Reference
REST API tự động sinh bởi Supabase PostgREST — endpoints, RPC functions và code examples
📋 Tổng Quan API — Tài liệu Public
📖 Đây là tài liệu API công khai — không cần đăng nhập
Trang này chứa toàn bộ thông tin API của hệ thống phòng khám: schema 10 bảng, 10 RPC functions, code examples, hướng dẫn authentication. Lập trình viên có thể đọc và tích hợp ngay mà không cần tài khoản Supabase.
API sử dụng Supabase PostgREST — REST API được tự động sinh từ PostgreSQL schema.
🔑 Authentication — Cách xác thực API
Mọi request đều cần 2 headers:
| Header | Value | Mô tả |
|---|---|---|
| apikey | SUPABASE_PUBLISHABLE_KEY | Publishable key (anon) — an toàn để dùng ở client |
| Authorization | Bearer SUPABASE_PUBLISHABLE_KEY | Hoặc Bearer JWT_TOKEN nếu user đã login |
🌐 Base URL
https://mrgdawvdnofcxamjvcbl.supabase.co/rest/v1🔑 API Key (lấy từ .env.local)
NEXT_PUBLIC_SUPABASE_URL=https://mrgdawvdnofcxamjvcbl.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_xxxxxservice_role key ở client-side. Chỉ dùng publishable key (anon key) cho frontend. Service role key chỉ dùng server-side (Server Actions, API Routes).Ví dụ cURL xác thực
# Thay YOUR_ANON_KEY bằng publishable key thực tế
curl "https://mrgdawvdnofcxamjvcbl.supabase.co/rest/v1/patients?select=patient_code,full_name&limit=3" \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"🚀 Quick Start
1. Cài đặt
npm install @supabase/supabase-js @supabase/ssr2. Cấu hình .env.local
NEXT_PUBLIC_SUPABASE_URL=https://mrgdawvdnofcxamjvcbl.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_xxxxx3. Tạo Supabase Client (Server Component)
import { createClient } from "@/lib/supabase/server";
// Trong Server Component hoặc Server Action
const supabase = await createClient();
// Query dữ liệu
const { data, error } = await supabase
.from("patients")
.select("*")
.limit(10);4. Tạo Supabase Client (Client Component)
import { createClient } from "@/lib/supabase/client";
// Trong Client Component
const supabase = createClient();
// Realtime subscription
supabase
.channel("visits")
.on("postgres_changes", { event: "INSERT", schema: "public", table: "visits" },
(payload) => console.log("New visit:", payload)
)
.subscribe();🌐 REST API Endpoints
https://mrgdawvdnofcxamjvcbl.supabase.co/rest/v1apikey + Authorization: Bearer KEYdepartmentsdoctorspatientsvisitsservicesvisit_servicesmedicinesprescriptionsinvoicesicd_codes⚡ RPC Functions (Stored Procedures)
POST /rest/v1/rpc/<fn_name>fn_dashboard_summaryTổng hợp KPI: doanh thu, lượt khám, bệnh nhân, BHYT + so sánh kỳ trước
p_start_datedatep_end_datedateJSON { total_revenue, total_visits, total_patients, new_patients, avg_revenue_per_visit, bhyt_visits, self_pay_visits, prev_revenue, prev_visits }const { data } = await supabase.rpc("fn_dashboard_summary", {
p_start_date: "2026-01-01",
p_end_date: "2026-02-28"
});
// → { total_revenue: 3144119500, total_visits: 4720, ... }fn_revenue_by_periodDoanh thu theo ngày/tuần/tháng với phân tách BHYT, tự chi, thuốc, dịch vụ
p_start_datedatep_end_datedatep_group_bytextJSON [{ period, total_revenue, bhyt_revenue, self_pay_revenue, medicine_revenue, service_revenue, visit_count, patient_count }]const { data } = await supabase.rpc("fn_revenue_by_period", {
p_start_date: "2026-01-01",
p_end_date: "2026-01-31",
p_group_by: "week" // 'day' | 'week' | 'month'
});fn_revenue_by_departmentDoanh thu theo từng khoa/phòng ban
p_start_datedatep_end_datedateJSON [{ department_name, department_code, total_revenue, bhyt_revenue, patient_revenue, visit_count, patient_count, avg_revenue }]const { data } = await supabase.rpc("fn_revenue_by_department", {
p_start_date: "2026-01-01",
p_end_date: "2026-02-28"
});fn_patient_statisticsThống kê bệnh nhân đa chiều: giới tính, tuổi, tỉnh, loại khám, BN mới/tái khám
p_start_datedatep_end_datedateJSON { by_gender, by_age_group, by_province, new_vs_returning, by_visit_type, daily_visits }const { data } = await supabase.rpc("fn_patient_statistics", {
p_start_date: "2026-01-01",
p_end_date: "2026-02-28"
});
// → { by_gender: [{gender:"Nam",count:1524}, ...], by_age_group: [...] }fn_doctor_performanceHiệu suất bác sĩ: lượt khám, doanh thu, BN mới vs tái khám
p_start_datedatep_end_datedateJSON [{ doctor_name, degree, department_name, visit_count, patient_count, total_revenue, avg_revenue_per_visit, new_visits, return_visits }]const { data } = await supabase.rpc("fn_doctor_performance", {
p_start_date: "2026-01-01",
p_end_date: "2026-02-28"
});fn_top_servicesTop dịch vụ được sử dụng nhiều nhất
p_start_datedatep_end_datedatep_limitintegerJSON [{ service_name, service_code, category_name, department_name, usage_count, total_revenue, bhyt_revenue, avg_price }]const { data } = await supabase.rpc("fn_top_services", {
p_start_date: "2026-01-01",
p_end_date: "2026-02-28",
p_limit: 10
});fn_insurance_reportBáo cáo BHYT chi tiết: tổng hợp, theo khoa, xu hướng tháng
p_start_datedatep_end_datedateJSON { summary: {total_amount, total_bhyt, ...}, by_department: [...], monthly_trend: [...] }const { data } = await supabase.rpc("fn_insurance_report", {
p_start_date: "2026-01-01",
p_end_date: "2026-02-28"
});fn_top_diagnosesTop chẩn đoán ICD-10 phổ biến nhất
p_start_datedatep_end_datedatep_limitintegerJSON [{ icd_code, diagnosis_name, category, visit_count, patient_count, departments }]const { data } = await supabase.rpc("fn_top_diagnoses", {
p_start_date: "2026-01-01",
p_end_date: "2026-02-28",
p_limit: 10
});fn_hourly_distributionPhân bổ lượt khám theo giờ trong ngày
p_start_datedatep_end_datedateJSON [{ hour, visit_count, avg_per_day }]const { data } = await supabase.rpc("fn_hourly_distribution", {
p_start_date: "2026-01-01",
p_end_date: "2026-02-28"
});fn_top_medicinesTop thuốc kê đơn nhiều nhất
p_start_datedatep_end_datedatep_limitintegerJSON [{ medicine_name, generic_name, unit, category, total_quantity, total_revenue, prescription_count }]const { data } = await supabase.rpc("fn_top_medicines", {
p_start_date: "2026-01-01",
p_end_date: "2026-02-28",
p_limit: 10
});🗄️ Database Schema
departmentsKhoa/Phòng banid*code*name*descriptionis_activecreated_at| Column | Type | Req | Ghi chú |
|---|---|---|---|
| id | uuid | * | PK, auto gen |
| code | varchar | * | Mã khoa (unique) |
| name | varchar | * | Tên khoa |
| description | text | – | |
| is_active | boolean | – | default true |
| created_at | timestamptz | – | auto |
doctorsBác sĩid*employee_code*full_name*department_iddegreespecialtyphoneemaillicense_numberis_activecreated_at| Column | Type | Req | Ghi chú |
|---|---|---|---|
| id | uuid | * | PK |
| employee_code | varchar | * | Mã nhân viên (unique) |
| full_name | varchar | * | |
| department_id | uuid | – | FK → departments |
| degree | varchar | – | Học vị: ThS, TS, PGS, GS |
| specialty | varchar | – | |
| phone | varchar | – | |
| varchar | – | ||
| license_number | varchar | – | Số CCHN |
| is_active | boolean | – | |
| created_at | timestamptz | – |
patientsBệnh nhânid*patient_code*full_name*date_of_birthgenderphoneaddresswarddistrictprovincecccdbhyt_numberbhyt_expiryblood_typeallergiesemergency_contactemergency_phonecreated_at| Column | Type | Req | Ghi chú |
|---|---|---|---|
| id | uuid | * | PK |
| patient_code | varchar | * | Mã BN (unique) |
| full_name | varchar | * | |
| date_of_birth | date | – | |
| gender | varchar | – | Nam, Nữ |
| phone | varchar | – | |
| address | text | – | |
| ward | varchar | – | Phường/Xã |
| district | varchar | – | Quận/Huyện |
| province | varchar | – | Tỉnh/TP |
| cccd | varchar | – | CCCD/CMND |
| bhyt_number | varchar | – | Số thẻ BHYT |
| bhyt_expiry | date | – | |
| blood_type | varchar | – | |
| allergies | text | – | |
| emergency_contact | varchar | – | |
| emergency_phone | varchar | – | |
| created_at | timestamptz | – |
visitsLượt khámid*visit_code*patient_iddoctor_iddepartment_idvisit_date*visit_typestatusdiagnosisicd_code_idpayment_methodvital_signsnotescreated_at| Column | Type | Req | Ghi chú |
|---|---|---|---|
| id | uuid | * | PK |
| visit_code | varchar | * | Mã lượt khám (unique) |
| patient_id | uuid | – | FK → patients |
| doctor_id | uuid | – | FK → doctors |
| department_id | uuid | – | FK → departments |
| visit_date | timestamptz | * | |
| visit_type | varchar | – | Khám mới, Tái khám, Cấp cứu |
| status | varchar | – | cho_kham, dang_kham, hoan_thanh, huy |
| diagnosis | text | – | |
| icd_code_id | uuid | – | FK → icd_codes |
| payment_method | varchar | – | bhyt, tu_tuc, hon_hop |
| vital_signs | jsonb | – | Sinh hiệu (JSON) |
| notes | text | – | |
| created_at | timestamptz | – |
servicesDanh mục dịch vụid*code*name*department_idcategoryunitprice*bhyt_coveredbhyt_percentageis_activecreated_at| Column | Type | Req | Ghi chú |
|---|---|---|---|
| id | uuid | * | PK |
| code | varchar | * | Mã DV (unique) |
| name | varchar | * | |
| department_id | uuid | – | FK → departments |
| category | varchar | – | Loại DV |
| unit | varchar | – | Đơn vị tính |
| price | numeric | * | Đơn giá |
| bhyt_covered | boolean | – | |
| bhyt_percentage | integer | – | % BHYT chi trả |
| is_active | boolean | – | |
| created_at | timestamptz | – |
visit_servicesChi tiết dịch vụ theo lượt khámid*visit_idservice_idquantityunit_price*discount_percentbhyt_amountpatient_amount*total_amount*created_at| Column | Type | Req | Ghi chú |
|---|---|---|---|
| id | uuid | * | PK |
| visit_id | uuid | – | FK → visits |
| service_id | uuid | – | FK → services |
| quantity | integer | – | |
| unit_price | numeric | * | |
| discount_percent | integer | – | |
| bhyt_amount | numeric | – | BHYT chi trả |
| patient_amount | numeric | * | BN chi trả |
| total_amount | numeric | * | |
| created_at | timestamptz | – |
medicinesDanh mục thuốcid*code*name*generic_nameunitcategorymanufacturercountryprice*bhyt_coveredis_activecreated_at| Column | Type | Req | Ghi chú |
|---|---|---|---|
| id | uuid | * | PK |
| code | varchar | * | Mã thuốc (unique) |
| name | varchar | * | |
| generic_name | varchar | – | Tên hoạt chất |
| unit | varchar | – | Đơn vị: viên, ống, chai... |
| category | varchar | – | |
| manufacturer | varchar | – | |
| country | varchar | – | |
| price | numeric | * | |
| bhyt_covered | boolean | – | |
| is_active | boolean | – | |
| created_at | timestamptz | – |
prescriptionsĐơn thuốcid*visit_idmedicine_idquantity*dosageduration_daysunit_price*total_price*bhyt_amountnotescreated_at| Column | Type | Req | Ghi chú |
|---|---|---|---|
| id | uuid | * | PK |
| visit_id | uuid | – | FK → visits |
| medicine_id | uuid | – | FK → medicines |
| quantity | integer | * | |
| dosage | varchar | – | Liều dùng |
| duration_days | integer | – | Số ngày dùng |
| unit_price | numeric | * | |
| total_price | numeric | * | |
| bhyt_amount | numeric | – | |
| notes | text | – | |
| created_at | timestamptz | – |
invoicesHóa đơnid*invoice_code*visit_idservice_totalmedicine_totaltotal_amount*bhyt_amountdiscount_amountpatient_amount*payment_statuspayment_methodpayment_datecashier_namenotescreated_at| Column | Type | Req | Ghi chú |
|---|---|---|---|
| id | uuid | * | PK |
| invoice_code | varchar | * | Mã HĐ (unique) |
| visit_id | uuid | – | FK → visits |
| service_total | numeric | – | |
| medicine_total | numeric | – | |
| total_amount | numeric | * | |
| bhyt_amount | numeric | – | |
| discount_amount | numeric | – | |
| patient_amount | numeric | * | BN phải trả |
| payment_status | varchar | – | chua_thanh_toan, da_thanh_toan, hoan_tien |
| payment_method | varchar | – | tien_mat, chuyen_khoan, the, vi_dien_tu |
| payment_date | timestamptz | – | |
| cashier_name | varchar | – | |
| notes | text | – | |
| created_at | timestamptz | – |
icd_codesMã bệnh ICD-10id*code*name_vi*name_encategory| Column | Type | Req | Ghi chú |
|---|---|---|---|
| id | uuid | * | PK |
| code | varchar | * | Mã ICD-10 (unique) |
| name_vi | varchar | * | Tên tiếng Việt |
| name_en | varchar | – | Tên tiếng Anh |
| category | varchar | – |
💻 Code Examples
CRUD: Tạo bệnh nhân mới
const { data, error } = await supabase
.from("patients")
.insert({
patient_code: "BN-20260001",
full_name: "Nguyễn Văn A",
date_of_birth: "1990-05-15",
gender: "Nam",
phone: "0901234567",
province: "Hồ Chí Minh",
bhyt_number: "HS4010012345678"
})
.select() // Trả về record vừa tạo
.single(); // Chỉ 1 record
console.log(data);
// → { id: "uuid...", patient_code: "BN-20260001", full_name: "Nguyễn Văn A", ... }Query: Tìm bệnh nhân có BHYT sắp hết hạn
const { data } = await supabase
.from("patients")
.select("patient_code, full_name, bhyt_number, bhyt_expiry")
.not("bhyt_number", "is", null)
.lte("bhyt_expiry", "2026-03-31")
.gte("bhyt_expiry", "2026-02-01")
.order("bhyt_expiry", { ascending: true });
// → Danh sách BN có BHYT hết hạn trong tháng 2-3/2026Join: Lấy lượt khám kèm thông tin bệnh nhân + bác sĩ
const { data } = await supabase
.from("visits")
.select(`
visit_code,
visit_date,
status,
diagnosis,
patients ( patient_code, full_name, phone ),
doctors ( full_name, degree ),
departments ( name ),
icd_codes ( code, name_vi )
`)
.eq("status", "hoan_thanh")
.order("visit_date", { ascending: false })
.limit(20);
// → Tự động JOIN 4 bảng liên quan qua Foreign KeysAggregate: Đếm lượt khám theo trạng thái
const { data } = await supabase
.from("visits")
.select("status")
.gte("visit_date", "2026-02-01")
.lte("visit_date", "2026-02-28");
// Hoặc dùng RPC function cho aggregate phức tạp:
const { data: summary } = await supabase.rpc("fn_dashboard_summary", {
p_start_date: "2026-02-01",
p_end_date: "2026-02-28"
});Realtime: Theo dõi lượt khám mới
// Client Component ("use client")
const supabase = createClient();
const channel = supabase
.channel("realtime-visits")
.on(
"postgres_changes",
{ event: "INSERT", schema: "public", table: "visits" },
(payload) => {
console.log("Lượt khám mới:", payload.new);
// Update UI...
}
)
.subscribe();
// Cleanup
return () => { supabase.removeChannel(channel); };cURL: Gọi REST API trực tiếp
# Lấy 5 bệnh nhân
curl "https://mrgdawvdnofcxamjvcbl.supabase.co/rest/v1/patients?limit=5&select=patient_code,full_name" \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"
# Gọi RPC function
curl -X POST "https://mrgdawvdnofcxamjvcbl.supabase.co/rest/v1/rpc/fn_dashboard_summary" \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{"p_start_date":"2026-01-01","p_end_date":"2026-02-28"}'📄 OpenAPI Specification (Public)
✅ OpenAPI JSON Spec — truy cập công khai bằng API key
Supabase expose OpenAPI 2.0 spec tại endpoint REST. Dùng spec này để import vào Swagger UI, Postman, Insomnia, hoặc bất kỳ tool nào hỗ trợ OpenAPI — không cần login Supabase Dashboard.
Lấy OpenAPI spec
# OpenAPI JSON spec (public với apikey)
curl "https://mrgdawvdnofcxamjvcbl.supabase.co/rest/v1/" \
-H "apikey: YOUR_ANON_KEY" \
| python -m json.tool > openapi-spec.json
# Mở bằng Swagger UI online:
# 1. Vào https://editor.swagger.io
# 2. File → Import File → chọn openapi-spec.json
# → Xem tất cả endpoints, schemas, try it liveImport vào Postman
# Cách 1: Import URL trực tiếp trong Postman
# Collections → Import → Link → paste:
# https://mrgdawvdnofcxamjvcbl.supabase.co/rest/v1/
# (thêm header apikey = YOUR_ANON_KEY)
# Cách 2: Import file
curl -o openapi.json "https://mrgdawvdnofcxamjvcbl.supabase.co/rest/v1/" \
-H "apikey: YOUR_ANON_KEY"
# → Postman → Import → Upload Files → openapi.jsonImport spec → xem & test endpoints trên browser
Import → tạo collection → set apikey variable
Import OpenAPI → test & share workspace