Nhờ AI hướng dẫn bạn Công cụ

Nếu bạn quen làm việc với ChatGPT, Claude hay Gemini, có một đường tắt: tải về một file Markdown chứa toàn bộ tài liệu API rồi đưa cho trợ lý AI của bạn. Từ đó cứ hỏi thẳng “lấy CPI 10 năm gần nhất vào pandas giúp tôi” và nó viết code đúng cho bạn.

dulieukinhte-api.md

Toàn bộ endpoint, mã lỗi, quy ước thời gian, mẫu code — cộng danh mục đầy đủ mọi bộ số liệu kèm mã để AI tra đúng thay vì đoán bừa.

Sau khi tải, mở trợ lý AI, đính kèm file và dán câu này:

Câu nhắc mẫu
Đây là tài liệu API của dulieukinhte.com. Hãy đọc kỹ, rồi hướng dẫn tôi
lấy số liệu tôi cần. Tôi dùng Python. API key của tôi đã nằm trong biến
môi trường DLKT_API_KEY — đừng hỏi xin key.

Chỉ tiêu tôi cần: <ghi rõ, ví dụ: CPI Việt Nam theo tháng, 10 năm gần nhất>

File được sinh lúc bạn bấm tải, nên hạn mức, danh mục và số liệu quy mô trong đó luôn khớp tài khoản và kho dữ liệu hiện tại. Tải lại khi bạn đổi gói hoặc khi một mã bộ số liệu báo not_found.

Bộ dựng yêu cầu Công cụ

Chọn endpoint và tham số, nhận lại lệnh chạy được. Công cụ này không gọi mạng — nó chỉ dựng chuỗi để bạn chép sang terminal, nên không tiêu hạn mức của bạn và không đòi bạn dán API key vào một trang web.

Endpoint chính — lấy số liệu thật của MỘT chuỗi. Chấp nhận ?api_key= để dán URL vào Excel / Google Sheets.
Danh sách 298 bộ số liệu có thật. Chọn xong, ô mã bên dưới tự điền và lệnh gọi cập nhật theo.
curl -s \
  -H "Authorization: Bearer $DLKT_API_KEY" \
  "https://api.dulieukinhte.com/v1/series/18692/observations"
Kết quả trả về trông như thế này · ví dụ minh hoạ
{ "data": {
  "series": { "id": 18692, "name": "Chỉ số giá tiêu dùng CPI", "unit": "%",
              "transform": "yoy" },
  "observations": [
    { "date": "2026-06-01", "period": "2026-06", "value": 3.57 },
    { "date": "2026-07-01", "period": "2026-07", "value": 3.42 }
  ]
},
  "meta": { "count": 2, "hasMore": false, "nextCursor": null, "limit": 1000 } }
date là ngày neo kỳ (tháng thì neo ngày 1), period là nhãn cho người đọc, value là con số. meta.hasMore = true nghĩa là còn dữ liệu; truyền meta.nextCursor vào ?cursor= để lấy trang tiếp.
Endpoint này còn nhận ?api_key= để dán thẳng URL vào Excel hoặc Google Sheets. Chỉ dùng khi bắt buộc — API key trong URL nằm lại trong lịch sử trình duyệt, header Referer và log của mọi máy chủ trung gian.
Hướng dẫn

Từ con số không đến số liệu chạy trong code của bạn

Tám bước, theo thứ tự. Làm hết mất khoảng 20 phút và bạn sẽ có một vòng đồng bộ chạy được thật, không phải một đoạn curl chạy một lần rồi thôi. Kho hiện có 300 bộ số liệu, 32.787 chuỗi thời gian và khoảng 7.918.541 điểm dữ liệu thuộc 4 nền kinh tế.

Đây là toàn bộ tài liệu API — hướng dẫn và tra cứu nằm chung một trang, không phải tìm ở hai nơi rồi tự đoán chỗ nào mới đúng.

Lần đầu dùng API này

Đi từ trên xuống, bắt đầu ở Trước khi bắt đầu. Tám bước, mỗi bước một việc, kết thúc bằng một vòng đồng bộ chạy được thật.

Đang vội? Đưa file .md cho trợ lý AI rồi nói thẳng chỉ tiêu bạn cần.

Đã quen tay, chỉ cần tra

Nhảy thẳng: tra cứu nhanh · 15 endpoint · tham số observations · phép biến đổi & quy ước kỳ · mã lỗi · giới hạn theo gói · sự cố thường gặp.

Mục lục bên trái luôn theo bạn khi cuộn.

Trước khi bắt đầu

Ba thứ cần có. Không cần gì hơn — không SDK, không OAuth, không token hết hạn.

  • Một tài khoản đã xác minh email. Khoá đầu tiên chỉ cấp sau khi bấm liên kết trong thư xác minh. Đăng ký ở đây
  • Một API key. Gói Free đủ để đi hết hướng dẫn này: 100 lượt/tháng là dư cho việc học.
  • Một terminal có curl (hoặc Python, hoặc Node — cả ba đều có ví dụ đầy đủ bên dưới).
Khi đã quen tay và chỉ cần tra “tham số order nhận giá trị gì”, đừng đọc lại từ đầu — mở Tra cứu nhanh hoặc bảng 15 endpoint ở cuối trang. Địa chỉ /tai-lieu cũ nay chuyển thẳng về đây.
1

Lấy API key

Mở trang Khoá APITạo key mới. Key hiện ra đúng một lần duy nhất: hệ thống chỉ lưu bản băm SHA-256, không có nút “xem lại”. Mất thì thu hồi và cấp key mới, không có cách khôi phục.

Hình dạng một API key
dlkt_live_8fK2mQ7xR4nL9pT1vB6cW3yZ0aH5jS8dG2eN7uM4iO1

Tiền tố dlkt_live_ cố định, phần sau là 32 byte ngẫu nhiên mã base64url. Tổng cộng 53 ký tự, chỉ gồm chữ, số, -_ — dán vào file cấu hình nào cũng không phải thoát ký tự.

Số key hoạt động tối đa theo gói: Free 1 · Pro 1 · Business 10. Mọi ví dụ bên dưới đọc key từ biến môi trường DLKT_API_KEY.

Thu hồi key có hiệu lực trong vòng 60 giây — đó là thời gian sống của bộ nhớ đệm key ở tầng API. Thường là tức thì, nhưng đừng trông đợi “tức thì” trong kịch bản xử lý sự cố: cứ tính một phút.
2

Lượt gọi đầu tiên

Bắt đầu bằng endpoint không cần API key, để tách bạch hai câu hỏi “mạng có thông không” và “API key có đúng không”. Trộn hai câu đó vào một lượt gọi là nguồn gốc của phần lớn thời gian debug bị phí.

a · Mạng có thông không
curl -s "https://api.dulieukinhte.com/v1/health"
# {"data":{"ok":true,"version":"v1","database":"up","time":"2026-08-29T…"}}

/v1/health không cần API key và không tính vào hạn mức. Dùng thoải mái trong healthcheck của bạn.

b · Khoá có đúng không
curl -s -H "Authorization: Bearer $DLKT_API_KEY" "https://api.dulieukinhte.com/v1/me"

Phản hồi cho biết mọi giới hạn bạn đang chịu — đọc kỹ một lần rồi khỏi đoán:

Phản hồi
Nghĩa là gì
"plan": { "code": "free",
Mã gói máy đọc được. Dùng cái này trong code, đừng dùng name (tên hiển thị có thể đổi).
"quotaMonthly": 100,
Trần lượt gọi mỗi tháng, tính theo tài khoản — ba key dùng chung một hạn mức.
"ratePerMinute": 10,
Trần lượt gọi mỗi phút, tính theo từng key — chỗ này khác hạn mức tháng.
"historyYears": 5,
Chỉ lấy được 5 năm gần nhất. null nghĩa là không giới hạn.
"maxPointsPerRequest": 1.000,
Trần của tham số limit trong một lượt gọi.
"scopes": ["macro"] },
Nhóm tài nguyên được phép: macro · company · updates · csv. Thiếu nhóm nào thì endpoint đó trả 403 plan_required.
"usage": { "used": 3, "remaining": 97 }
Mức dùng của kỳ này. Kỳ là tháng dương lịch giờ Việt Nam, đặt lại 00:00 ngày 1.
/v1/me có tính 1 lượt vào hạn mức tháng. Đừng gọi nó trong vòng lặp để “kiểm tra còn hạn mức không” — con số đó đã nằm sẵn trong header của mọi phản hồi (xem bước 5).

Hai cách gửi API key

Authorization: Bearer dlkt_live_…    # nên dùng
X-API-Key: dlkt_live_…               # tương đương, tiện cho vài client cũ

Có một cách thứ ba, ?api_key= trong URL, nhưng chỉ dùng được ở observations, financialsprices — và chỉ nên dùng khi công cụ của bạn không gửi được header (Excel, Google Sheets). Xem công thức Excel & Sheets.

3

Mô hình dữ liệu — bốn tầng

Hiểu bốn tầng này là hiểu 90% API. Mọi endpoint chỉ là cách đi lên hoặc đi xuống giữa chúng.

Nền kinh tếVN · US · EU · CNBộ số liệu300 bộ · datasetChuỗi32.787 chuỗi · seriesQuan sátngày + giá trị?economy=VN/datasets/152/series/82412026-06 → 3,57
Tầng
Là gì, và bạn dùng nó để làm gì
economy
Nền kinh tế. 4 mã: VN, US, EU, CN. Chỉ dùng để lọc — không có số liệu ở tầng này.
dataset
Bộ số liệu — tương ứng một bảng trên website, ví dụ “Chỉ số giá tiêu dùng”. Có tần suất, đơn vị, nguồn. Không chứa giá trị.
series
Chuỗi thời gian — một dòng trong bảng đó, ví dụ “Nhóm Lương thực”. Đây là thứ bạn thật sự cần id của nó. Chuỗi xếp thành cây cha–con qua parentId.
observation
Một điểm: date + period + value. Đây là hàng hoá cuối cùng.
Điều dễ nhầm nhất: datasets/152 không trả về số liệu nào cả — nó trả về danh sách chuỗi con. Số liệu chỉ có ở /series/{id}/observations. Tách như vậy để bạn tải một danh mục 3.000 dòng mà không kéo theo 3 triệu điểm dữ liệu.
4

Tìm đúng chuỗi

Ba đường, dùng ở ba tình huống khác nhau.

Đường A — đã biết đại khái tên: dùng /search

curl -s -H "Authorization: Bearer $DLKT_API_KEY" \
  --data-urlencode "q=chỉ số giá tiêu dùng" \
  --data-urlencode "type=series" \
  --data-urlencode "economy=VN" \
  -G "https://api.dulieukinhte.com/v1/search"

-G + --data-urlencode là cách đúng để gửi tiếng Việt có dấu qua curl. Gõ thẳng dấu tiếng Việt vào URL sẽ hỏng ở một số shell.

q cần tối thiểu 2 ký tự. type nhận all (mặc định), dataset, series, company. Kết quả trả tối đa limit mục (mặc định 20, trần 100), mỗi mục có webUrl mở thẳng trang tương ứng trên dulieukinhte.com để bạn mắt thường xác nhận đúng chuỗi.

Đây là tìm khớp chuỗi ký tự, không phải tìm ngữ nghĩa. Hỏi “lạm phát năm nay bao nhiêu” sẽ không ra gì. Dùng nó để tra id theo tên, không phải để đặt câu hỏi.

Đường B — muốn xem có gì: duyệt danh mục

# tất cả bộ số liệu của Việt Nam, 50 bộ mỗi trang
curl -s -H "Authorization: Bearer $DLKT_API_KEY" \
  "https://api.dulieukinhte.com/v1/datasets?economy=VN&perPage=50&page=1"

# rồi mở một bộ ra xem cây chuỗi con
curl -s -H "Authorization: Bearer $DLKT_API_KEY" \
  "https://api.dulieukinhte.com/v1/datasets/152"

Bộ lớn có hàng nghìn chuỗi con. Khi seriesCount lớn, đừng dùng /datasets/{id} (trả nguyên cây) mà dùng /datasets/{id}/series?page=&perPage= — mặc định 100, trần 500 mỗi trang.

Đường C — đang xem trên website: lấy id từ URL

Trang bảng trên dulieukinhte.com có dạng /du-lieu/<slug>-<id> — số cuối cùng chính là datasetId. Đây thường là đường nhanh nhất: tìm bằng mắt trên web, rồi mang id sang API.

Xác nhận trước khi kéo

Có id rồi thì gọi /series/{id} một lần để biết mình sắp kéo cái gì:

{"data":{
  "id": 8241,
  "name": "Lương thực",
  "path": ["Chỉ số giá tiêu dùng","CPI chung","Hàng ăn và dịch vụ ăn uống","Lương thực"],
  "frequency": "monthly",
  "unit": "%",
  "source": "Cục Thống kê (NSO)",
  "firstDate": "2021-08-01",
  "lastDate": "2026-06-01",
  "count": 59,
  "historyLimited": true,
  "historyYears": 5
}}
firstDate là điểm đầu bạn thật sự lấy được, đã trừ giới hạn lịch sử của gói — không phải điểm đầu trong kho. historyLimited: true nghĩa là kho còn dữ liệu cũ hơn nhưng gói của bạn không với tới. path cho biết chuỗi này nằm ở đâu trong cây, để bạn chắc chắn không nhầm hai dòng trùng tên ở hai nhánh khác nhau.
5

Kéo số liệu về

curl -s -H "Authorization: Bearer $DLKT_API_KEY" \
  "https://api.dulieukinhte.com/v1/series/8241/observations?from=2015-01-01&order=asc&limit=1000"

Toàn bộ tham số

Tham sốMặc địnhÝ nghĩa
fromYYYY-MM-DD. Lọc theo kỳ, hiểu theo giờ Việt Nam.
toYYYY-MM-DD, bao gồm cả mốc này.
limit1000Trần theo gói: 1.000 (Free) / 10.000 (Pro) / 50.000 (Business).
cursorNguyên văn meta.nextCursor của lượt trước. Xem bước 6.
orderascasc cũ→mới, desc mới→cũ.
transformnoneyoy mom qoq ttm ttm_yoy ytd ytd_yoy. Xem bước 7.
formatjsonjson | csv. CSV cần gói Pro trở lên.

Đọc phản hồi

JSON
Nghĩa
"data": { "series": { … },
Metadata chuỗi lặp lại ở mỗi lượt: unit ở đây là đơn vị sau khi biến đổi (dùng transform=yoy thì unit thành “%”).
"observations": [ { "date": "2026-06-01", "period": "2026-06", "value": 3.57 } ] },
date để máy xử lý (ISO, luôn là ngày neo kỳ). period để người đọc. value luôn là số, không bao giờ là chuỗi.
"meta": { "count": 1000,
Số điểm trong lượt này — không phải tổng số điểm của chuỗi.
"hasMore": true,
Còn dữ liệu phía sau. Đây là thứ duy nhất bạn nên dùng làm điều kiện dừng vòng lặp.
"nextCursor": "MTc0…" }
Truyền lại nguyên văn vào ?cursor=. Đây là chuỗi mờ — đừng giải mã và đừng tự chế.

Không có meta.total ở đây, và đó là chủ ý: đếm chính xác trên 7.918.541 dòng cho mỗi trang là tiêu hạn mức của bạn và thời gian của cả hai bên để lấy một con số bạn không dùng đến.

Bốn header cần đọc trong code

HeaderNghĩa
X-RateLimit-RemainingCòn bao nhiêu lượt trong phút này, của key này.
X-Quota-RemainingCòn bao nhiêu lượt trong tháng này, của cả tài khoản.
X-Quota-ResetEpoch giây, thời điểm sang kỳ mới.
X-Request-IdGhi lại khi có lỗi. Gửi kèm mã này thì chúng tôi tra được đúng lượt gọi đó trong log.
Xem header mà không in cả body
curl -sS -D - -o /dev/null \
  -H "Authorization: Bearer $DLKT_API_KEY" \
  "https://api.dulieukinhte.com/v1/series/8241/observations?limit=1"

X-RateLimit-Reset trả 60 khi bạn còn hạn (độ dài cửa sổ trượt), và trả số giây phải đợi khi đã chạm trần. Khi bị chặn, cứ đọc Retry-After — đơn giản hơn.

6

Lấy hết bằng con trỏ

API dùng hai kiểu phân trang, và dùng nhầm kiểu là lỗi phổ biến nhất của tuần đầu tiên.

Danh mục → page / perPage

datasets, datasets/{id}/series, companies, updates.

meta.totalmeta.totalPages, nhảy trang tuỳ ý.

Số liệu → cursor

series/{id}/observations.

Không có total. Đi tuần tự bằng meta.nextCursor, dừng khi hasMorefalse.

Vòng lặp con trỏ — bash
CURSOR=""
while :; do
  URL="https://api.dulieukinhte.com/v1/series/8241/observations?limit=10000&from=2000-01-01"
  [ -n "$CURSOR" ] && URL="$URL&cursor=$CURSOR"
  BODY=$(curl -s -H "Authorization: Bearer $DLKT_API_KEY" "$URL")
  echo "$BODY" | jq -c '.data.observations[]' >> cpi.ndjson
  [ "$(echo "$BODY" | jq -r '.meta.hasMore')" = "true" ] || break
  CURSOR=$(echo "$BODY" | jq -r '.meta.nextCursor')
done
Hai bẫy của vòng lặp này. Một: dừng theo count < limit thay vì theo hasMore — sai khi dùng transform, vì các kỳ thiếu dữ kiện gốc bị loại nên một trang đầy vẫn có thể trả về ít điểm hơn limit. Hai: quên rằng dấu ngoặc kép quanh URL là bắt buộc trong shell — không có nó, & đẩy lệnh xuống chạy nền và curl chỉ nhận được nửa URL, im lặng, không báo lỗi.

Với /companies/{symbol}/prices, cách đi trang là dùng from = ngày cuối của trang trước cộng một ngày. Endpoint đó có trả meta.nextCursor nhưng chưa nhận tham số cursor.

7

Để máy chủ tính YoY, MoM, TTM

Bạn tự tính được, nhưng đừng. Các phép này chạy đúng công thức dulieukinhte.com dùng để vẽ biểu đồ — số bạn lấy qua API khớp chính xác số bạn thấy trên web. Tự tính lại là tự tạo ra một cách để hai con số lệch nhau.

curl -s -H "Authorization: Bearer $DLKT_API_KEY" \
  "https://api.dulieukinhte.com/v1/series/8241/observations?from=2024-01-01&transform=yoy"
transformNghĩaĐơn vị trả vềTần suất dùng được
yoyso cùng kỳ năm trước%tháng, quý, năm
momso tháng trước%chỉ tháng
qoqso quý trước%chỉ quý
ttmluỹ kế 12 tháng gần nhấtđơn vị gốctháng, quý
ttm_yoyTTM so cùng kỳ%tháng, quý
ytdluỹ kế từ đầu nămđơn vị gốctháng, quý
ytd_yoyYTD so cùng kỳ%tháng, quý

Chuỗi tần suất ngày không hỗ trợ phép biến đổi nào — yêu cầu sẽ trả 400 kèm danh sách phép hợp lệ cho tần suất đó. Chuỗi năm chỉ có yoy.

Ba điều đã được xử lý sẵn cho bạn

  • Cửa sổ nới về quá khứ. Xin from=2024-01-01&transform=yoy thì điểm 01/2024 vẫn có giá trị: máy chủ tự đọc thêm dữ liệu 2023 làm kỳ gốc, tính, rồi cắt lại đúng khoảng bạn xin. Bạn không phải tự nới rồi tự cắt.
  • Kỳ thiếu dữ kiện gốc bị loại khỏi kết quả, không trả null. Nghĩa là số điểm trả về có thể ít hơn số kỳ trong khoảng — đúng như thiết kế, không phải mất dữ liệu.
  • Giới hạn lịch sử của gói vẫn được tôn trọng. Cửa sổ nới không phải là cửa sau để gói Free với tới dữ liệu ngoài 5 năm.

Quy ước neo kỳ — đọc một lần rồi khỏi thắc mắc

Tần suấtdate neo ởperiod
thángngày 1 của tháng2026-06
quýngày 1 tháng cuối quý2026-Q2
nămngày 1 của năm2026
ngàychính ngày đó2026-06-15
Quý neo ở tháng cuối: Q2/2026 nằm ở 2026-06-01, không phải 2026-04-01. Quy ước này có từ trước và giữ nguyên để khớp với website. Nếu bạn lọc to=2026-05-31 thì sẽ không lấy được Q2/2026.

Mọi mốc neo 00:00 giờ Việt Nam (UTC+7), và from/to cũng được hiểu theo giờ VN. Nếu code của bạn sinh ngày bằng UTC, bạn sẽ lệch một kỳ ở biên — dùng chuỗi YYYY-MM-DD nguyên bản, đừng đi qua toISOString() của một đối tượng ngày giờ địa phương.

8

Đồng bộ hằng ngày mà không đốt hạn mức

Kho có 32.787 chuỗi. Kéo lại toàn bộ mỗi ngày thì gói nào cũng không đủ, kể cả Enterprise. Cách đúng là hỏi cái gì đã đổi, rồi chỉ kéo về những chuỗi đó.

# 1. mỗi sáng: chuỗi nào có số mới trong 24 giờ qua?
SINCE=$(date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%SZ)
curl -s -H "Authorization: Bearer $DLKT_API_KEY" \
  "https://api.dulieukinhte.com/v1/updates?since=$SINCE&perPage=500"

# 2. rồi gom id lại, kéo 20 chuỗi mỗi lượt
curl -s -H "Authorization: Bearer $DLKT_API_KEY" \
  "https://api.dulieukinhte.com/v1/series/observations?ids=8241,8242,8243&from=2026-01-01"

Một vòng như thế thường tốn vài chục lượt gọi mỗi ngày — nằm gọn trong gói Pro với hạn mức 10.000 lượt/tháng, còn dư rất nhiều cho việc khác.

Tham số của /updatesGhi chú
sinceBắt buộc. ISO 8601. Lùi tối đa 90 ngày — xa hơn thì dùng observations, đúng công cụ hơn.
page, perPageMặc định 100, trần 500. Có meta.total.

Mỗi mục trả về seriesId, changedAt, pointsChangedlatestPeriod — đủ để bạn quyết định có cần kéo lại chuỗi đó không mà chưa phải kéo.

/updates thuộc gói Pro trở lên. Ở gói Free, cách thay thế là lưu lastUpdated của từng bộ số liệu (có sẵn trong /datasets) và so sánh mỗi ngày — tốn nhiều lượt gọi hơn nhưng vẫn rẻ hơn kéo lại tất cả.

Công thức: Python + pandas

Kéo trọn một chuỗi vào Series của pandas

Có xử lý con trỏ, có phân biệt hai loại 429, có tôn trọng Retry-After. Chép nguyên là chạy được.

import os, time, requests, pandas as pd

BASE = "https://api.dulieukinhte.com/v1"
H = {"Authorization": f"Bearer {os.environ['DLKT_API_KEY']}"}

def observations(series_id, **params):
    """Lấy hết các trang bằng con trỏ. Trả pd.Series đánh chỉ mục theo ngày."""
    rows, cursor = [], None
    while True:
        p = {**params, "limit": 10000}
        if cursor:
            p["cursor"] = cursor
        r = requests.get(f"{BASE}/series/{series_id}/observations",
                         headers=H, params=p, timeout=60)

        if r.status_code == 429:
            code = r.json()["error"]["code"]
            if code == "rate_limited":
                # "chậm lại" -> đợi rồi thử lại chính lượt này
                time.sleep(int(r.headers.get("Retry-After", 5)))
                continue
            # "hết tháng" -> thử lại chỉ tốn thêm, dừng hẳn
            raise RuntimeError(r.json()["error"]["message"])

        r.raise_for_status()
        j = r.json()
        rows += j["data"]["observations"]
        if not j["meta"]["hasMore"]:
            break
        cursor = j["meta"]["nextCursor"]

    df = pd.DataFrame(rows)
    df["date"] = pd.to_datetime(df["date"])
    return df.set_index("date")["value"]

# 'from' là từ khoá của Python nên phải truyền qua **{}
cpi = observations(8241, transform="yoy", **{"from": "2015-01-01"})
print(cpi.tail())

Công thức: nhiều chuỗi trong một lượt gọi

20 chuỗi, 1 lượt hạn mức

Đây là kỹ thuật tiết kiệm hạn mức hiệu quả nhất: một lượt gọi gộp tối đa 20 chuỗi vẫn chỉ tính 1 lượt.

def many(ids, **params):
    """Tối đa 20 id mỗi lượt. Trả DataFrame dạng RỘNG: mỗi chuỗi một cột."""
    out = {}
    for i in range(0, len(ids), 20):
        chunk = ids[i:i + 20]
        r = requests.get(f"{BASE}/series/observations", headers=H, timeout=60,
                         params={**params, "ids": ",".join(map(str, chunk)), "limit": 5000})
        r.raise_for_status()
        j = r.json()

        # Chuỗi không tồn tại KHÔNG làm hỏng cả lượt — nó nằm ở meta.missing
        for m in j["meta"].get("missing", []):
            print(f"bỏ qua id không tồn tại: {m}")

        for s in j["data"]:
            # transform không áp được cho tần suất của chuỗi này -> bị bỏ qua,
            # 19 chuỗi còn lại vẫn về bình thường
            if s.get("skipped"):
                print(f"bỏ qua {s['id']} ({s['name']}): {s['reason']}")
                continue
            ser = pd.DataFrame(s["observations"])
            ser["date"] = pd.to_datetime(ser["date"])
            out[s["name"]] = ser.set_index("date")["value"]
    return pd.DataFrame(out)

df = many([8241, 8242, 8243, 8244], transform="yoy", **{"from": "2020-01-01"})
print(df.tail())

Trần limit ở đây được chia đều: mỗi chuỗi lấy nhiều nhất maxPointsPerRequest ÷ số chuỗi điểm (tối thiểu 50). Xin 20 chuỗi ở gói Pro thì mỗi chuỗi tối đa 500 điểm.

Công thức: Excel & Google Sheets

Bảng tính tự cập nhật, không cần code

Đây là trường hợp duy nhất nên đặt API key vào URL — bảng tính không gửi được header. Dùng một API key riêng cho việc này để thu hồi được mà không ảnh hưởng gì khác.

Google Sheets
=IMPORTDATA("https://api.dulieukinhte.com/v1/series/8241/observations?format=csv&from=2015-01-01&api_key=dlkt_live_…")
Excel · Dữ liệu → Lấy dữ liệu → Từ Web
https://api.dulieukinhte.com/v1/series/8241/observations?format=csv&from=2015-01-01&api_key=dlkt_live_…
  • File CSV có BOM UTF-8, nên Excel trên Windows đọc đúng tiếng Việt thay vì ra “Chỉ số giá”.
  • Nhiều chuỗi (/series/observations?ids=…&format=csv) trả về dạng dài: mỗi dòng một quan sát, có cột series_id. Dạng này pandas đọc thẳng và không vỡ khi các chuỗi có kỳ lệch nhau — điều luôn xảy ra khi ghép chuỗi tháng với chuỗi quý.
  • Ô bắt đầu bằng = + - @ được thêm dấu nháy đầu dòng, để Excel không diễn giải tên chỉ tiêu thành công thức.
API key trong URL sẽ nằm lại trong lịch sử trình duyệt, trong header Referer và trong log của mọi máy chủ trung gian. Nếu chia sẻ bảng tính cho người khác, họ thấy được API key của bạn trong thanh công thức. Xuất CSV cần gói Pro trở lên.

Công thức: báo cáo tài chính doanh nghiệp

1.563 mã niêm yết trên HOSE, HNX và UPCoM

Gói Pro trở lên (nhóm quyền company).

# hồ sơ: sàn, ngành, có những loại báo cáo nào, dải giá đến đâu
curl -s -H "Authorization: Bearer $DLKT_API_KEY" "https://api.dulieukinhte.com/v1/companies/FPT"

# kết quả kinh doanh, theo quý, 12 kỳ gần nhất
curl -s -H "Authorization: Bearer $DLKT_API_KEY" \
  "https://api.dulieukinhte.com/v1/companies/FPT/financials?statementType=2&freq=quarter&limit=12"
statementTypeBáo cáo
1Cân đối kế toán
2Kết quả kinh doanh (mặc định)
3Lưu chuyển tiền tệ
4Chỉ số tài chính
5Báo cáo tháng (chỉ vài mã tự công bố)

freq nhận year (mặc định) hoặc quarter; limit mặc định 12, trần 60 kỳ.

Phản hồi tách làm ba mảng: lines (các chỉ tiêu, có parentId dựng lại cây), periods (các kỳ), và values (nối lineId × period → giá trị). Tách như vậy để tên chỉ tiêu không bị lặp lại ở mỗi kỳ.

fs = requests.get(f"{BASE}/companies/FPT/financials",
                  headers=H, params={"statementType": 2, "freq": "quarter", "limit": 12}
                  ).json()["data"]

names = {l["id"]: l["name"] for l in fs["lines"]}
df = (pd.DataFrame(fs["values"])
        .assign(line=lambda d: d["lineId"].map(names))
        .pivot(index="line", columns="period", values="value"))
print(df.head(20))

Công thức: giá cổ phiếu theo ngày

OHLCV, thẳng vào DataFrame

Gói Pro trở lên. Đi trang bằng from, không phải bằng con trỏ.

def prices(symbol, start="2020-01-01"):
    out, cur = [], start
    while True:
        r = requests.get(f"{BASE}/companies/{symbol}/prices", headers=H, timeout=60,
                         params={"from": cur, "order": "asc", "limit": 5000})
        r.raise_for_status()
        j = r.json()
        bars = j["data"]["bars"]
        if not bars:
            break
        out += bars
        if not j["meta"]["hasMore"]:
            break
        # trang sau bắt đầu từ ngày kế tiếp ngày cuối của trang này
        cur = (pd.Timestamp(bars[-1]["date"]) + pd.Timedelta(days=1)).strftime("%Y-%m-%d")

    df = pd.DataFrame(out).drop_duplicates("date")
    df["date"] = pd.to_datetime(df["date"])
    return df.set_index("date")

print(prices("FPT").tail())

drop_duplicates ở đây là chốt chặn: nếu sau này endpoint đổi cách cắt trang, vòng lặp vẫn không nhân đôi ngày ở mép trang.

Công thức: vòng đồng bộ hoàn chỉnh

Chạy hằng ngày, chỉ kéo cái gì đã đổi

Ghép bước 8 thành một script chạy được. Đây là hình dạng cuối cùng của một tích hợp nghiêm túc.

import os, time, requests, pandas as pd
from datetime import datetime, timedelta, timezone

BASE = "https://api.dulieukinhte.com/v1"
H = {"Authorization": f"Bearer {os.environ['DLKT_API_KEY']}"}

def get(path, **params):
    """Một chỗ duy nhất xử lý lỗi cho mọi lượt gọi."""
    for attempt in range(5):
        r = requests.get(f"{BASE}{path}", headers=H, params=params, timeout=60)
        if r.status_code == 429:
            body = r.json()["error"]
            if body["code"] == "quota_exceeded":
                raise SystemExit(f"HẾT HẠN MỨC THÁNG: {body['message']}")
            time.sleep(int(r.headers.get("Retry-After", 5)))
            continue
        if r.status_code >= 500:
            time.sleep(2 ** attempt)   # lùi theo cấp số nhân
            continue
        if not r.ok:
            e = r.json()["error"]
            raise RuntimeError(f"{e['code']}: {e['message']} (requestId={e['requestId']})")
        return r.json()
    raise RuntimeError("thử lại 5 lần vẫn không xong")

# ── 1. Chuỗi nào đã đổi kể từ lần chạy trước? ───────────────────────────
since = (datetime.now(timezone.utc) - timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%SZ")
changed, page = [], 1
while True:
    j = get("/updates", since=since, page=page, perPage=500)
    changed += [u["seriesId"] for u in j["data"]]
    if not j["meta"]["hasMore"]:
        break
    page += 1

print(f"{len(changed)} chuỗi có số mới")

# ── 2. Kéo về, 20 chuỗi mỗi lượt ────────────────────────────────────────
frames = []
for i in range(0, len(changed), 20):
    j = get("/series/observations", ids=",".join(map(str, changed[i:i + 20])),
            limit=2000, **{"from": "2020-01-01"})
    for s in j["data"]:
        if s.get("skipped"):
            continue
        for o in s["observations"]:
            frames.append({"series_id": s["id"], "name": s["name"],
                           "date": o["date"], "value": o["value"]})

pd.DataFrame(frames).to_parquet("cap-nhat.parquet")
print(f"ghi {len(frames)} điểm")

Xử lý lỗi cho đúng

Mọi lỗi trả về cùng một hình dạng, với mã chuỗi máy đọc được:

{
  "error": {
    "code": "quota_exceeded",
    "message": "Đã dùng hết 100 yêu cầu của gói Free trong kỳ này.",
    "requestId": "3f2a8c1e…",
    "detail": { "used": 100, "limit": 100, "resetAt": "2026-09-01T00:00:00.000Z" }
  }
}

Bắt theo error.code, đừng bắt theo số HTTP. Lý do nằm ngay ở hai mã 429 dưới đây: cùng số, ngược nhau hoàn toàn về cách xử lý.

codeHTTPNên làm gì trong code
unauthorized401Thiếu header API key. Không thử lại — sửa cấu hình.
invalid_key401API key sai hoặc đã xoá. Không thử lại.
key_revoked401Key đã bị thu hồi — cấp key mới. Không thử lại.
account_suspended403Dừng và báo người vận hành. Liên hệ hỗ trợ.
ip_not_allowed403IP ngoài danh sách của key. Sửa danh sách trong trang Khoá API.
plan_required403Nâng gói. detail.requiredScope nói cần nhóm quyền nào.
bad_request400Sai tham số — message nói rõ sai chỗ nào và giá trị hợp lệ là gì.
not_found404Không tồn tại. Kiểm lại id.
payload_too_large413Giảm limit hoặc thu hẹp khoảng thời gian.
rate_limited429Đợi Retry-After giây rồi thử lại chính lượt đó.
quota_exceeded429Đừng thử lại. Hết hạn mức tháng — dừng job, nâng gói hoặc đợi kỳ mới.
internal500Thử lại có lùi. Lặp lại thì gửi requestId cho chúng tôi.
Sai — bắt theo số
if r.status_code == 429:
    time.sleep(60)
    retry()

Hết hạn mức tháng cũng là 429. Đoạn này sẽ thử lại mỗi phút, suốt đến hết tháng, và không lượt nào thành công.

Đúng — bắt theo mã
if r.status_code == 429:
    c = r.json()["error"]["code"]
    if c == "rate_limited":
        time.sleep(retry_after); retry()
    else:
        raise SystemExit(...)

“Chậm lại” thì đợi vài giây. “Hết tháng” thì dừng hẳn và báo người vận hành.

requestId có mặt trong mọi phản hồi (header X-Request-Id) và trong mọi lỗi. Ghi nó vào log của bạn: khi báo lỗi kèm mã này, chúng tôi tra được đúng lượt gọi đó thay vì đoán.

Tiêu ít hạn mức đi

Năm kỹ thuật, xếp theo mức tiết kiệm giảm dần.

Kỹ thuậtThay vìCòn
Đồng bộ tăng dần qua /updatesKéo lại 32.787 chuỗi mỗi ngàyVài chục lượt/ngày
Gộp 20 chuỗi vào /series/observations20 lượt gọi1 lượt
Nâng limit sát trần của gói10 trang × 1.000 điểm1 trang × 10.000 điểm
Đọc header thay vì gọi /v1/me1 lượt kiểm tra + 1 lượt lấy số liệu1 lượt
Cache phía bạn theo tần suất chuỗiGọi lại chuỗi tháng 24 lần/ngày1 lần/ngày
Cache theo tần suất, không theo đồng hồ. Một chuỗi monthly chỉ đổi vài lần mỗi tháng — hỏi lại nó mỗi giờ là 700 lượt gọi để nhận về cùng một con số. Đọc frequency lastUpdated của chuỗi rồi đặt TTL cho tương xứng.

Lưu ý về đường bảo vệ: mọi phản hồi đều mang Cache-Control: private, no-store, nên CDN hay proxy dùng chung sẽ không giữ bản sao. Muốn cache thì cache trong ứng dụng của bạn.

Giới hạn theo gói

Giới hạnFreeProBusinessEnterprise
Lượt gọi / tháng (theo tài khoản)10010.000500.0005.000.000
Lượt gọi / phút (theo từng key)101006003.000
Số API key111050
Lịch sử5 nămđầy đủđầy đủđầy đủ
Điểm / lượt gọi (trần của limit)1.00010.00050.000200.000
Doanh nghiệp, /updates, CSV
Hỗ trợCộng đồngEmail trong 48hEmail trong 24hSLA theo hợp đồng

Hai giới hạn, hai cách đếm — đừng nhầm. Hạn mức tháng tính theo tài khoản: ba key dùng chung một hạn mức, tạo thêm key không nhân được hạn mức. Hạn mức phút tính theo từng key: tách key cho cron và cho notebook thì một đợt chạy nặng của cron không làm notebook của bạn bị chặn.

Phạm vi bán là toàn bộ 300 bộ số liệu có trên website — không bộ nào bị giữ lại, không có bản rút gọn riêng cho API. Giá và cách nâng gói ở trang Gói.

Bảy sự cố thường gặp

Triệu chứng
Nguyên nhân & cách sửa
401 unauthorized dù đã gửi API key
Thiếu chữ Bearer (có dấu cách) trước key, hoặc biến môi trường rỗng vì shell chưa nạp lại. Kiểm bằng echo $DLKT_API_KEY | head -c 12.
curl chỉ lấy được nửa URL
Thiếu dấu nháy kép quanh URL. Ký tự & trong shell đẩy lệnh xuống chạy nền — không có thông báo lỗi nào cả. Luôn viết curl "…".
Chuỗi có dữ liệu từ 1995, API chỉ trả từ 2021
Giới hạn lịch sử của gói. Kiểm historyLimited/series/{id}. Gói Free chỉ 5 năm gần nhất.
transform=yoy trả 400
Chuỗi theo ngày không suy được. message liệt kê đúng các phép hợp lệ cho tần suất của chuỗi đó.
Số điểm ít hơn số kỳ mong đợi
Đúng như thiết kế: kỳ thiếu dữ kiện gốc bị loại thay vì trả null. Dừng vòng lặp theo meta.hasMore, đừng theo count < limit.
to=2026-05-31 không ra Q2/2026
Quý neo ở ngày 1 tháng cuối quý: Q2/2026 nằm ở 2026-06-01. Nới to đến hết tháng cuối quý.
Lệch đúng một kỳ ở biên khoảng
Code của bạn sinh ngày theo UTC; API hiểu from/to theo giờ Việt Nam (UTC+7). Dùng chuỗi YYYY-MM-DD nguyên bản.

Còn một loại 429 nữa không nằm trong bảng trên: gọi không có API key hoặc với API key sai quá 30 lần/phút từ một IP cũng bị chặn. Nếu gặp lúc đang thử nghiệm, thường là do biến môi trường rỗng nên mọi lượt gọi đều đi ra mà không mang key.

Trước khi lên production

  • API key nằm trong biến môi trường, không nằm trong git.
  • API key của production tách riêng khỏi key của dev và của notebook.
  • Đã khai IP cho phép cho key chạy trên máy chủ cố định.
  • Code phân biệt rate_limited với quota_exceeded, không chỉ bắt số 429.
  • Thử lại có lùi theo cấp số nhân cho 5xx, và có trần số lần thử.
  • Ghi X-Request-Id vào log ứng dụng khi có lỗi.
  • Theo dõi X-Quota-Remaining, cảnh báo khi xuống dưới 20%.
  • Vòng lặp phân trang dừng theo meta.hasMore, và có trần số vòng chống lặp vô hạn.
  • Job đồng bộ chạy qua /updates, không quét lại toàn bộ danh mục.
  • Khi công bố lại số liệu, đã dẫn nguồn gốc (source, sourceUrl trả kèm mỗi chuỗi).
Về việc dẫn nguồn. Phần lớn dữ liệu đến từ cơ quan thống kê nhà nước, ngân hàng trung ương, hiệp hội ngành và các cổng số liệu chuyên ngành. Mỗi chuỗi trả kèm sourcesourceUrl — khi công bố lại, hãy dẫn nguồn gốc đó chứ không phải dẫn chúng tôi.

Tra cứu nhanh Một màn hình

Gốc: https://api.dulieukinhte.com/v1. Chỉ GET, chỉ JSON và CSV. Không SDK bắt buộc, không OAuth, không token hết hạn.

Ba lệnh đầu tiên

Kiểm khoá → tra mã → lấy số
# 1. khoá này của ai, gói gì, còn bao nhiêu hạn mức
curl -s -H "Authorization: Bearer $DLKT_API_KEY" "https://api.dulieukinhte.com/v1/me"

# 2. tra mã chuỗi theo tên (nhớ -G + --data-urlencode cho tiếng Việt)
curl -s -H "Authorization: Bearer $DLKT_API_KEY"   --data-urlencode "q=chỉ số giá tiêu dùng" --data-urlencode "type=series"   -G "https://api.dulieukinhte.com/v1/search"

# 3. kéo số liệu, kèm phép biến đổi nếu cần
curl -s -H "Authorization: Bearer $DLKT_API_KEY"   "https://api.dulieukinhte.com/v1/series/18692/observations?from=2015-01-01&transform=yoy"

Gửi API key

Authorization: Bearer dlkt_live_…    # nên dùng
X-API-Key: dlkt_live_…               # tương đương

Cách thứ ba, ?api_key= trong URL, chỉ dùng được ở observations, financials, prices và chỉ nên dùng khi công cụ không gửi được header (Excel, Sheets) — xem công thức Excel & Sheets. Thu hồi khoá có hiệu lực trong vòng 60 giây.

Toàn bộ header của phản hồi

Bốn dòng in đậm là bốn dòng đáng đọc trong code — vì sao thì ở bước 5.

HeaderNghĩa
X-RateLimit-LimitLượt gọi/phút của gói, tính theo từng key.
X-RateLimit-RemainingCòn lại trong phút này.
X-RateLimit-Reset60 khi còn hạn (độ dài cửa sổ trượt); số giây phải đợi khi đã chạm trần.
X-Quota-LimitLượt gọi/tháng của gói, tính theo tài khoản.
X-Quota-RemainingCòn lại trong kỳ. Cảnh báo khi xuống dưới 20%.
X-Quota-ResetEpoch giây, thời điểm sang kỳ mới.
X-Request-IdCó ở mọi phản hồi. Ghi vào log — gửi kèm khi báo lỗi thì chúng tôi tra được đúng lượt gọi đó.
Retry-AfterChỉ xuất hiện khi 429: đợi bao nhiêu giây rồi thử lại.

Kỳ hạn mức là tháng dương lịch giờ Việt Nam (UTC+7), đặt lại 00:00 ngày 1. Mọi phản hồi đều mang Cache-Control: private, no-store — CDN và proxy dùng chung không giữ bản sao, muốn cache thì cache trong ứng dụng của bạn.

Cần gì thì xem ở đâu

Bạn cần
Ở mục nào
Tham số của observations
Bước 5 — bảng đầy đủ from, to, limit, cursor, order, transform, format kèm mặc định và trần theo gói.
Phân trang
Bước 6 — danh mục dùng page/perPage, số liệu dùng cursor. Kèm hai bẫy hay gặp nhất của vòng lặp.
yoy, mom, ttm… và quy ước kỳ
Bước 7 — bảy phép biến đổi, tần suất nào dùng được, và chuyện quý neo ở tháng cuối.
Mã lỗi
Xử lý lỗi cho đúng — mười hai mã, kèm cách xử lý đúng cho từng mã. Hai mã 429 ngược nhau hoàn toàn.
Gói nào có gì
Giới hạn theo gói — hạn mức, số key, lịch sử, quyền doanh nghiệp/CSV/updates.
Dẫn nguồn khi công bố lại
Mỗi chuỗi trả kèm sourcesourceUrl — dẫn nguồn gốc đó, không phải dẫn chúng tôi. Xem Trước khi lên production.

Mười lăm endpoint, mỗi cái một ví dụ

Tất cả đều là GET, tất cả đều bắt đầu bằng https://api.dulieukinhte.com/v1, và tất cả đều gửi API key theo cùng một cách:

Dạng chung của mọi lệnh gọi
curl -s -H "Authorization: Bearer $DLKT_API_KEY" "<URL bên dưới>"

Các URL dưới đây dùng số thật: 272 là bộ “Chỉ số giá tiêu dùng (CPI)”, 18692 là dòng CPI chung của bộ đó. Mã của chỉ tiêu bạn cần thì tra bằng /search ở bước 4.

Kiểm tra

/health
API còn sống không, DB còn nối không. Không cần API key và không tính hạn mức — dùng thoải mái trong healthcheck.
/me
Key này của ai, gói gì, còn bao nhiêu hạn mức. Gọi đầu tiên khi tích hợp. Có tính 1 lượt.

Tìm mã

/search?q=chỉ số giá tiêu dùng&type=dataset
Tra mã theo tên. Trả mảng kết quả, mỗi cái có id, name, frequency, webUrl. Đây gần như luôn là lệnh gọi ĐẦU TIÊN của bạn. Nhớ URL-encode tiếng Việt — xem bước 4.
/economies
Bốn nền kinh tế đang có (VN, US, EU, CN) và số bộ số liệu của mỗi nền.

Duyệt danh mục

/datasets?economy=VN&perPage=50&page=1
Danh mục bộ số liệu, có metadata (tần suất, đơn vị, nguồn, lastUpdated) nhưng KHÔNG kèm giá trị. meta.total cho biết tổng số.
/datasets/272
Một bộ + TOÀN BỘ cây chuỗi con. Đây là chỗ bạn lấy được seriesId để gọi observations.
/datasets/272/series?perPage=100&page=1
Cùng danh sách chuỗi con nhưng CÓ phân trang. Dùng khi bộ có hàng nghìn dòng.
/series/18692
Hồ sơ một chuỗi: tên, đường dẫn trong cây, đơn vị, nguồn, và firstDate / lastDate / count đã tính theo gói của bạn.

Lấy số liệu

/series/18692/observations?from=2015-01-01&transform=yoy
ENDPOINT CHÍNH. Trả mảng { date, period, value }. Phân trang bằng cursor (meta.nextCursor).
/series/observations?ids=18692,18693,18694&from=2020-01-01
Tối đa 20 chuỗi trong MỘT lượt gọi, và vẫn chỉ tính 1 lượt hạn mức. Cách rẻ nhất để lấy nhiều chuỗi.
/series/18692/observations?format=csv&from=2015-01-01
Cùng dữ liệu ở dạng CSV có BOM UTF-8 (Excel đọc đúng tiếng Việt). Cần gói Pro trở lên.
/updates?since=2026-08-28T00:00:00Z&perPage=500
Chuỗi nào vừa có số mới. since lùi tối đa 90 ngày. Xương sống của việc đồng bộ hằng ngày. Gói Pro trở lên.

Doanh nghiệp niêm yết

/companies?exchange=HOSE&perPage=50
Danh mục mã niêm yết trên HOSE / HNX / UPCOM.
/companies/FPT
Hồ sơ một doanh nghiệp: sàn, ngành, có sẵn những loại báo cáo nào, dải giá đến đâu.
/companies/FPT/financials?statementType=2&freq=quarter
Báo cáo tài chính. statementType 1=cân đối, 2=kết quả kinh doanh, 3=lưu chuyển tiền tệ, 4=chỉ số, 5=báo cáo tháng. Trả ba mảng lines / periods / values.
/companies/FPT/prices?from=2024-01-01&order=asc
Giá OHLCV theo ngày. Đi trang bằng from (endpoint này không nhận cursor).

Muốn dựng URL với tham số của riêng bạn thì dùng bộ dựng yêu cầu ở trên — nó sinh sẵn lệnh curl, Python và JavaScript.

Đi tiếp

  • Tra cứu nhanh — ba lệnh đầu, cách gửi khoá và toàn bộ header, gói trong một màn hình cho lúc đã quen tay.
  • dulieukinhte-api.md — toàn bộ tài liệu trong một file, dành cho trợ lý AI đọc. Xem mục Nhờ AI hướng dẫn bạn.
  • Đặc tả OpenAPI 3.1 — dán vào Swagger UI, Scalar, Postman hoặc bộ sinh client để có SDK cho ngôn ngữ của bạn.
  • dulieukinhte.com — xem số liệu bằng mắt trước khi viết code. Thường là cách nhanh nhất để tìm id.
  • Đăng ký — gói Free miễn phí, 100 lượt/tháng, không cần thẻ.
Vướng ở đâu đó?

Gửi thư kèm requestId tới [email protected] — chúng tôi tra được đúng lượt gọi đó trong log thay vì đoán.