Thiết kế API công khai cho người xây dựng SaaS lần đầu: những điều cơ bản
Thiết kế API công khai thực dụng cho người xây dựng SaaS lần đầu: chọn versioning, phân trang, giới hạn tốc độ, tài liệu và một SDK nhỏ để phát hành nhanh.

Vấn đề thật sự: phát hành một API mà bạn có thể duy trì
API công khai không chỉ là một endpoint ứng dụng của bạn phơi ra. Đó là một cam kết với những người ngoài nhóm rằng hợp đồng sẽ tiếp tục hoạt động ngay cả khi bạn thay đổi sản phẩm.
Khó khăn không phải là viết v1. Khó là giữ nó ổn định trong khi bạn sửa bug, thêm tính năng, và học xem khách hàng thực sự cần gì.
Những lựa chọn ban đầu sẽ hiện ra sau này dưới dạng ticket hỗ trợ. Nếu phản hồi thay đổi mà không báo trước, nếu đặt tên không nhất quán, hoặc client không biết một request có thành công hay không, bạn tạo ra ma sát. Ma sát đó biến thành mất niềm tin, và khi mất niềm tin, người ta ngừng xây dựng trên nền tảng của bạn.
Tốc độ cũng quan trọng. Hầu hết người xây dựng SaaS lần đầu cần phát hành thứ gì đó hữu dụng nhanh, rồi cải thiện dần. Đổi lại là rõ ràng: càng phát hành nhanh mà không có quy tắc, bạn càng tốn thời gian undo các quyết định đó khi người dùng thật đến.
"Đủ tốt cho v1" thường có nghĩa là một tập nhỏ endpoints phản ánh hành động thực của người dùng, tên và cấu trúc phản hồi nhất quán, chiến lược thay đổi rõ ràng (ngay cả khi chỉ là v1), phân trang dự đoán được và giới hạn tốc độ hợp lý, cùng tài liệu cho thấy chính xác gửi gì và nhận lại điều gì.
Một ví dụ cụ thể: tưởng tượng một khách hàng xây integration tạo hóa đơn mỗi đêm. Nếu sau này bạn đổi tên một trường, thay đổi định dạng ngày, hoặc âm thầm bắt đầu trả partial results, công việc của họ sẽ lỗi lúc 2 giờ sáng. Họ sẽ đổ lỗi cho API của bạn, không phải code của họ.
Nếu bạn xây bằng công cụ chat như Koder.ai, sẽ rất hấp dẫn khi sinh nhiều endpoint nhanh. Điều đó ổn, nhưng giữ bề mặt public nhỏ. Bạn có thể giữ endpoint nội bộ private trong khi học xem phần nào nên trở thành hợp đồng lâu dài.
Bắt đầu với một bề mặt API nhỏ, rõ ràng
Thiết kế API công khai tốt bắt đầu bằng việc chọn một tập nhỏ danh từ (resources) khớp với cách khách hàng nói về sản phẩm của bạn. Giữ tên resource ổn định ngay cả khi database nội bộ thay đổi. Khi thêm tính năng, ưu tiên thêm trường hoặc endpoint mới hơn là đổi tên resource lõi.
Một tập bắt đầu thực tế cho nhiều sản phẩm SaaS là: users, organizations, projects, và events. Nếu bạn không thể giải thích một resource trong một câu, có lẽ nó chưa sẵn sàng để công khai.
Giữ việc sử dụng HTTP nhàm chán và dự đoán được:
- GET đọc dữ liệu (không có side effects)
- POST tạo thứ gì đó (hoặc bắt đầu một hành động)
- PATCH cập nhật vài trường
- DELETE xóa hoặc vô hiệu hóa
Auth không cần quá phức tạp ngày đầu. Nếu API của bạn chủ yếu server-to-server (khách gọi từ backend của họ), API keys thường đủ. Nếu khách cần hành động như người dùng cuối cá nhân, hoặc bạn kỳ vọng tích hợp bên thứ ba nơi người dùng cấp quyền, OAuth thường phù hợp hơn. Viết quyết định bằng ngôn ngữ rõ ràng: ai là caller, và họ được phép chạm vào dữ liệu của ai?
Đặt kỳ vọng sớm. Rõ ràng điều gì được hỗ trợ và điều gì là cố gắng tốt nhất. Ví dụ: các endpoint list là ổn định và tương thích ngược, nhưng bộ lọc tìm kiếm có thể mở rộng và không đảm bảo đầy đủ. Điều này giảm ticket hỗ trợ và giúp bạn tự do cải thiện.
Nếu bạn xây trên nền tảng vibe-coding như Koder.ai, hãy coi API như một sản phẩm hợp đồng: giữ hợp đồng nhỏ trước, rồi mở rộng dựa trên sử dụng thực, không phải đoán mò.
Versioning mà không tự bẫy mình
Versioning chủ yếu về kỳ vọng. Client muốn biết: integration của tôi có bị phá tuần sau không? Bạn muốn có không gian để cải thiện mà không sợ.
Versioning trong URL vs trong header
Versioning theo header có thể trông sạch, nhưng dễ bị ẩn trong logs, cache, và ảnh chụp màn hình hỗ trợ. Versioning trong URL thường là lựa chọn đơn giản nhất: /v1/.... Khi khách gửi request lỗi, bạn có thể thấy version ngay. Nó cũng giúp chạy v1 và v2 song song dễ dàng.
Thay đổi phá vỡ thực sự là gì?
Thay đổi là phá vỡ nếu một client cư xử đúng có thể ngừng hoạt động mà không cần thay đổi code. Ví dụ thường gặp:
- Đổi tên trường (ví dụ
customer_idthànhcustomerId) - Thay đổi kiểu trường (string thành number) hoặc ý nghĩa của nó
- Loại bỏ endpoint hoặc trường phản hồi mà client có thể dựa vào
- Siết validation (trước optional, giờ bắt buộc)
- Thay đổi yêu cầu auth hoặc quyền mặc định
Một thay đổi an toàn là thay đổi mà client cũ có thể bỏ qua. Thêm một trường tùy chọn thường an toàn. Ví dụ, thêm plan_name vào phản hồi GET /v1/subscriptions sẽ không phá vỡ client chỉ đọc status.
Một quy tắc thực tế: đừng xóa hoặc tái sử dụng trường trong cùng một major version. Thêm trường mới, giữ trường cũ, và chỉ retire khi bạn sẵn sàng deprecate toàn bộ version.
Chính sách deprecation bạn có thể theo
Giữ đơn giản: thông báo sớm, trả về message cảnh báo rõ trong phản hồi, và đặt ngày kết thúc. Với API đầu tiên, cửa sổ 90 ngày thường thực tế. Trong thời gian đó, giữ v1 hoạt động, xuất một ghi chú migration ngắn, và đảm bảo support có thể chỉ vào một câu: v1 hoạt động đến ngày này; đây là điều khác trong v2.
Nếu bạn xây trên nền tảng như Koder.ai, coi các version API như snapshots: phát hành cải tiến trong version mới, giữ cũ ổn định, và chỉ cắt khi đã cho khách hàng thời gian di chuyển.
Mẫu phân trang giúp dự đoán được
Phân trang là nơi giành được hoặc mất niềm tin. Nếu kết quả nhảy lung tung giữa các request, người ta ngừng tin API của bạn.
Dùng page/limit khi dataset nhỏ, query đơn giản, và người dùng thường muốn trang 3 của 20. Dùng cursor khi list có thể lớn, mục mới đến thường xuyên, hoặc người dùng có thể sort và filter nhiều. Cursor giữ thứ tự ổn định ngay cả khi bản ghi mới được thêm.
Một vài quy tắc giúp phân trang đáng tin:
- Luôn định nghĩa sort mặc định (ví dụ: created_at desc).
- Thêm tie-breaker (ví dụ: id) để thứ tự có thể xác định.
- Xem phân trang là một phần của hợp đồng: thay đổi sort sau này là breaking change.
- Trả về tối thiểu cần thiết để tiếp tục: items cộng next cursor (hoặc next page).
Tổng số (totals) khó xử lý. total_count có thể tốn kém trên bảng lớn, đặc biệt với filters. Nếu bạn có thể cung cấp rẻ, hãy bao gồm. Nếu không, bỏ nó hoặc làm tùy chọn qua query flag.
Dưới đây là các hình dạng request/response đơn giản.
// Page/limit
GET /v1/invoices?page=2&limit=25&sort=created_at_desc
{
"items": [{"id":"inv_1"},{"id":"inv_2"}],
"page": 2,
"limit": 25,
"total_count": 142
}
// Cursor-based
GET /v1/invoices?limit=25&cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0wOVQxMDozMDowMFoiLCJpZCI6Imludl8xMDAifQ==
{
"items": [{"id":"inv_101"},{"id":"inv_102"}],
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0wOVQxMDoyNTowMFoiLCJpZCI6Imludl8xMjUifQ=="
}
Giới hạn tốc độ và retry thân thiện
Giới hạn tốc độ không phải chỉ để nghiêm ngặt mà để giữ hệ thống trực tuyến. Chúng bảo vệ app khỏi traffic spikes, database khỏi các query tốn kém lặp lại quá thường, và ví của bạn khỏi hóa đơn hạ tầng bất ngờ. Giới hạn cũng là một hợp đồng: client biết sử dụng bình thường trông như thế nào.
Bắt đầu đơn giản và tinh chỉnh sau. Chọn mức bao phủ sử dụng điển hình với dư địa cho burst ngắn, rồi quan sát traffic thực. Nếu chưa có dữ liệu, mặc định an toàn là giới hạn theo API key như 60 requests/phút cộng một allowance burst nhỏ. Nếu một endpoint nặng hơn nhiều (như search hoặc exports), áp giới hạn chặt hơn hoặc quy tắc chi phí riêng thay vì phạt mọi request.
Khi bạn áp limit, hãy làm cho client dễ làm điều đúng. Trả về 429 Too Many Requests và bao gồm vài header tiêu chuẩn:
X-RateLimit-Limit: tối đa cho windowX-RateLimit-Remaining: còn bao nhiêuX-RateLimit-Reset: khi window reset (timestamp hoặc seconds)Retry-After: nên chờ bao lâu trước khi thử lại
Client nên coi 429 là điều bình thường, không phải lỗi phải chiến đấu. Mẫu retry lịch sự giữ cả hai bên hài lòng:
- Chờ
Retry-Afterkhi có - Nếu không, dùng exponential backoff (ví dụ 1s, 2s, 4s)
- Thêm chút randomness (jitter) để nhiều client không retry cùng lúc
- Giới hạn thời gian chờ (ví dụ 30–60s)
Ví dụ: nếu khách chạy sync hàng đêm gây tải nặng, job của họ có thể dàn requests trong một phút và tự chậm lại khi gặp 429 thay vì làm hỏng toàn bộ chạy.
Lỗi, status codes và ghi an toàn
Nếu lỗi API khó đọc, ticket hỗ trợ sẽ tăng nhanh. Chọn một hình dạng lỗi và dùng nó mọi nơi, kể cả 500s. Một chuẩn đơn giản là: code, message, details, và request_id để người dùng dán vào chat hỗ trợ.
Đây là một định dạng nhỏ, dễ đoán:
{
"error": {
"code": "validation_error",
"message": "Some fields are invalid.",
"details": {
"fields": [
{"name": "email", "issue": "must be a valid email"},
{"name": "plan", "issue": "must be one of: free, pro, business"}
]
},
"request_id": "req_01HT..."
}
}
Dùng HTTP status codes cùng cách mọi lần: 400 cho input sai, 401 khi thiếu hoặc auth không hợp lệ, 403 khi đã auth nhưng không được phép, 404 khi resource không tìm thấy, 409 cho conflicts (ví dụ duplicate unique hoặc state sai), 429 cho rate limits, và 500 cho lỗi server. Tính nhất quán thắng sự sáng tạo.
Làm cho lỗi validation dễ sửa. Gợi ý theo trường nên trỏ tới tên parameter mà docs dùng, không phải cột database nội bộ. Nếu có yêu cầu format (date, currency, enum), nói rõ những gì bạn chấp nhận và đưa ví dụ.
Retries là nơi nhiều API vô tình tạo dữ liệu trùng lặp. Với các POST quan trọng (payments, tạo invoice, gửi email), hỗ trợ idempotency keys để client retry an toàn.
- Chấp nhận header
Idempotency-Keytrên các endpoint POST được chọn. - Lưu key cùng kết quả trong một cửa sổ ngắn (ví dụ 24 giờ).
- Với key lặp, trả về cùng phản hồi như yêu cầu đầu.
- Nếu yêu cầu đầu còn đang chạy, trả về response rõ ràng yêu cầu thử lại thay vì tạo resource thứ hai.
Header đó ngăn nhiều edge case đau đầu khi mạng không ổn hoặc client timeout.
Tình huống ví dụ: một API billing SaaS nhỏ trong thực tế
Giả sử bạn chạy SaaS đơn giản với ba đối tượng chính: projects, users, và invoices. Một project có nhiều user, và mỗi project nhận hóa đơn hàng tháng. Client muốn sync invoices vào công cụ kế toán của họ và hiển thị billing cơ bản trong app của họ.
Một v1 sạch có thể trông như sau:
GET /v1/projects/{project_id}
GET /v1/projects/{project_id}/invoices
POST /v1/projects/{project_id}/invoices
Bây giờ xảy ra một breaking change. Trong v1, bạn lưu tiền là integer cents: amount_cents: 1299. Sau đó bạn cần multi-currency và decimals, nên muốn amount: "12.99" và currency: "USD". Nếu bạn ghi đè trường cũ, mọi integration hiện có sẽ vỡ. Versioning tránh hoảng loạn: giữ v1 ổn định, phát hành /v2/... với trường mới, và hỗ trợ cả hai cho đến khi client migrate.
Với listing invoices, dùng phân trang dự đoán. Ví dụ:
GET /v1/projects/p_123/invoices?limit=50&cursor=eyJpZCI6Imludl85OTkifQ==
200 OK
{
"data": [ {"id":"inv_1001"}, {"id":"inv_1000"} ],
"next_cursor": "eyJpZCI6Imludl8xMDAwIn0="
}
Một ngày khách import invoices trong vòng lặp và gặp giới hạn. Thay vì lỗi ngẫu nhiên, họ nhận được phản hồi rõ:
429 Too Many RequestsRetry-After: 20- body lỗi nhỏ như
{ "error": { "code": "rate_limited" } }
Bên họ, client có thể pause 20 giây, rồi tiếp tục từ cùng cursor mà không tải lại mọi thứ hoặc tạo invoice trùng.
Bước theo bước: kế hoạch phát hành đơn giản cho v1
Phát hành v1 tốt hơn khi bạn coi nó như một release sản phẩm nhỏ, không phải một đống endpoints. Mục tiêu đơn giản: mọi người có thể xây trên đó, và bạn có thể cải thiện mà không có bất ngờ.
Kế hoạch thực tế 1 tuần (dù đội rất nhỏ)
Bắt đầu bằng việc viết một trang giải thích API dành cho gì và không dành cho gì. Giữ bề mặt nhỏ đủ để bạn có thể giải thích bằng lời trong một phút.
Dùng trình tự này và đừng chuyển bước cho đến khi mỗi bước đủ tốt:
- Phác thảo spec một trang liệt kê core resources và 5–10 endpoint đầu bạn sẽ hỗ trợ. Bao gồm base URL pattern, phương thức auth, và header cần thiết.
- Với mỗi endpoint, viết một request thực tế và một response thực tế. Dùng tên trường bạn sẽ phát hành. Các ví dụ này trở thành docs đầu tiên và tests đầu tiên.
- Thêm phần rules ngắn: lỗi trông thế nào, phân trang hoạt động ra sao (nếu có list), giới hạn tốc độ là gì, và trường nào ổn định vs có thể thay đổi.
- Thử nội bộ với một fake client. Giả vờ bạn là khách hàng xây integration trong repo mới. Tính thời gian đến lần gọi thành công đầu tiên, và ghi chỗ gây bối rối.
- Công bố hợp đồng v1: thay đổi nào an toàn (thêm trường), thay đổi nào cần version mới, và bạn sẽ thông báo bao lâu trước breaking changes.
Nếu bạn xây bằng workflow sinh code (ví dụ dùng Koder.ai để scaffold endpoints và responses), vẫn làm fake-client test. Code sinh có thể trông đúng nhưng vẫn khó dùng.
Lợi tức là ít email hỗ trợ hơn, ít hotfix, và một v1 bạn thực sự có thể duy trì.
Phát hành một SDK nhỏ mà không quá thiết kế
SDK đầu tiên không phải là một sản phẩm thứ hai. Hãy coi nó như một wrapper mỏng, thân thiện quanh HTTP API. Nó nên làm các gọi phổ biến dễ, nhưng không che giấu cách API hoạt động. Nếu ai đó cần tính năng bạn chưa bọc, họ vẫn nên có thể quay về gọi HTTP thô.
Chọn một ngôn ngữ để bắt đầu, dựa vào ngôn ngữ khách hàng thực sự dùng. Với nhiều API B2B SaaS, thường là JavaScript/TypeScript hoặc Python. Phát hành một SDK tốt hơn ba SDK nửa vời.
SDK nhỏ nên bao gồm gì
Một tập khởi đầu tốt là:
- Xử lý auth (API key hoặc OAuth token) ở một chỗ
- Timeout hợp lý và retry tự động cho request an toàn (GET), với backoff
- Trợ giúp phân trang trả về iterator hoặc hàm next page
- Mô hình request/response rõ ràng (dù chỉ là types cơ bản)
- Lối thoát cho header tùy chỉnh và gọi HTTP thô
Bạn có thể viết bằng tay hoặc sinh từ OpenAPI spec. Sinh mã tuyệt khi spec chính xác và bạn muốn typing đồng nhất, nhưng thường sinh ra nhiều code. Ban đầu, một client tối giản viết tay cộng file OpenAPI cho docs thường đủ. Bạn có thể chuyển sang client sinh sau mà không phá vỡ người dùng, miễn là interface SDK công khai ổn định.
Version SDK riêng biệt với API
API version theo quy tắc tương thích. SDK version theo quy tắc đóng gói.
Nếu bạn thêm param tùy chọn hoặc endpoints mới, đó thường là bump nhỏ cho SDK. Dành major SDK release cho breaking change trong SDK (đổi tên method, đổi default), ngay cả khi API không thay đổi. Sự tách này giữ nâng cấp trơn tru và ít ticket hỗ trợ.
Sai lầm phổ biến gây phiền toái hỗ trợ
Hầu hết ticket API không phải do bug. Làm người dùng bất ngờ mới là vấn đề. Thiết kế API công khai phần lớn là làm cho mọi thứ nhàm chán và dự đoán được để code client chạy liên tục tháng này qua tháng khác.
Cách nhanh nhất để mất niềm tin là thay đổi phản hồi mà không nói ai. Nếu bạn đổi tên trường, thay đổi kiểu, hoặc bắt đầu trả null thay vì giá trị, bạn sẽ phá vỡ client theo cách khó chẩn đoán. Nếu phải thay đổi behavior, version nó, hoặc thêm trường mới và giữ trường cũ một thời gian với kế hoạch sunset rõ ràng.
Phân trang là lỗi lặp lại khác. Vấn đề xuất hiện khi một endpoint dùng page/pageSize, endpoint khác dùng offset/limit, và endpoint thứ ba dùng cursors, tất cả với default khác nhau. Chọn một mẫu cho v1 và dùng cho mọi nơi. Giữ sort ổn định để trang sau không bỏ sót hoặc lặp khi bản ghi mới xuất hiện.
Lỗi gây nhiều trao đổi khi không nhất quán. Một chế độ thất bại phổ biến là một service trả { "error":"..." } và service khác trả { "message":"..." }, với status code khác nhau cho cùng một vấn đề. Client rồi phải xây handler rối rắm theo endpoint.
Đây là năm sai lầm gây ra nhiều email nhất:
- Thay đổi trường âm thầm (tên, kiểu, ý nghĩa) mà không bump version hoặc không có cửa sổ deprecation
- Quy tắc phân trang khác nhau theo endpoint hoặc thay đổi default theo thời gian
- Định dạng lỗi, status code, hoặc thông điệp validation khác nhau giữa các endpoint
- Thiếu request IDs, nên cả hai bên không nhanh chóng tìm ra call lỗi trong logs
- Giới hạn tốc độ bật đột ngột, không có header, không có hướng dẫn retry rõ ràng, và không có ví dụ backoff
Một thói quen đơn giản giúp: mỗi phản hồi nên bao gồm một request_id, và mỗi 429 nên giải thích khi nào retry.
Checklist nhanh và bước tiếp theo
Trước khi xuất bản, rà soát cuối cùng tập trung vào tính nhất quán. Phần lớn ticket hỗ trợ xảy ra vì chi tiết nhỏ không khớp giữa endpoints, docs, và ví dụ.
Kiểm tra nhanh bắt được phần lớn vấn đề:
- Naming: danh từ nhất quán, resources số nhiều, và cách viết trường (chọn một và giữ)
- Ví dụ: mỗi endpoint có một request và response thực tế, bao gồm phân trang
- Lỗi: hình dạng lỗi rõ ràng, code lỗi ổn định, và thông điệp hữu ích cho lỗi phổ biến
- Giới hạn: hành vi rate limit được tài liệu và phản hồi bao gồm header bạn mong đợi
- An toàn: idempotency cho retry trên create, và xử lý timeout dự đoán được
Sau khi ra mắt, theo dõi những gì người ta thực sự gọi, không phải những gì bạn mong họ dùng. Bảng điều khiển nhỏ và review hàng tuần là đủ sớm.
Theo dõi các tín hiệu này trước:
- Spike 429 (ai bị giới hạn và vì sao)
- p95 latency theo endpoint (endpoint chậm thường ẩn N+1 queries)
- Top endpoints và parameters (bề mặt API thực của bạn)
- Tỷ lệ lỗi theo status code (400 vs 401 vs 500)
- Timeouts và retries (client có thể đang loop mà không nhận ra)
Thu thập phản hồi mà không rewrite mọi thứ. Thêm đường báo lỗi ngắn trong docs, và gắn mỗi báo cáo với endpoint, request id, và client version. Khi bạn sửa, ưu tiên các thay đổi additive: trường mới, param tùy chọn, hoặc endpoint mới, thay vì phá vỡ behavior hiện có.
Bước tiếp: viết spec API một trang với resources, kế hoạch versioning, quy tắc phân trang, và định dạng lỗi. Sau đó xuất docs và một SDK khởi tạo bao gồm auth + 2–3 endpoint lõi. Nếu muốn nhanh hơn, bạn có thể phác thảo spec, docs, và SDK khởi tạo từ một kế hoạch chat sử dụng công cụ như Koder.ai (chế độ planning của nó tiện để map endpoints và ví dụ trước khi sinh code).
Câu hỏi thường gặp
Bao nhiêu endpoint nên công khai trong API đầu tiên?
Bắt đầu với 5–10 endpoints liên quan trực tiếp đến hành động của khách hàng.
Một quy tắc hay: nếu bạn không thể giải thích một resource trong một câu (nó là gì, ai sở hữu nó, cách dùng ra sao), hãy giữ nó ở nội bộ cho đến khi bạn có dữ liệu sử dụng rõ ràng.
Nên bắt đầu với những resources nào cho API SaaS?
Chọn một tập nhỏ các danh từ ổn định (resources) mà khách hàng đã dùng khi nói về sản phẩm, và giữ tên đó ổn định ngay cả khi cơ sở dữ liệu nội bộ thay đổi.
Các starter phổ biến cho SaaS là users, organizations, projects, và events — chỉ thêm khi có nhu cầu rõ ràng.
Nên dùng các phương thức HTTP nào, và tại sao nó quan trọng?
Dùng ý nghĩa chuẩn của HTTP và nhất quán:
GET= đọc (không có side effects)POST= tạo hoặc bắt đầu một hành độngPATCH= cập nhật một vài trườngDELETE= xóa hoặc vô hiệu hóa
Lợi ích chính là tính dự đoán: client không nên đoán phương thức sẽ làm gì.
Nên version API trong URL hay trong header?
Mặc định chọn version trong URL như /v1/....
Nó dễ nhìn trong logs và ảnh chụp màn hình, dễ debug với khách hàng, và đơn giản khi chạy v1 và v2 song song khi cần thay đổi phá vỡ.
Thay đổi nào được tính là breaking change trong API?
Một thay đổi là phá vỡ nếu một client đúng đắn có thể ngừng hoạt động mà không cần chỉnh code. Ví dụ thông thường:
- Đổi tên trường
- Thay đổi kiểu trường hoặc ý nghĩa
- Loại bỏ trường hoặc endpoint mà client có thể phụ thuộc
- Từ optional sang required
- Thay đổi rules auth hoặc quyền mặc định
Thêm một trường tùy chọn thường an toàn.
Chính sách deprecation thực tế cho v1 nên như thế nào?
Giữ đơn giản:
- Thông báo sớm
- Trả về cảnh báo rõ ràng trong phản hồi (và/hoặc docs)
- Đặt ngày kết thúc cụ thể
Một mặc định thực tế cho API đầu tiên là 90 ngày để khách hàng có thời gian di chuyển.
Nên dùng phân trang theo trang hay theo cursor?
Chọn một mẫu và dùng nó cho tất cả endpoint list.
- Dùng page/limit khi dataset nhỏ và người dùng hay nhảy tới trang 3.
- Dùng cursor khi danh sách lớn hoặc thay đổi thường xuyên.
Luôn định nghĩa sort mặc định và tie-breaker (ví dụ created_at + id) để kết quả không nhảy lung tung.
Nên đặt giới hạn tốc độ bao nhiêu và nên trả gì khi 429?
Bắt đầu với giới hạn rõ ràng theo key (ví dụ 60 requests/phút + burst nhỏ), sau đó điều chỉnh theo traffic thực tế.
Khi giới hạn, trả về 429 và bao gồm:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-ResetRetry-After
Điều này giúp client retry có dự đoán và giảm ticket hỗ trợ.
Phản hồi lỗi tốt nên trông như thế nào?
Dùng một định dạng lỗi duy nhất mọi nơi (kể cả 500). Cấu trúc thực dụng gồm:
code(identifier ổn định)message(dễ đọc)details(vấn đề theo trường)request_id(cho hỗ trợ)
Giữ status codes nhất quán (400/401/403/404/409/429/500) để client xử lý dễ dàng.
Làm sao để phát hành docs và SDK nhỏ mà không quá thiết kế phức tạp?
Nếu bạn sinh nhiều endpoint nhanh (ví dụ bằng Koder.ai), giữ bề mặt công khai nhỏ và xem đó là hợp đồng dài hạn.
Trước khi ra mắt:
- Viết một ví dụ request + response thực tế cho mỗi endpoint
- Khóa mẫu phân trang và định dạng lỗi
- Thêm idempotency keys cho các
POSTquan trọng - Xây một “fake client” nội bộ để thấy chỗ gây nhầm lẫn
Sau đó xuất một SDK nhỏ giúp auth, timeout, retry cho request an toàn, và phân trang — nhưng không che giấu cách hoạt động HTTP.