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.
Endpoint families
Endpoints are grouped by what they answer rather than by product.
/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 unversioned /catalog/* and /catalog-all/* endpoints are deprecated and are not part of this reference. Use their -v2 equivalents, whose response shape differs.
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:
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
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.
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
Which frequency values an endpoint supports varies by endpoint and by entity, so a frequency valid for one asset or index can be rejected for another on the same endpoint. Read the supported set from /catalog-v2/*, where frequencies are listed per entity, rather than assuming a family-wide list. An unsupported value returns bad_parameter.
Timestamps
Timestamps follow ISO 8601. Requests accept ten forms, and fractional seconds must carry exactly 3, 6, or 9 digits.
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.
A fraction of any other length is rejected with bad_parameter, so 2020-03-13T15:25:15.12345789 fails on its eight digits. The error message lists every accepted form.
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:
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/*
Because most timeseries endpoints page from end, a request whose page size is smaller than the interval returns the newest rows in that interval, not the oldest. Pass paging_from=start when you intend to read a range forward. /timeseries/protocol-metrics is the one timeseries endpoint that already defaults to start.
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.
Because json_stream ignores paging_from, it also changes which rows limit_per_<entity> selects. One otherwise identical query, assets=btc&metrics=PriceUSD&frequency=1d&limit_per_asset=1, returns Bitcoin's most recent daily price as json and its earliest, dated 2010-07-18, as json_stream.
Response formats
The format parameter selects the response encoding. json is the default and is accepted everywhere, and the other two are not universal.
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
json_stream ignores page_size and paging_from rather than rejecting them. It returns the whole requested range in one response, oldest first, even where the same endpoint would have returned the newest rows first as json. A query whose row order or row count changes when you switch format is almost always this.
Errors
Errors return a JSON body with a stable machine-readable type and a human-readable message.
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.
Related
Rate limits: request budgets, parallel-request caps, and the
X-RateLimit-*headers.Backward Compatibility Policy: which of these conventions may change, and in what kind of release.
How To Use the Coin Metrics API Efficiently: applying paging and formats to large reads.
How to Troubleshoot Common Errors: what to do about a specific status code.
Python API Client: a client that handles paging and formats for you.
Last updated

