API là cách các chương trình giao tiếp với nhau. Cái này yêu cầu, cái kia trả lời. Qua ví dụ một marketplace, chúng ta sẽ xem một yêu cầu (request) gồm những gì, cách đọc phản hồi (response) JSON và cần kiểm tra gì khi Claude Code kết nối một nguồn dữ liệu bên ngoài. Cuối bài sẽ nói ngắn gọn FastAPI có thể xuất hiện ở đâu trong chuyện này.

API là gì, nói một cách đơn giản

API (Application Programming Interface) là một giao diện lập trình. Nó xác định các thao tác khả dụng và quy tắc gọi chúng. API không chỉ có ở các ứng dụng riêng lẻ mà còn ở các thư viện, hệ điều hành và trình duyệt. Thông qua nó, một chương trình có thể yêu cầu một chương trình khác hoặc một phần của nó thực hiện điều gì đó và nhận kết quả. Ở đây, chúng ta chủ yếu sẽ nói về các API mạng, được gọi qua một địa chỉ.

Hãy tưởng tượng một nhà hàng. Là khách, bạn không vào bếp và không giải thích cho đầu bếp cách nấu - bạn nói với người phục vụ món bạn muốn trong thực đơn, người đó chuyển order vào bếp và mang món ăn đã nấu xong ra cho bạn. API là người phục vụ. Chương trình client đặt món thông qua nó, còn nhà bếp (một chương trình khác) thực hiện. Bạn không cần biết bên trong được tổ chức thế nào - chỉ cần biết thực đơn, hiểu mình có thể yêu cầu gì và sẽ nhận lại gì.

Về cơ bản, API là một hợp đồng: "bạn có thể gọi tôi theo cách này, và tôi sẽ trả về kết quả như vậy".

Khi Claude Code đề xuất kết nối một dịch vụ, nó cần tìm hiểu có thể yêu cầu gì và sẽ nhận lại gì. Tài liệu API mô tả "thực đơn" này.

Còn một video hài hước ngắn bằng tiếng Anh - "How API work". Đó chỉ là một hình minh họa cho cùng một ý tưởng, không phải là lời giải thích chủ đề. Tất cả những gì bạn cần đều có trong văn bản dưới đây.

Request và response qua ví dụ marketplace

Hãy tưởng tượng một marketplace mà nhà cung cấp đưa sản phẩm lên từ chương trình của mình. Nhập hàng nghìn mặt hàng bằng các nút trên trang web thì rất bất tiện, vì vậy marketplace cung cấp các thao tác qua API. Ví dụ, POST /products thêm một sản phẩm, và GET /reports trả về một báo cáo.

Địa chỉ của một thao tác khả dụng được gọi là endpoint. Để hiểu một request, cần xem cả địa chỉ lẫn HTTP method. GET /products và POST /products có thể thực hiện các hành động khác nhau. GET thường dùng để đọc, còn POST dùng để gửi dữ liệu cho server xử lý.

Dữ liệu thường được truyền ở định dạng JSON. Dấu ngoặc nhọn gom các trường của một đối tượng, chuỗi được viết trong dấu ngoặc kép, còn danh sách đối tượng nằm trong dấu ngoặc vuông:

{
  "name": "Đèn bàn",
  "price": 15000,
  "count": 350
}

Client gửi request, máy chủ xử lý và trả về response. Dưới đây là một sơ đồ trao đổi rút gọn, không phải là một gói HTTP đầy đủ:

POST /products
{ "name": "Đèn bàn", "price": 15000, "count": 350 }

201 Created
{ "id": 4567, "status": "added" }

201 Created là mã trạng thái của response, còn JSON bên dưới là phần thân chứa kết quả. Với một API thực tế, tài liệu sẽ ghi rõ endpoint, method, các tham số bắt buộc, header, dạng của response và các lỗi có thể gặp. Bắt đầu với một đối tượng thử nghiệm sẽ an toàn hơn là gửi ngay toàn bộ dữ liệu.

API là một hợp đồng. Claude Code có thể viết request, nhưng việc code có đúng hợp đồng hay không thì phải kiểm tra theo tài liệu mới nhất và response thực tế của dịch vụ.

Frontend, backend và API giữa chúng

API không chỉ dùng để kết nối các ứng dụng khác nhau. Thường thì bản thân một ứng dụng đã gồm hai chương trình, và chúng cũng giao tiếp với nhau qua API.

Frontend - phần của ứng dụng mà bạn tương tác trong trình duyệt. Nó bao gồm các nút, hình ảnh, menu, biểu mẫu và hành vi của trang. Logic của nó thường được viết bằng JavaScript. Nó có thể tự thực hiện những phép tính phức tạp, cũng có thể gọi máy chủ khi cần dữ liệu hoặc thao tác ở phía dịch vụ.

Backend - phần máy chủ của ứng dụng. Nó xử lý thanh toán, lưu trữ đơn đặt hàng, kiểm tra quyền truy cập và tìm kiếm các sản phẩm. Trong quá trình phát triển, máy chủ có thể chạy trên máy tính của bạn, sau khi phát hành - trên hosting. Người dùng không nhận được mã nguồn của nó cùng với trang. Trình duyệt chỉ nhận về các response cho request.

Chúng giao tiếp như thế nào? Thông qua cùng một API. Backend cung cấp các endpoint không chỉ cho các nhà cung cấp bên ngoài mà còn cho chính frontend của nó:

  • bạn mở trang - frontend gọi endpoint /products/list, backend trả về danh sách sản phẩm, frontend hiển thị chúng thật đẹp;
  • bạn nhấp "mua" - frontend gửi dữ liệu đến endpoint /orders, backend xử lý đơn đặt hàng.
  • Hành động trong trình duyệt
  • Request từ frontend
  • Xử lý trên backend
  • Response với dữ liệu

Như vậy, qua API có thể giao tiếp cả các ứng dụng khác nhau lẫn các phần của cùng một ứng dụng. Trong sơ đồ này, trình duyệt gửi request, backend làm việc với dữ liệu và trả về kết quả. Cơ sở dữ liệu không nhất thiết phải có thể truy cập trực tiếp từ trình duyệt.

Request từ trình duyệt

Ở trên, chúng ta nói rằng frontend "gọi endpoint" của backend. Hãy xem điều này trông như thế nào trong code. Trình duyệt có sẵn một hàm cho việc này là fetch. Nó gửi một HTTP request đến địa chỉ cần thiết và trả về response. Phần thân của response thường là JSON, và được phân tích thành một đối tượng bằng phương thức .json().

Lấy ví dụ danh mục locale - tổ hợp ngôn ngữ và thiết lập khu vực. Giả sử backend đã có thể trả về danh sách này tại địa chỉ /locales, còn frontend cần hiển thị nó. Ví dụ, máy chủ trả về:

[
  { "code": "en-GB", "language": "English", "country": "United Kingdom" },
  { "code": "de-DE", "language": "Deutsch", "country": "Germany" }
]

Response đến như thế nào

Đoạn code dưới đây chạy bên trong một hàm bất đồng bộ xử lý sự kiện của trang. Nó chỉ lấy dữ liệu; phần hiển thị bảng không có ở đây.

const res = await fetch("/locales");
if (!res.ok) {
  throw new Error(`HTTP ${res.status}`);
}
const locales = await res.json();
// Bây giờ locales chứa dữ liệu cho bảng

Chúng ta kiểm tra res.ok trước khi đọc dữ liệu. Bản thân một lỗi HTTP như 404 hay 500 không khiến fetch ném ra ngoại lệ. Ngoài ra, request mạng có thể không gửi được, hoặc JSON không phân tích được. Code của trang phải bắt lỗi từ đoạn này và hiển thị một thông báo dễ hiểu. Đây là những nguyên nhân khác nhau, dù trong trường hợp nào người dùng cũng không lấy được dữ liệu. Chi tiết hơn - xem hướng dẫn MDN về fetch.

Có một vài điều nhỏ dễ bỏ lỡ:

  • theo mặc định, fetch thực hiện GET - request để lấy dữ liệu. Các thao tác khác được chỉ định qua method; ví dụ, marketplace của chúng ta chấp nhận một sản phẩm mới thông qua POST;
  • địa chỉ /locales là tương đối. Trên một trang HTTP bình thường, nó chỉ đến root của cùng một origin. Origin được xác định bởi scheme, host và port; máy chủ phải có một route như vậy;
  • fetch và .json() không trả về kết quả ngay lập tức, vì vậy phía trước chúng có await - "chờ response".

Nếu bạn mở HTML dưới dạng một tệp qua file://, cách viết /locales sẽ không trỏ tới HTTP server của bạn. Với ví dụ này, trang được mở qua một máy chủ có triển khai route đó. Bản thân tệp HTML không tạo ra route như vậy.

Frontend và API có thể nằm trên các origin khác nhau. Khi đó, để trình duyệt đọc được response, máy chủ API phải cho phép quyền truy cập cần thiết thông qua CORS. Đây là cơ chế truy cập giữa các origin (cross-origin), không phải tên gọi chung cho mọi lỗi mạng. Đặt trang và API trên cùng một origin giúp ví dụ đơn giản hơn, nhưng không phải ứng dụng nào cũng bắt buộc như vậy. Chi tiết có trong mô tả CORS trên MDN.

Ai trả lời cho trình duyệt

Ai trả lời cho fetch("/locales")? Backend, và nó không nhất thiết phải là Python với FastAPI. API là một thỏa thuận giữa các chương trình, không phải một ngôn ngữ cụ thể. Cùng một endpoint có thể viết bằng Node.js, Go hoặc Java. Ví dụ, trong handler của máy chủ Node.js, bạn có thể trả về một mảng locales được chuẩn bị sẵn. Dưới đây chỉ là một đoạn của handler, không phải một máy chủ hoàn chỉnh:

// req và res do handler nhận được, locales đã chuẩn bị sẵn
if (req.method === "GET" && req.url === "/locales") {
  res.statusCode = 200;
  res.setHeader("Content-Type", "application/json; charset=utf-8");
  res.end(JSON.stringify(locales));
}

Nhìn từ bên ngoài, đó vẫn là HTTP request GET /locales, và response có trạng thái 200 kèm JSON. Frontend không cần biết backend được viết bằng gì. Nó cần địa chỉ, quy tắc gửi request và định dạng response.

Khi Claude Code viết fetch("/locales"), hãy yêu cầu nó cho xem cả phía bên kia. Hãy để agent chỉ ra code nào trả lời request này. Sau đó kiểm tra những gì người dùng sẽ thấy nếu response không đến.

Có những loại API nào

Có thể phân loại API theo hai tiêu chí:

  • cục bộ và qua mạng - ví dụ, API của thư viện được gọi từ code của chương trình, còn API mạng được gọi qua mạng. Mạng đó có thể là mạng nội bộ, không cần internet công cộng;
  • công khai và nội bộ - API công khai được thiết kế cho các nhà phát triển bên ngoài, API nội bộ kết nối các phần của hệ thống. API công khai cũng có thể yêu cầu đăng ký, thanh toán và quyền.

Web API thường dùng HTTP - giao thức request và response. REST, cái tên bạn sẽ gặp trong tài liệu, mô tả một cách tiếp cận để xây dựng API; không phải HTTP API nào cũng tự động là REST. Lúc này, hữu ích hơn là học cách đọc mã trạng thái trong response:

  • 200 - mọi thứ đều ổn;
  • 201 - đã tạo (ví dụ, một đối tượng mới sau POST);
  • 404 - không tìm thấy;
  • 401 - request không chứa dữ liệu authentication phù hợp;
  • 429 - quá nhiều request;
  • 500 - lỗi máy chủ nội bộ.

Lúc này chưa cần đi sâu vào phần này. Điều quan trọng là nắm được ý tưởng trao đổi request và response. Tạm thời vậy là đủ.

Không cần học thuộc các mã này. Trên trang http.cat, mỗi HTTP status được minh họa bằng một bức ảnh mèo.

Tại sao dịch vụ cần một khóa truy cập

Nhiều dịch vụ yêu cầu bạn lấy API key trước - một chuỗi gắn với tài khoản hoặc dự án của bạn. Key được truyền theo cách mà tài liệu chỉ định, thường là trong header của request. Dịch vụ kiểm tra quyền truy cập và có thể đếm số request bạn đã gửi. Ở những tích hợp khác, bạn sẽ gặp từ token. Nó có mục đích tương tự, nhưng cách lấy, thời hạn hiệu lực và quyền hạn có thể khác.

Không có quyền truy cập phù hợp thì request sẽ không thực hiện được; ví dụ, máy chủ có thể trả về 401. Việc gửi request quá nhanh, vượt giới hạn tần suất, thường được báo bằng mã 429. Ý nghĩa chính xác của response và hạn mức cho phép cần xem trong tài liệu của từng dịch vụ. Cũng có những nguồn dữ liệu mở hoàn toàn không cần key.

Khóa bí mật cho phép truy cập trong phạm vi quyền đã cấp cho nó, đôi khi là bằng tiền của bạn. Không dán nó vào tin nhắn, code công khai hay Git. Ở phía máy chủ, giá trị có thể được truyền qua biến môi trường của tiến trình; còn một biến đã lọt vào JavaScript của trình duyệt thì không còn giữ được bí mật. Đặt giá trị ở đâu và truyền nó cho chương trình thế nào là một chủ đề riêng về khóa và biến môi trường.

Tìm API có sẵn ở đâu

Nhờ API, bản đồ, thời tiết, thanh toán và giao hàng được nhúng vào ứng dụng. Tài liệu công khai giúp bạn hiểu dịch vụ làm được gì ngay cả trước khi kết nối. Tốt nhất là bắt đầu từ chính bài toán. Xác định bạn cần dữ liệu gì, bao lâu cần cập nhật một lần và bạn định làm gì với nó.

Để tìm kiếm, có thể xem Postman Public API Network, một danh mục API và ví dụ request. Ví dụ tìm được chỉ là điểm xuất phát. Địa chỉ, định dạng, quyền truy cập và giới hạn vẫn cần đối chiếu với tài liệu chính thức của chủ sở hữu dịch vụ.

FastAPI nằm ở đâu trong bức tranh này

FastAPI là một trong những framework để tạo API và web server bằng Python. Nó có thể gắn một endpoint với code phía server của bạn, nhận request và trả về response JSON. FastAPI cần khi bạn tự viết backend bằng Python; để dùng một API có sẵn thì không cần học nó. Ở cấp độ 7, chỉ cần nhận ra cái tên và hiểu ranh giới này.

Kết hợp với Claude Code thì thế nào

Nắm được các khái niệm này, bạn sẽ dễ đánh giá công việc của agent hơn. Nó có thể đọc tài liệu API của bên khác, viết request phía client hoặc tạo API của riêng bạn bằng FastAPI. Điều quan trọng là bạn nhìn thấy được đường đi cụ thể từ request đến dữ liệu trên trang. Ai gửi request, ai trả lời và response được xử lý như thế nào?

Ví dụ, có một GeoJSON feed công khai của USGS cho radar động đất. Địa chỉ của danh sách trong giờ qua:

GET https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_hour.geojson

Dưới đây là một ví dụ minh họa nhỏ về cấu trúc response, với một sự kiện hư cấu và bộ trường rút gọn. Đây không phải dữ liệu hiện tại của USGS:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": { "mag": 4.2, "place": "Điểm minh họa" },
      "geometry": { "type": "Point", "coordinates": [-122.5, 38.0, 8.0] }
    }
  ]
}

Trong features là mảng các sự kiện. Đường dẫn features[0].properties.mag dẫn đến độ lớn (magnitude) của sự kiện đầu tiên. Chỉ số 0 có nghĩa là phần tử đầu tiên của mảng. Tọa độ theo thứ tự "kinh độ, vĩ độ, độ sâu tính bằng km". Agent có thể dùng độ lớn để quyết định kích thước điểm, và tọa độ cho vị trí của nó. Nếu mảng rỗng thì không có phần tử đầu tiên; nếu độ lớn bằng null, đó là giá trị chưa biết, không phải một sự kiện có độ lớn bằng không.

Còn lại là quyết định hiển thị gì sau request:

Nhận được gìHiển thị gì
Response thành công có sự kiệnTập dữ liệu hiện tại. Nó có thể trùng với lần trước, vì không phải lần request nào cũng có sự kiện mới.
Response thành công với mảng rỗngTrong khoảng thời gian đã chọn không có sự kiện nào. Các điểm cũ có thể đã ra khỏi khoảng đó, không hiển thị chúng như dữ liệu hiện tại.
Lỗi khi nhận hoặc đọc responseThông báo sự cố. Nếu còn bản dữ liệu thành công gần nhất, có thể giữ lại kèm mốc thời gian và cảnh báo rằng dữ liệu đã cũ. Nếu không có bản nào, tạm thời không có gì để hiển thị.

Trong lúc request đang chạy, trang có thể hiển thị trạng thái đang tải. Để kiểm tra các tình huống khác nhau, nên dùng các response mẫu. Như vậy bạn không phải chờ dịch vụ gặp sự cố thật hay một trận động đất mới.

Giờ đây, yêu cầu giao cho Claude Code có thể như sau:

Kết nối trang với dữ liệu từ API đã chỉ định.
Trước tiên, hãy xem tài liệu chính thức: địa chỉ, method,
định dạng response và điều kiện truy cập. Cho biết bạn dùng những trường nào.
Xử lý các trạng thái: đang tải, danh sách rỗng và lỗi cập nhật.
Nếu dùng dữ liệu cũ sau sự cố, hãy ghi rõ điều đó trên trang.
Không đặt khóa bí mật trong code phía client.
Kiểm tra kết quả và cho biết đã kiểm tra được những trạng thái nào.

Khi agent làm xong, hãy xem chính request và kết quả kiểm tra, chứ không chỉ nhìn giao diện cuối cùng. Cần phân biệt quyền truy cập của agent với quyền truy cập của trang. MCP có thể kết nối một công cụ với Claude Code, nhưng ứng dụng đã xuất bản cần tích hợp riêng của nó. Phiên làm việc của agent đã đóng thì không được làm dừng các request của trang, nếu chúng được triển khai ngay trong ứng dụng.