Ref. API · v1
The registry, as JSON
The coins, exchanges, prices and daily history behind this site, through an open API. Reading needs no key and no sign-up.
04:20:21UTC Last markets run
Last read 2 minutes ago. 12 venues answered in the last two hours. The sync runs every five minutes; /up/data calls the feed stale after 20 minutes without a run.
- Base URL
https://chainregistry.net/api/v1- Key
- None to read
- Limit
- 120 requests a minute per IP address
- Caching
- 60 seconds and an ETag on most reads
- Browsers
- CORS open to any site
- Format
- JSON, US dollars, UTC
Start here
Every route lives under https://chainregistry.net/api/v1. Send Accept: application/json and you get JSON back, errors included. This is the registry's headline reading:
curl "https://chainregistry.net/api/v1/stats" -H "Accept: application/json"{
"data": {
"updated_at": "2026-10-26T12:20:45Z",
"coins_ranked": 2155,
"venues_live": 11,
"venues_total": 12,
"market_cap": 2708778581796.619,
"volume_24h": 12628536326.38349,
"btc_dominance": 59.57,
"history_from": "2013-03-31"
}
}- A list comes as
{"data": [...], "meta": {"page", "per_page", "total", "last_page"}}; ask for the next one with?page=2. A single thing comes as{"data": {...}}. - Prices, caps and volumes are plain numbers in US dollars, and
nullwhen the registry has no figure. - Changes are percentages:
-0.21means down 0.21%. - Times are ISO 8601 in UTC, like
2026-10-26T12:20:45Z. Days are written2026-10-26. - Prices are volume-weighted across the exchanges the registry reads every five minutes. None of it comes from a market-data aggregator.
Limits
Each IP address gets 120 requests a minute, all routes together. Some routes also keep a count of their own, with a lower limit:
| Route | Limit |
|---|---|
GET /search | 60 a minuteper IP address |
POST /auth/register | 5 a minuteper IP address |
POST /auth/login | 10 a minuteper IP address |
POST /auth/forgot | 5 a minuteper IP address |
POST /coins/{}/vote POST /watchlist/{} DELETE /watchlist/{} | 60 a minuteper member |
POST /posts | 10 a minuteper member |
POST /posts/{}/replies | 20 a minuteper member |
POST /posts/{}/report POST /replies/{}/report | 10 a minuteper member |
POST /devices | 10 a minuteper IP address |
PATCH /devices/me DELETE /devices/me GET /alerts POST /alerts DELETE /alerts/{} | 30 a minuteper device key |
Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining for the limit that applies to it. Past the limit you get 429 with Retry-After, the seconds to wait. Failed requests count too: a 401 or a 404 uses up a request like any other. In a browser on another site, CORS keeps those headers from your script, so watch for the 429 itself.
Caching
The figures change with the five-minute sync, so most reads say you can keep their answer for 60 seconds: Cache-Control: public, max-age=60 and an ETag. Send the ETag back as If-None-Match and you get an empty 304 Not Modified until something changes. A 304 still counts toward the limit.
HTTP/2 200
content-type: application/json
cache-control: max-age=60, public
etag: "1e46cfbed4526354674177bf74596641"
x-ratelimit-limit: 120
x-ratelimit-remaining: 119
access-control-allow-origin: *Left out: the security headers every page sends.
curl -i "https://chainregistry.net/api/v1/stats" -H 'If-None-Match: "1e46cfbed4526354674177bf74596641"'/coins and /coins/{slug} cache the same way when you send no token. With a token, the body carries your own votes, so the answer is private, and Vary: Authorization says so. Search, the community, your account and alerts aren't cached. Each endpoint below shows how long its answer keeps.
Errors
An error is JSON with a message. When the input doesn't validate, errors says what's wrong with each field:
{"message": "The q field is required.", "errors": {"q": ["The q field is required."]}}| Status | When | Body |
|---|---|---|
| 401 | No token, or one that has been revoked. On the alert routes: no device key, or one the registry doesn't know. | {"message": "Unauthenticated."} {"message": "Unknown device. Register it first."} |
| 403 | The account is suspended, and its tokens are revoked with it. Removing someone else's post or reply is a 403 too. | {"message": "This account has been suspended."} |
| 404 | Nothing there: an unknown or hidden coin, a venue the registry doesn't read, a mistyped path. | {"message": "Not found."} |
| 405 | The route exists, but not for that method. Allow lists the ones it takes. | {"message": "Method not allowed."} |
| 422 | The input didn't validate. | {"message": "…", "errors": {…}} |
| 429 | Too many requests. Wait Retry-After seconds. | {"message": "Too Many Attempts."} |
Members and devices
Reading is open. Voting, posting, reporting, the watchlist and your own account need a member token. Sign in with your email address or username, and name the device the token is for:
curl -X POST "https://chainregistry.net/api/v1/auth/login" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "your password", "device_name": "my laptop"}'The answer holds a token and your profile. Send the token as Authorization: Bearer <token>; the examples below read it from $TOKEN. Tokens don't expire. POST /auth/logout revokes the one you used, and changing your password revokes all the others. When an account is suspended, its tokens stop working.
Price alerts belong to a phone, not to an account. An app registers with POST /devices and its Expo push token, gets a device_key back once, and sends it as X-Device-Key to /alerts and /devices/me; the examples read it from $DEVICE_KEY. An alert fires once, as a push notification, shortly after the sync that crosses its price.
Endpoints
The endpoint list isn't available right now: the file it's built from didn't load. Everything above still holds.
Live prices
The figures that refresh on this site's pages come from /api/live: the latest reading for up to 120 coins in one request. It's made for these pages, so it sits outside v1 and keeps its field names short. CORS covers /api/v1 only, so browser apps on other origins can't use it; call it from a server instead. For anything you mean to keep, use v1.
curl "https://chainregistry.net/api/live?coins=bitcoin,ethereum" -H "Accept: application/json"{
"at": "2026-10-26T12:20:45Z",
"coins": {
"bitcoin": {
"p": 80265.34069075,
"c1": -0.0828,
"c24": -0.20959655176109,
"c7": -2.7871,
"cap": 1613616224158.4,
"vol": 4475175777.2506,
"r": 1
}
}
}Bitcoin at the 12:20:45 UTC reading of Oct 26, 2026: the figures of the /coins example, under this route's short names.
at is when the last markets run finished, and null before the first one. Each coin has p, its price in US dollars; c1, c24 and c7, its change over an hour, a day and a week in percent; cap, vol and r, its rank. A figure the registry doesn't have is null. Pass the slugs as ?coins=a,b or as a coins[] list. Unknown slugs are ignored, and so are hidden coins and coins that aren't ranked or haven't been priced in two days. The answer is the same for everyone and keeps for 30 seconds; send its ETag back as If-None-Match for a 304. Each IP address gets 120 requests a minute here.
Share cards
Every coin has a share card: a 1200 × 630 JPEG with its price, 24-hour change, rank, market cap and volume, stamped with the time of the reading. Link previews use it, and you can put it on any page:
<a href="https://chainregistry.net/coins/bitcoin">
<img src="https://chainregistry.net/coins/bitcoin/card.jpg" width="600" height="315" alt="Bitcoin's price on ChainRegistry">
</a>Swap bitcoin for any coin's slug, the last part of its address here. A card is drawn again once it's 10 minutes old, and sent with Cache-Control: public, max-age=600. Card images are limited to 30 a minute per IP address; every visitor's browser fetches its own, so a busy page is fine. If a card can't be drawn, the same address answers with the site's brand card.

Health check
For an uptime monitor, use /up/data. It answers 200 with "status": "fresh" while the last markets run is under 20 minutes old, and 503 with "status": "stale" after that, so a plain HTTP check catches a stalled feed. It's never cached. Plain /up only tells you the app is answering.
curl -i "https://chainregistry.net/up/data"{"status": "fresh", "markets_age_seconds": 259, "last_markets_run": "2026-10-26T12:20:45Z", "venues_live": 12}markets_age_seconds is how long ago that run finished, and venues_live counts the exchanges the registry read without an error in the last two hours. The instrument at the top of this page shows the same reading, refreshed every minute.