For the complete documentation index, see llms.txt. This page is also available as Markdown.

API Conventions

The rules on this page hold across every endpoint in this reference. Each endpoint's own page lists the parameters it accepts and the fields it returns, and this page describes the behavior those parameters share: how timestamps are parsed, how numbers and nulls are represented, how paging works, and what an error looks like.

Rate limits, authentication, and diagnostic response headers have their own pages in this reference: Rate limits, Authentication, and Response headers.

Endpoint families

Endpoints are grouped by what they answer rather than by product.

Family
Answers
Notes

/reference-data/*

What entities and metrics exist, and how their identifiers are formed

Descriptions, units, and data types. Independent of your entitlements.

/catalog-v2/*

What your API key can query, and over what time range

Returns min_time and max_time per frequency.

/catalog-all-v2/*

What Coin Metrics supports in full, regardless of entitlement

Use to compare your coverage against the whole dataset.

/timeseries/*

Historical and current observations over HTTP

Computed, sampled, or event series. See Frequency and granularity.

/timeseries-stream/*

The same data in real time over WebSocket

Named to match the HTTP endpoint, with a different parameter set.

Endpoints that do not fit this shape follow the same conventions: ATLAS blockchain data, taxonomy, constituents, profiles, and blockchain metadata.

The discovery loop

Those families compose into one pattern that holds across the entire API, whatever the data type: find out what exists, check what is available to you and over what range, then read it. The three layers are separate because the answers change on different schedules. What exists changes when Coin Metrics adds coverage, what is available to you changes with your subscription, and the data itself changes continuously.

What exists. The 13 /reference-data/* endpoints describe the universe independently of any entitlement. Six return entities and their attributes (assets, exchanges, markets, pairs, indexes, protocols), and seven return the metrics defined at each entity level, such as /reference-data/asset-metrics, with each metric's full name, description, unit, and data type, and for most of them a docs_url pointing at its documentation page. This layer is where identifier formats are established, so it is the right place to learn that a spot market is written coinbase-btc-usd-spot before passing one anywhere else.

What is available, and when. The 31 /catalog-v2/* endpoints report min_time and max_time per entity and frequency, so they answer whether a series covers the window you need. Each has an exact /catalog-all-v2/* twin: the first reflects your API key's entitlements, the second the whole Coin Metrics dataset. Comparing the two is how you tell an absence of data from an absence of access.

Read it. The 28 /timeseries/* endpoints serve history and current values over HTTP, and 11 /timeseries-stream/* endpoints serve real-time messages over WebSocket.

Reading the Bitcoin reference rate runs the loop like this, and the same three calls work for any metric by substituting the entity and metric names:

Step
Call
What it returns

1

/reference-data/asset-metrics?metrics=ReferenceRateUSD

That the metric is quoted in USD, is a decimal, and what it means

2

/catalog-v2/asset-metrics?assets=btc&metrics=ReferenceRateUSD

The frequencies carrying it, each with its own min_time and max_time

3

/timeseries/asset-metrics?assets=btc&metrics=ReferenceRateUSD&frequency=1d

The values

Step 2 is the step most often skipped and the one that explains most empty responses. The same metric at the same frequency can report a much shorter history on /catalog-v2 than on /catalog-all-v2, because the first is bounded by what your key is entitled to. A query outside that window returns no rows rather than an error, so an empty response is worth checking against the catalog before treating it as missing data.

Coverage presents the first two layers as browsable tables, which is usually faster than steps 1 and 2 when you are exploring rather than automating.

Frequency and granularity

Two different parameters control the time resolution of a timeseries response, and confusing them is easy because both take values that look like intervals. frequency selects the rows, and granularity downsamples them.

frequency sets the interval between the rows you get back. What each row means is a property of the metric rather than of frequency, and Coin Metrics uses two timestamp conventions for that. Under the point-in-time convention a row is the value at its own timestamp, so changing frequency changes only how often you sample and the values at timestamps common to both are identical. Under the beginning-of-interval convention a row is a summary statistic over the interval starting at its timestamp. A metric's name is the signal: an interval suffix such as _1d means it is an aggregation, and no suffix means it is point-in-time. What timestamp conventions does Coin Metrics use? lists which endpoints and metrics follow which.

granularity downsamples a stream of observations that already exists at its own natural cadence. It defaults to raw, which returns every observation, and 1m, 1h, or 1d return a subset instead. Every row is still one real observation rather than an average of the interval, so changing granularity changes how many rows come back and never what any row says.

Shape
Parameter
What a row is
Examples

Frequency series

frequency

Either the value at its timestamp or a summary of the interval starting there, depending on the metric

/timeseries/asset-metrics, /timeseries/market-candles, /timeseries/pair-metrics

Sampled series

granularity

One observation, as recorded

/timeseries/market-quotes, /timeseries/market-orderbooks, /timeseries/market-openinterest

Event series

neither

One event, at the time it happened

/timeseries/market-trades, /timeseries/market-liquidations, /timeseries/market-funding-rates

Timestamps

Timestamps follow ISO 8601. Requests accept ten forms, and fractional seconds must carry exactly 3, 6, or 9 digits.

Precision
Accepted forms

Date only

2020-03-13, 20200313

Seconds

2020-03-13T15:25:15, 2020-03-13T152515

Milliseconds

2020-03-13T15:25:15.123, 2020-03-13T152515.123

Microseconds

2020-03-13T15:25:15.123456, 2020-03-13T152515.123456

Nanoseconds

2020-03-13T15:25:15.123456789, 2020-03-13T152515.123456789

A trailing Z is optional and its case is not significant. A compact date combined with a time, such as 20200313T152515, is not accepted.

Query timestamps are interpreted as UTC unless timezone names another zone. Responses are always UTC with nanosecond precision, regardless of the request.

start_time defaults to the Unix epoch and end_time to the current time. Both bounds are inclusive by default. Setting start_inclusive or end_inclusive to false excludes that boundary observation, so a 1d range of 2020-01-05 to 2020-01-08 returns four rows by default and three with either flag set to false.

Numbers

Numbers in responses are JSON strings rather than JSON numbers, because many of these values do not survive a round trip through a 64-bit float. The decimal separator is always a dot, and the number of decimal places is whatever the source blockchain or exchange reported. The API does not round or pad.

Amounts are denominated in an asset's whole unit rather than its smallest indivisible subunit. Bitcoin values are in BTC, not satoshis, and Ether values are in ETH, not wei. This is the unit an exchange or a price feed quotes in, not the integer representation a blockchain stores internally, so a value is directly comparable to a quoted price and needs no rescaling by the asset's decimal precision.

Choosing the whole unit costs no precision, since subunit precision is carried in the decimal places instead. An Ether amount can arrive as 467.472695979897181983, which is the same information wei would carry as an integer, and also a good illustration of why the value is a string.

Nulls

A field with no value is normally omitted from the response rather than returned as null.

An explicit null means the value is genuinely undefined rather than missing: a metric that cannot be computed for that timestamp, for example because it would divide by zero, or a metric requested alongside others that has no value at a timestamp where they do.

/timeseries/asset-metrics accepts null_as_zero=true to convert those nulls to zero. No other endpoint takes the parameter, and sending it elsewhere returns unsupported_parameter, so treat it as a feature of that one endpoint rather than a convention.

Paging

Responses are paged. page_size defaults to 100 and accepts up to 10,000, and a few endpoints set their own bounds.

When more results exist, the response carries next_page_token and next_page_url at the top level. Fetch next_page_url unmodified to get the next page, and treat the absence of the field as the end of the result set.

paging_from controls which end of the time interval the first page comes from, and its default is not uniform:

Default
Endpoints

end, newest first

/timeseries/*, /blockchain/*, /blockchain-v2/*, /constituent-snapshots/*, /constituent-timeframes/*

start, oldest first

/catalog-v2/*, /catalog-all-v2/*, /reference-data/*, /taxonomy/*, /taxonomy-metadata/*, /profile/*

Limiting rows per entity

limit_per_<entity> caps how many rows each requested entity contributes, which is how you read the latest value for many entities in a single request rather than one request each. The name matches the entity, and one of limit_per_asset, limit_per_market, limit_per_exchange, limit_per_exchange_asset, limit_per_exchange_pair, limit_per_pair, limit_per_index, or limit_per_institution is available on 21 of the 28 /timeseries/* endpoints.

Which rows you get follows paging_from. On an endpoint defaulting to end, limit_per_asset=1 returns each asset's most recent value, and adding paging_from=start returns its earliest instead. page_size still applies to the response as a whole, so a request whose per-entity limits add up to more than one page still pages normally.

Response formats

The format parameter selects the response encoding. json is the default and is accepted everywhere, and the other two are not universal.

Value
Behavior
Accepted on

json

A single object with a data array plus paging fields.

Every endpoint

json_stream

Newline-delimited objects in one unbounded response, with no paging round trips.

17 of 28 /timeseries/*, plus every /catalog-v2/*, /catalog-all-v2/*, and /reference-data/*

csv

Flat rows with a header.

25 of 28 /timeseries/*. Not accepted on catalog or reference data

Errors

Errors return a JSON body with a stable machine-readable type and a human-readable message.

Status
Type
Cause

400

bad_parameter

A parameter is present but its value is malformed or unsupported. The message names the parameter and, where the set is bounded, the accepted values.

400

missing_parameter

A required parameter was not supplied.

400

unsupported_parameter

The parameter is not valid for this endpoint, even though another endpoint accepts it.

400

bad_parameters

Each parameter is individually valid but the combination is not.

401

unauthorized

No API key was supplied to an endpoint that requires one.

401

wrong_credentials

The API key is not valid or has expired.

403

forbidden

The key is valid but is not entitled to at least one requested resource, or to the requested time range.

404

not_found

No such endpoint or path.

413

request_too_large

An ATLAS job request body exceeds the accepted size.

414

none

The request URI exceeded roughly 16 KB, which a long comma-separated markets or assets list will reach. Split the list across requests. Returned by the edge with no JSON body, so handle it separately from the errors above.

429

too_many_requests

A rate limit was exceeded, or too many WebSocket connections are open for the key.

500

operation_failed

A server-side failure.

type is the field to write code against. It is a fixed identifier covered by the Backward Compatibility Policy, whereas message is written for a human reading a log and may be reworded in any release, which that policy explicitly permits. Matching on message substrings is the most common way an integration breaks on an otherwise compatible change.

Neither type nor the status code alone is a complete signal, so pair them. A single status can cover several types: 401 is unauthorized when no key was supplied and wrong_credentials when the key is invalid, and only the second is worth alerting on. A single type also covers several causes: bad_parameter is returned both for a page_size above the maximum and for a format an endpoint does not support. Use type to decide what to do, and read message when you need to know which parameter was at fault, since it names the parameter and, where the set is bounded, lists the accepted values.

Streaming

/timeseries-stream/* endpoints deliver each observation as a JSON message as soon as it is known.

backfill sets what arrives on connect. The default latest sends the most recent value for each subscribed entity before real-time messages begin, and none starts from live data.

Every message carries cm_sequence_id, a counter that increases by one per message and resets to zero on every reconnect. A gap in the sequence means messages were dropped within that connection. It is not a cursor and cannot be used to resume where a previous connection stopped.

The server sends a WebSocket PING frame to any connection idle for 10 seconds and expects the PONG that standard WebSocket libraries answer with automatically. Connections are also recycled periodically by the infrastructure in front of the API, so treat a close as routine and reconnect rather than as an error.

Last updated