{"openapi":"3.1.0","info":{"title":"doanhnghiep.vn API","version":"1.0.0","description":"Public REST JSON API for Vietnamese company KYB data. Surfaces only registry-grade fields (no PDPL-sensitive personal data). Rate limit: 60 req/min per IP, shared across /api/v1/*.","contact":{"name":"doanhnghiep.vn","url":"https://doanhnghiep.vn/lien-he"},"license":{"name":"Public-data ToS","url":"https://doanhnghiep.vn/dieu-khoan"}},"servers":[{"url":"https://doanhnghiep.vn/api/v1","description":"Production"}],"components":{"schemas":{"Province":{"type":"object","properties":{"code":{"type":"string","example":"VN-HN"},"name_vi":{"type":"string","example":"Hà Nội"},"slug":{"type":"string","example":"ha-noi"},"region":{"type":"string","nullable":true,"example":"Đồng bằng sông Hồng"}},"required":["code","name_vi","slug"]},"IndustryRef":{"type":"object","properties":{"code":{"type":"string","example":"J","description":"VSIC level-1 code"},"name_vi":{"type":"string","example":"Thông tin và truyền thông"},"name_en":{"type":"string","nullable":true}},"required":["code","name_vi"]},"District":{"type":"object","properties":{"name_vi":{"type":"string","example":"Quận Cầu Giấy"}},"required":["name_vi"]},"Industry":{"type":"object","properties":{"code":{"type":"string","example":"J6201","description":"VSIC code"},"name_vi":{"type":"string","example":"Lập trình máy vi tính","nullable":true}},"required":["code"]},"CompanyDetail":{"type":"object","properties":{"mst":{"type":"string","example":"0101248141","description":"10 or 13 digits"},"name_vi":{"type":"string","example":"Công ty Cổ phần FPT"},"name_en":{"type":"string","nullable":true},"legal_form":{"type":"string","nullable":true,"example":"Cổ phần"},"registered_at":{"type":"string","nullable":true,"example":"1988-09-13","description":"YYYY-MM-DD in Asia/Ho_Chi_Minh"},"status":{"type":"string","enum":["active","suspended","dissolved"]},"charter_capital_vnd":{"type":"string","nullable":true,"description":"BigInt as numeric string (JSON round-trip safety)","example":"12500000000000"},"address_full":{"type":"string","nullable":true},"legal_rep_name":{"type":"string","nullable":true},"is_listed":{"type":"boolean"},"listed_exchange":{"type":["string","null"],"enum":["HOSE","HNX","UPCOM",null]},"listed_ticker":{"type":"string","nullable":true},"first_trade_date":{"type":"string","nullable":true,"example":"2009-06-30"},"data_freshness_at":{"type":"string","nullable":true,"format":"date-time"},"province":{"allOf":[{"$ref":"#/components/schemas/Province"}],"nullable":true},"district":{"allOf":[{"$ref":"#/components/schemas/District"}],"nullable":true},"industry":{"allOf":[{"$ref":"#/components/schemas/Industry"}],"nullable":true},"industry_main_code":{"type":"string","nullable":true,"deprecated":true},"related":{"type":"array","description":"Up to 3 same-industry-prefix peers (excluding the queried company). Empty when industry is null. mst + name_vi only required; is_listed nullable to distinguish unknown from a verified false. Call /api/v1/companies/{mst} for full peer detail.","items":{"type":"object","properties":{"mst":{"type":"string"},"name_vi":{"type":"string"},"is_listed":{"type":"boolean","nullable":true},"listed_ticker":{"type":"string","nullable":true}},"required":["mst","name_vi"]}}},"required":["mst","name_vi","status","is_listed","related"]},"CompanyListItem":{"type":"object","description":"List response items. Includes nested industry/district objects PLUS flat industry_main_code/district_name for backward compatibility with v1.0 clients.","properties":{"mst":{"type":"string"},"name_vi":{"type":"string"},"legal_form":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","suspended","dissolved"]},"charter_capital_vnd":{"type":"string","nullable":true},"registered_at":{"type":"string","nullable":true},"is_listed":{"type":"boolean"},"listed_exchange":{"type":["string","null"],"enum":["HOSE","HNX","UPCOM",null]},"listed_ticker":{"type":"string","nullable":true},"legal_rep_name":{"type":"string","nullable":true},"industry":{"allOf":[{"$ref":"#/components/schemas/Industry"}],"nullable":true},"industry_main_code":{"type":"string","nullable":true,"deprecated":true},"district":{"allOf":[{"$ref":"#/components/schemas/District"}],"nullable":true},"district_name":{"type":"string","nullable":true,"deprecated":true}}},"EventChange":{"type":"object","properties":{"field":{"type":"string"},"label":{"type":"string","description":"Vietnamese display label"},"before":{"type":"string"},"after":{"type":"string"}}},"Event":{"type":"object","properties":{"id":{"type":"string","description":"BigInt as string for DB-backed events. Synthetic ids prefixed 'mock-' for pre-launch placeholder rows."},"event_type":{"type":"string","example":"capital_changed","description":"One of: capital_changed, name_changed, address_changed, status_changed, ipo_listed, owner_changed, lawsuit_filed, tax_debt_flagged, news_mentioned, registered, suspended, dissolved"},"occurred_at":{"type":"string","example":"2026-04-22","description":"YYYY-MM-DD in Asia/Ho_Chi_Minh"},"observed_at":{"type":"string","format":"date-time"},"source_url":{"type":"string","nullable":true},"changes":{"type":"array","items":{"$ref":"#/components/schemas/EventChange"}}}},"Pagination":{"type":"object","properties":{"page":{"type":"integer","minimum":1},"page_size":{"type":"integer","minimum":1,"maximum":100},"total":{"type":"integer","minimum":0},"page_count":{"type":"integer","minimum":1}}},"Error":{"type":"object","properties":{"error":{"type":"string","enum":["invalid_mst","invalid_filter","not_found","rate_limited","server_error"]},"detail":{"type":"string","nullable":true},"retry_after_seconds":{"type":"integer","nullable":true,"description":"Only set on 429 rate_limited"},"mst":{"type":"string","nullable":true,"description":"Echo of the MST that triggered the error. Set on 404 from /companies/{mst} and /companies/{mst}/events; omitted otherwise."}},"required":["error"]}},"headers":{"RateLimitLimit":{"schema":{"type":"integer"},"description":"Per-IP requests allowed in the window. Currently 60.","example":60},"RateLimitRemaining":{"schema":{"type":"integer"},"description":"Requests remaining in the current window.","example":59},"RateLimitReset":{"schema":{"type":"integer"},"description":"Unix epoch seconds when the window resets."}},"responses":{"RateLimited":{"description":"Rate limit exceeded (60 req/min/IP shared across /api/v1/*)","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"},"retry-after":{"schema":{"type":"integer"},"description":"Seconds until the rate-limit window resets (mirrors retry_after_seconds in the body)."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":{"MstPath":{"name":"mst","in":"path","required":true,"schema":{"type":"string","pattern":"^\\d{10}(?:-?\\d{3})?$"},"description":"Mã số thuế — 10 or 13 digits"},"Page":{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},"PageSize":{"name":"page_size","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}}},"paths":{"/stats":{"get":{"summary":"Aggregate metrics for the indexed companies dataset","description":"Total + status breakdown + listed count + top 10 provinces/industries by company count. Reference-style endpoint: not rate-limited, public-cached 1h.","responses":{"200":{"description":"OK — public-cached aggregate stats","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer","minimum":0},"status":{"type":"object","properties":{"active":{"type":"integer"},"suspended":{"type":"integer"},"dissolved":{"type":"integer"}}},"listed":{"type":"integer"},"latest_data_freshness_at":{"type":"string","format":"date-time","nullable":true},"top_provinces":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","nullable":true},"name_vi":{"type":"string","nullable":true},"slug":{"type":"string","nullable":true},"count":{"type":"integer"}}}},"top_industries":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","nullable":true},"name_vi":{"type":"string","nullable":true},"count":{"type":"integer"}}}}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/industries":{"get":{"summary":"VSIC level-1 industry reference list","description":"Use these codes as the ?industry= filter on /companies. Roll-up: 'J' matches all J* leaves.","responses":{"200":{"description":"OK — public-cached reference data (Cache-Control: public, max-age=3600). NOT rate-limited; does not emit x-ratelimit-* headers.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/IndustryRef"}}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/provinces":{"get":{"summary":"Administrative units (province / TP) reference list","description":"Use the slug as the ?province= filter on /companies. Grouped by region for UI display.","responses":{"200":{"description":"OK — public-cached reference data (Cache-Control: public, max-age=3600). NOT rate-limited; does not emit x-ratelimit-* headers.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Province"}}}}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/search":{"get":{"summary":"Fuzzy free-text search","description":"Meilisearch-backed (typo-tolerance, ranking, freshness). MST-shaped queries short-circuit to Postgres findUnique. For bulk filtered listing use /companies?province= or industry=.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1},"description":"Search term — MST, company name, or legal rep name."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":20,"default":10}},{"name":"status","in":"query","schema":{"type":"string","enum":["active","suspended","dissolved"]},"description":"Narrow results to companies with this status."},{"name":"is_listed","in":"query","schema":{"type":"string","enum":["true","false","1","0"]},"description":"Narrow to listed (or unlisted) companies."},{"name":"industry","in":"query","schema":{"type":"string","pattern":"^[A-Za-z][A-Za-z0-9]*$"},"description":"VSIC prefix filter (J → all J*, J62 → J62*, etc.). Same semantics as /companies."},{"name":"province","in":"query","schema":{"type":"string"},"description":"Province slug (e.g. 'ha-noi') OR code ('VN-HN'). Either accepted."}],"responses":{"200":{"description":"OK","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","properties":{"q":{"type":"string"},"limit":{"type":"integer"},"count":{"type":"integer"},"filters":{"type":"object","nullable":true,"description":"Echo of active filters when any of status/is_listed/industry/province are set. null when no filters were applied.","properties":{"status":{"type":"string","nullable":true,"enum":["active","suspended","dissolved",null]},"is_listed":{"type":"boolean","nullable":true},"industry":{"type":"string","nullable":true},"province":{"type":"string","nullable":true,"description":"The raw province slug or code as submitted by the caller."}}},"items":{"type":"array","items":{"type":"object","properties":{"mst":{"type":"string"},"name_vi":{"type":"string"},"legal_form":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","suspended","dissolved"]},"charter_capital_vnd":{"type":"string","nullable":true},"registered_at":{"type":"string","nullable":true},"is_listed":{"type":"boolean"},"listed_exchange":{"type":["string","null"],"enum":["HOSE","HNX","UPCOM",null]},"listed_ticker":{"type":"string","nullable":true}}}}}}}}},"400":{"description":"Missing q","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/companies":{"get":{"summary":"List companies with filters","parameters":[{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/PageSize"},{"name":"province","in":"query","schema":{"type":"string"},"description":"Province slug (e.g. 'ha-noi'). Exact match."},{"name":"industry","in":"query","schema":{"type":"string","pattern":"^[A-Za-z][A-Za-z0-9]*$"},"description":"VSIC code prefix. 'J' matches all J*, 'J62' matches J62*, 'J6201' matches that leaf + sub-leaves."},{"name":"format","in":"query","schema":{"type":"string","enum":["json","csv"],"default":"json"},"description":"Response format. csv returns text/csv with UTF-8 BOM; pagination metadata moves to x-total-count / x-page-count / x-page / x-page-size headers (exposed via CORS)."},{"name":"status","in":"query","schema":{"type":"string","enum":["active","suspended","dissolved"]},"description":"Exact status match."},{"name":"is_listed","in":"query","schema":{"type":"string","enum":["true","false","1","0"]},"description":"Filter by listed-on-exchange flag. Accepts true/false or 1/0."},{"name":"min_capital_vnd","in":"query","schema":{"type":"string","pattern":"^\\d+$"},"description":"Minimum charter capital in VND (BigInt — pass as numeric string)."},{"name":"max_capital_vnd","in":"query","schema":{"type":"string","pattern":"^\\d+$"},"description":"Maximum charter capital in VND (BigInt — pass as numeric string)."},{"name":"sort","in":"query","schema":{"type":"string","enum":["freshness","capital_desc","capital_asc","registered_desc","registered_asc"],"default":"freshness"},"description":"Sort key. Each picks a deterministic tertiary by id desc to break ties."},{"name":"mst","in":"query","schema":{"type":"array","items":{"type":"string","pattern":"^\\d{10}(?:-?\\d{3})?$"},"maxItems":50},"style":"form","explode":true,"description":"Filter to specific MSTs. Repeatable: ?mst=A&mst=B&mst=C. Also accepts comma-joined: ?mst=A,B,C. Max 50 unique per request (deduped server-side, dups don't count toward the cap). When set without explicit page_size, the implicit default rises from 20 to the deduped batch size (capped at 100) so the entire batch returns in one page."}],"responses":{"200":{"description":"OK","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"},"x-total-count":{"schema":{"type":"integer"},"description":"csv only"},"x-page-count":{"schema":{"type":"integer"},"description":"csv only"},"x-page":{"schema":{"type":"integer"},"description":"csv only"},"x-page-size":{"schema":{"type":"integer"},"description":"csv only"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"type":"object","properties":{"filters":{"type":"object","properties":{"province":{"type":"string","nullable":true},"industry":{"type":"string","nullable":true},"status":{"type":"string","nullable":true,"enum":["active","suspended","dissolved",null]},"is_listed":{"type":"boolean","nullable":true},"min_capital_vnd":{"type":"string","nullable":true},"max_capital_vnd":{"type":"string","nullable":true},"sort":{"type":"string","enum":["freshness","capital_desc","capital_asc","registered_desc","registered_asc"]},"mst":{"type":"array","nullable":true,"items":{"type":"string"}}}},"items":{"type":"array","items":{"$ref":"#/components/schemas/CompanyListItem"}}}}]}},"text/csv":{"schema":{"type":"string","description":"UTF-8 BOM + RFC-4180 quoted CSV. Header row: mst, name_vi, legal_form, status, charter_capital_vnd, registered_at, is_listed, listed_ticker, legal_rep_name, industry_code, industry_name_vi, district_name, listed_exchange. listed_exchange (HOSE/HNX/UPCOM/empty) appended at the end so existing positional consumers stay backward-compatible. Pagination metadata in response headers (x-total-count etc.)."}}}},"400":{"description":"Invalid filter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/companies/{mst}":{"get":{"summary":"Company detail","parameters":[{"$ref":"#/components/parameters/MstPath"}],"responses":{"200":{"description":"OK","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyDetail"}}}},"400":{"description":"Invalid MST","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found — body includes `mst` echo of the looked-up identifier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/companies/{mst}/events":{"get":{"summary":"Change history (capital, name, address, status, ...)","parameters":[{"$ref":"#/components/parameters/MstPath"},{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/PageSize"},{"name":"format","in":"query","schema":{"type":"string","enum":["json","csv"],"default":"json"},"description":"csv flattens each event to one row; changes joined as 'label: before → after · ...'. Pagination via x-total-count/x-page-count/x-page/x-page-size headers."}],"responses":{"200":{"description":"OK","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"},"x-total-count":{"schema":{"type":"integer"},"description":"csv only"},"x-page-count":{"schema":{"type":"integer"},"description":"csv only"},"x-page":{"schema":{"type":"integer"},"description":"csv only"},"x-page-size":{"schema":{"type":"integer"},"description":"csv only"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Pagination"},{"type":"object","properties":{"mst":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/Event"}}}}]}},"text/csv":{"schema":{"type":"string","description":"UTF-8 BOM + RFC-4180 quoted CSV. Header row: mst, event_id, event_type, occurred_at, observed_at, source_url, changes. `changes` is the joined human-readable string 'label: before → after · ...'. Pagination metadata in response headers (x-total-count etc.)."}}}},"400":{"description":"Invalid MST","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found — body includes `mst` echo of the looked-up identifier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"externalDocs":{"description":"Hướng dẫn API (Vietnamese, with code examples)","url":"https://doanhnghiep.vn/api/docs"}}