Tài liệu này dành cho partner tích hợp lần đầu API chia sẻ học liệu trên Trục LMS (bao gồm luồng upload tệp đính kèm và chia sẻ học liệu).
Chọn nhánh triển khai
Chọn đúng tài liệu theo loại đơn vị để triển khai nhanh và giảm nhầm lẫn:
| Đơn vị tích hợp | Tài liệu nên đọc trước | Endpoint học liệu | Ghi chú chính |
|---|---|---|---|
| Nhà trường | Tích hợp cho Nhà trường | POST /api/lms-base/learning-resources | Kiểm tra chặt authorPinCode/creatorPinCode theo ngữ cảnh dữ liệu Sở |
| Tổ chức | Tích hợp cho Tổ chức | POST /api/lms-base/learning-resources/organizations | Upsert theo partnerId + sourceId; có thể để trống thông tin người chia sẻ để hệ thống tự điền |
Trang này giữ vai trò tài liệu tổng quan + reference dùng chung cho cả 2 nhánh. Hướng dẫn tích hợp API tải học liệu đã duyệt: Tải học liệu đã duyệt.
Tích hợp nhanh
Happy path chuẩn:
- Upload file qua
POST /api/lms-base/attachments. - Nhận
idvàpathcho từng file. - Replace URL trong HTML (
src/href/poster) bằngpathđã upload. - Gọi
POST /api/lms-base/learning-resourcesvớifileIdskhớp đúng các file đang dùng. - Poll trạng thái duyệt qua endpoint danh sách tương ứng scope:
- Nhà trường:
GET /api/lms-base/learning-resources - Tổ chức:
GET /api/lms-base/learning-resources/organizations
- Nhà trường:
Lưu ý quan trọng:
POST /learning-resourcesđang xử lý theo cơ chế upsert theo cặpschoolCode + sourceIdtrong phạm vi partner gọi API (X-Api-Key): nếu đã có bản ghiPending/Rejectedthì cập nhật, nếu chưa có thì tạo mới, nếu đãApprovedthì bị từ chối.- Nếu chia sẻ theo scope tổ chức, dùng
POST /learning-resources/organizations; cơ chế upsert theo cặppartnerId + sourceId. authorPinCodevàcreatorPinCodevới endpointPOST /learning-resourcesphải là CCCD 12 số, tồn tại đúng ngữ cảnh dữ liệu Sở (doetCode/divisionCode/schoolCode/yearCode).gradeCode/subjectCode/knowledgeUnitCodephải map theo danh mục dùng chung:https://docs.vstudy.edu.vn/docs/knowledge-units.- Nếu truyền
fileIds, tất cảfileIdsbắt buộc phải thực sự xuất hiện trongthumbnailUrlhoặccontents. - Giai đoạn hiện tại hệ thống chưa xây dựng webhook/callback duyệt; cơ chế theo dõi là polling.
1. Tổng quan bài toán
Bài toán tích hợp gồm 2 phần:
- Quản lý file dùng trong học liệu (attachments).
- Tạo bản ghi học liệu số (learning resource) và theo dõi duyệt.
Thông tin kết nối:
- Endpoint theo môi trường:
- Môi trường Production (
PROD):https://partner.vstudy.edu.vn/ - Môi trường Development (
DEV):https://lmsbpartnerapi.vstudy.edu.vn/
- Môi trường Production (
- Auth header:
X-Api-Key: <api-key-don-vi-duoc-cap> - JSON API:
Content-Type: application/json - Upload file:
multipart/form-datacho/api/lms-base/attachments
Lưu ý: các ví dụ curl trong tài liệu này đang dùng endpoint môi trường Development (DEV). Khi triển khai thật, đổi sang môi trường Production (PROD).
Liên hệ cấp X-Api-Key
Thông tin dưới đây chỉ dùng cho việc cấp API key tích hợp Partner API:
| Đơn vị | Người phụ trách | Số điện thoại | |
|---|---|---|---|
| QIG | Nguyễn Thành Luân | 0988933774 | thanhluan@qig.vn |
Format response chung:
{
"success": true,
"message": "Mô tả kết quả",
"data": {}
}2. Luồng tích hợp end-to-end
- Chuẩn bị metadata học liệu (
sourceId, mã phân loại, thông tin tác giả/người gửi). - Upload các file/media sẽ được tham chiếu trong học liệu (ảnh đại diện nếu dùng ảnh đã upload, file trong
contents, SCORM.zipnếu có). VớiYOUTUBEhoặcthumbnailUrldùng URL ngoài hợp lệ, không cần upload file tương ứng. - Lưu lại
id+pathtừ response upload. - Replace URL trong
contents[].contentbằngpathtương ứng;thumbnailUrlcó thể dùng URL ảnh đã upload hoặc URL ảnh ngoài hợp lệ. - Tạo payload học liệu với
fileIdskhớp đúng file đang dùng. - Gọi API chia sẻ học liệu (upsert) theo scope phù hợp:
POST /api/lms-base/learning-resources(scope trường)POST /api/lms-base/learning-resources/organizations(scope tổ chức)
- Poll API danh sách theo scope để theo dõi
approvalStatus:- Nhà trường:
GET /api/lms-base/learning-resources - Tổ chức:
GET /api/lms-base/learning-resources/organizations
- Nhà trường:
- Khi học liệu đã được duyệt và đủ điều kiện khai thác, tích hợp API tải theo tài liệu: Tải học liệu đã duyệt.
- Nếu cần, kiểm tra trạng thái từng file bằng
GET /api/lms-base/attachments/{attachmentId}. - Nếu cần thu hồi bản ghi đang chờ duyệt, gọi API xóa theo scope:
- Nhà trường:
DELETE /api/lms-base/learning-resources - Tổ chức:
DELETE /api/lms-base/learning-resources/organizations
3. Checklist trước khi gọi API
Checklist bắt buộc:
- Đã có
X-Api-Keyhợp lệ. - Đã map đúng mã dùng chung của Sở (
gradeCode,subjectCode,knowledgeUnitCode) từ danh mụcknowledge-units. - Với endpoint
/learning-resources, đã xác thựcauthorPinCode/creatorPinCodeđúng người, đúng ngữ cảnh (doetCode/divisionCode/schoolCode/yearCode). - Đã chọn đúng endpoint theo scope chia sẻ:
POST /learning-resources: upsert theoschoolCode + sourceIdtrong phạm vi partner gọi API (X-Api-Key).POST /learning-resources/organizations: upsert theopartnerId + sourceId.- Poll duyệt/xóa pending cũng theo đúng scope (
/learning-resourceshoặc/learning-resources/organizations).
- Nếu
sourceIdđã có bản ghiApproved, phải dùngsourceIdmới để chia sẻ tiếp (không cập nhật đè). - File upload đúng loại MIME cho mục đích sử dụng.
- Tổng từng file upload
> 0và<= 104857600byte (100 MB). - Tất cả URL trong
thumbnailUrlvàcontentslà URL tuyệt đối hợp lệ (http/https). - Nếu có
fileIds, mỗi id phải là ULID 26 ký tự, thuộc đúng partner và thực sự được dùng trongthumbnailUrlhoặccontents. - HTML content không chứa thành phần bị cấm (CSS/JS/iframe/object/embed/on*/protocol không an toàn).
4. Quickstart với 1 ví dụ đầy đủ
4.1. Nội dung HTML ban đầu (before replace)
<p>Xem video minh họa:</p>
<video controls poster="https://partner.example.com/media/thumb-phan-so.png">
<source src="https://partner.example.com/media/video-phan-so.mp4" type="video/mp4" />
</video>
<p>Tải tài liệu PDF: <a href="https://partner.example.com/media/phieu-bai-tap.pdf">Tại đây</a></p>4.2. Upload file như thế nào
Upload từng file riêng qua POST /api/lms-base/attachments.
curl --location 'https://lmsbpartnerapi.vstudy.edu.vn/api/lms-base/attachments' \
--header 'X-Api-Key: <api-key-don-vi-duoc-cap>' \
--form 'doetCode="01"' \
--form 'divisionCode="0001"' \
--form 'schoolCode="01000001"' \
--form 'educationLevelCode="02"' \
--form 'file=@"/path/to/thumb-phan-so.png"'{
"success": true,
"message": "Tải tệp thành công.",
"data": {
"id": "01JQ2S96MKZ8E8GHW8EXB4VPA7",
"path": "https://<cdn-or-storage>/partner-code/thumb-phan-so.png",
"useStatus": "Draft"
}
}Tương tự upload video và pdf (mỗi file 1 request), ví dụ nhận:
- Video:
id=01JQ2SAXA9QXRVQ8S9QPA4J2AN,path=https://<cdn-or-storage>/partner-code/video-phan-so.mp4 - PDF:
id=01JQ2SB6R52J01Q2RC9F0M6V7P,path=https://<cdn-or-storage>/partner-code/phieu-bai-tap.pdf
4.3. HTML sau khi replace URL (after replace)
<p>Xem video minh họa:</p>
<video controls poster="https://<cdn-or-storage>/partner-code/thumb-phan-so.png">
<source src="https://<cdn-or-storage>/partner-code/video-phan-so.mp4" type="video/mp4" />
</video>
<p>Tải tài liệu PDF: <a href="https://<cdn-or-storage>/partner-code/phieu-bai-tap.pdf">Tại đây</a></p>4.4. Payload cuối cùng gửi lên learning-resources
{
"sourceId": "LR-HN-000001",
"title": "Bài học: So sánh phân số",
"thumbnailUrl": "https://<cdn-or-storage>/partner-code/thumb-phan-so.png",
"authorPinCode": "001095123456",
"authorName": "Nguyen Van A",
"creatorPinCode": "001095123456",
"creatorName": "Nguyen Van A",
"fileIds": [
"01JQ2S96MKZ8E8GHW8EXB4VPA7",
"01JQ2SAXA9QXRVQ8S9QPA4J2AN",
"01JQ2SB6R52J01Q2RC9F0M6V7P"
],
"contents": [
{
"name": "Mở đầu",
"displayOrder": 1,
"type": "TEXT",
"content": "<p>Xem video minh họa:</p><video controls poster=\"https://<cdn-or-storage>/partner-code/thumb-phan-so.png\"><source src=\"https://<cdn-or-storage>/partner-code/video-phan-so.mp4\" type=\"video/mp4\" /></video><p>Tải tài liệu PDF: <a href=\"https://<cdn-or-storage>/partner-code/phieu-bai-tap.pdf\">Tại đây</a></p>",
"description": "Mở đầu"
}
],
"gradeCode": "06",
"subjectCode": "0601",
"knowledgeUnitCode": "201100101",
"doetCode": "01",
"divisionCode": "0001",
"schoolCode": "01000001",
"yearCode": 2025,
"educationLevelCode": "02",
"semesterCode": 1
}Gọi API:
curl --location 'https://lmsbpartnerapi.vstudy.edu.vn/api/lms-base/learning-resources' \
--header 'X-Api-Key: <api-key-don-vi-duoc-cap>' \
--header 'Content-Type: application/json' \
--data '<payload-json-o-tren>'Response thành công:
{
"success": true,
"message": "Tạo mới học liệu số thành công.",
"data": {
"sourceId": "LR-HN-000001",
"id": "01JQ2T5W11Z9J6AEX4Q6F90HME",
"approvalStatus": 0,
"operation": "created"
}
}operation có 2 giá trị:
created: tạo mới học liệu.updated: cập nhật bản ghi đã có ở trạng tháiPendinghoặcRejected.
4.5. Poll trạng thái duyệt
curl --location --get 'https://lmsbpartnerapi.vstudy.edu.vn/api/lms-base/learning-resources' \
--header 'X-Api-Key: <api-key-don-vi-duoc-cap>' \
--data-urlencode 'approvalStatus=0' \
--data-urlencode 'doetCode=01' \
--data-urlencode 'schoolCode=01000001' \
--data-urlencode 'skip=0' \
--data-urlencode 'take=20'Response thành công:
{
"success": true,
"message": "Lấy danh sách học liệu thành công.",
"data": [
{
"id": "01JQ2T5W11Z9J6AEX4Q6F90HME",
"sourceId": "LR-HN-000001",
"title": "Bài học: So sánh phân số",
"approvalStatus": 0,
"approvalStatusReason": null,
"approvalStatusDate": "2026-04-12T07:12:44.0000000Z",
"canDelete": true,
"createdAt": "2026-04-12T07:12:44.0000000Z",
"updatedAt": "2026-04-12T07:12:44.0000000Z"
}
]
}Khi duyệt xong, approvalStatus đổi từ 0 sang:
1(Approved)2(Rejected)
Nếu chia sẻ theo scope tổ chức, poll bằng endpoint:
curl --location --get 'https://lmsbpartnerapi.vstudy.edu.vn/api/lms-base/learning-resources/organizations' \
--header 'X-Api-Key: <api-key-don-vi-duoc-cap>' \
--data-urlencode 'approvalStatus=0' \
--data-urlencode 'skip=0' \
--data-urlencode 'take=20'Response của endpoint tổ chức có cùng cấu trúc (success, message, data[]).
5. Upload attachments
5.1. Upload tệp
- Method:
POST - Path:
/api/lms-base/attachments - Content-Type:
multipart/form-data
Form-data fields:
| Field | Bắt buộc | Kiểu dữ liệu | Kích thước/giới hạn | Mô tả |
|---|---|---|---|---|
doetCode | Có | string | Tối đa 50 ký tự | Mã Sở GDĐT |
divisionCode | Không | string / null | Tối đa 50 ký tự | Mã phường/xã |
schoolCode | Có (scope trường) / Không (scope tổ chức) | string / null | Tối đa 50 ký tự | Scope trường: bắt buộc và phải hợp lệ theo dữ liệu trường. Scope tổ chức: cho phép để trống "". |
educationLevelCode | Có | string | Tối đa 50 ký tự | Mã cấp học |
file | Có | IFormFile (binary) | > 0 và <= 104857600 byte | File/media cần upload |
Ràng buộc kỹ thuật:
- Kích thước file:
> 0và<= 104857600byte (100 MB). - Tệp upload phải có phần mở rộng và phần mở rộng phải nằm trong danh mục hệ thống hỗ trợ.
- Ngoài giới hạn request
100 MB, hệ thống còn đối chiếu giới hạn dung lượng theo từng phần mở rộng từ cấu hình. - MIME được kiểm tra theo chữ ký file (không chỉ extension).
- MIME được chấp nhận:
image:image/jpeg,image/png,image/gif,image/webp.document:application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document.spreadsheet:application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.slide:application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation.video:video/mp4,video/webm.audio:audio/mpeg.scorm:application/zip.- Scope trường:
doetCode/schoolCode/educationLevelCodeđược kiểm tra theo dữ liệu master;divisionCodenếu truyền cũng phải hợp lệ theo ngữ cảnh trường và năm học. - Scope tổ chức: cho phép
schoolCode = ""; hệ thống kiểm tradoetCode, không áp ngữ cảnh trường choschoolCode/divisionCode. - File upload phải được mapping dùng trong
thumbnailUrlhoặccontents; file upload không được mapping sử dụng sẽ bị xóa khỏi hệ thống. - Cơ chế dọn file không được mapping do hệ thống trục có job tự động quét dọn; partner không cần xử lý luồng này.
5.2. Kiểm tra trạng thái tệp
- Method:
GET - Path:
/api/lms-base/attachments/{attachmentId}
Ví dụ response:
{
"success": true,
"message": "Lấy trạng thái tệp thành công.",
"data": {
"useStatus": "Used",
"ownerId": "01JQ2T5W11Z9J6AEX4Q6F90HME",
"accessUrl": "https://<cdn-or-storage>/partner-code/uuid.mp4"
}
}Ý nghĩa useStatus:
Draft: file chưa gắn vào học liệu.Used: file đã gắn với một học liệu.
6. Tạo learning resource
6.1. Endpoint
- Method:
POST - Path (scope trường):
/api/lms-base/learning-resources - Path (scope tổ chức):
/api/lms-base/learning-resources/organizations - Content-Type:
application/json
Rule upsert theo endpoint:
- Scope trường (
/learning-resources): xác định bản ghi theoschoolCode + sourceIdtrong phạm vi partner gọi API (X-Api-Key). - Scope tổ chức (
/learning-resources/organizations): xác định bản ghi theopartnerId + sourceId. - Nếu đã có bản ghi
Approved, API từ chối cập nhật và yêu cầusourceIdmới.
6.2. Các trường request
Quy ước đọc cột Bắt buộc:
Có: bắt buộc cho cả 2 endpoint.Có (scope trường): bắt buộc vớiPOST /learning-resources; endpoint tổ chức có thể để trống để hệ thống tự gán.Không: không bắt buộc với cả 2 endpoint.
| Field | Bắt buộc | Kiểu dữ liệu | Kích thước/giới hạn | Ghi chú |
|---|---|---|---|---|
sourceId | Có | string | Tối đa 128 ký tự | Khóa upsert của đơn vị: endpoint trường dùng cặp schoolCode + sourceId trong phạm vi X-Api-Key; endpoint tổ chức dùng cặp partnerId + sourceId. Chỉ chấp nhận văn bản thuần, không chứa HTML |
title | Có | string | Tối đa 500 ký tự | Tiêu đề học liệu. Chỉ chấp nhận văn bản thuần, không chứa HTML |
description | Không | string / null | Tối đa 1024 ký tự | Mô tả học liệu dạng văn bản hoặc HTML an toàn (áp dụng rule an toàn nội dung) |
thumbnailUrl | Có | string | Tối đa 1024 ký tự; URL tuyệt đối | Bắt buộc. Có thể là path ảnh đã upload hoặc URL ảnh ngoài hợp lệ (.jpg, .jpeg, .png, .gif, .bmp, .webp) |
authorPinCode | Có (scope trường) | string | Tối đa 50 ký tự; endpoint trường yêu cầu CCCD 12 chữ số | Với /learning-resources: bắt buộc CCCD hợp lệ theo ngữ cảnh dữ liệu Sở. Với /learning-resources/organizations: có thể để trống, hệ thống tự điền mặc định theo thông tin tổ chức |
authorName | Có (scope trường) | string | Tối đa 256 ký tự | Tên tác giả học liệu; endpoint tổ chức có thể để trống để hệ thống tự điền. Nếu truyền thì chỉ chấp nhận văn bản thuần, không chứa HTML |
creatorPinCode | Có (scope trường) | string | Tối đa 50 ký tự; endpoint trường yêu cầu CCCD 12 chữ số | Với /learning-resources: CCCD người chia sẻ phải hợp lệ theo ngữ cảnh dữ liệu Sở. Với /learning-resources/organizations: có thể để trống, hệ thống tự điền mặc định theo thông tin tổ chức |
creatorName | Có (scope trường) | string | Tối đa 256 ký tự | Tên người chia sẻ; endpoint tổ chức có thể để trống để hệ thống tự điền. Nếu truyền thì chỉ chấp nhận văn bản thuần, không chứa HTML |
fileIds | Không | array<string> | Mỗi phần tử ULID 26 ký tự | Danh sách attachment dùng cho thumbnailUrl và contents; nếu truyền thì tất cả phải hợp lệ và thuộc đối tác |
contents | Có | array<object> | Ít nhất 1 phần tử | Danh sách nội dung học liệu |
gradeCode | Có | string | Tối đa 20 ký tự | Mã khối theo danh mục định danh của Sở |
subjectCode | Có | string | Tối đa 20 ký tự | Mã môn học theo danh mục định danh của Sở |
knowledgeUnitCode | Có | string | Tối đa 10 ký tự | Mã đơn vị kiến thức theo danh mục định danh của Sở |
doetCode | Có (scope trường) | string | Tối đa 20 ký tự | Với /learning-resources: bắt buộc và dùng để validate ngữ cảnh. Với /learning-resources/organizations: request có thể để trống, hệ thống tự gán cố định 01 |
divisionCode | Không | string / null | Tối đa 20 ký tự | Với /learning-resources: mã phường/xã theo ngữ cảnh dữ liệu. Với /learning-resources/organizations: request có thể để trống, hệ thống tự gán "" (rỗng) |
schoolCode | Có (scope trường) | string | Tối đa 50 ký tự | Với /learning-resources: bắt buộc và dùng để validate ngữ cảnh. Với /learning-resources/organizations: request có thể để trống, hệ thống tự gán "" (rỗng) |
yearCode | Có | int | Từ 1000 đến 9999 | Năm học dạng 4 chữ số |
educationLevelCode | Có | string | Tối đa 20 ký tự | Mã cấp học |
semesterCode | Có | int | Chỉ nhận 1 hoặc 2 | Học kỳ |
6.3. Mapping mã dùng chung (bắt buộc)
Các trường phân loại học liệu phải dùng mã định danh do Sở quy định.
| Trường | Yêu cầu mapping | Nguồn tra cứu |
|---|---|---|
gradeCode | Map đúng mã khối thuộc educationLevelCode | https://docs.vstudy.edu.vn/docs/knowledge-units |
subjectCode | Map đúng mã môn học thuộc gradeCode | https://docs.vstudy.edu.vn/docs/knowledge-units |
knowledgeUnitCode | Map đúng mã đơn vị kiến thức thuộc cặp gradeCode/subjectCode | https://docs.vstudy.edu.vn/docs/knowledge-units |
6.4. Cấu trúc contents[]
| Field | Bắt buộc | Kiểu dữ liệu | Kích thước/giới hạn | Ghi chú |
|---|---|---|---|---|
name | Có | string | Tối đa 256 ký tự | Tên nội dung. Chỉ chấp nhận văn bản thuần, không chứa HTML |
displayOrder | Không | int / null | >= 1; không truyền thì tự gán | Nếu không truyền, hệ thống tự gán theo thứ tự mảng (bắt đầu từ 1) |
type | Có | string (enum) | Tối đa 20 ký tự | TEXT, DOCUMENT, SPREADSHEET, VIDEO, AUDIO, YOUTUBE, SCORM, SLIDE |
content | Có | string | Không giới hạn độ dài (nvarchar(max)) | Nội dung theo rule từng type |
description | Không | string / null | Tối đa 1024 ký tự | Mô tả nội dung dạng văn bản hoặc HTML an toàn |
Rule content theo type:
type | Rule cho content |
|---|---|
TEXT | Văn bản thuần hoặc HTML; nếu HTML thì URL trong src/href/poster phải là URL tuyệt đối hợp lệ |
DOCUMENT | URL tuyệt đối; nếu map file upload thì MIME phải là application/pdf, application/msword, hoặc application/vnd.openxmlformats-officedocument.wordprocessingml.document |
SPREADSHEET | URL tuyệt đối; nếu map file upload thì MIME phải là application/vnd.ms-excel hoặc application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
VIDEO | URL tuyệt đối; nếu map file upload thì MIME phải là video/mp4 hoặc video/webm |
AUDIO | URL tuyệt đối; nếu map file upload thì MIME phải là audio/mpeg |
YOUTUBE | Chỉ chấp nhận URL tuyệt đối dạng https://youtube.com/watch?v=... (bao gồm www.youtube.com/watch?v=...) hoặc https://youtu.be/...; không truyền iframe |
SCORM | URL tuyệt đối trỏ tới file .zip đã upload; MIME phải là application/zip |
SLIDE | URL tuyệt đối; nếu map file upload thì MIME phải là application/vnd.ms-powerpoint hoặc application/vnd.openxmlformats-officedocument.presentationml.presentation |
Định dạng được hỗ trợ theo type:
TEXT: nội dung inline.DOCUMENT:pdf,doc,docx.SPREADSHEET:xls,xlsx.VIDEO:mp4,webm.AUDIO:mp3.YOUTUBE: link YouTube tuyệt đối hợp lệ (không áp dụng định dạng file upload).SCORM:zip.SLIDE:ppt,pptx.
Rule an toàn nội dung (bắt buộc):
contentkhông được chứa CSS/TypeScript/JavaScript.- Không dùng
iframe,object,embed. - Không dùng event handler dạng
on*. - Không dùng protocol/script không an toàn:
javascript:,vbscript:,data:text/html,data:application/javascript. - Chỉ hỗ trợ SCORM
1.2hoặc2004. - Nếu truyền
fileIds, tất cảfileIdsphải được dùng trongthumbnailUrlhoặccontents; nếu không sẽ bị từ chối.
7. Tra cứu trạng thái duyệt
7.1. Endpoint danh sách học liệu nhà trường
- Method:
GET - Path:
/api/lms-base/learning-resources - Bắt buộc truyền
doetCodetrong query.
Query params:
| Field | Kiểu dữ liệu | Giới hạn | Ghi chú |
|---|---|---|---|
sourceId | string / null | Tối đa 128 ký tự | Lọc theo mã học liệu nguồn |
educationLevelCode | string / null | Tối đa 20 ký tự | Trim khoảng trắng đầu/cuối trước khi lọc |
doetCode | string | Tối đa 20 ký tự | Bắt buộc, lọc theo mã Sở GDĐT |
divisionCode | string / null | Tối đa 20 ký tự | Lọc theo mã phường/xã |
schoolCode | string / null | Tối đa 50 ký tự | Lọc theo mã trường |
approvalStatus | int / null | 0, 1, 2 | 0=Pending, 1=Approved, 2=Rejected |
skip | int / null | >= 0 | Mặc định 0 |
take | int / null | 1..200 | Mặc định 20 |
Ví dụ gọi API:
curl --location --get 'https://lmsbpartnerapi.vstudy.edu.vn/api/lms-base/learning-resources' \
--header 'X-Api-Key: <api-key-don-vi-duoc-cap>' \
--data-urlencode 'approvalStatus=0' \
--data-urlencode 'doetCode=01' \
--data-urlencode 'skip=0' \
--data-urlencode 'take=20'Ví dụ item response:
{
"id": "01JQ2T5W11Z9J6AEX4Q6F90HME",
"sourceId": "LR-HN-000001",
"title": "Bài học: So sánh phân số",
"approvalStatus": 0,
"approvalStatusReason": null,
"approvalStatusDate": "2026-04-12T07:12:44.0000000Z",
"canDelete": true,
"createdAt": "2026-04-12T07:12:44.0000000Z",
"updatedAt": "2026-04-12T07:12:44.0000000Z"
}7.2. Endpoint danh sách học liệu tổ chức
- Method:
GET - Path:
/api/lms-base/learning-resources/organizations
Query params (đều không bắt buộc):
| Field | Kiểu dữ liệu | Giới hạn | Ghi chú |
|---|---|---|---|
sourceId | string / null | Tối đa 128 ký tự | Lọc theo mã học liệu nguồn |
educationLevelCode | string / null | Tối đa 20 ký tự | Lọc theo mã cấp học |
approvalStatus | int / null | 0, 1, 2 | 0=Pending, 1=Approved, 2=Rejected |
skip | int / null | >= 0 | Mặc định 0 |
take | int / null | 1..200 | Mặc định 20 |
Ví dụ gọi API:
curl --location --get 'https://lmsbpartnerapi.vstudy.edu.vn/api/lms-base/learning-resources/organizations' \
--header 'X-Api-Key: <api-key-don-vi-duoc-cap>' \
--data-urlencode 'approvalStatus=0' \
--data-urlencode 'skip=0' \
--data-urlencode 'take=20'7.3. Cách theo dõi duyệt cho partner
- Giai đoạn hiện tại hệ thống chưa xây dựng webhook/callback cho duyệt.
- Cơ chế theo dõi đang dùng polling qua API danh sách theo scope:
- Nhà trường:
GET /api/lms-base/learning-resources - Tổ chức:
GET /api/lms-base/learning-resources/organizations
- Nhà trường:
- Khuyến nghị lọc theo ngữ cảnh của đơn vị và theo
approvalStatusđể giảm dữ liệu trả về. - Partner API hiện chưa có endpoint tra cứu trạng thái duyệt theo từng
learningResourceIdriêng biệt; theo dõi duyệt vẫn dùng API danh sách. - Field trạng thái mới trong item response:
approvalStatusReason: lý do duyệt/từ chối (thường có khiRejected).approvalStatusDate: thời điểm ghi nhận trạng thái duyệt hiện tại.
8. Xóa học liệu pending
- Method:
DELETE - Path (scope trường):
/api/lms-base/learning-resources - Path (scope tổ chức):
/api/lms-base/learning-resources/organizations - Content-Type:
application/json
8.1. Dữ liệu đầu vào (request)
Request body là mảng string[] chứa danh sách id học liệu (ULID) cần xóa.
| Field | Kiểu dữ liệu | Bắt buộc | Ràng buộc |
|---|---|---|---|
ids (request body) | array<string> | Có | Mỗi phần tử là ULID học liệu; mảng không rỗng; không trùng id trong cùng request; tối đa 200 id/lần |
8.2. Mô hình phản hồi (response model)
API trả về response envelope chuẩn:
success:true/falsetheo kết quả tổng thể của lô.message: thông điệp tổng hợp (ví dụ: số lượng thành công/thất bại).data: danh sách kết quả theo từng id đã gửi.
Chi tiết từng phần tử data[]:
| Field | Kiểu dữ liệu | Ý nghĩa |
|---|---|---|
sourceId | string | Mã học liệu nguồn tương ứng bản ghi được xử lý |
id | string (ULID) | Mã ULID học liệu đã xử lý |
deleted | bool | true: xóa thành công, false: không xóa được |
message | string | Lý do/diễn giải kết quả cho từng bản ghi |
processedAt | string (datetime UTC) | Thời điểm hệ thống xử lý bản ghi |
Lưu ý: API có thể trả 200 OK nhưng vẫn có item deleted = false (thành công một phần), cần xử lý theo từng phần tử data[].
8.3. Ví dụ request
Ví dụ request:
[
"01JQ2T5W11Z9J6AEX4Q6F90HME",
"01JQ2T5W11Z9J6AEX4Q6F90HMF"
]8.4. Ví dụ response (thành công một phần)
Ví dụ response:
{
"success": false,
"message": "Xử lý xóa 2 học liệu, xóa thành công 1, thất bại 1.",
"data": [
{
"sourceId": "LR-HN-000001",
"id": "01JQ2T5W11Z9J6AEX4Q6F90HME",
"deleted": true,
"message": "Xóa học liệu thành công.",
"processedAt": "2026-04-14T02:00:00.0000000Z"
},
{
"sourceId": "LR-HN-000002",
"id": "01JQ2T5W11Z9J6AEX4Q6F90HMF",
"deleted": false,
"message": "Chỉ được xóa học liệu ở trạng thái chờ duyệt.",
"processedAt": "2026-04-14T02:00:00.0000000Z"
}
]
}Ràng buộc xóa theo lô:
- Chỉ xóa được học liệu ở trạng thái
Pending (0). - Chỉ xóa được học liệu thuộc đúng đối tác gọi API (
X-Api-Key). - Tối đa
200id/lần. - Danh sách id không được rỗng và không được trùng trong cùng request.
- API có thể trả
200 OKnhưng có bản ghi xóa thất bại; luôn kiểm tradata[].deleted.
9. Các rule validate dễ sai
- API tạo/chia sẻ học liệu chạy theo cơ chế upsert theo
sourceId:/learning-resources: key làschoolCode + sourceIdtrong phạm vi partner gọi API (X-Api-Key)./learning-resources/organizations: key làpartnerId + sourceId.- Nếu bản ghi hiện tại đã
Approvedthì không được cập nhật đè.
authorPinCode/creatorPinCodeở endpoint/learning-resourcesphải là CCCD 12 số tồn tại trong hệ thống Sở theo đúng ngữ cảnh dữ liệu gửi lên.gradeCode/subjectCode/knowledgeUnitCodekhông dùng mã tự đặt; phải map từ danh mục dùng chung.sourceId,title,authorName,creatorName,contents[].namechỉ chấp nhận văn bản thuần, không chứa HTML.descriptionhọc liệu (nếu truyền) cho phép văn bản hoặc HTML an toàn, tối đa1024ký tự.- Khi tạo mới/cập nhật hợp lệ, hệ thống luôn gán trạng thái mặc định:
ApprovalStatus = Pending (0)vàModerationStatus = Normal. thumbnailUrllà bắt buộc và phải là URL tuyệt đối hợp lệ. Có thể dùng path ảnh đã upload hoặc URL ảnh ngoài đúng định dạng ảnh hỗ trợ.contentslà bắt buộc và phải có ít nhất1phần tử.SCORMphải là URL tới.zipđã upload, đúng MIMEapplication/zip, và đúng chuẩn SCORM hỗ trợ.YOUTUBEphải là liên kết YouTube tuyệt đối hợp lệ (không truyền iframe HTML).fileIdsnếu truyền thì tất cả phải thuộc partner và phải được dùng thực tế trongthumbnailUrlhoặccontents.- URL trong
TEXTHTML (src/href/poster) bắt buộc là URL tuyệt đối hợp lệ (http/https). - HTML không được chứa nội dung không an toàn (CSS/JS/iframe/object/embed/on*/protocol cấm).
semesterCodechỉ nhận1hoặc2;yearCodephải là năm 4 chữ số hợp lệ.- API danh sách nhà trường bắt buộc
doetCode; cả 2 API danh sách đều yêu cầutaketrong khoảng1..200vàskip >= 0.
10. Các lỗi thường gặp và cách xử lý
10.1. Theo HTTP status
| HTTP status | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
401 Unauthorized | Thiếu/sai X-Api-Key | Kiểm tra key và header |
400 Bad Request | Sai format dữ liệu, ULID, mã danh mục, rule mapping nội dung/file | Đối chiếu payload theo checklist và rule validate |
404 Not Found | Không tìm thấy attachmentId | Kiểm tra lại id file và ngữ cảnh partner |
200 OK (API xóa lô) | Có thể thành công một phần | Luôn đọc data[].deleted và data[].message |
10.2. Theo message nghiệp vụ thường gặp
| Message lỗi | Nguyên nhân | Cách xử lý |
|---|---|---|
Mã sở giáo dục là bắt buộc. | Gọi API danh sách nhà trường nhưng thiếu doetCode | Bổ sung doetCode khi gọi GET /learning-resources, hoặc dùng endpoint tổ chức nếu đang ở scope tổ chức |
Không tìm thấy file cho các FileId: ... | fileIds không tồn tại hoặc sai id | Kiểm tra lại id upload trả về |
FileId ... không thuộc đối tác gọi API. | Dùng file của partner khác | Chỉ dùng attachment thuộc cùng X-Api-Key |
FileId ... đã được dùng bởi đối tượng khác. | File đã bị gán owner khác | Upload file mới hoặc dùng đúng file đang thuộc học liệu cần thao tác |
Các FileId đã upload nhưng chưa được replace vào nội dung: ... | Truyền fileIds nhưng chưa dùng trong thumbnailUrl/contents | Replace đúng URL trước khi gọi tạo học liệu |
Đường dẫn ảnh đại diện là bắt buộc. | Thiếu thumbnailUrl | Bổ sung thumbnailUrl hợp lệ trước khi gọi API |
Đường dẫn ảnh đại diện phải là URL tuyệt đối. | thumbnailUrl không phải URL tuyệt đối | Chuẩn hóa thành URL tuyệt đối trước khi gửi |
Đường dẫn ảnh đại diện phải là URL tuyệt đối hợp lệ. | thumbnailUrl không phải URL tuyệt đối http/https | Chuẩn hóa thành URL tuyệt đối hợp lệ |
Đường dẫn ảnh đại diện (URL ngoài) phải có định dạng ảnh hợp lệ (.jpg, .jpeg, .png, .gif, .bmp, .webp). | thumbnailUrl là URL ngoài nhưng không đúng định dạng ảnh hỗ trợ | Dùng URL ảnh ngoài đúng định dạng hỗ trợ hoặc dùng ảnh đã upload lên trục |
Mã học liệu nguồn chỉ chấp nhận văn bản thuần, không chứa HTML. | Truyền HTML vào sourceId | Chỉ gửi văn bản thuần cho mã nguồn học liệu |
Tên học liệu chỉ chấp nhận văn bản thuần, không chứa HTML. | Truyền HTML vào title | Chỉ gửi văn bản thuần cho trường metadata |
Tên tác giả chỉ chấp nhận văn bản thuần, không chứa HTML. | Truyền HTML vào authorName hoặc creatorName | Chỉ gửi văn bản thuần cho tên tác giả/người chia sẻ |
Tên nội dung học liệu chỉ chấp nhận văn bản thuần, không chứa HTML. | Truyền HTML vào contents[].name | Chỉ gửi văn bản thuần cho tên nội dung |
Học liệu phải có ít nhất 1 nội dung. | Mảng contents rỗng | Bổ sung ít nhất 1 phần tử nội dung hợp lệ |
SCORM content phải replace bằng path file package .zip đã upload lên trục. | SCORM không trỏ vào file .zip đã upload | Upload .zip đúng chuẩn rồi replace URL |
Content với type YOUTUBE chỉ chấp nhận dạng https://youtube.com/watch?v=... (bao gồm www.youtube.com/watch?v=...) hoặc https://youtu.be/... | YOUTUBE không đúng 1 trong 2 pattern hỗ trợ hoặc đang gửi iframe | Chỉ gửi URL tuyệt đối đúng pattern, không gửi HTML iframe |
Content type '...' không phù hợp định dạng file '...'. | type không khớp MIME file thực tế | Chọn type đúng với MIME đã upload |
Content không được chứa CSS/TypeScript/JavaScript, iframe hoặc mã không an toàn. | HTML vi phạm rule an toàn | Loại bỏ thành phần cấm trước khi gửi |
Học liệu SourceId '...' đã được duyệt. Không cho phép cập nhật, vui lòng tạo học liệu mới để chia sẻ. | Upsert gặp bản ghi cùng key đang ở trạng thái Approved | Dùng sourceId mới để tạo bản ghi chia sẻ mới |
Không tìm thấy học liệu để cập nhật. | Bản ghi upsert vừa đổi trạng thái hoặc không còn tồn tại tại thời điểm cập nhật | Gọi lại API với payload mới nhất |
Học liệu SourceId '...' đã được duyệt hoặc không còn ở trạng thái cho phép cập nhật. | Bản ghi không còn ở trạng thái Pending/Rejected khi xử lý cập nhật | Tạo bản ghi mới với sourceId khác |
Không thể tải tệp lên hệ thống. | File vi phạm validate extension/size/MIME hoặc lỗi upload storage | Kiểm tra extension, kích thước, MIME thực tế và thử upload lại |
11. FAQ cho partner
1) Có bắt buộc truyền fileIds không?
- Không bắt buộc.
- Nhưng nếu truyền thì tất cả id phải hợp lệ, thuộc partner, và được dùng thực tế trong
thumbnailUrlhoặccontents.
2) authorPinCode và creatorPinCode có thể giống nhau không?
- Có thể giống hoặc khác.
- Với endpoint
/learning-resources, cả hai phải là CCCD 12 số hợp lệ và tồn tại trong ngữ cảnh dữ liệu Sở. - Với endpoint
/learning-resources/organizations, có thể để trống để hệ thống tự điền mặc định theo thông tin tổ chức.
3) Có thể gửi TEXT với HTML không?
- Có.
- Nhưng URL trong
src/href/posterphải là URL tuyệt đối và nội dung phải qua rule an toàn HTML.
4) Có webhook/callback khi duyệt học liệu không?
- Giai đoạn hiện tại hệ thống chưa xây dựng webhook/callback duyệt.
- Cơ chế theo dõi đang là polling API danh sách theo scope:
- Nhà trường:
GET /learning-resources - Tổ chức:
GET /learning-resources/organizations
- Nhà trường:
- Nếu có roadmap webhook ở giai đoạn sau, nên phát hành spec riêng để partner triển khai tách biệt.
5) Khi nào được xóa học liệu?
- Chỉ xóa được học liệu ở trạng thái
Pending (0)và thuộc đúng partner.
6) Tôi bị lỗi trùng sourceId, xử lý thế nào?
- Nếu bản ghi cùng key đang
Approved, cần dùngsourceIdmới để chia sẻ bản ghi mới. - Nếu bản ghi cùng key đang
PendinghoặcRejected, API sẽ cập nhật bản ghi cũ và trảoperation = updated. - Khuyến nghị lưu mapping
sourceIdở hệ thống nguồn để kiểm soát luồng upsert.
11.1. Quy ước vận hành đã chốt
- Scope upsert của
sourceId:/learning-resources:schoolCode + sourceIdtrong phạm viX-Api-Key/learning-resources/organizations:partnerId + sourceId
- Cơ chế dọn file upload không được mapping: hệ thống trục có job tự động quét dọn.
- Trạng thái duyệt: theo dõi bằng polling API danh sách, chưa có webhook/callback trong giai đoạn hiện tại.
