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.
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:
Đâ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.
curl -s \ -H "Authorization: Bearer $DLKT_API_KEY" \ "https://api.dulieukinhte.com/v1/series/18692/observations"
{ "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 } }?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.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.
Đ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.
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).
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.Lấy API key
Mở trang Khoá API → Tạ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.
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ố, - và _ — 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.
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í.
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.
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:
/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, financials và prices — 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.
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.
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.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.
Đườ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.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 |
|---|---|---|
| from | — | YYYY-MM-DD. Lọc theo kỳ, hiểu theo giờ Việt Nam. |
| to | — | YYYY-MM-DD, bao gồm cả mốc này. |
| limit | 1000 | Trần theo gói: 1.000 (Free) / 10.000 (Pro) / 50.000 (Business). |
| cursor | — | Nguyên văn meta.nextCursor của lượt trước. Xem bước 6. |
| order | asc | asc cũ→mới, desc mới→cũ. |
| transform | none | yoy mom qoq ttm ttm_yoy ytd ytd_yoy. Xem bước 7. |
| format | json | json | csv. CSV cần gói Pro trở lên. |
Đọc phản hồi
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
| Header | Nghĩa |
|---|---|
| X-RateLimit-Remaining | Còn bao nhiêu lượt trong phút này, của key này. |
| X-Quota-Remaining | Còn bao nhiêu lượt trong tháng này, của cả tài khoản. |
| X-Quota-Reset | Epoch giây, thời điểm sang kỳ mới. |
| X-Request-Id | Ghi 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. |
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.
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.
datasets, datasets/{id}/series, companies, updates.
Có meta.total và meta.totalPages, nhảy trang tuỳ ý.
series/{id}/observations.
Không có total. Đi tuần tự bằng meta.nextCursor, dừng khi hasMore là false.
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
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.
Để 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 mà 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"
| transform | Nghĩa | Đơn vị trả về | Tần suất dùng được |
|---|---|---|---|
| yoy | so cùng kỳ năm trước | % | tháng, quý, năm |
| mom | so tháng trước | % | chỉ tháng |
| qoq | so quý trước | % | chỉ quý |
| ttm | luỹ kế 12 tháng gần nhất | đơn vị gốc | tháng, quý |
| ttm_yoy | TTM so cùng kỳ | % | tháng, quý |
| ytd | luỹ kế từ đầu năm | đơn vị gốc | tháng, quý |
| ytd_yoy | YTD 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=yoythì đ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ất | date neo ở | period |
|---|---|---|
| tháng | ngày 1 của tháng | 2026-06 |
| quý | ngày 1 tháng cuối quý | 2026-Q2 |
| năm | ngày 1 của năm | 2026 |
| ngày | chính ngày đó | 2026-06-15 |
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.
Đồ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 /updates | Ghi chú |
|---|---|
| since | Bắ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, perPage | Mặc định 100, trần 500. Có meta.total. |
Mỗi mục trả về seriesId, changedAt, pointsChanged và latestPeriod — đủ để 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.
=IMPORTDATA("https://api.dulieukinhte.com/v1/series/8241/observations?format=csv&from=2015-01-01&api_key=dlkt_live_…")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ộtseries_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.
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"
| statementType | Báo cáo |
|---|---|
| 1 | Cân đối kế toán |
| 2 | Kết quả kinh doanh (mặc định) |
| 3 | Lưu chuyển tiền tệ |
| 4 | Chỉ số tài chính |
| 5 | Bá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ý.
| code | HTTP | Nên làm gì trong code |
|---|---|---|
| unauthorized | 401 | Thiếu header API key. Không thử lại — sửa cấu hình. |
| invalid_key | 401 | API key sai hoặc đã xoá. Không thử lại. |
| key_revoked | 401 | Key đã bị thu hồi — cấp key mới. Không thử lại. |
| account_suspended | 403 | Dừng và báo người vận hành. Liên hệ hỗ trợ. |
| ip_not_allowed | 403 | IP ngoài danh sách của key. Sửa danh sách trong trang Khoá API. |
| plan_required | 403 | Nâng gói. detail.requiredScope nói cần nhóm quyền nào. |
| bad_request | 400 | Sai tham số — message nói rõ sai chỗ nào và giá trị hợp lệ là gì. |
| not_found | 404 | Không tồn tại. Kiểm lại id. |
| payload_too_large | 413 | Giảm limit hoặc thu hẹp khoảng thời gian. |
| rate_limited | 429 | Đợi Retry-After giây rồi thử lại chính lượt đó. |
| quota_exceeded | 429 | Đừ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. |
| internal | 500 | Thử lại có lùi. Lặp lại thì gửi requestId cho chúng tôi. |
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.
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ật | Thay vì | Còn |
|---|---|---|
Đồng bộ tăng dần qua /updates | Kéo lại 32.787 chuỗi mỗi ngày | Vài chục lượt/ngày |
Gộp 20 chuỗi vào /series/observations | 20 lượt gọi | 1 lượt |
Nâng limit sát trần của gói | 10 trang × 1.000 điểm | 1 trang × 10.000 điểm |
Đọc header thay vì gọi /v1/me | 1 lượt kiểm tra + 1 lượt lấy số liệu | 1 lượt |
| Cache phía bạn theo tần suất chuỗi | Gọi lại chuỗi tháng 24 lần/ngày | 1 lần/ngày |
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 và 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ạn | Free | Pro | Business | Enterprise |
|---|---|---|---|---|
| Lượt gọi / tháng (theo tài khoản) | 100 | 10.000 | 500.000 | 5.000.000 |
| Lượt gọi / phút (theo từng key) | 10 | 100 | 600 | 3.000 |
| Số API key | 1 | 1 | 10 | 50 |
| Lịch sử | 5 năm | đầy đủ | đầy đủ | đầy đủ |
Điểm / lượt gọi (trần của limit) | 1.000 | 10.000 | 50.000 | 200.000 |
Doanh nghiệp, /updates, CSV | — | có | có | có |
| Hỗ trợ | Cộng đồng | Email trong 48h | Email trong 24h | SLA 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
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_limitedvớiquota_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-Idvà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,sourceUrltrả kèm mỗi chuỗi).
source và sourceUrl — 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
# 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.
| Header | Nghĩa |
|---|---|
| X-RateLimit-Limit | Lượt gọi/phút của gói, tính theo từng key. |
| X-RateLimit-Remaining | Còn lại trong phút này. |
| X-RateLimit-Reset | 60 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-Limit | Lượt gọi/tháng của gói, tính theo tài khoản. |
| X-Quota-Remaining | Còn lại trong kỳ. Cảnh báo khi xuống dưới 20%. |
| X-Quota-Reset | Epoch giây, thời điểm sang kỳ mới. |
| X-Request-Id | Có ở 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-After | Chỉ 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
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:
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
Tìm mã
Duyệt danh mục
Lấy số liệu
Doanh nghiệp niêm yết
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ẻ.
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.