Tìm kiếm tài liệu...
Mẹo tìm kiếm
Hỏi bằng tiếng Việt. AI sẽ tìm đúng nội dung trong docs cho bạn.

Hướng dẫn tích hợp API Chia sẻ học liệu

Tài liệu tích hợp Partner API cho Attachments và Learning Resources trên Trục LMS Hà Nội.


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ợpTài liệu nên đọc trướcEndpoint học liệuGhi chú chính
Nhà trườngTích hợp cho Nhà trườngPOST /api/lms-base/learning-resourcesKiểm tra chặt authorPinCode/creatorPinCode theo ngữ cảnh dữ liệu Sở
Tổ chứcTích hợp cho Tổ chứcPOST /api/lms-base/learning-resources/organizationsUpsert 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:

  1. Upload file qua POST /api/lms-base/attachments.
  2. Nhận idpath cho từng file.
  3. Replace URL trong HTML (src/href/poster) bằng path đã upload.
  4. Gọi POST /api/lms-base/learning-resources với fileIds khớp đúng các file đang dùng.
  5. 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

Lưu ý quan trọng:

  • POST /learning-resources đang xử lý theo cơ chế upsert theo cặp schoolCode + sourceId trong phạm vi partner gọi API (X-Api-Key): nếu đã có bản ghi Pending/Rejected thì cập nhật, nếu chưa có thì tạo mới, nếu đã Approved thì bị từ chối.
  • Nếu chia sẻ theo scope tổ chức, dùng POST /learning-resources/organizations; cơ chế upsert theo cặp partnerId + sourceId.
  • authorPinCodecreatorPinCode với endpoint POST /learning-resources phải là CCCD 12 số, tồn tại đúng ngữ cảnh dữ liệu Sở (doetCode/divisionCode/schoolCode/yearCode).
  • gradeCode/subjectCode/knowledgeUnitCode phả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ả fileIds bắt buộc phải thực sự xuất hiện trong thumbnailUrl hoặc contents.
  • 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:

  1. Quản lý file dùng trong học liệu (attachments).
  2. 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/
  • Auth header: X-Api-Key: <api-key-don-vi-duoc-cap>
  • JSON API: Content-Type: application/json
  • Upload file: multipart/form-data cho /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áchSố điện thoạiEmail
QIGNguyễn Thành Luân0988933774thanhluan@qig.vn

Format response chung:

{
  "success": true,
  "message": "Mô tả kết quả",
  "data": {}
}
json

2. Luồng tích hợp end-to-end

  1. Chuẩn bị metadata học liệu (sourceId, mã phân loại, thông tin tác giả/người gửi).
  2. 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 .zip nếu có). Với YOUTUBE hoặc thumbnailUrl dùng URL ngoài hợp lệ, không cần upload file tương ứng.
  3. Lưu lại id + path từ response upload.
  4. Replace URL trong contents[].content bằng path tương ứng; thumbnailUrl có thể dùng URL ảnh đã upload hoặc URL ảnh ngoài hợp lệ.
  5. Tạo payload học liệu với fileIds khớp đúng file đang dùng.
  6. 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)
  7. 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
  8. 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.
  9. Nếu cần, kiểm tra trạng thái từng file bằng GET /api/lms-base/attachments/{attachmentId}.
  10. 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-Key hợp lệ.
  • Đã map đúng mã dùng chung của Sở (gradeCode, subjectCode, knowledgeUnitCode) từ danh mục knowledge-units.
  • Với endpoint /learning-resources, đã xác thực authorPinCode/creatorPinCode đúng người, đúng ngữ cảnh (doetCode/divisionCode/schoolCode/yearCode).
  • Đã chọn đúng endpoint theo scope chia sẻ:
    • POST /learning-resources: upsert theo schoolCode + sourceId trong phạm vi partner gọi API (X-Api-Key).
    • POST /learning-resources/organizations: upsert theo partnerId + sourceId.
    • Poll duyệt/xóa pending cũng theo đúng scope (/learning-resources hoặc /learning-resources/organizations).
  • Nếu sourceId đã có bản ghi Approved, phải dùng sourceId mớ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 > 0<= 104857600 byte (100 MB).
  • Tất cả URL trong thumbnailUrlcontents là 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 trong thumbnailUrl hoặc contents.
  • 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>
html

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"'
bash
{
  "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"
  }
}
json

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>
html

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
}
json

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>'
bash

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"
  }
}
json

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ái Pending hoặc Rejected.

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'
bash

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"
    }
  ]
}
json

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'
bash

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:

FieldBắt buộcKiểu dữ liệuKích thước/giới hạnMô tả
doetCodestringTối đa 50 ký tựMã Sở GDĐT
divisionCodeKhôngstring / nullTối đa 50 ký tựMã phường/xã
schoolCodeCó (scope trường) / Không (scope tổ chức)string / nullTố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 "".
educationLevelCodestringTối đa 50 ký tựMã cấp học
fileIFormFile (binary)> 0<= 104857600 byteFile/media cần upload

Ràng buộc kỹ thuật:

  • Kích thước file: > 0<= 104857600 byte (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; divisionCode nế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 tra doetCode, không áp ngữ cảnh trường cho schoolCode/divisionCode.
  • File upload phải được mapping dùng trong thumbnailUrl hoặc contents; 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"
  }
}
json

Ý 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 theo schoolCode + sourceId trong phạm vi partner gọi API (X-Api-Key).
  • Scope tổ chức (/learning-resources/organizations): xác định bản ghi theo partnerId + sourceId.
  • Nếu đã có bản ghi Approved, API từ chối cập nhật và yêu cầu sourceId mới.

6.2. Các trường request

Quy ước đọc cột Bắt buộc:

  • : bắt buộc cho cả 2 endpoint.
  • Có (scope trường): bắt buộc với POST /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.
FieldBắt buộcKiểu dữ liệuKích thước/giới hạnGhi chú
sourceIdstringTố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
titlestringTố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
descriptionKhôngstring / nullTố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)
thumbnailUrlstringTối đa 1024 ký tự; URL tuyệt đốiBắt buộc. Có thể là path ảnh đã upload hoặc URL ảnh ngoài hợp lệ (.jpg, .jpeg, .png, .gif, .bmp, .webp)
authorPinCodeCó (scope trường)stringTố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
authorNameCó (scope trường)stringTố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
creatorPinCodeCó (scope trường)stringTố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
creatorNameCó (scope trường)stringTố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
fileIdsKhôngarray<string>Mỗi phần tử ULID 26 ký tựDanh sách attachment dùng cho thumbnailUrlcontents; nếu truyền thì tất cả phải hợp lệ và thuộc đối tác
contentsarray<object>Ít nhất 1 phần tửDanh sách nội dung học liệu
gradeCodestringTối đa 20 ký tựMã khối theo danh mục định danh của Sở
subjectCodestringTối đa 20 ký tựMã môn học theo danh mục định danh của Sở
knowledgeUnitCodestringTối đa 10 ký tựMã đơn vị kiến thức theo danh mục định danh của Sở
doetCodeCó (scope trường)stringTố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
divisionCodeKhôngstring / nullTố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)
schoolCodeCó (scope trường)stringTố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)
yearCodeintTừ 1000 đến 9999Năm học dạng 4 chữ số
educationLevelCodestringTối đa 20 ký tựMã cấp học
semesterCodeintChỉ nhận 1 hoặc 2Họ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ườngYêu cầu mappingNguồn tra cứu
gradeCodeMap đúng mã khối thuộc educationLevelCodehttps://docs.vstudy.edu.vn/docs/knowledge-units
subjectCodeMap đúng mã môn học thuộc gradeCodehttps://docs.vstudy.edu.vn/docs/knowledge-units
knowledgeUnitCodeMap đúng mã đơn vị kiến thức thuộc cặp gradeCode/subjectCodehttps://docs.vstudy.edu.vn/docs/knowledge-units

6.4. Cấu trúc contents[]

FieldBắt buộcKiểu dữ liệuKích thước/giới hạnGhi chú
namestringTố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
displayOrderKhôngint / null>= 1; không truyền thì tự gánNếu không truyền, hệ thống tự gán theo thứ tự mảng (bắt đầu từ 1)
typestring (enum)Tối đa 20 ký tựTEXT, DOCUMENT, SPREADSHEET, VIDEO, AUDIO, YOUTUBE, SCORM, SLIDE
contentstringKhông giới hạn độ dài (nvarchar(max))Nội dung theo rule từng type
descriptionKhôngstring / nullTố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:

typeRule cho content
TEXTVă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ệ
DOCUMENTURL 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
SPREADSHEETURL 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
VIDEOURL tuyệt đối; nếu map file upload thì MIME phải là video/mp4 hoặc video/webm
AUDIOURL tuyệt đối; nếu map file upload thì MIME phải là audio/mpeg
YOUTUBEChỉ 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
SCORMURL tuyệt đối trỏ tới file .zip đã upload; MIME phải là application/zip
SLIDEURL 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):

  • content khô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.2 hoặc 2004.
  • Nếu truyền fileIds, tất cả fileIds phải được dùng trong thumbnailUrl hoặc contents; 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 doetCode trong query.

Query params:

FieldKiểu dữ liệuGiới hạnGhi chú
sourceIdstring / nullTối đa 128 ký tựLọc theo mã học liệu nguồn
educationLevelCodestring / nullTối đa 20 ký tựTrim khoảng trắng đầu/cuối trước khi lọc
doetCodestringTối đa 20 ký tựBắt buộc, lọc theo mã Sở GDĐT
divisionCodestring / nullTối đa 20 ký tựLọc theo mã phường/xã
schoolCodestring / nullTối đa 50 ký tựLọc theo mã trường
approvalStatusint / null0, 1, 20=Pending, 1=Approved, 2=Rejected
skipint / null>= 0Mặc định 0
takeint / null1..200Mặ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'
bash

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"
}
json

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):

FieldKiểu dữ liệuGiới hạnGhi chú
sourceIdstring / nullTối đa 128 ký tựLọc theo mã học liệu nguồn
educationLevelCodestring / nullTối đa 20 ký tựLọc theo mã cấp học
approvalStatusint / null0, 1, 20=Pending, 1=Approved, 2=Rejected
skipint / null>= 0Mặc định 0
takeint / null1..200Mặ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'
bash

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
  • 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 learningResourceId riê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ó khi Rejected).
    • 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.

FieldKiểu dữ liệuBắt buộcRàng buộc
ids (request body)array<string>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/false theo 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[]:

FieldKiểu dữ liệuÝ nghĩa
sourceIdstringMã học liệu nguồn tương ứng bản ghi được xử lý
idstring (ULID)Mã ULID học liệu đã xử lý
deletedbooltrue: xóa thành công, false: không xóa được
messagestringLý do/diễn giải kết quả cho từng bản ghi
processedAtstring (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"
]
json

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"
    }
  ]
}
json

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 200 id/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 OK nhưng có bản ghi xóa thất bại; luôn kiểm tra data[].deleted.

9. Các rule validate dễ sai

  1. API tạo/chia sẻ học liệu chạy theo cơ chế upsert theo sourceId:
    • /learning-resources: key là schoolCode + sourceId trong 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 đã Approved thì không được cập nhật đè.
  2. authorPinCode/creatorPinCode ở endpoint /learning-resources phả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.
  3. gradeCode/subjectCode/knowledgeUnitCode không dùng mã tự đặt; phải map từ danh mục dùng chung.
  4. sourceId, title, authorName, creatorName, contents[].name chỉ chấp nhận văn bản thuần, không chứa HTML.
  5. description học liệu (nếu truyền) cho phép văn bản hoặc HTML an toàn, tối đa 1024 ký tự.
  6. 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)ModerationStatus = Normal.
  7. thumbnailUrl là 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ợ.
  8. contents là bắt buộc và phải có ít nhất 1 phần tử.
  9. SCORM phải là URL tới .zip đã upload, đúng MIME application/zip, và đúng chuẩn SCORM hỗ trợ.
  10. YOUTUBE phải là liên kết YouTube tuyệt đối hợp lệ (không truyền iframe HTML).
  11. fileIds nếu truyền thì tất cả phải thuộc partner và phải được dùng thực tế trong thumbnailUrl hoặc contents.
  12. URL trong TEXT HTML (src/href/poster) bắt buộc là URL tuyệt đối hợp lệ (http/https).
  13. HTML không được chứa nội dung không an toàn (CSS/JS/iframe/object/embed/on*/protocol cấm).
  14. semesterCode chỉ nhận 1 hoặc 2; yearCode phải là năm 4 chữ số hợp lệ.
  15. API danh sách nhà trường bắt buộc doetCode; cả 2 API danh sách đều yêu cầu take trong khoảng 1..200skip >= 0.

10. Các lỗi thường gặp và cách xử lý

10.1. Theo HTTP status

HTTP statusNguyên nhân thường gặpCách xử lý
401 UnauthorizedThiếu/sai X-Api-KeyKiểm tra key và header
400 Bad RequestSai 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 FoundKhông tìm thấy attachmentIdKiể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ầnLuôn đọc data[].deleteddata[].message

10.2. Theo message nghiệp vụ thường gặp

Message lỗiNguyên nhânCá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 doetCodeBổ 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 idKiể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ácChỉ 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ácUpload 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/contentsReplace đú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 thumbnailUrlBổ 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 đốiChuẩ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/httpsChuẩ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 sourceIdChỉ 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 titleChỉ 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 creatorNameChỉ 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[].nameChỉ 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ỗngBổ 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 đã uploadUpload .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 iframeChỉ 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ànLoạ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 ApprovedDù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ậtGọ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ậtTạ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 storageKiể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 thumbnailUrl hoặc contents.

2) authorPinCodecreatorPinCode 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/poster phả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
  • 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ùng sourceId mới để chia sẻ bản ghi mới.
  • Nếu bản ghi cùng key đang Pending hoặc Rejected, 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 + sourceId trong phạm vi X-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.