Skip to main content
API chia 2 tier theo mức độ nhạy cảm của dữ liệu. Tier 1 là mặc định cho dữ liệu thị trường công khai; Tier 2 áp dụng cho các endpoint user-scoped (đánh dấu riêng trong API Reference).

Tier 1 — Chỉ cần API key

Chỉ cần gửi header X-FH-APIKEY trên mọi request. Áp dụng cho các endpoint dữ liệu thị trường công khai:
  • Toàn bộ GET /market/**
  • GET /trading/market/**
  • GET /trading/securities/**
  • Toàn bộ GET /fund-trading/public/** (quỹ mở)
curl -H "X-FH-APIKEY: $FINHAY_API_KEY" \
  "https://open-api.fhsc.com.vn/market/stock-realtime?symbol=VNM"

Tier 2 — HMAC signing đầy đủ

Áp dụng cho các endpoint liên quan đến thông tin user / tài khoản / lệnh giao dịch / lãi-lỗ. Mỗi request cần đủ 4 header (cộng X-FH-BODYHASH khi request có body):
HeaderKhi nàoGiá trị
X-FH-APIKEYLuôn luônClient API key (dài hạn)
X-FH-TIMESTAMPTier 2Unix time hiện tại, đơn vị mili-giây
X-FH-NONCETier 2UUIDv4, duy nhất per request
X-FH-SIGNATURETier 2HMAC_SHA256(secret, payload) — hex lowercase
X-FH-BODYHASHTier 2 có bodySHA256(body) — hex lowercase
Endpoint Tier 2 hiện có:
  • GET /users/v1/users/me
  • GET /users/v1/users/{userId}/sub-accounts
  • GET /users/v3/users/{userId}/assets/summary
  • GET /trading/accounts/{subAccountId}/summary
  • GET /trading/v1/accounts/{subAccountId}/order-book
  • GET /trading/v1/accounts/{subAccountId}/order-book/{orderId}
  • GET /trading/v2/sub-accounts/{subAccountId}/portfolio
  • GET /trading/v5/account/{subAccountId}/user-rights
  • GET /trading/pnl-today/{userId}
Endpoint Tier 2 thuộc nhóm Thực thi lệnh (preview) — yêu cầu thêm X-FH-BODYHASH (có body) và X-FH-2FA-TOKEN (daily 2FA session):
  • POST /trading/oa/sub-accounts/{subAccountId}/orders
  • PUT /trading/oa/sub-accounts/{subAccountId}/orders/{orderId}
  • DELETE /trading/oa/sub-accounts/{subAccountId}/orders/{orderId}
Endpoint nhóm Thực thi lệnh đang ở giai đoạn preview.

Signing payload

Payload đầu vào cho HMAC được build theo template dưới (mỗi field nối bằng \n):
{TIMESTAMP}\n{METHOD}\n{PATH}[?{QUERY}]\n{BODYHASH}
1

TIMESTAMP

Unix mili-giây, chuỗi số thập phân (ví dụ "1714464000123"). Server chấp nhận lệch tối đa ±30 giây so với giờ thực — đảm bảo client đồng bộ NTP.
2

METHOD

HTTP method viết hoa: GET, POST, PUT, DELETE.
3

PATH [?QUERY]

Path của request, ví dụ /trading/accounts/0001234567/summary. Nếu request có query string, nối thêm với prefix ? (ví dụ /market/stock-realtime?symbol=VNM). Bỏ hoàn toàn segment ? nếu không có query.
4

BODYHASH

SHA256(body) ở dạng hex lowercase. Là chuỗi rỗng khi body rỗng. Nối thẳng sau \n cuối cùng — không thêm newline riêng sau BODYHASH.
Replay protection: server nhớ cặp (apiKey, nonce) trong 5 phút. Nonce reused trong window đó sẽ bị từ chối với mã AUTH_NONCE_REUSED (HTTP 401). Mỗi request phải sinh nonce mới — recommend UUIDv4.
Toàn bộ metadata signing (algorithm, tên header, replay window, …) được expose qua OpenAPI extension x-finhay-signing ở root spec, để các tool codegen tự sinh signing layer.

2FA session (X-FH-2FA-TOKEN)

Riêng các write operation thuộc nhóm Thực thi lệnh (preview — POST / PUT / DELETE dưới /trading/oa/**), ngoài 5 header HMAC client còn phải gửi kèm X-FH-2FA-TOKEN — daily JWT session token do server cấp sau khi user xác thực qua OTP.
  • Token có tuổi thọ 1 ngày giao dịch, scope theo apiKey.
  • Lưu vào memory hoặc secret store; tự re-init khi sang ngày mới.
  • Khi token hết hạn / không hợp lệ / bị revoke, server trả HTTP 403 với error_code là 1 trong:
    • OTP_SESSION_REQUIRED
    • OTP_SESSION_EXPIRED
    • OTP_SESSION_INVALID
    • OTP_SESSION_REVOKED
  • Bắt 1 trong 4 mã trên → chạy lại OTP flow để lấy token mới → retry request.
OTP flow để xin X-FH-2FA-TOKEN không thuộc surface của tài liệu này. Liên hệ team Finhay để biết chi tiết quy trình nội bộ.

Signing middleware

OpenAPI generator thường sinh 5 setter độc lập (setApiKey, setTimestamp, setNonce, setSignature, setBodyHash) — chỉ API key được set bằng tay. 4 setter còn lại cần được tính per-request bởi middleware chạy ngay trước HTTP call. Tích hợp đoạn code dưới vào client (hoặc tương đương) và bỏ qua các setter còn lại.
import axios from "axios";
import crypto from "node:crypto";

const API_KEY    = process.env.FINHAY_API_KEY!;
const API_SECRET = process.env.FINHAY_API_SECRET!;
const BASE_URL   = process.env.FINHAY_BASE_URL ?? "https://open-api.fhsc.com.vn";

const API_KEY_ONLY: RegExp[] = [
  /^\/market\/.*$/,
  /^\/trading\/market\/.*$/,
  /^\/trading\/securities\/.*$/,
  /^\/fund-trading\/public\/.*$/,
];

const isApiKeyOnly = (method: string, path: string): boolean =>
  method === "GET" && API_KEY_ONLY.some((re) => re.test(path));

export const client = axios.create({ baseURL: BASE_URL });

client.interceptors.request.use((config) => {
  const method = (config.method ?? "get").toUpperCase();
  const url    = new URL(config.url ?? "", BASE_URL);
  const path   = url.pathname;

  // Luôn gửi API key.
  config.headers.set("X-FH-APIKEY", API_KEY);

  // Tier 1: market read — dừng ở đây.
  if (isApiKeyOnly(method, path)) return config;

  // Tier 2: HMAC sign request.
  const timestamp = Date.now().toString();
  const nonce     = crypto.randomUUID();
  const query     = url.search.slice(1); // bỏ leading '?'
  const body      = typeof config.data === "string"
    ? config.data
    : config.data != null
      ? JSON.stringify(config.data)
      : "";
  const bodyHash  = body
    ? crypto.createHash("sha256").update(body).digest("hex")
    : "";

  let payload = `${timestamp}\n${method}\n${path}`;
  if (query)    payload += `?${query}`;
  payload += "\n";
  if (bodyHash) payload += bodyHash;

  const signature = crypto
    .createHmac("sha256", API_SECRET)
    .update(payload)
    .digest("hex");

  config.headers.set("X-FH-TIMESTAMP", timestamp);
  config.headers.set("X-FH-NONCE",     nonce);
  config.headers.set("X-FH-SIGNATURE", signature);
  if (bodyHash) config.headers.set("X-FH-BODYHASH", bodyHash);

  // Preview: write ops dưới /trading/oa/** cần thêm 2FA token.
  if (/^\/trading\/oa\//.test(path)) {
    const token = process.env.FINHAY_2FA_TOKEN;
    if (!token) throw new Error("Missing FINHAY_2FA_TOKEN — run OTP flow first");
    config.headers.set("X-FH-2FA-TOKEN", token);
  }

  return config;
});
Lưu ý về form “Try It”: form gọi thử endpoint tích hợp trong tab API Reference KHÔNG tự tính HMAC signature. Để test endpoint Tier 2, hãy dùng Postman (setup pre-request script để sign), hoặc tự tính signature rồi paste vào header tương ứng.