Skip to main content

Vietnamese Market API Specifications

This document contains the reverse-engineered API specifications extracted from the vnstock Python library for implementing native Rust clients.

Overview​

ProviderBase URLData TypeAuth Required
VCI (Vietcap)https://trading.vietcap.com.vn/api/Stocks, IndicesNo
FMarkethttps://api.fmarket.vn/res/productsMutual FundsNo
SJChttps://sjc.com.vn/GoldPrice/Services/PriceService.ashxGold PricesNo

1. VCI (Vietcap) - Stocks & Indices​

1.1 Base Configuration​

Base URL: https://trading.vietcap.com.vn/api/
GraphQL URL: https://trading.vietcap.com.vn/data-mt/graphql

1.2 Required Headers​

let headers = [
("Accept", "application/json, text/plain, */*"),
("Accept-Language", "en-US,en;q=0.9,vi-VN;q=0.8,vi;q=0.7"),
("Content-Type", "application/json"),
("Referer", "https://trading.vietcap.com.vn/"),
("Origin", "https://trading.vietcap.com.vn/"),
("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36"),
];

1.3 Get All Symbols (Listing)​

Endpoint: GET /price/symbols/getAll

Response:

[
{
"id": 1,
"symbol": "VNM",
"board": "HSX", // Exchange: HSX, HNX, UPCOM
"type": "STOCK", // STOCK, ETF, BOND, etc.
"organName": "Công ty Cổ phần Sữa Việt Nam",
"organShortName": "Vinamilk",
"enOrganName": "Vietnam Dairy Products JSC",
"enOrganShortName": "Vinamilk"
}
]

Rust Implementation:

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VciSymbol {
pub symbol: String,
pub board: String, // HSX -> HOSE, HNX, UPCOM
#[serde(rename = "type")]
pub asset_type: String,
pub organ_name: String,
pub organ_short_name: Option<String>,
}

async fn get_all_symbols(client: &Client) -> Result<Vec<VciSymbol>, Error> {
let url = "https://trading.vietcap.com.vn/api/price/symbols/getAll";
let response = client.get(url).headers(VCI_HEADERS).send().await?;
response.json().await
}

1.4 Get Historical OHLC Data​

Endpoint: POST /chart/OHLCChart/gap-chart

Request Body:

{
"timeFrame": "ONE_DAY", // ONE_MINUTE, ONE_HOUR, ONE_DAY
"symbols": ["VNM"],
"to": 1735689600, // Unix timestamp (end date)
"countBack": 365 // Number of candles to fetch
}

TimeFrame Mapping:

const INTERVAL_MAP: [(&str, &str); 6] = [
("1m", "ONE_MINUTE"),
("5m", "ONE_MINUTE"), // Requires resampling
("15m", "ONE_MINUTE"), // Requires resampling
("1H", "ONE_HOUR"),
("1D", "ONE_DAY"),
("1W", "ONE_DAY"), // Requires resampling
];

Response:

[
{
"t": [1704067200, 1704153600, ...], // Timestamps
"o": [85.5, 86.0, ...], // Open prices (in 1000 VND)
"h": [86.2, 86.5, ...], // High prices
"l": [85.0, 85.5, ...], // Low prices
"c": [86.0, 86.2, ...], // Close prices
"v": [1500000, 1200000, ...] // Volume
}
]

⚠️ IMPORTANT: Price values are in 1000 VND units. Multiply by 1000 to get actual VND.

Rust Implementation:

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OhlcRequest {
pub time_frame: String,
pub symbols: Vec<String>,
pub to: i64,
pub count_back: i32,
}

#[derive(Deserialize)]
pub struct OhlcResponse {
pub t: Vec<i64>, // timestamps
pub o: Vec<f64>, // open (multiply by 1000)
pub h: Vec<f64>, // high
pub l: Vec<f64>, // low
pub c: Vec<f64>, // close
pub v: Vec<i64>, // volume
}

async fn get_stock_history(
client: &Client,
symbol: &str,
start: DateTime<Utc>,
end: DateTime<Utc>
) -> Result<Vec<Quote>, Error> {
let url = "https://trading.vietcap.com.vn/api/chart/OHLCChart/gap-chart";
let end_timestamp = end.timestamp();
let days = (end - start).num_days() + 1;

let payload = OhlcRequest {
time_frame: "ONE_DAY".to_string(),
symbols: vec![symbol.to_string()],
to: end_timestamp,
count_back: days as i32,
};

let response = client
.post(url)
.headers(VCI_HEADERS)
.json(&payload)
.send()
.await?;

let data: Vec<OhlcResponse> = response.json().await?;
// Transform and multiply prices by 1000
// ...
}

1.5 Index Symbol Mapping​

const INDEX_MAPPING: [(&str, &str); 3] = [
("VNINDEX", "VNINDEX"),
("HNXINDEX", "HNXIndex"),
("UPCOMINDEX", "HNXUpcomIndex"),
];

2. FMarket - Mutual Funds​

2.1 Base Configuration​

Base URL: https://api.fmarket.vn/res/products

2.2 Required Headers​

let headers = [
("Accept", "application/json, text/plain, */*"),
("Content-Type", "application/json"),
("Referer", "https://fmarket.vn/"),
("Origin", "https://fmarket.vn/"),
("User-Agent", "Mozilla/5.0 ..."),
];

2.3 Get Fund Listing​

Endpoint: POST /filter

Request Body:

{
"types": ["NEW_FUND", "TRADING_FUND"],
"issuerIds": [],
"sortOrder": "DESC",
"sortField": "navTo6Months",
"page": 1,
"pageSize": 100,
"isIpo": false,
"fundAssetTypes": [], // [], ["BALANCED"], ["BOND"], ["STOCK"]
"bondRemainPeriods": [],
"searchField": "",
"isBuyByReward": false,
"thirdAppIds": []
}

Response:

{
"data": {
"total": 50,
"rows": [
{
"id": 23,
"shortName": "VESAF",
"name": "Quỹ Đầu tư Cổ phiếu Việt Nam",
"code": "VESAFUND",
"dataFundAssetType": { "name": "STOCK" },
"owner": { "name": "VinaCapital" },
"nav": 25000.5,
"firstIssueAt": 1609459200000, // Unix ms
"productNavChange": {
"navToPrevious": 0.5,
"navTo1Months": 2.3,
"navTo3Months": 5.1,
"navTo6Months": 8.7,
"navTo12Months": 15.2,
"updateAt": 1704067200000
}
}
]
}
}

Rust Implementation:

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FundFilterRequest {
pub types: Vec<String>,
pub page: i32,
pub page_size: i32,
pub search_field: String,
// ... other fields
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FundInfo {
pub id: i32,
pub short_name: String,
pub name: String,
pub nav: Option<f64>,
}

2.4 Get Fund NAV History​

Endpoint: POST /product/get-nav-history

Request Body:

{
"isAllData": 0, // 0 = use date range, 1 = all data
"productId": 23, // Fund ID from listing
"fromDate": "20240101", // YYYYMMDD format
"toDate": "20241231" // YYYYMMDD format
}

Response:

{
"data": [
{
"navDate": "2024-01-15",
"nav": 25100.5
},
{
"navDate": "2024-01-16",
"nav": 25150.0
}
]
}

Rust Implementation:

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NavHistoryRequest {
pub is_all_data: i32,
pub product_id: i32,
pub from_date: String, // YYYYMMDD
pub to_date: String, // YYYYMMDD
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NavRecord {
pub nav_date: String,
pub nav: f64,
}

async fn get_fund_nav_history(
client: &Client,
fund_id: i32,
start: &str, // YYYY-MM-DD
end: &str
) -> Result<Vec<NavRecord>, Error> {
let url = "https://api.fmarket.vn/res/product/get-nav-history";

let payload = NavHistoryRequest {
is_all_data: 0,
product_id: fund_id,
from_date: start.replace("-", ""),
to_date: end.replace("-", ""),
};

let response = client.post(url).json(&payload).send().await?;
let result: ApiResponse<Vec<NavRecord>> = response.json().await?;
Ok(result.data)
}

2.5 Get Fund Details​

Endpoint: GET /{fundId}

Response: Contains top holdings, industry allocation, asset holdings.


3. SJC - Gold Prices​

3.1 Base Configuration​

URL: https://sjc.com.vn/GoldPrice/Services/PriceService.ashx
Data Available: From 2016-01-02

3.2 Required Headers​

let headers = [
("Content-Type", "application/x-www-form-urlencoded"),
("Referer", "https://sjc.com.vn/bieu-do-gia-vang"),
("Origin", "https://sjc.com.vn"),
("User-Agent", "Mozilla/5.0 ..."),
];

3.3 Get Gold Price by Date​

Endpoint: POST /

Request Body (form-urlencoded):

method=GetSJCGoldPriceByDate&toDate=15/01/2024

Date format: DD/MM/YYYY

Response:

{
"success": true,
"data": [
{
"TypeName": "Vàng miếng SJC",
"BranchName": "Hồ Chí Minh",
"BuyValue": 79500000,
"SellValue": 81500000
}
]
}

Rust Implementation:

#[derive(Deserialize)]
pub struct SjcResponse {
pub success: bool,
pub data: Vec<SjcGoldPrice>,
}

#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SjcGoldPrice {
pub type_name: String,
pub branch_name: String,
pub buy_value: f64,
pub sell_value: f64,
}

async fn get_sjc_gold_price(client: &Client, date: NaiveDate) -> Result<SjcGoldPrice, Error> {
let url = "https://sjc.com.vn/GoldPrice/Services/PriceService.ashx";
let date_str = date.format("%d/%m/%Y").to_string();
let body = format!("method=GetSJCGoldPriceByDate&toDate={}", date_str);

let response = client
.post(url)
.headers(SJC_HEADERS)
.body(body)
.send()
.await?;

let result: SjcResponse = response.json().await?;

if !result.success || result.data.is_empty() {
return Err(Error::NoData);
}

// Return first item (usually SJC standard gold bar)
Ok(result.data.into_iter().next().unwrap())
}

4. Rate Limiting Recommendations​

Based on the Python implementation analysis:

ProviderRecommended RateNotes
VCI10 req/secCan handle burst
FMarket5 req/secMore conservative
SJC2 req/secHistorical data fetched day-by-day

5. Error Handling​

Common Error Responses​

pub enum MarketDataError {
// Network errors
ConnectionError(String),
Timeout,

// API errors
RateLimited,
InvalidSymbol(String),
NoDataAvailable,

// Parse errors
InvalidResponse(String),
}

6. Implementation Checklist​

Phase 1: Core HTTP Clients​

  • Create VciClient with authentication headers
  • Create FMarketClient with authentication headers
  • Create SjcClient with authentication headers
  • Add rate limiting layer

Phase 2: Data Models​

  • Port VnMarketQuote struct (already exists)
  • Add VnFundInfo struct
  • Add VnGoldPrice struct

Phase 3: Provider Implementation​

  • Implement get_stock_history() for VCI
  • Implement get_fund_nav_history() for FMarket
  • Implement get_gold_history() for SJC
  • Add caching layer

Phase 4: Integration​

  • Update existing VnMarketProvider to use new clients
  • Add health check endpoints
  • Add background refresh tasks

7. Testing​

Test Symbols​

TypeSymbolNotes
StockVNMBlue chip, high liquidity
StockFPTTech sector
IndexVNINDEXMain index
FundVESAFVinaCapital stock fund
FundTCBFTCB bond fund
GoldVN.GOLDSJC gold bar

Sample cURL Commands​

Get Stock History:

curl -X POST 'https://trading.vietcap.com.vn/api/chart/OHLCChart/gap-chart' \
-H 'Content-Type: application/json' \
-H 'Referer: https://trading.vietcap.com.vn/' \
-d '{"timeFrame":"ONE_DAY","symbols":["VNM"],"to":1735689600,"countBack":30}'

Get Fund Listing:

curl -X POST 'https://api.fmarket.vn/res/products/filter' \
-H 'Content-Type: application/json' \
-H 'Referer: https://fmarket.vn/' \
-d '{"types":["NEW_FUND","TRADING_FUND"],"page":1,"pageSize":100}'

Get Gold Price:

curl -X POST 'https://sjc.com.vn/GoldPrice/Services/PriceService.ashx' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Referer: https://sjc.com.vn/bieu-do-gia-vang' \
-d 'method=GetSJCGoldPriceByDate&toDate=15/01/2024'

Document generated from vnstock v3.3.0 source code analysis