API guide
Crypto price API: aggregated real-time prices, bars and streams
Bitcoin does not have one price. It has hundreds, one per venue and quote currency, and the API you pick decides which of them your users see.
On this page
Key takeaways
- A crypto price API returns bid, ask and last trade for pairs such as BTCUSD, plus OHLCV bars and a real-time stream.
- Aggregated prices combine many venues into one quote; a single-venue price inherits that venue's outages and wicks.
- USDT is not USD: on 28 September 2026 USDT traded at about 0.99955 dollars, 4.5 basis points below par.
- Crypto bars run 24/7 on UTC days; in one September week BTCUSD's median hourly range on Saturday was a third of Monday's.
- On crypto quotes `ts` is when the aggregation received the quote; on trades it is the trade's own event time.
A crypto price API gives your code the current price of a coin pair such as BTCUSD over HTTP: best bid, best ask and last trade, each with a timestamp. Good ones add OHLCV bars and a WebSocket stream of real-time crypto prices. The decision that matters most is not the shape of the endpoint but whose price it returns: one venue's, or an aggregate of many.
This pillar of our market data API series explains aggregation, the stablecoin basis, 24/7 bars and streaming, with code tested against the live API on 28 September 2026. TickerLayer publishes aggregated prices for 2,500+ crypto pairs over REST and WebSocket. Start with one request:
curl -sS "https://api.tickerlayer.com/crypto/quote/BTCUSD" \
-H "x-api-key: $TICKERLAYER_API_KEY"GET /crypto/quote/BTCUSD
{
"symbol": "BTCUSD",1
"bid": 82820,2
"ask": 82820.01,
"bid_size": 8.15315,3
"ask_size": 3.0544,
"timestamp": 17905911129684
}
symbolBase and quote with no separator: bitcoin priced in US dollars.bidBest aggregated bid. The ask is one cent higher: on the busiest pairs the spread sits at the minimum price step.bid_sizeSize available at the bid, in coins.timestampUnix milliseconds. On crypto quotes this is when the aggregation received the quote.
The same key and response shape cover the rest of the surface: /crypto/trade/last/{symbol} for the latest print, /crypto/snapshot/{symbol} for bid, ask, last trade and the day's change in one call, and /crypto/agg/... for bars. GET /crypto/symbols returns the enabled symbol list with names.
| Job | Call | Why |
|---|---|---|
| Price tile with daily change | GET /crypto/snapshot/{symbol} | Bid, ask, last trade, prev_close and change in one request |
| Portfolio value every few minutes | GET /crypto/quote/{symbol} | Value at the mid, and keep the spread for context |
| Chart or backtest | GET /crypto/agg/{symbol}/... | OHLCV bars from 1 minute to 1 day, paginated |
| Ticker, alerts, bots | crypto.quotes and crypto.trades | Every change pushed to you on one socket |
Aggregated vs single-venue crypto prices
Every crypto venue runs its own order book. At any moment their best prices differ by a few basis points, and by far more when one of them is thin, halted, or quoting in a different stablecoin. A single-venue API gives you one of those books faithfully, outages and wicks included. A cryptocurrency data aggregator combines many of them into one quote per pair.
| Feature | Single-venue feed | Aggregated feed |
|---|---|---|
| What the price means | Where that venue would fill you | A consensus across venues |
| One venue goes down | Your price stops or jumps | The others keep the quote alive |
| Thin-book wicks | Passed straight through | Diluted by deeper books |
| Executable as shown | Yes, on that venue | No: a reference price |
| Best for | Trading on that venue | Display, valuation, alerts, analytics |
TickerLayer is the second kind: one aggregated quote per pair, built from the deepest global liquidity, with no venue named in the payload. We compare it with regulated fiat-dollar venues that sit outside the aggregation and publish the result, basis included, on the crypto data-quality page. If you execute on a specific venue, use that venue's own feed for order decisions; an aggregated price is for everything around the trade.
USDT is not USD: the stablecoin basis
Much of the world's crypto liquidity is not quoted in dollars at all. It is quoted in dollar stablecoins, tokens designed to trade at one dollar. Designed to, not guaranteed to: they drift a few basis points either side of par on a normal day, and further under stress. A price in USDT is a price in a token, and turning it into dollars takes one more multiplication.
- 0.99953 / 0.99956USDTUSD bid / ask
- −4.5 bpsUSDT against one dollar, at the mid
- 1.0003 / 1.0004USDCUSD bid / ask
- +3.5 bpsUSDC against one dollar, at the mid
price in USD = price in USDT × USDTUSDbasis (bps) = (USDTUSD − 1) × 10,000
- USDTUSD
- Dollars per USDT: the stablecoin's own mid price.
- basis
- How far the stablecoin sits from par. Negative means below a dollar.
An aggregate that blends stablecoin-quoted and dollar-quoted books therefore carries a small signed basis against venues that quote only in dollars. We report it as a number instead of hiding it, and you can measure it yourself, because the stablecoins are ordinary symbols on the same endpoint: USDTUSD and USDCUSD. During issuer or banking stress, major stablecoins have briefly traded several percent below a dollar, so a converter that hard-codes 1.0 fails silently at exactly the wrong moment.
The same basis runs through perpetual futures, which are priced in stablecoin terms. It is one reason the gap between a coin and its perpetual is never only a funding story; our perpetual futures guide separates the two with live numbers.
Crypto never closes: 24/7 bars on UTC days
Stock bars follow a session; crypto bars follow the clock. GET /crypto/agg/{symbol}/{multiplier}/{timespan}/{from}/{to} returns 1, 5 or 15-minute, 1 or 4-hour, or daily bars, and a daily bar is simply a UTC day, midnight to midnight, weekends included. The snapshot's prev_close is the close of the last completed UTC day: on Monday morning BTCUSD's was 84,472, Sunday's close.
BTCUSD daily bars, Saturday 19 to Sunday 27 September 2026
Weekends are in the series, but they do not behave like weekdays. Hourly bars for one week make the point, and the script is a reusable pattern for any bar request: follow next_offset until it is null.
import os
from collections import defaultdict
from datetime import datetime, timezone
import requests
BASE_URL = "https://api.tickerlayer.com"
HEADERS = {"x-api-key": os.environ["TICKERLAYER_API_KEY"]}
def bars(symbol, multiplier, timespan, start, end):
"""All bars in a UTC date range, following next_offset until it is null."""
path = f"{BASE_URL}/crypto/agg/{symbol}/{multiplier}/{timespan}/{start}/{end}"
params = {"sort": "asc", "limit": 5000, "offset": 0}
out = []
while True:
resp = requests.get(path, params=params, headers=HEADERS, timeout=15)
resp.raise_for_status()
body = resp.json()
out.extend(body["results"])
if body.get("next_offset") is None:
return out
params["offset"] = body["next_offset"]
# Hourly high-low range in basis points, grouped by UTC weekday.
ranges = defaultdict(list)
for bar in bars("BTCUSD", 1, "hour", "2026-09-21", "2026-09-27"):
weekday = datetime.fromtimestamp(bar["t"] / 1000, tz=timezone.utc).strftime("%a")
ranges[weekday].append((bar["h"] - bar["l"]) / bar["o"] * 10_000)
for weekday in ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]:
r = sorted(ranges[weekday])
print(f"{weekday} {len(r)} bars median hourly range {r[len(r) // 2]:5.1f} bps")Mon 24 bars median hourly range 63.5 bps
Tue 24 bars median hourly range 52.5 bps
Wed 24 bars median hourly range 54.3 bps
Thu 24 bars median hourly range 52.4 bps
Fri 24 bars median hourly range 44.7 bps
Sat 24 bars median hourly range 21.4 bps
Sun 24 bars median hourly range 31.3 bpsBTCUSD median hourly range by UTC weekday
In that week a typical Saturday hour moved a third as much as a Monday hour. If you run volatility alerts or position sizing on crypto, a single threshold for the whole week will be too loose on weekends or too twitchy on weekdays; key it to the hour of the week instead. Long ranges of minute bars take several requests, because each interval has a maximum span per request (31 days for 1-minute bars); the crypto docs list them.
Real-time crypto prices over WebSocket
Polling one quote a second for 20 pairs costs 1.7 million requests a day. A stream costs one connection. Connect to wss://stream.tickerlayer.com/?apiKey=..., wait for the ready frame, then subscribe to crypto.quotes and crypto.trades. This Node script reconnects with backoff and prints the mid, the spread and each trade:
// npm install ws
import WebSocket from "ws";
const URL = "wss://stream.tickerlayer.com/?apiKey=" +
encodeURIComponent(process.env.TICKERLAYER_API_KEY);
const SUBSCRIBE = {
action: "subscribe",
channels: ["crypto.quotes", "crypto.trades"],
symbols: ["BTCUSD", "ETHUSD"],
};
let backoffMs = 1000;
function onFrame(msg) {
if (msg.type === "quote") {
// Crypto numerics arrive as strings: convert before doing arithmetic.
const bid = Number(msg.bid);
const ask = Number(msg.ask);
const mid = (bid + ask) / 2;
const bps = ((ask - bid) / mid) * 10_000;
console.log(`${msg.symbol} quote mid ${mid.toFixed(2)} spread ${bps.toFixed(3)} bps`);
} else if (msg.type === "trade") {
const at = new Date(msg.ts).toISOString().slice(11, 23);
console.log(`${msg.symbol} trade ${Number(msg.price)} x ${Number(msg.size)} at ${at} UTC`);
} else if (msg.type === "system" || msg.type === "error") {
console.log(msg.type, msg.event, msg.code ?? "");
}
}
function connect() {
// The stream sends uncompressed frames; keep compression off on the client too.
const ws = new WebSocket(URL, { perMessageDeflate: false });
ws.on("message", (data) => {
const msg = JSON.parse(data.toString());
if (msg.type === "system" && msg.event === "ready") {
backoffMs = 1000;
ws.send(JSON.stringify(SUBSCRIBE));
}
onFrame(msg);
});
ws.on("close", (code) => {
const wait = backoffMs + (Math.random() * backoffMs) / 2;
console.log(`closed with ${code}, reconnecting in ${Math.round(wait)} ms`);
backoffMs = Math.min(backoffMs * 2, 30_000);
setTimeout(connect, wait);
});
ws.on("error", (err) => console.error("socket error:", err.message));
}
connect();system ready
system subscribed
ETHUSD quote mid 2655.73 spread 0.038 bps
BTCUSD trade 82921.32 x 7e-8 at 10:55:31.946 UTC
ETHUSD trade 2654.43 x 0.01940123 at 10:55:31.998 UTC
ETHUSD trade 2655.56 x 0.021283 at 10:55:31.955 UTC
BTCUSD trade 82921.32 x 0.02 at 10:55:32.076 UTC
BTCUSD quote mid 82968.01 spread 0.001 bps
BTCUSD trade 82968.01 x 0.00006 at 10:55:32.052 UTC
BTCUSD trade 82970.7 x 0.340013 at 10:55:32.582 UTCA crypto.quotes frame
{
"type": "quote",1
"channel": "crypto.quotes",
"asset": "crypto",
"symbol": "BTCUSD",
"bid": "82849.99",2
"ask": "82850",
"bid_size": "15.50507",
"ask_size": "1.87429",
"ts": 17905912038363
}
typeSwitch on it: quote, trade, system or error.bidA string on the stream. Parse with Number() or a decimal type, never by slicing text.tsReceipt time on quotes. On trade frames, ts is the trade's own event time.
- Measure delay on trades, not quotes. Quote
tsis when the aggregation received the quote, sonow - tson a quote tells you little about the market. Tradetsis the event time of the print. - Expect interleaving. Trades from many venues arrive with their own event times, so consecutive frames can be a few milliseconds out of order (10:55:31.998, then 10:55:31.955 above) and a few basis points apart. Sort by
tsif you rebuild a tape. - Ignore dust for display. A last-price line drawn through a 7e-8 BTC print jumps for no economic reason. Chart the mid or bars, or filter prints by size.
- Keep the handler fast. Parse, update state, return. Hand heavy work to a queue so a busy minute never backs up the socket.
Reconnects, heartbeats and snapshot behaviour are covered in depth in the WebSocket market data guide. If you plan to act on this stream, the Python trading bot tutorial builds a paper-trading loop on top of it, and the Discord price bot sends alerts from the same stream to a chat channel.
How to choose the best crypto API for your app
Questions to ask any crypto API
- Whose price is it: one venue, or an aggregate? Is that stated?
- Which quote currency: USD, USDT or a blend? Is the basis measured?
- Bid and ask with sizes, not only a last price.
- Bars on documented UTC boundaries, weekends included, with pagination.
- A stream with a ready handshake, snapshots and documented error frames.
- Readable limits:
X-RateLimit-Limiton every response and a 429 withRetry-After. - Stocks, FX and commodities on the same key, if your app will ever show them next to coins.
Ask, too, how the price is checked. Our crypto benchmark compares the aggregate with venues that are not part of it and publishes the method next to the numbers; any provider you shortlist should be able to show you the same kind of evidence.
A free crypto API is fine for a prototype. Before you ship, read the licence: many free tiers forbid commercial display, and redistribution is usually a separate right. The TickerLayer free tier includes 3,000 REST requests a month with no card; WebSocket comes with paid plans, and free accounts can request a trial from the dashboard.
| Crypto feed | Price | REST requests | WebSocket | Use |
|---|---|---|---|---|
| Free | $0 | 3,000 a month | Trial on request | Prototyping |
| Individual | $49 a month | 250,000 a month | 1 connection, 10 symbols | Personal and research |
| Business | $449 a month | 25,000,000 a month | 10 connections, unlimited symbols | Commercial use |
Symbol lists and sample responses are on the crypto data API product page, and pricing has the bundles for stocks, forex and perpetuals on the same key.
Questions
Is there a free crypto price API?
Yes. The TickerLayer free tier includes 3,000 REST requests a month with no card. Streaming comes with paid plans, and free accounts can request a WebSocket trial from the dashboard.
What is the best crypto API for real-time prices?
The one whose price definition matches your use. For display, alerts and valuation, an aggregated feed with bid, ask and a stated quote currency is the safer default; for executing on one venue, use that venue's own feed.
Why is BTCUSDT different from BTCUSD?
BTCUSDT is priced in the USDT stablecoin and BTCUSD in US dollars. USDT trades a few basis points either side of one dollar, so the two prices differ by that basis before any venue differences.
Do crypto daily candles have a close if the market never closes?
Yes. A daily bar is a UTC day, midnight to midnight, and its close is the last price before 00:00 UTC. Weekends are full days in the series, with their own bars.
What is a cryptocurrency data aggregator?
A service that combines quotes and trades from many venues into one price per pair, so a single venue's outage or thin book does not become your price.
How many crypto pairs does TickerLayer cover?
2,500+ pairs across the aggregation layer. GET /crypto/symbols returns the enabled symbol list with names, and the same key works for forex, stocks and commodities once those feeds are on the account.