Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là BA của một sàn thương mại điện tử ở TP.HCM. Một sáng, đội vận hành báo: "Khách bấm thanh toán mà đơn không tạo được". Đội backend nói API "vẫn chạy bình thường". Đội frontend nói "tại server trả lỗi". Cả hai cãi nhau cả buổi sáng. Cuối cùng bạn mở log lên và thấy một con số: 409. Chỉ một dòng đó thôi đã chấm dứt cuộc tranh cãi — server không "lỗi", nó đang nói rằng đơn hàng bị trùng (conflict). Vấn đề nằm ở chỗ frontend gửi request hai lần.
Status code (mã trạng thái HTTP) chính là "ngôn ngữ chung" giữa client và server. Mỗi response HTTP đều mang theo một con số ba chữ số cho biết: request đã thành công, bị từ chối, cần chuyển hướng, hay server đang gặp sự cố. Với một BA, đọc hiểu status code không phải là kỹ năng "của dev". Đó là kỹ năng giúp bạn:
- Viết được acceptance criteria chính xác ("khi tạo user trùng email, API phải trả về
409, không phải500"). - Phân định trách nhiệm khi sự cố xảy ra — lỗi ở client hay ở server.
- Thiết kế luồng xử lý lỗi (error handling) cho sản phẩm thay vì để màn hình trắng xóa.
- Giao tiếp với dev, QA, và đối tác tích hợp bằng cùng một thứ ngôn ngữ.
Khái niệm cốt lõi
Status code luôn là một số nguyên gồm ba chữ số, nằm ở dòng đầu tiên của HTTP response (gọi là status line), ví dụ HTTP/1.1 200 OK. Chữ số đầu tiên quyết định "loại" của response, nên chỉ cần nhìn chữ số đầu là bạn đã biết chuyện gì đang xảy ra ở mức cao.
| Nhóm | Ý nghĩa | "Ai chịu trách nhiệm" |
|---|---|---|
| 1xx | Thông tin (informational) — request đã nhận, đang xử lý tiếp | — |
| 2xx | Thành công (success) | Mọi thứ ổn |
| 3xx | Chuyển hướng (redirection) — cần thêm bước nữa | Client cần đi tiếp |
| 4xx | Lỗi phía client | Client gửi sai |
| 5xx | Lỗi phía server | Server xử lý hỏng |
1xx — Informational (hiếm gặp trong REST)
Nhóm 1xx báo rằng server đã nhận phần đầu của request và client có thể tiếp tục. Là BA làm việc với REST API, bạn gần như không phải xử lý nhóm này trực tiếp, nhưng nên biết hai mã:
100 Continue: client gửi request lớn (ví dụ upload file vài chục MB) kèm headerExpect: 100-continueđể "hỏi trước" xem server có chấp nhận không, tránh gửi nguyên khối dữ liệu rồi mới bị từ chối.101 Switching Protocols: server đồng ý đổi giao thức. Thường gặp khi nâng cấp kết nối HTTP lên WebSocket (chúng ta sẽ học sâu về WebSocket ở bài sau).
2xx — Success
Đây là nhóm "tin vui". Request đã được tiếp nhận, hiểu và xử lý thành công. Nhưng "thành công" cũng có nhiều sắc thái, và một spec API tốt phải chọn đúng mã:
200 OK: thành công chung chung. Phổ biến nhất choGET(lấy dữ liệu) và cảPUT/PATCHkhi cập nhật xong và trả về dữ liệu mới.201 Created: đã tạo mới một resource thành công. Đây là mã đúng choPOSTtạo đơn hàng, tạo user, tạo bài viết. Response thường kèm headerLocationchỉ tới URL của resource vừa tạo.202 Accepted: server đã nhận request nhưng chưa xử lý xong — sẽ xử lý bất đồng bộ (async). Ví dụ: bạn gửi yêu cầu xuất báo cáo 1 triệu dòng, server trả202và xử lý nền, kết quả gửi sau qua email hoặc webhook.204 No Content: thành công nhưng không có dữ liệu trả về. Phù hợp choDELETE(xóa xong, không cần trả gì) hoặcPUTcập nhật mà không cần echo lại.
200 và 201 nghe có vẻ nhỏ nhặt, nhưng nó ảnh hưởng tới cách frontend và đối tác tích hợp xử lý. Một BA viết spec rõ "POST /orders trả về 201 kèm Location" sẽ tránh được rất nhiều hiểu lầm về sau.3xx — Redirection
Nhóm 3xx nói: "Cái bạn cần không nằm ở đây, hãy đi chỗ khác". Trong REST API thuần, 3xx ít gặp hơn so với web thường, nhưng vẫn quan trọng:
301 Moved Permanently: resource đã chuyển vĩnh viễn sang URL mới. Hữu ích khi bạn đổi domain hoặc đổi cấu trúc URL API và muốn client cập nhật.302 Found(tạm thời): chuyển hướng tạm. Hay gặp trong luồng đăng nhập, OAuth — sau khi xác thực, server đẩy người dùng về một URL khác.304 Not Modified: dữ liệu không đổi kể từ lần lấy trước, client cứ dùng bản đã cache. Đây là mã "tiết kiệm băng thông" — server không gửi lại body, giúp ứng dụng nhanh hơn. Liên quan tới caching mà chúng ta sẽ học ở các bài về Cache-Control.
4xx — Client Error
Đây là nhóm BA cần nắm chắc nhất, vì phần lớn acceptance criteria và luồng xử lý lỗi xoay quanh nó. 4xx nghĩa là request có vấn đề — client gửi sai gì đó, và server từ chối.
400 Bad Request: request sai cú pháp hoặc dữ liệu không hợp lệ. Ví dụ: thiếu trường bắt buộc, sai định dạng email, JSON hỏng.401 Unauthorized: chưa xác thực — thiếu hoặc sai thông tin đăng nhập/token. Thực ra nên hiểu là "Unauthenticated" (chưa biết bạn là ai).403 Forbidden: đã xác thực, biết bạn là ai, nhưng bạn không có quyền làm việc này. Ví dụ: nhân viên thường cố truy cập trang quản trị.404 Not Found: không tìm thấy resource. Sản phẩm đã bị xóa, ID không tồn tại, URL gõ sai.409 Conflict: xung đột trạng thái. Tạo tài khoản trùng email, đặt đơn khi hết hàng, sửa bản ghi đã bị người khác sửa.422 Unprocessable Entity: cú pháp đúng nhưng ngữ nghĩa sai — server hiểu request nhưng không xử lý được vì vi phạm quy tắc nghiệp vụ. Ví dụ: ngày kết thúc nhỏ hơn ngày bắt đầu.429 Too Many Requests: gửi quá nhiều request trong thời gian ngắn — bị rate limit (sẽ học sâu ở bài rate limiting).
401 và 403 là câu hỏi phỏng vấn kinh điển: 401 = "bạn là ai?" (chưa đăng nhập), 403 = "tôi biết bạn là ai rồi, nhưng bạn không được phép". Phân biệt 400 và 422 cũng tinh tế: 400 là "tôi không đọc nổi request của bạn", còn 422 là "tôi đọc được nhưng nó vi phạm luật nghiệp vụ".5xx — Server Error
5xx nghĩa là client gửi đúng, nhưng server tự gặp vấn đề khi xử lý. Đây là lúc đội backend phải vào cuộc, không phải client.
500 Internal Server Error: lỗi chung chung không lường trước — thường là bug, exception chưa được bắt. Đây là mã "đáng sợ" nhất vì nó che giấu nguyên nhân thật.502 Bad Gateway: server đứng giữa (gateway/proxy) nhận phản hồi không hợp lệ từ server phía sau. Hay gặp khi API Gateway gọi tới một microservice đang chết.503 Service Unavailable: server tạm thời quá tải hoặc đang bảo trì. Thường kèm headerRetry-Aftergợi ý lúc nào thử lại.504 Gateway Timeout: server phía sau xử lý quá lâu, gateway hết kiên nhẫn và ngắt.
Tình huống thực tế
Tình huống 1: Cuộc tranh cãi "lỗi tại ai" ở sàn TMĐT
Bối cảnh: Một sàn thương mại điện tử giả định tên ShopViet, dịp sale 12/12, ghi nhận 8% đơn thanh toán thất bại. Đội frontend đổ tại backend; backend khẳng định "code chạy ổn". Là BA, bạn yêu cầu kéo log status code của API POST /payments trong khung giờ cao điểm và phân loại theo nhóm.
Diễn giải: Kết quả cho thấy trong số đơn lỗi, có 60% là 409 Conflict (đơn trùng do người dùng bấm nút hai lần khi mạng chậm), 30% là 503 Service Unavailable (server thanh toán quá tải), và 10% là 400 Bad Request (frontend gửi thiếu mã giảm giá). Con số này lập tức phân chia trách nhiệm: 409 là vấn đề thiết kế (cần idempotency key — chống bấm trùng), 503 là vấn đề hạ tầng (cần scale server), 400 là bug frontend.
Bài học: Nếu không có status code phân loại rõ ràng, cuộc tranh cãi sẽ kéo dài vô tận. Chỉ cần nhóm log theo mã, bạn biến một vấn đề mơ hồ thành ba việc cụ thể giao cho ba đội khác nhau. Đây chính là giá trị của một BA biết đọc status code.
Tình huống 2: Đối tác ngân hàng và mã 401 vs 403
Bối cảnh: Một fintech tại Hà Nội tích hợp API của một ngân hàng để truy vấn số dư. Trong môi trường test, mọi request đều trả về 401 Unauthorized dù đội dev khẳng định đã gắn token. Đối tác ngân hàng nói "phía bạn sai token". Là BA giữ vai trò cầu nối, bạn yêu cầu chụp lại nguyên response cả header lẫn body.
Diễn giải: Response body ghi rõ "error": "token_expired". Hóa ra token có hiệu lực 15 phút và đội dev đã hardcode một token cũ từ hôm trước. Nhưng tình huống còn một lớp nữa: sau khi cấp token mới, một số API khác lại trả 403 Forbidden. Lần này không phải token sai — token đúng, nhưng tài khoản test chưa được ngân hàng cấp quyền (scope) truy cập API "lịch sử giao dịch". Hai mã 401 và 403 chỉ ra hai vấn đề hoàn toàn khác nhau: một bên là xác thực, một bên là phân quyền.
Bài học: Khi làm việc với đối tác, hãy luôn yêu cầu cả status code lẫn error body. 401 đẩy bạn đi kiểm tra token; 403 đẩy bạn đi kiểm tra quyền/scope. Nhầm lẫn hai mã này khiến bạn debug sai hướng hàng giờ đồng hồ.
Tình huống 3: Báo cáo nặng và mã 202
Bối cảnh: Một công ty logistics ở Đông Nam Á có tính năng "xuất báo cáo vận đơn theo tháng", mỗi báo cáo có thể tới 500.000 dòng. Ban đầu API POST /reports xử lý đồng bộ và thường xuyên trả 504 Gateway Timeout vì mất hơn 60 giây để tạo file. Người dùng tưởng hệ thống hỏng và bấm xuất lại nhiều lần, càng làm server quá tải.
Diễn giải: BA đề xuất đổi sang luồng bất đồng bộ. Giờ đây API nhận yêu cầu và trả ngay 202 Accepted kèm một report_id, đồng thời báo "báo cáo đang được tạo, bạn sẽ nhận thông báo khi xong". Server xử lý nền và gửi link tải qua email khi hoàn tất. Tỷ lệ timeout giảm về gần 0, và trải nghiệm người dùng tốt hơn hẳn vì họ không còn phải ngồi chờ màn hình treo.
Bài học: Chọn đúng status code không chỉ là chuyện kỹ thuật — nó định hình cả luồng nghiệp vụ và trải nghiệm. 202 mở ra mô hình xử lý bất đồng bộ, biến một thao tác "treo máy" thành một thao tác "nhận việc và làm sau".
Hướng dẫn từng bước
Đây là quy trình để một BA đọc và áp dụng status code khi điều tra sự cố hoặc viết spec:
- Nhìn chữ số đầu tiên trước. Đừng vội tra cứu mã chi tiết. Chỉ cần biết 2xx (ổn), 3xx (chuyển hướng), 4xx (lỗi client), 5xx (lỗi server). Riêng việc này đã định hướng được 80% việc điều tra.
- Phân định trách nhiệm. Nếu là 4xx, vấn đề ở phía client (frontend, app, đối tác gọi API) — họ gửi sai. Nếu là 5xx, vấn đề ở phía server/backend — code hoặc hạ tầng hỏng. Giao việc cho đúng đội ngay từ bước này.
- Đọc kèm response body, không chỉ con số. Status code cho biết "loại lỗi", còn body mới cho biết "lỗi cụ thể gì". Ví dụ
400kèm body"field": "email", "message": "không hợp lệ"mới đủ để hành động.
- Kiểm tra các header liên quan.
401thường đi vớiWWW-Authenticate;429và503thường kèmRetry-After;201kèmLocation. Header bổ sung ngữ cảnh quan trọng.
- Đối chiếu với spec. Hỏi: API này lẽ ra phải trả mã gì trong tình huống này? Nếu spec nói "email trùng trả 409" mà thực tế trả 500, bạn vừa tìm ra một bug.
- Khi viết spec mới, với mỗi endpoint hãy liệt kê một bảng "tình huống → status code → ý nghĩa body". Ví dụ cho
POST /users: tạo thành công →201; email trùng →409; thiếu trường →400; chưa đăng nhập →401. Bảng này là tài liệu vàng cho cả dev lẫn QA.
Lỗi thường gặp & mẹo
Lỗi 1: Dùng 200 cho mọi thứ, kể cả khi lỗi. Một số hệ thống cũ luôn trả 200 OK rồi nhét lỗi vào body ({"success": false, "error": "..."}). Điều này khiến client, công cụ giám sát và retry logic không thể tự động nhận biết lỗi. Hãy yêu cầu dùng đúng status code theo chuẩn HTTP.
Lỗi 2: Trả 500 cho lỗi nghiệp vụ. "Hết hàng", "vượt hạn mức", "sai định dạng" là lỗi do dữ liệu/nghiệp vụ — phải là 4xx. 500 chỉ dành cho lỗi ngoài dự kiến của server. Nhầm lẫn này làm hệ thống giám sát báo động giả liên tục.
Lỗi 3: Nhầm 401 với 403. Nhớ: 401 = chưa biết bạn là ai (xác thực), 403 = biết rồi nhưng không cho phép (phân quyền).
Lỗi 4: Dùng 404 thay cho 403 một cách vô tình — hoặc cố ý. Đôi khi vì lý do bảo mật, hệ thống trả 404 thay vì 403 để không lộ rằng resource có tồn tại. Đây là lựa chọn có chủ đích, BA nên ghi rõ trong spec để tránh hiểu nhầm.
Mẹo 1: Khi viết acceptance criteria, luôn nêu rõ status code mong đợi: "Given email đã tồn tại, When gọi POST /users, Then trả về 409 với body chứa mã lỗi EMAIL_EXISTS". Cụ thể như vậy giúp QA test chính xác và dev không tự suy diễn.
Mẹo 2: 429 và 503 thường kèm header Retry-After — đây là tín hiệu để client biết khi nào thử lại. Đảm bảo luồng xử lý của sản phẩm tôn trọng header này thay vì retry dồn dập làm server tệ hơn.
Mẹo 3: Khi gặp 502 hoặc 504, đừng vội đổ cho ứng dụng — thường gốc rễ nằm ở tầng gateway, proxy hoặc một service phụ thuộc phía sau. Đây là gợi ý quan trọng cho đội hạ tầng.
Bài tập thực hành
- Phân nhóm nhanh: Với mỗi tình huống sau, hãy ghi status code phù hợp nhất: (a) người dùng tạo bài viết thành công; (b) gọi API mà chưa gắn token; (c) xóa sản phẩm xong và không cần trả dữ liệu; (d) database của server bị sập; (e) đặt mua sản phẩm đã hết hàng.
- Viết acceptance criteria: Cho endpoint
POST /coupons/redeem(đổi mã giảm giá). Hãy liệt kê ít nhất 4 tình huống và status code tương ứng (gợi ý: đổi thành công, mã không tồn tại, mã đã hết hạn, chưa đăng nhập).
- Điều tra sự cố: Đội QA báo API
GET /orders/{id}trả về403cho một số đơn. Hãy viết 3 câu hỏi bạn sẽ đặt ra để tìm nguyên nhân, và giải thích vì sao403(chứ không phải404hay401) lại định hướng cách điều tra của bạn.
- Phát hiện anti-pattern: Một API trả về
200 OKvới body{"status": "error", "message": "Số dư không đủ"}. Hãy chỉ ra vấn đề và đề xuất status code đúng nên dùng.
Tóm tắt
- Status code là con số ba chữ số ở đầu mỗi HTTP response, là "ngôn ngữ chung" giữa client và server. Chữ số đầu quyết định loại: 1xx (thông tin), 2xx (thành công), 3xx (chuyển hướng), 4xx (lỗi client), 5xx (lỗi server).
- Nguyên tắc cốt lõi: 4xx là lỗi phía client, 5xx là lỗi phía server. Đây là công cụ phân định trách nhiệm mạnh nhất khi điều tra sự cố.
- Các mã 2xx quan trọng:
200 OK,201 Created(tạo mới),202 Accepted(xử lý bất đồng bộ),204 No Content(xóa xong). - Các mã 4xx một BA phải nắm chắc:
400(request sai),401(chưa xác thực),403(không có quyền),404(không tìm thấy),409(xung đột),422(vi phạm quy tắc nghiệp vụ),429(quá nhiều request). - Các mã 5xx:
500(lỗi server không lường trước),502/503/504(gateway, quá tải, timeout). Tuyệt đối không dùng 5xx cho lỗi nghiệp vụ. - Luôn đọc status code kèm response body và các header liên quan (
Location,Retry-After,WWW-Authenticate) để có bức tranh đầy đủ. - Khi viết spec, lập bảng "tình huống → status code → ý nghĩa" cho mỗi endpoint. Đó là tài liệu giúp dev, QA và đối tác cùng hiểu đúng một thứ.