---
url: https://docs.nemo12.com/reference/errors.md
description: >-
  Error Catalog: hợp đồng phản hồi API { data } / { error: { code, message } }
  và bảng mã lỗi client dùng để rẽ nhánh.
---

# Error Catalog

## Hợp đồng phản hồi

```jsonc
// thành công
{ "data": … }

// lỗi
{ "error": { "code": "VALIDATION_ERROR", "message": "…" } }
```

Client **luôn** đọc `error.code` để rẽ nhánh, và hiển thị `error.message` cho người dùng. `message` viết bằng tiếng Việt, cho người đọc — không bao giờ nhét stack trace hay tên bảng vào đó.

## 15 mã lỗi

Định nghĩa duy nhất tại `workers/api/src/shared/errors.ts`. **Không thêm mã mới mà không sửa file này** — enum là hợp đồng.

| Code | HTTP | Nghĩa | Client nên làm |
| --- | --- | --- | --- |
| `VALIDATION_ERROR` | 400 | Đầu vào sai (zod) hoặc thiếu ngữ cảnh bắt buộc | Sửa dữ liệu, **đừng retry** |
| `AUTHORIZATION_ERROR` | 401 | Chưa đăng nhập, phiên hết hạn, Origin sai, thiếu role | Đăng nhập lại hoặc báo không có quyền |
| `NOT_FOUND` | 404 | Không có tài nguyên | Không retry |
| `LESSON_LOCKED` | 403 | Bài nằm ngoài phần học thử, learner chưa có quyền học khoá | Mời ghi danh, **đừng** bắt đăng nhập lại |
| `HOST_ONLY` | 403 | Người trong phòng nói nhưng không phải host bấm Record/Pause/Stop hoặc chuyển host (SRC-1166) | Ẩn nút điều khiển, **đừng** bắt đăng nhập lại |
| `SCOPE_REQUIRED` | 403 | Mentor đã xác nhận vai nhưng thiếu phạm vi cho việc này: phát hành nội dung AI Teen (SRC-1284; sửa bản nháp không cần phạm vi từ SRC-1291) | Hiện chế độ chỉ đọc, **đừng** bắt đăng nhập lại |
| `NOT_LEARNER` | 403 | Phụ huynh, mentor hay staff đọc được learner nhưng ghi thứ chỉ chính learner được ghi: tick bước AI Teen (SRC-1283) | Ẩn ô tick, **đừng** bắt đăng nhập lại |
| `UNKNOWN_STEP` | 400 | Id bước không có trong bản kê nội dung (gõ sai, bước đã nghỉ) (SRC-1283) | Tải lại nội dung, **đừng retry** |
| `STEP_NOT_TICKABLE` | 422 | Bước có thật nhưng máy chấm: phần đọc có cổng, câu kiểm (SRC-1283) | Không cho tick tay; bước tự xong khi qua cổng / đúng câu |
| `CONFLICT` | 409 | Xung đột trạng thái (trùng, đã xử lý) | Đọc lại rồi thử lại |
| `STALE_DRAFT` | 409 | Lưu bản nháp với `rev` cũ: người khác vừa lưu trước (SDD-056 §5) | Tải lại bản mới rồi sửa tiếp |
| `RATE_LIMITED` | 429 | Chạm hạn mức | Đợi theo `Retry-After` rồi thử lại |
| `TEMPORARY_FAILURE` | 503 | Lỗi tạm của chính ta | **Retry có backoff** |
| `DEPENDENCY_FAILURE` | 503 | Phụ thuộc ngoài hỏng (AI Gateway, dịch vụ ngoài) | Retry hoặc suy giảm dịch vụ |
| `INTERNAL_ERROR` | 500 | Lỗi không lường trước | Báo lỗi, không retry mù |

## Quy ước

**401 chứ không 403.** Cả "chưa đăng nhập" lẫn "đăng nhập rồi nhưng không có quyền" đều trả 401 `AUTHORIZATION_ERROR`. Có chủ ý: 403 tiết lộ rằng tài nguyên **tồn tại** và người này chỉ thiếu quyền — với dữ liệu trẻ em, đó đã là rò rỉ thông tin.

**Ngoại lệ thứ hai: `HOST_ONLY` trả 403** (30.09.2026, SRC-1166): chỉ trả SAU khi đã xác nhận người gọi ở trong phòng, nên không lộ gì họ chưa thấy trên màn hình.

**Ngoại lệ thứ ba: `SCOPE_REQUIRED` trả 403** (07.10.2026, SRC-1284): chỉ trả SAU khi đã xác nhận người gọi là mentor, về nội dung AI Teen vốn công khai, nên không lộ gì; trả 401 thì Dolphin hiểu là phiên hết hạn và đẩy một mentor được quyền xem đi đăng nhập lại.

**Ngoại lệ thứ tư: `NOT_LEARNER` trả 403** (07.10.2026, SRC-1283): chỉ trả SAU khi `canAccessLearner` đã cho người gọi ĐỌC learner ấy, nên không lộ gì họ chưa thấy; trả 401 thì phụ huynh bị bảo đăng nhập lại mà vẫn không tick được.

**Ngoại lệ đầu tiên: `LESSON_LOCKED` trả 403** (2026-09-05). Lý do của luật trên không áp được ở đây — sự tồn tại của mọi bài học đã công khai tại `/v1/public/ielts/catalog` cho ielts.nemo12.com, nên 403 không nói thêm điều gì. Còn trả 401 thì gây hại thật: client hiểu là phiên hết hạn và bảo learner đăng nhập lại, làm lại, rồi vẫn không mở được bài.

**503 tách làm hai.** `TEMPORARY_FAILURE` là lỗi của ta, `DEPENDENCY_FAILURE` là lỗi bên ngoài. Phân biệt được thì dashboard mới trả lời được câu "ai đang hỏng".

**Suy giảm ≠ lỗi.** Có những thứ hỏng mà **không** trả lỗi, vì nghiệp vụ chính vẫn đúng:

| Sự cố | Hành vi | Log |
| --- | --- | --- |
| Model update hỏng sau khi nộp bài | Học sinh vẫn nộp được | `model_update_degraded` |
| Ghi run log hỏng | Nghiệp vụ chạy tiếp | `RUNLOG_DEGRADED` |
| Publish event hỏng | Nghiệp vụ chạy tiếp | `EVENT_PUBLISH_FAILED` |

Ba dòng log này là tín hiệu quan sát chính của hệ thống — thấy chúng tăng nghĩa là có thứ đang hỏng dù người dùng chưa kêu.

## Tương thích ngược

Trong `/v1`: **không** bỏ mã lỗi, **không** đổi HTTP status của mã đã có, **không** đổi ý nghĩa. Thêm mã mới là thay đổi tương thích; đổi mã cũ thì phải lên `/v2` (QG-003).

## Trace

* REQ-NFR-09 (error taxonomy), REQ-PLT-04 (OpenAPI sinh từ router).
* Thiết kế: [SDD-006](../architecture/sdd-006-reliability.md) §11.
* Kiểm chứng: QG-003, QG-009.
