API reference
A read-only indexer for browsing. Writes always go on-chain through the SDK: no endpoint here can move funds.
Conventions
The base URL comes from configuration, not from source: the app reads it from OTC_API_URL on the server and exposes reads through its own cached mirror at /api/public/otc. Responses are bare JSON: an array for collections, an object for a single record. Every u64 is serialised as a decimal string so it survives JSON, and every timestamp is Unix seconds as a number.
TypeScript
const res = await fetch(`${API_URL}/listings?tokenMint=${mint}`);
const listings = await res.json(); // a bare array, no envelope
// Convert at the boundary, once.
const decoded = listings.map((l) => ({
...l,
amount: BigInt(l.amount),
remainingAmount: BigInt(l.remaining_amount),
pricePerToken: BigInt(l.price_per_token),
}));Listings
Resting asks. Each row carries full token and payment-asset metadata, so a card needs no second request.
| Name | Description |
|---|---|
| openOnly | Exclude closed, filled and expired orders |
| tokenMint | Filter by token mint |
| quoteMint | Filter by payment mint |
| verifiedOnly | Only mints the indexer has verified |
| excludeFreezable | Drop mints that hold a freeze authority |
| minPrice / maxPrice | Price per whole token, base units |
| minSize | Minimum remaining size, base units |
| sort | price, size, created or expiry |
| order | asc or desc. Asks read best-first as asc |
| limit | Page size |
Example response
200 OK
[
{
"address": "7Yq3nDkKCk6bPZ1sVJ8mUuA2rXoT4hLdWc5FgEsRk3Np",
"seller": "5xoT4hLdWc5FgEsRk3Np7Yq3nDkKCk6bPZ1sVJ8mUuA2",
"token": {
"mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
"symbol": "BONK",
"name": "Bonk",
"decimals": 5,
"token_program": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"logo_uri": "https://arweave.net/hQiPZOsRZXGXBJd_82PhVdlM_hACsT_q6wqwf5cSY1I",
"price_usd": "0.0000214",
"liquidity_usd": "8412000",
"token_verified": true
},
"quote_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"quote_symbol": "USDC",
"quote_decimals": 6,
"quote_program": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"price_per_token": "20800",
"pricing_kind": "fixed",
"amount": "1000000000",
"remaining_amount": "640000000",
"min_fill_amount": "1000000",
"expiry_ts": 1772150400,
"allowed_buyer": null,
"created_ts": 1772063991,
"closed": false,
"seller_trades_completed": 12,
"seller_volume_usd": "184320"
}
]GET /listings/{address} returns a single ask, with its fills array attached.
Bids
Resting bids, same parameters as listings. Default sort is price descending, because the best bid is the highest one.
| Name | Description |
|---|---|
| tokenMint | Filter by token mint |
| quoteMint | Filter by payment mint |
| sort | price, size, created or expiry |
| order | asc or desc. Bids read best-first as desc |
| limit | Page size |
Example response
200 OK
[
{
"address": "3Np7Yq3nDkKCk6bPZ1sVJ8mUuA2rXoT4hLdWc5FgEsRk",
"buyer": "9rXoT4hLdWc5FgEsRk3Np7Yq3nDkKCk6bPZ1sVJ8mUuA",
"token": { "mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263", "symbol": "BONK", "decimals": 5 },
"quote_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"quote_symbol": "USDC",
"quote_decimals": 6,
"price_per_token": "20800",
"pricing_kind": "fixed",
"quote_amount": "1005000000",
"remaining_quote_amount": "1005000000",
"remaining_token_capacity": "4807692307",
"min_fill_amount": "10000000",
"expiry_ts": 1772150400,
"allowed_seller": null,
"created_ts": 1772063800,
"closed": false
}
]GET /bids/{address} returns a single bid with its fills array.
Token summary
Traded volume, VWAP, last traded price and open interest for one mint. This is the data behind a token block in the app. Traded statistics derive only from executed fills, so resting orders never average into a price.
Example response
200 OK
{
"token": {
"mint": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
"symbol": "BONK",
"decimals": 5,
"price_usd": "0.0000214"
},
"volume_usd": "912400",
"vwap_usd": "0.0000209",
"best_ask_price": "20800",
"best_ask_quote_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"best_bid_price": "20150",
"best_bid_quote_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"open_listings": 14,
"open_bids": 6
}Token book
Best bid, best ask and the spread, one entry per payment asset. Use it for a header quote without pulling both order lists.
Impersonations
Unverified mints using a symbol that a verified mint already claims. Ticker collisions are free to create on Solana, so match on the mint address and never on the symbol.
Treasury
What the protocol has accrued, per mint and per side. Half of every fee is taken in the traded token, so this is the only place token-side balances are visible.
Price series
Time-bucketed price points built from executed fills only, oldest first. One series per token and payment-asset pair.
Example response
200 OK
[
{ "ts": 1772060400, "price": "20950", "volume": "1840000000" },
{ "ts": 1772064000, "price": "20800", "volume": "640000000" }
]Fills
Settled trades, newest first, both sides mixed. Read
side on every row: 0 is a fill against an ask, 1 against a bid. It drives the row label and which order page you link to.| Name | Description |
|---|---|
| side | 0 for asks, 1 for bids. Omit for both |
| tokenMint | Filter by token mint |
| order | Fills against one ask or bid address |
| wallet | Fills where this wallet was either side |
| limit | Page size |
Example response
200 OK
[
{
"signature": "4hLdWc5FgEsRk3Np7Yq3nDkKCk6bPZ1sVJ8mUuA2rXoT4hLdWc5FgEsRk3Np7Yq3nDkKCk6bPZ1sVJ8mUuA2rXoT",
"side": 0,
"order": "7Yq3nDkKCk6bPZ1sVJ8mUuA2rXoT4hLdWc5FgEsRk3Np",
"buyer": "9rXoT4hLdWc5FgEsRk3Np7Yq3nDkKCk6bPZ1sVJ8mUuA",
"seller": "5xoT4hLdWc5FgEsRk3Np7Yq3nDkKCk6bPZ1sVJ8mUuA2",
"amount": "360000000",
"quote_amount": "74880000",
"quote_symbol": "USDC",
"ts": 1772063991
}
]Venue stats
Venue totals: settled volume, largest single trade, price impact avoided and the number of open asks.
Example response
200 OK
{
"volume_usd": "4182400",
"largest_trade_usd": "184320",
"impact_saved_usd": "212900",
"open_listings": 148
}Health
Indexer liveness.
lastSlot is a decimal string because slots outgrow a safe JS integer, and staleAfterSeconds is the cutoff the indexer itself considers stale.Example response
200 OK
{
"ok": true,
"indexer": {
"lastSlot": "312884473",
"lagSeconds": 0.8,
"staleAfterSeconds": 240
}
}