Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Cryptokeyhole Charts API
version: 1.0.0
contact:
name: CryptoKeyhole
url: https://cryptokeyhole.com/data-access/
license:
name: CC BY 4.0 (free data; paid-plan data under the terms of service)
url: https://cryptokeyhole.com/methodology/#licence-title
termsOfService: https://cryptokeyhole.com/terms/
x-logo:
url: https://cryptokeyhole.com/favicon.svg
description: 'Operations tagged Charts across 2 of this provider''s published API definitions: cryptokeyhole-openapi.json, cryptokeyhole-openapi.json. Each path carries the servers of the definition it was published in.'
servers:
- url: https://cryptokeyhole.com/api/v1
tags:
- name: Charts
description: Chart data, the catalogue and downloads
paths:
/assets:
get:
tags:
- Charts
operationId: listAssets
summary: Assets
description: The public assets, Bitcoin first. Every other asset's chart ids start with its prefix (for example eth.).
security:
- {}
responses:
'200':
description: The assets
content:
application/json:
schema:
$ref: '#/components/schemas/Assets'
servers:
- url: https://cryptokeyhole.com/api/v1
/catalog:
get:
tags:
- Charts
operationId: getCatalog
summary: Metric catalogue
description: Every chart of one asset with its id, name, unit, description, method, intervals, coverage and status. A planned chart that has not published carries status held and its reason.
security:
- {}
- bearerAuth: []
parameters:
- name: asset
in: query
description: An asset code from /assets. Default BTC, or the asset of the first of ids.
schema:
type: string
example: ETH
- name: ids
in: query
description: 'Comma-separated chart ids of one asset: only these charts.'
schema:
type: string
example: eth.PriceUSD
- name: view
in: query
description: 'directory: the chart directory''s tree instead of the flat list (not with ids).'
schema:
type: string
enum:
- directory
responses:
'200':
description: The catalogue
content:
application/json:
schema:
$ref: '#/components/schemas/Catalog'
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
servers:
- url: https://cryptokeyhole.com/api/v1
/series:
get:
tags:
- Charts
operationId: getSeries
summary: Chart series
description: 'One chart''s observations, one point per period, and for charts with several lines their components. Free charts at 24h, 1w, 1M and 1Y need no key. A chart or interval above the plan answers 200 with state locked and the plan it needs, or a fixed historical sample named by the X-CKH-Preview header. Price-distribution charts (catalogue distribution.type price_histogram) take one UTC day: start at midnight and end exactly one day later.'
security:
- {}
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/metric'
- $ref: '#/components/parameters/interval'
- $ref: '#/components/parameters/start'
- $ref: '#/components/parameters/end'
- name: limit
in: query
description: Most points returned (12 to 20,000, default 3,000); a longer range is reduced to it.
schema:
type: integer
minimum: 12
maximum: 20000
default: 3000
- $ref: '#/components/parameters/sma'
- $ref: '#/components/parameters/smoothing'
- $ref: '#/components/parameters/publication'
responses:
'200':
description: The series, or its plan lock
headers:
X-CKH-Preview:
description: 'Set when the answer is a fixed sample of a chart above the plan: plan, name, from, through (UTC seconds) and sample=1.'
schema:
type: string
Link:
description: 'Set on a free chart''s answer at a free resolution when the data is ours to license: <https://creativecommons.org/licenses/by/4.0/>; rel="license".'
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/Series'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
servers:
- url: https://cryptokeyhole.com/api/v1
/formula:
get:
tags:
- Charts
operationId: getFormula
summary: Saved formula as a series
description: A saved formula read like a chart. Anyone can read a formula by the share id of its public link; with your key you can also read your own saved formulas by their id. Every input chart and the interval must be in the requester's plan. Without interval, start and end the formula's saved interval and range apply.
security:
- {}
- bearerAuth: []
parameters:
- name: id
in: query
required: true
description: A share id, or the id of one of your own saved formulas.
schema:
type: string
- name: interval
in: query
schema:
type: string
enum:
- 10m
- 1h
- 24h
- 1w
- 1M
- $ref: '#/components/parameters/start'
- $ref: '#/components/parameters/end'
- name: limit
in: query
schema:
type: integer
minimum: 12
maximum: 20000
responses:
'200':
description: The formula's series
content:
application/json:
schema:
$ref: '#/components/schemas/Series'
'400':
$ref: '#/components/responses/BadRequest'
'402':
$ref: '#/components/responses/PlanRequired'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
servers:
- url: https://cryptokeyhole.com/api/v1
/export:
get:
tags:
- Charts
operationId: exportSeries
summary: Chart download (Plus and higher)
description: The chart pages' CSV or JSON download of one chart, with every provenance column, the source, licence and attribution of each series. Included with Plus and higher plans.
security:
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/metric'
- $ref: '#/components/parameters/interval'
- $ref: '#/components/parameters/start'
- $ref: '#/components/parameters/end'
- $ref: '#/components/parameters/sma'
- $ref: '#/components/parameters/smoothing'
- name: format
in: query
schema:
type: string
enum:
- csv
- json
default: csv
- $ref: '#/components/parameters/publication'
responses:
'200':
description: The download, as an attachment
content:
text/csv:
schema:
type: string
application/json:
schema:
$ref: '#/components/schemas/Series'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PlanRequired'
'429':
$ref: '#/components/responses/TooManyRequests'
servers:
- url: https://cryptokeyhole.com/api/v1
/sql:
get:
tags:
- Charts
operationId: querySql
summary: SQL over every chart's daily values
description: 'One read-only statement (SELECT, or WITH ... SELECT; SQLite''s dialect) over every public chart''s daily values: tables series (asset, metric, chart, component, interval, day, t, value, finality), latest, metrics and assets; GET /sql/schema lists them with examples. Pro and API & Team: a key''s query counts one API call. At most 100,000 rows (truncated), 30 seconds per statement. Values are 64-bit floats; the series route keeps exact decimals. The database is read again every hour.'
security:
- bearerAuth: []
parameters:
- name: q
in: query
required: true
description: The SQL statement.
schema:
type: string
example: SELECT a.asset, l.value FROM assets a JOIN latest l ON l.metric = a.price ORDER BY l.value DESC
- name: format
in: query
schema:
type: string
enum:
- json
- csv
default: json
- name: maxRows
in: query
schema:
type: integer
minimum: 1
maximum: 100000
responses:
'200':
description: columns, rows, rowCount, truncated and ms (JSON), or CSV
content:
application/json:
schema:
type: object
properties:
columns:
type: array
items:
type: string
rows:
type: array
items:
type: array
items: {}
rowCount:
type: integer
truncated:
type: boolean
ms:
type: integer
text/csv:
schema:
type: string
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PlanRequired'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: The database is being built or the query service is busy
post:
tags:
- Charts
operationId: querySqlPost
summary: SQL over every chart's daily values (long queries)
description: As GET /sql, with the statement in a JSON body. A signed-in browser session sends its X-CSRF-Token.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- query
properties:
query:
type: string
format:
type: string
enum:
- json
- csv
maxRows:
type: integer
responses:
'200':
description: As GET /sql
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PlanRequired'
'429':
$ref: '#/components/responses/TooManyRequests'
servers:
- url: https://cryptokeyhole.com/api/v1
/sql/schema:
get:
tags:
- Charts
operationId: getSqlSchema
summary: SQL tables, columns and examples
description: The tables and views of the SQL database with their columns, the finality words, example queries and when the data was read. No key needed.
security:
- {}
responses:
'200':
description: The schema
content:
application/json:
schema:
type: object
'503':
description: The database is being built
servers:
- url: https://cryptokeyhole.com/api/v1
/bulk/metric:
get:
tags:
- Charts
operationId: getEveryCoin
summary: One metric for every coin
description: 'One chart for every coin that publishes it, in one call: each coin''s . chart (Bitcoin''s ckh.) with its components, as daily values with their finality, from the SQL database. Plus and higher; a coin''s chart above the plan is listed under locked. One API call. At most 500,000 rows (truncated).'
security:
- bearerAuth: []
parameters:
- name: metric
in: query
required: true
description: A chart name without its coin prefix, or any coin's full chart id.
schema:
type: string
example: PriceUSD
- name: assets
in: query
description: Comma-separated coins (all when absent).
schema:
type: string
example: BTC,ETH
- name: since
in: query
description: First UTC day, YYYY-MM-DD or Unix seconds.
schema:
type: string
- name: until
in: query
description: Last UTC day, YYYY-MM-DD or Unix seconds.
schema:
type: string
- name: format
in: query
schema:
type: string
enum:
- json
- csv
default: json
responses:
'200':
description: metric, database (builtAt, kind, fullAt), columns (t, value, finality), series (asset, metric, chart, component, name, unit, interval, plan, url, points), locked, rows, truncated, licence; or CSV (asset, metric, chart, component, day, t, value, finality)
content:
application/json:
schema:
type: object
text/csv:
schema:
type: string
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PlanRequired'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: The database is being built
servers:
- url: https://cryptokeyhole.com/api/v1
/bulk/files:
get:
tags:
- Charts
operationId: listBulkFiles
summary: The nightly files
description: 'The latest nightly files the plan includes: one gzip CSV per coin and the series list (Pro and API & Team) and the whole SQLite database (API & Team), each with bytes, rows, SHA-256 and a download link that works for an hour without a key. One API call.'
security:
- bearerAuth: []
responses:
'200':
description: builtAt, database, linksExpire, files (name, kind, asset, plan, bytes, rows, sha256, url), locked
content:
application/json:
schema:
type: object
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PlanRequired'
'503':
description: The files are being built for the first time
servers:
- url: https://cryptokeyhole.com/api/v1
/bulk/file/{name}:
get:
tags:
- Charts
operationId: downloadBulkFile
summary: One nightly file (a signed link)
description: A file from GET /bulk/files, by the link it signed (e and s); no key needed. Byte ranges are supported for resumed downloads.
security:
- {}
parameters:
- name: name
in: path
required: true
schema:
type: string
- name: e
in: query
required: true
description: The link's expiry (Unix seconds)
schema:
type: integer
- name: s
in: query
required: true
description: The link's signature
schema:
type: string
responses:
'200':
description: The file
content:
application/gzip:
schema:
type: string
format: binary
'206':
description: The requested byte range
'403':
description: The link is not valid or has expired
'404':
description: The file was replaced by a newer set
'429':
$ref: '#/components/responses/TooManyRequests'
servers:
- url: https://cryptokeyhole.com/api/v1
/vintages:
get:
tags:
- Charts
operationId: getVintages
summary: First-published values
description: Each period's first published value, the reading a backtest would have seen at the time, or with asOf each period's value as it stood then. Every point carries seen, when CryptoKeyhole's recorder first saw that value. History starts when the recorder started (recordedSince) and cannot be recreated for earlier times. API & Team. One API call.
security:
- bearerAuth: []
parameters:
- name: metric
in: query
required: true
description: A recorded chart id; /vintages/charts lists them.
schema:
type: string
example: eth.ExchangeNetflow
- name: interval
in: query
schema:
type: string
enum:
- 1h
- 24h
default: 24h
- name: start
in: query
description: 'First period: YYYY-MM-DD, ISO time or Unix seconds.'
schema:
type: string
- name: end
in: query
description: 'End of the window, exclusive: YYYY-MM-DD, ISO time or Unix seconds.'
schema:
type: string
- name: asOf
in: query
description: Read each period as it stood at this time; without it, each period's first published value.
schema:
type: string
- name: format
in: query
schema:
type: string
enum:
- json
- csv
default: json
responses:
'200':
description: metric, interval, mode (first or asOf), asOf, recordedSince, truncated, points (t, v, finality, seen), about, docs; or CSV
content:
application/json:
schema:
type: object
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PlanRequired'
'404':
description: No vintages are recorded for this chart and interval
servers:
- url: https://cryptokeyhole.com/api/v1
/vintages/charts:
get:
tags:
- Charts
operationId: listVintageCharts
summary: The charts with first-published values
description: The recorded charts and intervals, and since when each has been recorded. No key needed.
security:
- {}
responses:
'200':
description: charts (metric, interval, recordedSince, vintages), listed, firstRun, lastRun, docs
content:
application/json:
schema:
type: object
servers:
- url: https://cryptokeyhole.com/api/v1
/data-webhooks:
get:
tags:
- Charts
operationId: listDataWebhooks
summary: Data webhooks and destinations
description: The account's data webhooks (a chart's new values, or a screen's changes, pushed to one of its webhooks) and its webhook destinations. Pro and API & Team.
security:
- bearerAuth: []
responses:
'200':
description: items, destinations, intervals
content:
application/json:
schema:
type: object
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PlanRequired'
post:
tags:
- Charts
operationId: addDataWebhook
summary: Add a data webhook
description: 'A chart ({destinationId, metric, interval}) or a screen ({destinationId, screen: {where}, interval}; the screener''s conditions). Each new value is sent once as provisional, then final (event data.value); a screen''s change in matching coins is one event (data.screen). It starts from the latest value. Checks run every five minutes. Pro holds 50, API & Team 200.'
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- destinationId
- interval
properties:
destinationId:
type: string
metric:
type: string
screen:
type: object
properties:
where:
type: string
interval:
type: string
enum:
- 10m
- 1h
- 24h
responses:
'201':
description: The new item
content:
application/json:
schema:
type: object
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PlanRequired'
'404':
$ref: '#/components/responses/NotFound'
servers:
- url: https://cryptokeyhole.com/api/v1
/data-webhooks/delete:
post:
tags:
- Charts
operationId: deleteDataWebhook
summary: Delete a data webhook
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- id
properties:
id:
type: string
responses:
'200':
description: Deleted
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
servers:
- url: https://cryptokeyhole.com/api/v1
components:
responses:
TooManyRequests:
description: Request limit reached; try again in a minute
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
BadRequest:
description: The request's parameters are invalid
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
PlanRequired:
description: Included with a higher plan; locked names it
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
NotFound:
description: Unknown chart, asset, id or API route
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: The API key is invalid or revoked, or was sent to a route keys do not read
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
parameters:
publication:
name: publication
in: query
description: A retained publication id, for the charts that keep them; omit for current data.
schema:
type: string
end:
name: end
in: query
description: UTC Unix seconds, not included. Without it the data runs to the latest value.
schema:
type: integer
interval:
name: interval
in: query
description: 'Resolution, as the chart offers it: Free 24h, 1w, 1M and 1Y; Plus also 1h; Pro also 10m.'
schema:
type: string
enum:
- 10m
- 1h
- 24h
- 1w
- 1M
- 1Y
default: 24h
start:
name: start
in: query
description: UTC Unix seconds, included. Without it the chart's default window applies.
schema:
type: integer
smoothing:
name: smoothing
in: query
description: Smoothing method, as on the charts.
schema:
type: string
enum:
- sma
- ema
- median
default: sma
metric:
name: metric
in: query
required: true
description: The chart id, as in its address (/charts/eth/?metric=eth.PriceUSD) or the catalogue.
schema:
type: string
example: eth.PriceUSD
sma:
name: sma
in: query
description: Smoothing window in observations (0 for none).
schema:
type: integer
minimum: 0
default: 0
schemas:
Point:
type: object
required:
- t
- v
- finality
properties:
t:
type: integer
description: The period's start, UTC seconds.
v:
type:
- string
- 'null'
description: Exact decimal; null where there is no observation.
finality:
type: string
enum:
- final
- provisional
- partial
Locked:
type: object
description: What a higher plan would include.
properties:
feature:
type: string
example: interval
requiredPlan:
type: string
enum:
- plus
- pro
- team
requiredPlanName:
type: string
example: Plus
currentPlan:
type: string
example: free
currentPlanName:
type: string
example: Free
upgradePath:
type: string
example: /pricing/
allowedIntervals:
type: array
items:
type: string
Assets:
type: object
properties:
assets:
type: array
items:
type: object
properties:
code:
type: string
example: ETH
name:
type: string
example: Ethereum
route:
type: string
example: eth
prefix:
type: string
example: eth.
description: The asset's chart ids start with it (not for BTC).
Error:
type: object
required:
- error
properties:
error:
type: string
locked:
$ref: '#/components/schemas/Locked'
Catalog:
type: object
properties:
asset:
type: string
metrics:
type: array
items:
$ref: '#/components/schemas/Metric'
Series:
type: object
properties:
metric:
$ref: '#/components/schemas/Metric'
interval:
type: string
state:
type: string
description: current; delayed (the source is late, with its delay); held (not published); locked (above the plan, see locked).
reason:
type:
- string
- 'null'
points:
type: array
items:
$ref: '#/components/schemas/Point'
components:
type: array
description: The lines of a chart with several, each with its points.
items:
type: object
properties:
id:
type: string
name:
type: string
unit:
type: string
points:
type: array
items:
$ref: '#/components/schemas/Point'
historyStart:
type:
- integer
- 'null'
historyEnd:
type:
- integer
- 'null'
generatedAt:
type: integer
sourceObservedAt:
type:
- integer
- 'null'
locked:
$ref: '#/components/schemas/Locked'
Metric:
type: object
properties:
id:
type: string
example: eth.PriceUSD
name:
type: string
family:
type: string
unit:
type: string
example: USD/ETH
description:
type: string
method:
type: string
intervals:
type: array
items:
type: string
status:
type: string
description: 'held: planned but not published (see reason).'
reason:
type:
- string
- 'null'
historyStart:
type:
- integer
- 'null'
description: UTC seconds, the first period.
historyEnd:
type:
- integer
- 'null'
description: UTC seconds, the end of the last period (not included).
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'An API key from your account page (Plus and higher plans): Authorization: Bearer <key>. Keys are read-only.'
externalDocs:
description: API keys, plans, Google Sheets, Excel and Python
url: https://cryptokeyhole.com/data-access/
x-refined-from:
- cryptokeyhole-openapi.json
- cryptokeyhole-openapi.json
x-provenance:
method: harvested
publisher: CryptoKeyhole
source: https://cryptokeyhole.com/api/v1/openapi.json
note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json);
stamped by stamp-openapi-provenance.py (roadmap#793)