Market Data Integration Guide
Step-by-step guide to fetching live cryptocurrency prices and streaming real-time updates.
Prerequisites
- A Rach API key (
live_sk_…for production,test_sk_…for sandbox) - Basic HTTP knowledge for REST endpoints
- WebSocket experience for the streaming section
Base URL
https://api.rach.finance/v1/marketAuthentication
Pass your API key on every REST request via header:
X-API-Key: live_sk_YOUR_KEYFor WebSocket connections use the key query parameter (browsers cannot set custom headers during WebSocket upgrade):
wss://api.rach.finance/v1/market/ws?key=live_sk_YOUR_KEYREST Endpoints
Health Check
Check whether market data is live. No authentication required.
curl https://api.rach.finance/v1/market/health{
"status": "ok",
"fresh": true
}status is "stale" (HTTP 503) when the cached data has not been refreshed within the expected window.
List All Coins
Returns a paginated list of all tracked coins ordered by market-cap rank. Each coin includes full market data.
GET /v1/market/coins?page=1&limit=50| Query param | Default | Max | Description |
|---|---|---|---|
page | 1 | — | 1-based page number |
limit | 50 | 250 | Results per page |
curl https://api.rach.finance/v1/market/coins \
-H "X-API-Key: live_sk_YOUR_KEY"const res = await fetch('https://api.rach.finance/v1/market/coins', {
headers: { 'X-API-Key': 'live_sk_YOUR_KEY' }
})
const { coins, total, page, limit, as_of } = await res.json()
console.log(`Showing ${coins.length} of ${total} coins`)
console.log(`Data as of: ${new Date(as_of * 1000).toISOString()}`)
coins.forEach(c => console.log(`${c.symbol.toUpperCase()}: $${c.current_price}`))import requests
res = requests.get(
'https://api.rach.finance/v1/market/coins',
headers={'X-API-Key': 'live_sk_YOUR_KEY'}
)
data = res.json()
for coin in data['coins']:
print(f"{coin['symbol'].upper()}: ${coin['current_price']:,.2f}")package main
import (
"encoding/json"
"fmt"
"net/http"
)
type Coin struct {
Symbol string `json:"symbol"`
Name string `json:"name"`
CurrentPrice float64 `json:"current_price"`
MarketCapRank int `json:"market_cap_rank"`
}
type CoinsResponse struct {
Coins []Coin `json:"coins"`
Total int `json:"total"`
AsOf int64 `json:"as_of"`
}
func main() {
req, _ := http.NewRequest("GET", "https://api.rach.finance/v1/market/coins", nil)
req.Header.Set("X-API-Key", "live_sk_YOUR_KEY")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var data CoinsResponse
json.NewDecoder(resp.Body).Decode(&data)
for _, c := range data.Coins {
fmt.Printf("#%d %s (%s): $%.2f\n",
c.MarketCapRank, c.Name, c.Symbol, c.CurrentPrice)
}
}Response:
{
"as_of": 1751672738,
"total": 100,
"page": 1,
"limit": 50,
"coins": [
{
"id": "bitcoin",
"symbol": "btc",
"name": "Bitcoin",
"image": "https://cdn.rach.finance/coins/bitcoin.png",
"current_price": 62150.43,
"market_cap": 1221043294823,
"market_cap_rank": 1,
"total_volume": 28430192837,
"high_24h": 62500.00,
"low_24h": 61400.00,
"price_change_24h": 430.21,
"price_change_percentage_24h": 0.697,
"price_change_percentage_1h_in_currency": 0.12,
"price_change_percentage_7d_in_currency": -2.34,
"circulating_supply": 19700000,
"total_supply": 21000000,
"max_supply": 21000000,
"ath": 108786.00,
"ath_change_percentage": -42.85,
"ath_date": "2024-12-17T15:02:41.429Z",
"atl": 67.81,
"atl_change_percentage": 91621.84,
"atl_date": "2013-07-06T00:00:00.000Z",
"last_updated": "2026-07-04T23:40:02.315Z"
}
]
}Get Single Coin Detail
Returns the full market data object for one coin. Use the symbol (e.g. btc, eth, sol).
GET /v1/market/coins/:symbolcurl https://api.rach.finance/v1/market/coins/btc \
-H "X-API-Key: live_sk_YOUR_KEY"const res = await fetch('https://api.rach.finance/v1/market/coins/btc', {
headers: { 'X-API-Key': 'live_sk_YOUR_KEY' }
})
const coin = await res.json()
console.log(`${coin.name}: $${coin.current_price}`)res = requests.get(
'https://api.rach.finance/v1/market/coins/btc',
headers={'X-API-Key': 'live_sk_YOUR_KEY'}
)
coin = res.json()
print(f"{coin['name']}: ${coin['current_price']:,.2f}")Response: A single CoinMarket object (same fields as in the list above).
Get Prices for Multiple Coins
Lightweight endpoint for looking up prices without full market data. Accepts up to 100 symbols per request.
GET /v1/market/prices?symbols=btc,eth,sol,bnbcurl "https://api.rach.finance/v1/market/prices?symbols=btc,eth,sol" \
-H "X-API-Key: live_sk_YOUR_KEY"const symbols = ['btc', 'eth', 'sol', 'bnb', 'usdt']
const res = await fetch(
`https://api.rach.finance/v1/market/prices?symbols=${symbols.join(',')}`,
{ headers: { 'X-API-Key': 'live_sk_YOUR_KEY' } }
)
const { prices } = await res.json()
// prices = { btc: 62150.43, eth: 3412.88, sol: 148.20, ... }res = requests.get(
'https://api.rach.finance/v1/market/prices',
params={'symbols': 'btc,eth,sol,bnb'},
headers={'X-API-Key': 'live_sk_YOUR_KEY'}
)
prices = res.json()['prices']
for symbol, price in prices.items():
print(f"{symbol.upper()}: ${price:,.2f}")Response:
{
"prices": {
"btc": 62150.43,
"eth": 3412.88,
"sol": 148.20,
"bnb": 594.71
}
}Get Single Coin Price
GET /v1/market/prices/:symbolcurl https://api.rach.finance/v1/market/prices/btc \
-H "X-API-Key: live_sk_YOUR_KEY"{
"symbol": "btc",
"price": 62150.43
}WebSocket Streaming
The WebSocket API delivers price updates in real time. After connecting and subscribing, you immediately receive a full snapshot, then lightweight tick messages whenever prices change.
Connection
wss://api.rach.finance/v1/market/ws?key=live_sk_YOUR_KEYThe key is validated on connection — an invalid or missing key closes the connection with HTTP 401 before the upgrade completes.
Message Flow
1. Connect → wss://api.rach.finance/v1/market/ws?key=…
2. Send → {"op":"subscribe","symbols":["btc","eth"]}
3. Receive ← {"op":"snapshot","as_of":…,"coins":[…]} ← immediate full data
4. Receive ← {"op":"tick","as_of":…,"changes":[…]} ← on every price changeClient → Server Messages
Subscribe to specific coins:
{ "op": "subscribe", "symbols": ["btc", "eth", "sol"] }Subscribe to all tracked coins:
{ "op": "subscribe", "symbols": ["*"] }You can re-subscribe at any time to change your coin filter. The server sends a new snapshot immediately on each subscribe.
Application-level ping (optional):
{ "op": "ping" }Response: { "op": "pong" }. The server also sends WebSocket protocol ping frames every 45 seconds — most clients handle pong automatically.
Server → Client Messages
snapshot — sent immediately after subscribe, contains full coin data for your subscribed symbols:
{
"op": "snapshot",
"as_of": 1751672738,
"coins": [
{
"id": "bitcoin",
"symbol": "btc",
"name": "Bitcoin",
"current_price": 62150.43,
"market_cap": 1221043294823,
"market_cap_rank": 1,
"price_change_percentage_24h": 0.697,
"high_24h": 62500.00,
"low_24h": 61400.00,
"...": "all CoinMarket fields"
}
]
}tick — sent after each poll when prices changed. Only coins that moved are included:
{
"op": "tick",
"as_of": 1751672978,
"changes": [
{ "symbol": "btc", "price": 62210.15, "pct_24h": 0.78, "direction": "raise" },
{ "symbol": "eth", "price": 3398.44, "pct_24h": -0.22, "direction": "fall" }
]
}direction is "raise" when the price increased since the last poll, "fall" when it decreased.
pong — response to a client ping:
{ "op": "pong" }Integration Examples
JavaScript / Browser
class RachMarketData {
constructor(apiKey) {
this.apiKey = apiKey
this.ws = null
this.reconnectDelay = 1000
this.prices = new Map()
}
connect(symbols = ['*'], onSnapshot, onTick) {
this.ws = new WebSocket(
`wss://api.rach.finance/v1/market/ws?key=${this.apiKey}`
)
this.ws.onopen = () => {
console.log('Connected to Rach market data')
this.reconnectDelay = 1000
this.ws.send(JSON.stringify({ op: 'subscribe', symbols }))
}
this.ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
if (msg.op === 'snapshot') {
// Full baseline — store all prices locally
msg.coins.forEach(c => this.prices.set(c.symbol, c.current_price))
onSnapshot?.(msg.coins, msg.as_of)
} else if (msg.op === 'tick') {
// Delta — update only changed coins
msg.changes.forEach(c => this.prices.set(c.symbol, c.price))
onTick?.(msg.changes, msg.as_of)
}
}
this.ws.onerror = (err) => console.error('WebSocket error', err)
this.ws.onclose = () => {
console.log(`Disconnected. Reconnecting in ${this.reconnectDelay}ms…`)
setTimeout(() => this.connect(symbols, onSnapshot, onTick), this.reconnectDelay)
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30_000)
}
}
getPrice(symbol) {
return this.prices.get(symbol.toLowerCase())
}
disconnect() {
this.ws?.close()
}
}
// Usage
const market = new RachMarketData('live_sk_YOUR_KEY')
market.connect(
['btc', 'eth', 'bnb', 'sol'],
(coins, asOf) => {
console.log('Snapshot received:', coins.length, 'coins')
coins.forEach(c => updatePriceUI(c.symbol, c.current_price))
},
(changes, asOf) => {
changes.forEach(c => {
updatePriceUI(c.symbol, c.price)
console.log(`${c.symbol} ${c.direction === 'raise' ? '▲' : '▼'} $${c.price}`)
})
}
)React Hook
import { useState, useEffect, useRef, useCallback } from 'react'
function useMarketData(apiKey, symbols = ['*']) {
const [prices, setPrices] = useState({})
const [coins, setCoins] = useState([])
const [connected, setConnected] = useState(false)
const wsRef = useRef(null)
useEffect(() => {
let reconnectTimer
let delay = 1000
function connect() {
const ws = new WebSocket(
`wss://api.rach.finance/v1/market/ws?key=${apiKey}`
)
wsRef.current = ws
ws.onopen = () => {
setConnected(true)
delay = 1000
ws.send(JSON.stringify({ op: 'subscribe', symbols }))
}
ws.onmessage = (e) => {
const msg = JSON.parse(e.data)
if (msg.op === 'snapshot') {
setCoins(msg.coins)
setPrices(Object.fromEntries(msg.coins.map(c => [c.symbol, c.current_price])))
} else if (msg.op === 'tick') {
setPrices(prev => {
const next = { ...prev }
msg.changes.forEach(c => { next[c.symbol] = c.price })
return next
})
}
}
ws.onclose = () => {
setConnected(false)
reconnectTimer = setTimeout(() => {
delay = Math.min(delay * 2, 30_000)
connect()
}, delay)
}
}
connect()
return () => {
clearTimeout(reconnectTimer)
wsRef.current?.close()
}
}, [apiKey])
const getPrice = useCallback((symbol) => prices[symbol.toLowerCase()], [prices])
return { coins, prices, connected, getPrice }
}
// Usage
function PriceTicker() {
const { coins, connected, getPrice } = useMarketData('live_sk_YOUR_KEY', ['btc', 'eth', 'sol'])
return (
<div>
<p>Status: {connected ? '● Live' : '○ Reconnecting…'}</p>
{coins.map(coin => (
<div key={coin.symbol}>
<img src={coin.image} width={20} alt={coin.name} />
<strong>{coin.name}</strong>
<span>${getPrice(coin.symbol)?.toLocaleString()}</span>
<span style={{ color: coin.price_change_percentage_24h >= 0 ? 'green' : 'red' }}>
{coin.price_change_percentage_24h?.toFixed(2)}%
</span>
</div>
))}
</div>
)
}Python (asyncio)
import asyncio
import json
import websockets
async def stream_prices(api_key: str, symbols: list[str]):
url = f"wss://api.rach.finance/v1/market/ws?key={api_key}"
prices = {}
async with websockets.connect(url) as ws:
print("Connected")
# Subscribe
await ws.send(json.dumps({"op": "subscribe", "symbols": symbols}))
async for raw in ws:
msg = json.loads(raw)
if msg["op"] == "snapshot":
for coin in msg["coins"]:
prices[coin["symbol"]] = coin["current_price"]
print(f"Snapshot: {len(msg['coins'])} coins loaded")
elif msg["op"] == "tick":
for change in msg["changes"]:
prices[change["symbol"]] = change["price"]
arrow = "▲" if change["direction"] == "raise" else "▼"
print(f"{change['symbol'].upper()} {arrow} ${change['price']:,.2f}")
asyncio.run(stream_prices("live_sk_YOUR_KEY", ["btc", "eth", "sol"]))Node.js (REST — one-shot price check)
import https from 'node:https'
async function getPrices(symbols, apiKey) {
const url = `https://api.rach.finance/v1/market/prices?symbols=${symbols.join(',')}`
const res = await fetch(url, { headers: { 'X-API-Key': apiKey } })
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
const { prices } = await res.json()
return prices
}
// Usage
const prices = await getPrices(['btc', 'eth', 'usdt'], 'live_sk_YOUR_KEY')
console.log(`BTC: $${prices.btc.toLocaleString()}`)
console.log(`ETH: $${prices.eth.toLocaleString()}`)Displaying Price Direction
The direction field in tick messages makes it easy to colour price movements in your UI:
// Map direction to styles
const style = {
raise: { color: '#16a34a', symbol: '▲' }, // green
fall: { color: '#dc2626', symbol: '▼' }, // red
}
market.connect(['*'], null, (changes) => {
changes.forEach(({ symbol, price, direction }) => {
const el = document.getElementById(`price-${symbol}`)
if (!el) return
const { color, symbol: arrow } = style[direction]
el.textContent = `${arrow} $${price.toLocaleString()}`
el.style.color = color
})
})Error Handling
REST
async function fetchWithRetry(url, options, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url, options)
if (res.status === 401) throw new Error('Invalid API key')
if (res.status === 404) return null // coin not found
if (res.status === 503) throw new Error('Market data temporarily unavailable')
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return await res.json()
} catch (err) {
if (i === retries - 1) throw err
await new Promise(r => setTimeout(r, 500 * 2 ** i))
}
}
}WebSocket Reconnection
The examples above already implement exponential backoff reconnection (1s → 2s → 4s → … → 30s). A few additional tips:
- Re-send the
subscribemessage on every reconnect insideonopen - Store the latest snapshot in memory so your UI isn't blank during a brief reconnect
- Check
freshfrom/v1/market/healthbefore displaying data if you need to indicate staleness
Best Practices
Use WebSocket for real-time display — REST endpoints are for initial page loads or server-side lookups. The WebSocket stream has no rate limits and pushes updates automatically.
Use /v1/market/prices for bulk price-only lookups — it's cheaper than /v1/market/coins when you only need numbers, not full metadata.
Apply the snapshot as your baseline — on connect, the server sends a full snapshot for your subscribed coins. Apply it to your local state immediately, then patch it with each subsequent tick.
Handle reconnects gracefully — network blips are normal. The reconnect pattern in the examples above keeps your UI live automatically.
Testing with wscat
npm install -g wscat
wscat -c "wss://api.rach.finance/v1/market/ws?key=live_sk_YOUR_KEY"
# Once connected, type:
{"op":"subscribe","symbols":["btc","eth","sol"]}
# → snapshot arrives immediately
# → ticks arrive whenever prices move