API Document – HEBE ACADEMY
REST API dùng JSON, địa chỉ gốc https://hoc.hebeacademy.vn/api/v1, xác thực bằng API key. Dành cho lập trình viên và người dựng luồng tự động (n8n, Make, Google Apps Script).
Mục lục
1. Xác thực và quyền
Mọi yêu cầu cần header Authorization: Bearer <API key>; yêu cầu có body gửi thêm Content-Type: application/json.
GET /api/v1/me HTTP/1.1
Host: hoc.hebeacademy.vn
Authorization: Bearer elc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Lấy API key
- Đăng nhập tài khoản Quản trị → Admin → API & tích hợp.
- Đặt tên key, tích các quyền cần dùng, bấm Tạo API key.
- Chép key ngay. Key có dạng
elc_+ 43 ký tự, chỉ hiện một lần; hệ thống chỉ lưu mã băm SHA-256. - Không dùng nữa hoặc nghi bị lộ: bấm Thu hồi, key ngừng hoạt động ngay. Quyền của key đã tạo không sửa được; cần thêm quyền thì tạo key mới.
Bảng quyền
| Quyền | Cho phép |
|---|---|
courses:read |
Xem khoá học, chương, bài học, chương trình |
courses:write |
Tạo, sửa, xoá khoá học, chương, bài học, chương trình |
users:read |
Xem người dùng |
users:write |
Tạo, sửa người dùng |
enrollments:read |
Xem ai đang học khoá nào và tiến độ |
enrollments:write |
Cấp, thu hồi quyền học |
orders:read |
Xem đơn hàng |
orders:write |
Xác nhận, huỷ đơn hàng |
classes:read |
Xem lớp offline |
classes:write |
Thêm học viên vào lớp offline |
Kiểm tra key: GET /me (không cần quyền nào) trả về tên key, 10 ký tự đầu và danh sách quyền.
{ "data": { "name": "n8n đồng bộ bài học", "prefix": "elc_So848D", "scopes": ["courses:read", "courses:write"] } }
Giới hạn cố định: API không tạo và không sửa được tài khoản ADMIN, dù key có quyền nào. API dành cho gọi từ server; trình duyệt gọi trực tiếp sẽ bị chặn CORS, và không bao giờ để key trong mã chạy trên trình duyệt.
2. Quy ước chung
Kết quả luôn nằm trong data; lỗi luôn nằm trong error. Ngày giờ trả về theo chuẩn ISO 8601 (UTC), số tiền là số nguyên đơn vị đồng.
Gọi tên đối tượng trên đường dẫn
| Tham số | Nhận | Ví dụ |
|---|---|---|
{course} |
id hoặc slug | quan-tri-dong-tien |
{category} |
id hoặc slug | tai-chinh |
{user} |
id hoặc email | an@gmail.com |
{order} |
mã đơn (không phân biệt hoa thường) | DHHU37B771 |
{class} |
id hoặc mã lớp (không phân biệt hoa thường) | CEO-K01 |
{chapter}, {lesson} |
id | lấy từ GET /courses/{course}/curriculum |
Phân trang (các endpoint danh sách): ?limit= mặc định 50, tối đa 200; ?offset= mặc định 0. Kết quả kèm lại limit và offset; lấy trang tiếp khi số dòng trả về bằng limit.
Dạng lỗi
{ "error": { "code": "bad_request", "message": "Dữ liệu không hợp lệ.", "details": [ { "path": "title", "message": "Thiếu tên khoá học" } ] } }
| Mã HTTP | code |
Khi nào |
|---|---|---|
| 200 | — | Thành công; hoặc tạo lại đối tượng đã có (trả bản cũ) |
| 201 | — | Đã tạo mới |
| 400 | bad_request |
Body không phải JSON, thiếu trường, sai kiểu; details ghi từng trường sai |
| 401 | unauthorized |
Thiếu key, key sai hoặc đã thu hồi |
| 403 | forbidden |
Key thiếu quyền, hoặc động vào tài khoản ADMIN |
| 404 | not_found |
Không có đối tượng hoặc endpoint |
| 405 | method_not_allowed |
Sai phương thức; message liệt kê phương thức được phép |
| 409 | conflict |
Trùng slug hoặc email, hoặc đơn không còn ở trạng thái chờ; khi trùng slug, details kèm id của đối tượng đã có |
| 500 | internal_error |
Lỗi hệ thống; thử lại sau |
Gọi lại an toàn: POST /users, cấp quyền học, thêm học viên vào lớp và PUT …/curriculum có thể gọi lại nhiều lần với cùng dữ liệu mà không tạo trùng.
3. Khoá học và chương trình
| Phương thức | Đường dẫn | Quyền | Việc |
|---|---|---|---|
| GET | /courses |
courses:read | Danh sách; lọc ?q= (tên, slug), ?published=true|false, ?category= |
| POST | /courses |
courses:write | Tạo khoá, có thể kèm chapters |
| GET | /courses/{course} |
courses:read | Chi tiết; thêm ?include=curriculum để lấy giáo trình |
| PATCH | /courses/{course} |
courses:write | Sửa, chỉ gửi trường cần đổi |
| DELETE | /courses/{course} |
courses:write | Xoá khoá, kèm ghi danh và tiến độ học viên |
| GET | /categories |
courses:read | Danh sách chương trình |
| POST | /categories |
courses:write | Tạo chương trình: name, slug?, description?, imageUrl?, sort? |
| PATCH | /categories/{category} |
courses:write | Sửa chương trình |
| DELETE | /categories/{category} |
courses:write | Xoá chương trình; các khoá thuộc chương trình đó còn lại, không thuộc chương trình nào |
Trường dữ liệu của khoá học
| Trường | Kiểu | Ghi chú |
|---|---|---|
title |
chuỗi | Bắt buộc khi tạo |
slug |
chuỗi | Bỏ trống thì tự tạo từ tên, bỏ dấu tiếng Việt. Trùng → 409 |
summary, description |
chuỗi | Mô tả ngắn và giới thiệu chi tiết |
outcomes |
mảng chuỗi | "Bạn sẽ học được"; trả về luôn dạng mảng |
coverUrl |
URL hoặc null |
Ảnh bìa, tỉ lệ 16:9 |
price, salePrice |
số nguyên (đ) | salePrice = null là không khuyến mãi |
mode |
ONLINE / OFFLINE / HYBRID |
Mặc định ONLINE |
level |
chuỗi | Mặc định "Cơ bản" |
published |
boolean | Mặc định false (nháp). true thì học viên mới thấy và mua được |
featured |
boolean | Hiện ở "Khoá học nổi bật" trên trang chủ |
category |
slug/id hoặc null |
Chương trình phải có sẵn |
instructorEmail |
email hoặc null |
Tài khoản phải có vai trò Giảng viên, Nhân viên hoặc Quản trị |
Ví dụ: tạo khoá
curl -X POST https://hoc.hebeacademy.vn/api/v1/courses \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"title": "Quản trị dòng tiền cho chủ doanh nghiệp",
"summary": "Đọc được dòng tiền, lập kế hoạch 13 tuần.",
"price": 1990000, "salePrice": 1490000,
"category": "tai-chinh", "instructorEmail": "giangvien@gmail.com",
"outcomes": ["Đọc báo cáo dòng tiền", "Lập kế hoạch 13 tuần"],
"published": true
}'
Kết quả (201):
{ "data": {
"id": "887ca0b8-…", "slug": "quan-tri-dong-tien-cho-chu-doanh-nghiep",
"title": "Quản trị dòng tiền cho chủ doanh nghiệp", "price": 1990000, "salePrice": 1490000,
"mode": "ONLINE", "published": true, "featured": false,
"category": { "id": "…", "slug": "tai-chinh", "name": "Tài chính" },
"instructor": { "email": "giangvien@gmail.com", "name": "…" },
"outcomes": ["Đọc báo cáo dòng tiền", "Lập kế hoạch 13 tuần"],
"curriculum": []
} }
4. Giáo trình: chương và bài học
Cách khuyên dùng là gửi cả giáo trình mỗi lần bằng PUT /courses/{course}/curriculum. Hệ thống khớp theo tên nên gọi lại bao nhiêu lần cũng an toàn, không mất tiến độ học viên.
| Phương thức | Đường dẫn | Việc |
|---|---|---|
| GET | /courses/{course}/curriculum |
Xem giáo trình kèm id chương, bài (courses:read) |
| PUT | /courses/{course}/curriculum |
Đồng bộ cả giáo trình |
| POST | /courses/{course}/chapters |
Thêm 1 chương vào cuối: title, lessons?, sort? |
| PATCH | /chapters/{chapter} |
Đổi tên hoặc thứ tự chương: title?, sort? |
| DELETE | /chapters/{chapter} |
Xoá chương cùng mọi bài trong đó |
| POST | /chapters/{chapter}/lessons |
Thêm 1 bài vào cuối chương |
| PATCH | /lessons/{lesson} |
Sửa bài; chapterId để chuyển sang chương khác |
| DELETE | /lessons/{lesson} |
Xoá bài (mất tiến độ của bài đó) |
Các endpoint ghi ở bảng trên đều cần quyền courses:write.
Trường của bài học
| Trường | Kiểu | Ghi chú |
|---|---|---|
title |
chuỗi | Bắt buộc; dùng để khớp khi đồng bộ |
videoUrl |
URL hoặc null |
YouTube (mọi dạng link), Vimeo hoặc file .mp4 |
content |
chuỗi | Ghi chú, tài liệu hiện dưới video; giữ xuống dòng |
durationMin |
số nguyên | Thời lượng, phút |
isPreview |
boolean | true = cho học thử miễn phí |
Cách đồng bộ hoạt động (mode: "sync", mặc định)
- Chương khớp theo tên (không phân biệt hoa thường, bỏ khoảng trắng thừa); bài khớp theo tên trong cùng chương.
- Bài đã có được cập nhật các trường gửi lên và giữ nguyên id, nên học viên không mất tiến độ. Trường không gửi thì giữ giá trị cũ.
- Chương, bài chưa có được thêm mới. Thứ tự theo đúng thứ tự trong danh sách gửi lên.
- Chương, bài có trên web mà không có trong danh sách: giữ nguyên, trừ khi gửi
"prune": truethì xoá.
mode: "append" luôn thêm các chương gửi lên vào cuối, không khớp gì. Đổi tên bài qua đồng bộ sẽ tạo bài mới; muốn đổi tên mà giữ tiến độ thì dùng PATCH /lessons/{lesson}.
Ví dụ
curl -X PUT https://hoc.hebeacademy.vn/api/v1/courses/quan-tri-dong-tien-cho-chu-doanh-nghiep/curriculum \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{
"prune": false,
"chapters": [
{ "title": "Nền tảng", "lessons": [
{ "title": "Vì sao doanh nghiệp có lãi vẫn chết", "videoUrl": "https://youtu.be/xxxxxxxxxxx", "durationMin": 12, "isPreview": true },
{ "title": "Ba báo cáo tài chính cần đọc", "videoUrl": "https://youtu.be/yyyyyyyyyyy", "content": "Tài liệu kèm: …" }
]},
{ "title": "Lập kế hoạch dòng tiền", "lessons": [
{ "title": "Kế hoạch 13 tuần", "videoUrl": "https://youtu.be/zzzzzzzzzzz", "durationMin": 18 }
]}
]
}'
Kết quả (200) báo số thay đổi và giáo trình sau khi đồng bộ:
{ "data": {
"stats": { "chaptersCreated": 1, "chaptersUpdated": 1, "chaptersDeleted": 0, "lessonsCreated": 1, "lessonsUpdated": 2, "lessonsDeleted": 0 },
"curriculum": [ { "id": "…", "title": "Nền tảng", "sort": 0, "lessons": [ { "id": "…", "title": "Vì sao doanh nghiệp có lãi vẫn chết", "videoUrl": "…", "durationMin": 12, "isPreview": true, "sort": 0 } ] } ]
} }
5. Người dùng
| Phương thức | Đường dẫn | Quyền | Việc |
|---|---|---|---|
| GET | /users |
users:read | Danh sách; lọc ?email=, ?role=, ?q= (tên, email, số điện thoại) |
| POST | /users |
users:write | Tạo người dùng |
| GET | /users/{user} |
users:read | Chi tiết theo id hoặc email |
| PATCH | /users/{user} |
users:write | Sửa |
Trường dữ liệu
| Trường | Kiểu | Ghi chú |
|---|---|---|
email |
Bắt buộc khi tạo; tự chuyển về chữ thường | |
name |
chuỗi | Bỏ trống thì lấy phần trước @ của email |
phone |
chuỗi | |
role |
STUDENT / INSTRUCTOR / STAFF |
Mặc định STUDENT; không gán được ADMIN |
password |
chuỗi ≥ 6 ký tự | Bỏ trống thì tài khoản chưa có mật khẩu, người dùng đăng nhập bằng Google |
title, bio, avatarUrl |
chuỗi / URL | Chức danh, giới thiệu, ảnh đại diện (dùng cho giảng viên) |
active |
boolean | Chỉ khi sửa: false = khoá tài khoản |
Tạo người dùng: email chưa có → 201, tạo mới. Email đã có → 200 kèm "existing": true, trả tài khoản cũ và không sửa gì; muốn cập nhật thì gọi PATCH.
curl -X POST https://hoc.hebeacademy.vn/api/v1/users \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "email": "hocvien@gmail.com", "name": "Trần Thị B", "phone": "0901234567" }'
{ "data": {
"id": "…", "email": "hocvien@gmail.com", "name": "Trần Thị B", "phone": "0901234567",
"role": "STUDENT", "title": null, "bio": null, "avatarUrl": null, "active": true,
"googleLinked": false, "hasPassword": false, "createdAt": "2026-09-26T08:00:00.000Z"
}, "existing": false }
googleLinked = đã từng đăng nhập bằng Google; hasPassword = đã có mật khẩu. Sửa tài khoản ADMIN trả 403; đổi sang email đã có người dùng trả 409.
6. Cấp quyền học
Cấp quyền theo email, kể cả người chưa có tài khoản: hệ thống tạo sẵn tài khoản, người học bấm "Đăng nhập với Google" đúng email đó là vào học.
| Phương thức | Đường dẫn | Quyền | Việc |
|---|---|---|---|
| POST | /courses/{course}/enrollments |
enrollments:write | Cấp cho 1 người hoặc danh sách (tối đa 500) |
| DELETE | /courses/{course}/enrollments/{user} |
enrollments:write | Thu hồi; {user} là id hoặc email |
| GET | /courses/{course}/enrollments |
enrollments:read | Ai đang học khoá và tiến độ từng người |
| GET | /users/{user}/enrollments |
enrollments:read | Các khoá một người đang học |
Body khi cấp: một người { "email", "name"?, "phone"? }, hoặc danh sách { "users": [ { "email", "name"? }, … ] }.
curl -X POST https://hoc.hebeacademy.vn/api/v1/courses/quan-tri-dong-tien-cho-chu-doanh-nghiep/enrollments \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "users": [ { "email": "a@gmail.com", "name": "Nguyễn Văn A" }, { "email": "b@gmail.com" } ] }'
Kết quả (201), mỗi email một dòng. status là granted (vừa cấp) hoặc already (đã có quyền từ trước):
{ "data": [
{ "email": "a@gmail.com", "userId": "…", "userCreated": true, "status": "granted" },
{ "email": "b@gmail.com", "userId": "…", "userCreated": false, "status": "already" }
],
"summary": { "granted": 1, "already": 1, "usersCreated": 1 } }
Tiến độ trả về ở GET /courses/{course}/enrollments, mỗi dòng có user, enrolledAt và progress: { "done": 3, "total": 12 } (số bài đã hoàn thành / tổng số bài). Thu hồi người không có quyền trả 404.
7. Đơn hàng và lớp offline
| Phương thức | Đường dẫn | Quyền | Việc |
|---|---|---|---|
| GET | /orders |
orders:read | Danh sách, mới nhất trước; lọc ?status=PENDING|PAID|CANCELLED, ?since= (ngày giờ ISO, lấy đơn tạo hoặc thanh toán từ thời điểm đó) |
| GET | /orders/{order} |
orders:read | Chi tiết đơn |
| POST | /orders/{order}/confirm |
orders:write | Xác nhận đã thu tiền: ghi danh khoá hoặc thêm vào lớp, giống nút Xác nhận trong admin |
| POST | /orders/{order}/cancel |
orders:write | Huỷ đơn đang chờ |
| GET | /classes |
classes:read | Danh sách lớp kèm sĩ số hiện tại; lọc ?status=UPCOMING|ONGOING|FINISHED|CANCELLED |
| GET | /classes/{class} |
classes:read | Chi tiết lớp, danh sách học viên, học phí, đã đóng, còn nợ |
| POST | /classes/{class}/students |
classes:write | Thêm học viên: email, name?, phone?, fee? (mặc định = học phí của lớp) |
Xác nhận hoặc huỷ đơn không còn ở trạng thái PENDING trả 409. Đơn chuyển khoản thường đã được GPM Pay tự xác nhận, nên confirm chủ yếu dùng cho đơn tiền mặt hoặc thu qua kênh khác.
Một đơn hàng trả về
{ "data": {
"code": "DHHU37B771", "status": "PAID", "method": "BANK",
"amount": 10000, "discount": 0, "total": 10000, "couponCode": null, "note": null,
"user": { "id": "…", "email": "hocvien@gmail.com", "name": "…" },
"course": { "slug": "…", "title": "…" }, "class": null,
"createdAt": "2026-09-26T03:38:10.000Z", "paidAt": "2026-09-26T03:39:14.000Z"
} }
method: BANK (chuyển khoản), CASH (tiền mặt), FREE (đơn 0đ). course hoặc class có giá trị tuỳ đơn mua khoá online hay đăng ký lớp.
Đồng bộ đơn sang hệ thống khác: gọi GET /orders?status=PAID&since=<lần gọi trước> theo chu kỳ (ví dụ mỗi 5 phút), lưu lại thời điểm gọi, dùng code làm khoá chống trùng.
8. Ví dụ tích hợp
Google Sheets → cấp khoá học. Sheet có cột A = email, B = họ tên, C = slug khoá. Dán vào Extensions → Apps Script, lưu key vào Project Settings → Script properties với tên ELC_KEY, rồi chạy capKhoaHoc. Kết quả ghi vào cột D.
const BASE = 'https://hoc.hebeacademy.vn/api/v1';
function capKhoaHoc() {
const key = PropertiesService.getScriptProperties().getProperty('ELC_KEY');
const sheet = SpreadsheetApp.getActiveSheet();
const rows = sheet.getDataRange().getValues();
for (let i = 1; i < rows.length; i++) { // bỏ dòng tiêu đề
const [email, name, course, done] = rows[i];
if (!email || !course || done) continue; // đã xử lý thì bỏ qua
const res = UrlFetchApp.fetch(`${BASE}/courses/${course}/enrollments`, {
method: 'post',
contentType: 'application/json',
headers: { Authorization: `Bearer ${key}` },
payload: JSON.stringify({ email, name }),
muteHttpExceptions: true,
});
const body = JSON.parse(res.getContentText());
const result = res.getResponseCode() < 300 ? body.data[0].status : `Lỗi: ${body.error.message}`;
sheet.getRange(i + 1, 4).setValue(result);
}
}
n8n. Dùng node HTTP Request:
| Mục | Giá trị |
|---|---|
| Authentication | Generic Credential Type → Header Auth |
| Header Auth | Name Authorization, Value Bearer elc_… (lưu trong Credentials, không dán thẳng vào node) |
| Method / URL | Ví dụ POST + https://hoc.hebeacademy.vn/api/v1/courses/{{$json.courseSlug}}/enrollments |
| Body | JSON, ví dụ { "email": "{{$json.email}}", "name": "{{$json.name}}" } |
| Settings | Bật Retry On Fail (3 lần) để chịu lỗi mạng tạm thời; các lệnh cấp và đồng bộ gọi lại đều an toàn |
Luồng gợi ý: Form đăng ký / đơn hàng bên ngoài → POST /users → POST /courses/{slug}/enrollments → gửi email chào mừng kèm link https://hoc.hebeacademy.vn/login.
Đẩy giáo trình từ file JSON (soạn giáo trình trong giao-trinh.json theo mẫu ở mục 4, sau mỗi lần sửa chỉ cần chạy lại):
export KEY=elc_xxx
curl -sS -X PUT https://hoc.hebeacademy.vn/api/v1/courses/quan-tri-dong-tien-cho-chu-doanh-nghiep/curriculum \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
--data @giao-trinh.json | jq '.data.stats // .error'
Giữ an toàn key: mỗi hệ thống một key riêng, chỉ đủ quyền cần dùng; không đưa key vào sheet dùng chung, mã nguồn công khai hay tin nhắn. Kiểm tra cột "Dùng lần cuối" trong Admin → API & tích hợp để phát hiện key bị dùng bất thường.