Developers
Data API
A public, read-only HTTP API that serves pre-assembled vault, gauge, token, and user-position data — every value the UI shows, computed server-side from the same sources and math, then cached.
- Base path:
/api/v1 - Network: BNB Smart Chain (chainId 56)
- Auth: none; open CORS, best-effort rate limiting
- Methods:
GET(+OPTIONSpreflight). No writes.
It exists so clients can read accurate vault data from one place instead of fanning out to RPC, the Stats API, and the subgraph and re-deriving everything. Add a vault to config and it appears here automatically.
Response envelope#
{
"ok": true,
"data": { /* payload */ },
"meta": {
"chainId": 56,
"network": "bsc",
"apiVersion": "v1",
"generatedAt": "2026-06-22T20:48:39.985Z",
"indexedBlock": 105784153, // subgraph head when subgraph data was used
"degraded": false, // true if any upstream came back partial
"conventions": { /* ... */ }
}
}
// Errors:
{ "ok": false, "error": { "code": "not_found", "message": "..." } }Error codes map to HTTP status: 400 invalid param/address, 404 not found, 429 rate limited, 502 upstream error.
Number conventions#
- Raw on-chain integers (wei, share base units, 1e36 prices, NFT ids) are decimal strings — no JSON precision loss.
- Scaled values (human token amounts, USD, percentages) are numbers, or
nullwhen an input (e.g. a price) is unavailable. - Addresses are EIP-55 checksummed.
Degradation, not fake zeros
meta.degraded is true whenever an upstream came back partial. When degraded, treat null scaled values as unknown, not zero. For portfolios, totals.usd / totalPnlUsd become null (with usdComplete / pnlCompleteflags) if any position's value is unknown — so a missing price can never fake a $0 portfolio.Endpoints#
| Endpoint | Returns |
|---|---|
GET /api/v1/meta | Protocol root: chainId, all shared contract addresses, protocol tokens, vault counts, global pause, total TVL. |
GET /api/v1/health | Per-source status: { onchain, stats, subgraph } + indexedBlock. |
GET /api/v1/vaults | All vaults assembled. ?kind=cl|v2, ?include=integration[,abi]. |
GET /api/v1/vaults/{keyOrAddress} | A single vault by config key or address; always includes the manifest. ?include=abi. |
GET /api/v1/vaults/{keyOrAddress}/history | Time-series for charts. ?period=hourly|daily, ?days=N (1–365). |
GET /api/v1/vaults/health | Per-vault rebalance/position health for keeper bots (CL only). ?actionable=true. |
GET /api/v1/gauges | Every gauge + APR breakdown, mapped to the Topaz vault that wraps it. ?alive=true. |
GET /api/v1/tokens | Tokens across the vaults with symbol, decimals, logo, USD price. |
GET /api/v1/users/{address}/positions | A wallet's active positions across all vaults + totals + P&L. |
GET /api/v1/users/{address}/vaults/{keyOrAddress} | One user's position in one vault, incl. interaction history. |
Integration manifest#
Returned per vault (single-vault endpoint, or list with ?include=integration). Everything needed to integrate a deposit/withdraw without reading our frontend — you bring the transaction flow:
{
"kind": "cl", // or "v2"
"contracts": { "vault","strategy","pool","gauge","zapper","positionManager","factory" },
"tokens": { "token0","token1","base","shares" },
"state": { "isCalm","globalPause","strategyPaused","depositsAvailable","slippageBps" },
"deposit": {
"direct": { "approvals":[{token,spender,symbol}], "methods":[...], "preview","gating","slippage" },
"zap": { "approvals":[...], "methods":["zapInToken","zapInBoth","zapInBNB"],
"quote":"simulate via eth_call (callStatic) — do NOT estimate naively",
"gating":"isCalm() must be true", "slippage","gas":"estimateGas*2 (>=3M)" }
},
"withdraw": { "approvals":[...], "methods":[...], "gating":"none (not gated)", "slippage" },
"abiSignatures": { "vault":[...], "zapper":[...], "erc20":[...] },
"abi": { /* full JSON ABIs, only when ?include=abi */ },
"notes": [ /* ... */ ]
}Build a CL deposit from the manifest#
- Read
contracts.vaultandtokens.token0/token1. - Check
state.depositsAvailable(requiresisCalmtrue, not paused/globalPaused). approve(token0 → vault)andapprove(token1 → vault).- Quote with
vault.previewDeposit(amount0, amount1)→shares. minShares = shares × (10000 − state.slippageBps) / 10000.vault.deposit(amount0, amount1, minShares).
(For zaps, static-call the zapper method to get exact shares, then guard below it and budget gasLimit = estimateGas × 2 — see Smart contracts.)
Examples#
# List every vault with full state + metrics
curl https://vaults.topazdex.com/api/v1/vaults
# One vault + integration manifest + full ABIs (by key or address)
curl "https://vaults.topazdex.com/api/v1/vaults/sol-wbnb-mid?include=integration,abi"
# Every gauge mapped back to the Topaz vault + zapper that wraps it
curl "https://vaults.topazdex.com/api/v1/gauges?alive=true"
# A wallet's positions across all vaults (live shares + P&L + totals)
curl https://vaults.topazdex.com/api/v1/users/0xYourWallet/positions
# Source health
curl https://vaults.topazdex.com/api/v1/healthCaching#
Each resource sets Cache-Control: public, s-maxage, stale-while-revalidate and is memoized server-side: vaults 15s, gauges/tokens 60s, history 60s, user positions 10s, health 10s. Public deployments should still enforce real rate limits at the edge/CDN.