Snapshot API
The Snapshot API renders on-chain analytics charts as PNG images or SVG vector graphics. Send metric IDs and optional display parameters, and receive a fully-rendered chart image ready for embedding in reports, dashboards, or AI conversations. Currently supports Bitcoin metrics, with multi-chain coverage (ETH, TON, TRON) expanding.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/chart/snapshot | Simple charts via query parameters |
POST | /v1/chart/snapshot | Full customization via JSON body |
Both endpoints return image/png by default. Switch to SVG (image/svg+xml) or JSON metadata (application/json) by setting the Accept request header, or — on POST — the format body field ("png", "svg", "json").
Successful PNG and SVG responses include an X-Render-Id header so the rendered chart can be re-fetched by URL within 1 hour:
| Output | Cached at |
|---|---|
| PNG | https://api.blocklens.co/v1/chart/renders/{X-Render-Id}.png |
| SVG | https://api.blocklens.co/v1/chart/renders/{X-Render-Id}.svg |
Authentication
All requests require an API key via the Authorization header:
Authorization: Bearer YOUR_API_KEY
See Authentication for details on obtaining API keys.
Access & Rate Limits
The Snapshot API requires Pro or Enterprise tier. Demo and Free tier keys receive 403 Forbidden.
Snapshot API follows the same rate limits as the Data API. See Rate Limits for details.
When rate limited, the API returns 429 Too Many Requests with a retry_after field.
Metric access is also grade-gated: grade 0 metrics (price, supply, MVRV) are available to all Pro+ keys, grade 1 metrics (SOPR, P&L) require Pro, and grade 2 metrics (CBD heatmaps) require Enterprise.
GET /v1/chart/snapshot
Generate a chart from query parameters. Best for simple, single-metric or template-based charts.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
metric | string | - | Single metric ID (e.g., price, lth_supply). Mutually exclusive with metrics and template. |
metrics | string | - | Comma-separated metric IDs, max 6 (e.g., lth_supply,sth_supply). Mutually exclusive with metric and template. |
template | string | - | Predefined template ID. Mutually exclusive with metric and metrics. |
days | integer | 365 | Days of historical data (7-10000). |
start_date | string | - | Start date (YYYY-MM-DD). Overrides days. |
end_date | string | - | End date (YYYY-MM-DD). Defaults to today. |
style | string | auto | Chart style: line, area, bar. Auto-detected from metric registry if omitted. |
scale | string | linear | Y-axis scale: linear or log. |
overlay | string | - | Add price overlay as a subtle dashed line on the right axis (use price). Currently BTC price. |
width | integer | 1200 | Image width in pixels (600-2400). |
height | integer | 600 | Image height in pixels (300-1200). |
title | string | auto | Chart title. Auto-generated from metric names if omitted. |
theme | string | light | Color theme: light or dark. |
Exactly one of metric, metrics, or template must be provided.
Available Templates
| Template | Metrics |
|---|---|
price | BTC Price |
price_volume | Price + Volume |
market_cap | Market Cap |
holder_supply | LTH Supply + STH Supply |
mvrv_ratio | LTH MVRV + STH MVRV |
realized_price | LTH Realized Price + STH Realized Price |
realized_cap | LTH Realized Cap + STH Realized Cap |
unrealized_pl | LTH Unrealized P/L + STH Unrealized P/L |
realized_pl | LTH Realized P/L + STH Realized P/L |
sopr | LTH SOPR + STH SOPR |
block_height | Block Height |
Examples
# Bitcoin example: BTC price, 1 year, line chart
curl -H "Authorization: Bearer YOUR_KEY" \
"https://api.blocklens.co/v1/chart/snapshot?metric=price" \
--output price.png
# Bitcoin example: two metrics with area style
curl -H "Authorization: Bearer YOUR_KEY" \
"https://api.blocklens.co/v1/chart/snapshot?metrics=lth_supply,sth_supply&style=area&days=730" \
--output supply.png
# Template with dark theme
curl -H "Authorization: Bearer YOUR_KEY" \
"https://api.blocklens.co/v1/chart/snapshot?template=mvrv_ratio&theme=dark" \
--output mvrv_dark.png
# Log scale with price overlay
curl -H "Authorization: Bearer YOUR_KEY" \
"https://api.blocklens.co/v1/chart/snapshot?metric=lth_supply&scale=log&overlay=price&days=1095" \
--output supply_with_price.png
# Get JSON metadata instead of image
curl -H "Authorization: Bearer YOUR_KEY" \
-H "Accept: application/json" \
"https://api.blocklens.co/v1/chart/snapshot?metric=price" | jq
# Get SVG instead of PNG
curl -H "Authorization: Bearer YOUR_KEY" \
-H "Accept: image/svg+xml" \
"https://api.blocklens.co/v1/chart/snapshot?metric=price" \
--output price.svg
Success Response
PNG (default):
HTTP/1.1 200 OK
Content-Type: image/png
Cache-Control: public, max-age=3600
X-Snapshot-Cache: HIT
X-Snapshot-Render-Ms: 0
X-Render-Id: 0a1b2c3d4e5f6789
<binary PNG data>
SVG (with Accept: image/svg+xml):
HTTP/1.1 200 OK
Content-Type: image/svg+xml
Cache-Control: public, max-age=3600
X-Snapshot-Cache: MISS
X-Snapshot-Render-Ms: 412
X-Render-Id: 0a1b2c3d4e5f6789
Content-Disposition: attachment; filename="chart.svg"
<svg xmlns="http://www.w3.org/2000/svg" ...>...</svg>
The X-Render-Id value can be used to re-fetch the same image at /v1/chart/renders/{render_id}.png or /v1/chart/renders/{render_id}.svg for up to 1 hour.
POST /v1/chart/snapshot
Full chart customization via JSON body. Supports per-metric styling, custom axes, formulas, reference lines, reference areas, and more. Designed for AI agents (via MCP) and advanced users.
Request Body
Provide exactly one data source (metric, metrics, or template):
{
"metric": "price",
"days": 365,
"start_date": "2025-01-01",
"end_date": "2026-02-23",
"title": "Custom Chart Title",
"width": 1200,
"height": 600,
"theme": "light",
"format": "png",
"style": "line",
"scale": "linear",
"overlay": "price",
"y_axes": [
{"id": "axis-left", "side": "left", "scale": "linear", "format": "number"},
{"id": "axis-right", "side": "right", "scale": "log", "format": "currency"}
],
"formulas": [
{"expression": "sma(m1, 200)", "label": "200-day SMA", "color": "#9333ea", "style": "line"}
],
"reference_lines": [
{"y": 1.0, "stroke": "#9ca3af", "stroke_dasharray": "3 3", "label": "Break-even"}
],
"reference_areas": [
{"y1": 0, "y2": 0.85, "fill": "#16a34a", "fill_opacity": 0.08, "label": "Undervalued"}
]
}
Note: Exactly one of metric, metrics, or template must be provided.
Metrics Field
The metrics field supports mixed types:
// String shorthand
["price", "lth_supply"]
// Full config objects
[{"id": "price", "axis": "right", "style": "line", "color": "#374151"}]
// Mixed
["price", {"id": "lth_supply", "axis": "left", "style": "area"}]
// With params for parameterized metrics
[
{"id": "funding_binance", "params": {"exchange": "binance"}},
{"id": "price"}
]
Some metrics require additional parameters (e.g., exchange, ticker, id). Include the params object from the metric definition when using these metrics.
Per-Metric Options
| Field | Type | Description |
|---|---|---|
id | string | Required. Metric ID from registry. |
params | object | Parameters for parameterized metrics (e.g., {"exchange": "binance"}). See metric definition for required params. |
axis | left / right | Y-axis side. Auto-assigned if omitted. |
y_axis_id | string | Bind to a specific Y-axis by ID (e.g. axis-diff). Overrides axis. |
style | line / area / bar | Chart style. Registry default if omitted. |
color | string | Hex color (e.g., #2563eb). Auto-assigned if omitted. |
label | string | Display label. Uses metric name if omitted. |
stroke_width | integer | Line width (0-5). Default: 2. Use 0 for bars without borders. |
fill_opacity | float | Fill opacity (0.0-1.0). For transparent bars, use low values like 0.2. |
stroke_dash | string | Dash pattern (e.g., 5 5). |
stack_group | string | Stack group ID for stacked charts. |
show_in_legend | boolean | Whether to show in the chart legend. Default: true. |
visible | boolean | Show/hide. Default: true. |
Custom Y-Axes
The y_axes field defines the Y-axes available to metrics and formulas. Each axis has a unique id that can be referenced via y_axis_id on metrics or formulas. You can define more than two axes — for example, a separate axis for computed differences:
"y_axes": [
{"id": "axis-left", "side": "left", "scale": "linear", "format": "number"},
{"id": "axis-right", "side": "right", "scale": "log", "format": "currency"},
{"id": "axis-diff", "side": "left", "scale": "linear", "format": "number"}
]
| Field | Type | Description |
|---|---|---|
id | string | Unique axis identifier. Referenced by y_axis_id on metrics/formulas. |
side | left / right | Which side of the chart the axis appears on. |
scale | linear / log | Axis scale. Default: linear. |
format | number / currency / percent | Number format for axis labels. |
range | [number, number] | Vertical zone as [from%, to%]. 0 = bottom, 100 = top. Default: full height. Use to stack axes vertically (e.g. volume in bottom 20%, price in top 80%). |
domain_min | number | Hard minimum domain clamp. The axis will never go below this value. |
domain_max | number | Hard maximum domain clamp. The axis will never go above this value. |
no_padding | "top" / "bottom" / "both" | Remove automatic 10% padding from edges. Useful when a domain clamp is a hard boundary (e.g. drawdown capped at 0). |
If y_axes is omitted, axes are auto-generated based on metric axis values.
Example: Price + Volume with Vertical Zones
Stack volume bars in the bottom 20% and price in the remaining space:
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"metrics": [
{"id": "price", "style": "line", "color": "#374151", "y_axis_id": "axis-price"},
{"id": "volume", "style": "bar", "color": "#3b82f6", "fill_opacity": 0.3, "stroke_width": 0, "y_axis_id": "axis-vol"}
],
"y_axes": [
{"id": "axis-price", "side": "right", "scale": "log", "format": "currency", "range": [20, 100]},
{"id": "axis-vol", "side": "left", "format": "number", "range": [0, 20], "no_padding": "bottom"}
],
"days": 365,
"title": "BTC Price + Volume (Zoned)"
}' --output price_volume_zones.png
What this produces:
- Volume bars fill the bottom 20% of the chart (
range: [0, 20]) with no padding at the bottom edge - Price occupies the top 80% (
range: [20, 100]) on a log scale - The two series never overlap, creating a clean stacked layout
Example: Drawdown with Domain Clamp
Cap drawdown at 0 (top edge) so the line touches the top of its zone:
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"metrics": ["price"],
"formulas": [
{"expression": "drawdown(price)", "label": "Drawdown from ATH", "style": "area", "color": "#dc2626", "fill_opacity": 0.15, "y_axis_id": "axis-dd"}
],
"y_axes": [
{"id": "axis-right", "side": "right", "scale": "log", "format": "currency"},
{"id": "axis-dd", "side": "left", "format": "percent", "domain_max": 0, "no_padding": "top"}
],
"days": 1825,
"title": "BTC Price + Drawdown from ATH"
}' --output drawdown.png
What this produces:
- Drawdown is always ≤ 0, so
domain_max: 0clamps the top edge no_padding: "top"removes the automatic 10% padding above 0, making the line touch the top when drawdown is 0%- Price is on the right axis with log scale
Formulas
The formulas field lets you define computed series derived from your metrics. Formulas can reference metrics by their id (e.g. lth_supply) or by positional index (m1, m2, etc. based on order in the metrics array).
"formulas": [
{
"expression": "lth_supply - shift(lth_supply, 30)",
"label": "LTH 30d Change",
"color": "#16a34a",
"style": "bar",
"y_axis_id": "axis-diff",
"fill_opacity": 0.2,
"stroke_width": 0
}
]
Supported functions (30 total):
Moving Averages & Rolling Statistics
| Function | Syntax | Description |
|---|---|---|
sma | sma(series, period) | Simple Moving Average |
ema | ema(series, period) | Exponential Moving Average |
median | median(series, period) | Rolling median |
sum | sum(series, period) | Rolling sum |
std | std(series, period) | Rolling standard deviation |
Cumulative
| Function | Syntax | Description |
|---|---|---|
cumsum | cumsum(series) | Expanding cumulative sum |
cummean | cummean(series) | Expanding cumulative mean |
cummedian | cummedian(series) | Expanding cumulative median |
cumstd | cumstd(series) | Expanding cumulative std dev |
cummax | cummax(series) | All-time cumulative max |
cummin | cummin(series) | All-time cumulative min |
Changes
| Function | Syntax | Description |
|---|---|---|
percent_change | percent_change(series, period) | Percentage change (decimal: 0.20 = +20%) |
diff | diff(series, period) | Absolute value change |
Math
| Function | Syntax | Description |
|---|---|---|
abs | abs(series) | Absolute value |
pow | pow(series, n) | Raise to power n |
log | log(series) | Base-10 logarithm |
round | round(series, digits) | Round to N decimal places |
max | max(a, b, ...) | Pointwise maximum |
min | min(a, b, ...) | Pointwise minimum |
Technical Indicators
| Function | Syntax | Description |
|---|---|---|
rsi | rsi(series, period) | Relative Strength Index (0-100) |
corr | corr(s1, s2, period) | Pearson correlation (-1 to 1) |
drawdown | drawdown(series) | Drawdown from ATH (negative decimal) |
Risk & Return
| Function | Syntax | Description |
|---|---|---|
mean_return | mean_return(series, period) | Annualized rolling mean return |
realized_vol | realized_vol(series, period) | Annualized realized volatility |
sharpe_ratio_arithmetic | sharpe_ratio_arithmetic(series, period) | Sharpe ratio (arithmetic) |
sharpe_ratio_geometric | sharpe_ratio_geometric(series, period) | Sharpe ratio (geometric) |
Series Manipulation
| Function | Syntax | Description |
|---|---|---|
shift | shift(series, period) | Shift series by N periods |
if | if(a, "op", b, then, else) | Conditional (op: =, !=, >, >=, <, <=) |
Operators
Arithmetic: +, -, *, /
Grouping: (, )
Referencing
- Metrics by name:
lth_supply + sth_supply— use metric IDs directly - Metrics by position:
m1 + m2— positional references (m1 = first metric, m2 = second, etc.) - Other formulas:
f1 * 2— reference earlier formulas (f1 = first formula, etc.) - Formulas are evaluated in order, so f2 can reference f1, but not vice versa.
Formula options:
| Field | Type | Description |
|---|---|---|
expression | string | Required. Formula expression using metric IDs or positional refs (m1, m2). |
label | string | Required. Display label for the legend. |
color | string | Hex color. Auto-assigned if omitted. |
style | line / area / bar | Chart style. Default: line. |
y_axis_id | string | Bind to a specific Y-axis by ID (e.g. axis-diff). Overrides axis. |
axis | left / right | Y-axis side. Default: left. Ignored if y_axis_id is set. |
fill_opacity | float | Fill opacity (0.0-1.0). For transparent bars, use low values like 0.2. |
stroke_width | integer | Stroke width (0-5). Use 0 for bars without borders. |
stroke_dash | string | Dash pattern (e.g., 6 3). |
stack_group | string | Stack group ID for stacking multiple formula series. |
Reference Lines & Areas
Reference lines add horizontal guide lines to highlight specific values (e.g. break-even at 1.0, overbought threshold). Reference areas add shaded zones to highlight value ranges (e.g. undervalued zone, Bollinger-like bands). Both support static values and dynamic formulas using the same formula engine as the formulas field.
Reference Line Options
| Field | Type | Description |
|---|---|---|
y | number | Static Y value. |
y_formula | string | Formula expression for dynamic Y (e.g. "sma(m1, 200)"). Uses the same engine as formulas. |
y_axis_id | string | Y-axis ID (defaults to first left axis). |
stroke | string | Line color hex (default: #9ca3af). |
stroke_dasharray | string | Dash pattern, e.g. "3 3" for dashed. |
label | string | Label text. |
Provide either y (static) or y_formula (dynamic), not both. Maximum 10 reference lines per chart.
Reference Area Options
| Field | Type | Description |
|---|---|---|
y1 | number | Static lower Y bound. |
y2 | number | Static upper Y bound. |
y1_formula | string | Formula for dynamic lower bound. |
y2_formula | string | Formula for dynamic upper bound. |
y_axis_id | string | Y-axis ID (defaults to first left axis). |
fill | string | Fill color hex (default: #3b82f6). |
fill_opacity | number | Fill opacity 0–1 (default: 0.1). |
label | string | Label text. |
Provide static (y1/y2) or formula-based (y1_formula/y2_formula) bounds — you can mix them (e.g. static y1 with dynamic y2_formula). Maximum 10 reference areas per chart.
Example: MVRV with Value Zones
Add green undervalued zone (0–0.85), red overvalued zone (8–100), and a dashed break-even line at 1.0:
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"metric": "mvrv",
"days": 1825,
"title": "MVRV Ratio — Value Zones",
"reference_lines": [
{"y": 1.0, "stroke": "#9ca3af", "stroke_dasharray": "3 3", "label": "Break-even"}
],
"reference_areas": [
{"y1": 0, "y2": 0.85, "fill": "#16a34a", "fill_opacity": 0.08, "label": "Undervalued"},
{"y1": 8, "y2": 100, "fill": "#dc2626", "fill_opacity": 0.08, "label": "Overvalued"}
]
}' --output mvrv_zones.png
Example: Formula-Based Band
Create a Bollinger-like band around the first formula (±10% envelope):
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"metrics": ["price"],
"days": 365,
"formulas": [
{"expression": "sma(m1, 50)", "label": "50-day SMA", "color": "#2563eb", "style": "line"}
],
"reference_areas": [
{"y1_formula": "f1 * 0.9", "y2_formula": "f1 * 1.1", "fill": "#2563eb", "fill_opacity": 0.06, "label": "±10% Band"}
],
"reference_lines": [
{"y_formula": "sma(m1, 200)", "stroke": "#9333ea", "stroke_dasharray": "6 3", "label": "200-day SMA"}
]
}' --output price_bands.png
Output Format
The format field controls the output:
| Format | Content-Type | Description |
|---|---|---|
png (default) | image/png | Rasterized chart image |
svg | image/svg+xml | Vector graphics (scalable, embeddable) |
json | application/json | Chart metadata (no rendering) |
Alternatively, use the Accept header:
Accept: image/png- PNG (default)Accept: image/svg+xml- SVGAccept: application/json- JSON metadata
JSON Metadata Response
When format=json or Accept: application/json:
{
"success": true,
"title": "BTC Price",
"metrics": [{"id": "price", "label": "BTC Price"}],
"days": 365,
"start_date": null,
"end_date": null,
"theme": "light",
"width": 1200,
"height": 600,
"formulas": [],
"data_points": 365,
"cached": false,
"render_time_ms": 0
}
Examples
# POST with single metric
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"metric": "price", "days": 365}' \
--output price.png
# Multi-metric with custom styling
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"metrics": [
{"id": "price", "axis": "right", "style": "line", "color": "#374151"},
{"id": "lth_supply", "axis": "left", "style": "area", "color": "#2563eb"}
],
"days": 730,
"title": "BTC Price vs LTH Supply",
"theme": "dark"
}' \
--output chart.png
# SVG output
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"template": "mvrv_ratio", "format": "svg"}' \
--output mvrv.svg
# JSON metadata
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"metric": "price", "format": "json"}' | jq
Heatmap Rendering
The Snapshot API can render heatmap charts for distribution-based metrics like Cost Basis Distribution. Heatmaps use a separate set of parameters from standard metric charts.
Heatmap Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
heatmap_id | string | - | Heatmap identifier (e.g., cost-basis-distribution) |
heatmap_period | string | 1y | Time period: 3m, 6m, 1y, 2y, 3y, 5y, all |
heatmap_color_scale | string | viridis | Color scale: viridis, plasma, inferno, magma, cividis |
heatmap_y_scale | string | linear | Y-axis scale: linear or log |
theme | string | light | Color theme: light or dark |
title | string | auto | Chart title |
width | integer | 1200 | Image width in pixels |
height | integer | 600 | Image height in pixels |
Example
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"heatmap_id": "cost-basis-distribution",
"heatmap_period": "1y",
"heatmap_color_scale": "viridis",
"heatmap_y_scale": "log",
"theme": "dark",
"title": "Cost Basis Distribution"
}' --output cbd_heatmap.png
Note: Heatmap parameters (heatmap_id, etc.) are mutually exclusive with standard metric parameters (metric, metrics, template). Heatmap metrics require Enterprise tier.
The color scale legend is included in the rendered image, showing the value range mapped to the selected color palette.
Cycle Performance Overlay Charts
Cycle performance charts overlay multiple Bitcoin cycles on the same X axis so you can compare them like-for-like. Instead of plotting against calendar dates, each cycle's price is indexed to its own anchor event (cycle low, ATH, or halving) and plotted against days since that anchor.
Switch on the overlay mode by setting x_axis: "day_offset" on a POST /v1/chart/snapshot request and supplying cycle metrics in the metrics array. The renderer uses the day_offset field on each data point instead of date, so all cycles start at Day 0 on the X axis.
Tick labels show calendar dates from the latest cycle of the chosen family. For example, an ATH overlay (cycle_ath_1…cycle_ath_5) anchors at 2025-10-06 and the ticks read Oct '25, Jan '26, Apr '26, etc. — not Day 0, Day 90, Day 180. Other cycles in the same chart are aligned to the same X-axis positions, so a value at the Apr '26 tick on cycle_ath_3 means "what BTC was doing 182 days into the 2017 cycle." Day-numbering reappears as a fallback only if the backend cannot determine the anchor.
Anchor Types
Three families of cycle metrics are available, one per anchor event:
| Anchor | Metric ID prefix | Description | Endpoint hint |
|---|---|---|---|
| Cycle low → next low | cycle_low_* | Price indexed to the cycle bottom — each series starts at 1.0 on the low date and runs until the next cycle's low. | endpoint: "cycle-performance?type=low" |
| ATH → ATH | cycle_ath_* | Price indexed to the cycle's all-time high — each series starts at 1.0 on the ATH date and runs until the next ATH. | endpoint: "cycle-performance?type=ath" |
| Halving → halving | cycle_halving_* | Price indexed to the halving block — each series starts at 1.0 on the halving date and runs until the next halving. | endpoint: "cycle-performance?type=halving" |
Within an anchor family every cycle is the same shape of data — an index that starts at 1.0 and tracks price(t) / price(anchor) until the next anchor event (or, for the current cycle, until today). That's what makes overlay comparison meaningful: a value of 5.0 on day 200 of cycle_ath_3 means "BTC was 5× its 2017 ATH 200 days after that ATH."
All Available Cycle Metrics
| Metric ID | Anchor date | Anchor → end date |
|---|---|---|
cycle_low_1 | 2011-11-18 | 2011-11-18 → 2015-01-14 |
cycle_low_2 | 2015-01-14 | 2015-01-14 → 2018-12-15 |
cycle_low_3 | 2018-12-15 | 2018-12-15 → 2022-11-21 |
cycle_low_4 | 2022-11-21 | 2022-11-21 → today (current cycle) |
cycle_ath_1 | 2011-06-11 | 2011-06-11 → 2013-11-30 |
cycle_ath_2 | 2013-11-30 | 2013-11-30 → 2017-12-17 |
cycle_ath_3 | 2017-12-17 | 2017-12-17 → 2021-11-10 |
cycle_ath_4 | 2021-11-10 | 2021-11-10 → 2025-10-06 |
cycle_ath_5 | 2025-10-06 | 2025-10-06 → today (current cycle) |
cycle_halving_1 | 2012-11-28 | 2012-11-28 → 2016-07-09 |
cycle_halving_2 | 2016-07-09 | 2016-07-09 → 2020-05-11 |
cycle_halving_3 | 2020-05-11 | 2020-05-11 → 2024-04-20 |
cycle_halving_4 | 2024-04-20 | 2024-04-20 → today (current cycle) |
- All
metricsmust come from the same anchor family. Mixingcycle_ath_*withcycle_low_*(or anything outsidedaily_cycle_performance) returns400 invalid_params. - The
x_axis: "day_offset"flag is only valid for cycle metrics — pairing it with any other metric returns400 invalid_params. - The standard "max 6 metrics per chart" limit applies — pick up to 6 cycles per overlay.
Cycle Performance Parameters
In addition to all the standard chart parameters (days, width, height, theme, scale, format, title, y_axes, reference_lines, reference_areas, formulas, etc.), the cycle overlay mode uses these fields:
| Field | Type | Default | Description |
|---|---|---|---|
x_axis | "date" / "day_offset" | "date" | Set to "day_offset" to enable the cycle overlay. Otherwise X axis is calendar-date. |
metrics | array | required | 1–6 cycle metric IDs from the same anchor family. Strings or objects ({id, color, style, label, stroke_width, fill_opacity, show_in_legend, y_axis_id}). |
days | integer | 365 | Caps the maximum day offset rendered (X-axis range, not calendar days). Set to 1500 to see ~4 years per cycle. |
scale | "linear" / "log" | "linear" | Log scale is almost always what you want — early cycle multipliers can be 100×–500×, which is unreadable on linear. |
theme | "light" / "dark" | "light" | Dark theme is the dashboard default for these charts. |
Reference lines (reference_lines) are particularly useful on overlay charts — e.g. a horizontal line at y: 1.0 marks the anchor level, y: 2.0 marks "2× the anchor," and so on.
Example: ATH-Anchored Overlay (Custom Colors per Cycle)
This recipe matches the dashboard's "Price Performance Since ATH" chart — five cycles, each in a distinct color, on a log axis.
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"metrics": [
{"id": "cycle_ath_1", "color": "#8b5cf6", "label": "2011 ATH cycle"},
{"id": "cycle_ath_2", "color": "#3b82f6", "label": "2013 ATH cycle"},
{"id": "cycle_ath_3", "color": "#22c55e", "label": "2017 ATH cycle"},
{"id": "cycle_ath_4", "color": "#f59e0b", "label": "2021 ATH cycle"},
{"id": "cycle_ath_5", "color": "#ef4444", "label": "2025 ATH cycle (current)"}
],
"x_axis": "day_offset",
"scale": "log",
"y_axes": [
{"id": "left", "side": "left", "scale": "log", "format": "number"}
],
"reference_lines": [
{"y": 1.0, "stroke": "#9ca3af", "stroke_dasharray": "3 3", "label": "ATH = 1.0"}
],
"days": 1500,
"theme": "dark",
"title": "Price Performance Since ATH"
}' --output cycle_ath.png
Example: Cycle-Low Overlay (Shorthand Form)
For a quick comparison without per-cycle styling, pass the metric IDs as plain strings — colors come from the registry palette automatically.
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"metrics": ["cycle_low_1", "cycle_low_2", "cycle_low_3", "cycle_low_4"],
"x_axis": "day_offset",
"scale": "log",
"days": 1500,
"title": "Price Since Cycle Low"
}' --output cycle_low.png
Example: Halving-Anchored Overlay
Same shape, four cycles anchored at each halving event.
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"metrics": [
"cycle_halving_1", "cycle_halving_2", "cycle_halving_3", "cycle_halving_4"
],
"x_axis": "day_offset",
"scale": "log",
"days": 1500,
"theme": "dark",
"title": "Price Since Halving"
}' --output cycle_halving.png
MCP Tool Equivalent
From an AI agent connected to the MCP server, the same chart is one tool call:
render_chart({
metrics: [
{ id: "cycle_ath_1", color: "#8b5cf6" },
{ id: "cycle_ath_2", color: "#3b82f6" },
{ id: "cycle_ath_3", color: "#22c55e" },
{ id: "cycle_ath_4", color: "#f59e0b" },
{ id: "cycle_ath_5", color: "#ef4444" }
],
x_axis: "day_offset",
scale: "log",
days: 1500,
theme: "dark",
title: "Price Performance Since ATH"
})
The MCP server accepts the same x_axis enum, the same metric IDs, and the same validation rules as the HTTP API.
Per-Metric Legend Control
Use show_in_legend on individual metric config objects to control whether a metric appears in the chart legend. This is useful when combining many series where some are context/background lines.
{
"metrics": [
{"id": "price", "show_in_legend": true},
{"id": "sma_200", "show_in_legend": false}
]
}
| Field | Type | Default | Description |
|---|---|---|---|
show_in_legend | boolean | true | Whether the metric appears in the chart legend |
Smart Defaults
The Snapshot API uses intelligent defaults so you can get great-looking charts with minimal configuration:
| Parameter | How Default Is Determined |
|---|---|
| Chart style | From metric registry (chart_types[0]) |
| Color | From metric registry, then palette rotation |
| Y-axis side | First metric on left; same-format metrics share axis; different format on right |
| Y-axis scale | linear (override with scale=log) |
| Time range | Category-based: 365 days for price/valuation/profit, 730 for supply, 180 for blockchain |
| Title | Single metric: metric name. Multiple: "Name1 vs Name2" |
| Image size | 1200 x 600 pixels |
| Theme | light |
Error Responses
All errors return JSON:
// 400 Bad Request
{"success": false, "error": "invalid_params", "message": "Exactly one of 'metric', 'metrics', or 'template' must be provided"}
// 401 Unauthorized
{"success": false, "error": "unauthorized", "message": "API key required"}
// 403 Forbidden
{"success": false, "error": "insufficient_tier", "message": "Metric 'lth_sopr' requires Pro tier", "upgrade_url": "https://blocklens.co/pricing"}
// 429 Rate Limited
{"success": false, "error": "rate_limited", "message": "Snapshot rate limit exceeded. Max 60 per hour for pro tier.", "retry_after": 120}
// 504 Timeout
{"success": false, "error": "render_timeout", "message": "Chart rendering timed out after 20.0s. Try a shorter date range."}
MCP Tool: render_chart
The Blocklens MCP server includes a render_chart tool that lets AI agents generate chart images directly in conversations. The tool calls the Snapshot API and returns the image inline as either a PNG (raster) or SVG (vector).
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
metric | string | - | Single metric ID |
metrics | array | - | Multiple metrics (strings or objects) |
template | string | - | Template ID |
days | integer | 365 | Days of history |
start_date | string | - | Start date (YYYY-MM-DD) |
end_date | string | - | End date (YYYY-MM-DD) |
overlay | price | - | Add price overlay (currently BTC) |
theme | light / dark | light | Color theme |
width | integer | 1200 | Image width |
height | integer | 600 | Image height |
title | string | auto | Chart title |
style | line / area / bar | auto | Chart style |
scale | linear / log | linear | Y-axis scale |
format | png / svg / json | png | Output format: png (raster image), svg (vector image, scales without quality loss), or json (metadata only — no render) |
y_axes | array | - | Custom Y-axes with zones (see Custom Y-Axes) |
x_axis | date / day_offset | date | X-axis mode. Use day_offset for cycle performance overlays — all metrics must be from the same cycle family. |
heatmap_id | cost-basis-distribution | - | Render a heatmap instead of a line chart (see Heatmap Rendering). Mutually exclusive with metric / metrics / template. |
heatmap_period | 3m / 6m / 1y / 2y / 3y / 5y / all | 1y | Heatmap binning window. |
heatmap_color_scale | viridis / plasma / inferno / magma / cividis | viridis | Heatmap color palette. |
heatmap_y_scale | linear / log | linear | Heatmap price-bin Y-axis scale. |
params | object | - | Per-call parameters for parameterized metrics (e.g. { "exchange": "binance" }, { "ticker": "IBIT" }, { "entity": "tesla" }). |
reference_lines | array | - | Horizontal reference lines (see Reference Lines & Areas) |
reference_areas | array | - | Shaded reference zones (see Reference Lines & Areas) |
Usage Examples
User: "Show me the MVRV ratio for the last 2 years"
Agent calls: render_chart({ template: "mvrv_ratio", days: 730 })
-> Returns PNG image inline
User: "Chart BTC price vs LTH supply"
Agent calls: render_chart({
metrics: [
{ id: "price", axis: "right" },
{ id: "lth_supply", axis: "left", style: "area" }
],
days: 365
})
-> Returns PNG image inline
User: "Is there capitulation? Show SOPR"
Agent calls: render_chart({ template: "sopr", days: 180, overlay: "price" })
-> Returns PNG image inline
User: "Render the MVRV chart as SVG so I can embed it in a slide deck"
Agent calls: render_chart({ template: "mvrv_ratio", format: "svg" })
-> Returns the chart inline as image/svg+xml and a /chart/renders/{id}.svg URL
MCP Configuration
Add to your Claude Desktop or Cursor config to enable chart rendering:
{
"mcpServers": {
"blocklens": {
"command": "npx",
"args": ["-y", "blocklens-mcp-server"],
"env": {
"BLOCKLENS_API_KEY": "your_api_key_here"
}
}
}
}
See MCP Server for full setup instructions.
Advanced Example: LTH Supply Chart
This example replicates the full Long-Term Holder Supply chart from the Blocklens dashboard — two metrics, two computed formula series, and three Y-axes:
curl -X POST "https://api.blocklens.co/v1/chart/snapshot" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"metrics": [
{"id": "price", "label": "BTC Price", "style": "line", "color": "#6b7280", "axis": "right", "fill_opacity": 0, "stroke_width": 1},
{"id": "lth_supply", "label": "LTH Supply", "style": "line", "color": "#2563eb", "axis": "left", "stroke_width": 2}
],
"y_axes": [
{"id": "axis-left", "side": "left", "scale": "linear"},
{"id": "axis-right", "side": "right", "scale": "log"},
{"id": "axis-diff", "side": "left", "scale": "linear"}
],
"formulas": [
{"expression": "max(lth_supply - shift(lth_supply, 30), 0)", "label": "LTH 30d+ diff", "color": "#16a34a", "style": "bar", "y_axis_id": "axis-diff", "fill_opacity": 0.2, "stroke_width": 0},
{"expression": "min(0, lth_supply - shift(lth_supply, 30))", "label": "LTH 30d- diff", "color": "#dc2626", "style": "bar", "y_axis_id": "axis-diff", "fill_opacity": 0.2, "stroke_width": 0}
],
"days": 730,
"title": "Long-Term Holder Supply"
}' --output lth_supply.png
What this produces:
- BTC Price — gray line on the right axis (log scale)
- LTH Supply — blue line on the left axis (linear)
- 30-day positive accumulation — semi-transparent green bars on a separate
axis-diffaxis - 30-day distribution — semi-transparent red bars on the same
axis-diffaxis
Key techniques used:
shift(lth_supply, 30)computes the 30-day lookback valuemax(..., 0)andmin(0, ...)split positive and negative changes into separate seriesy_axis_id: "axis-diff"binds both formula bars to a dedicated axis independent of the main metric axesfill_opacity: 0.2+stroke_width: 0creates subtle, borderless transparent bars
Chart Templates
Full chart configurations (including metrics, formulas, axes, and styling) are available as database-backed templates via:
GET /api/lab/templates
These templates can serve as reference for building your own POST request bodies. Each template contains the complete config that produces a specific chart on the Blocklens dashboard.
Watermark
All rendered chart images include a blocklens.co watermark and copyright notice. This applies to both PNG and SVG output formats.
Caching
Rendered snapshots are cached for 1 hour in Redis. Cache behavior:
- Identical chart configurations return cached results instantly
- Cache key is derived from the full chart config (metrics, axes, time range, theme, size) plus the output format — PNG and SVG variants of the same chart are cached independently
- The
X-Snapshot-Cacheresponse header indicatesHITorMISS - The
X-Snapshot-Render-Msheader shows render time (0 for cache hits) - Rendered files are also persisted on disk under
/v1/chart/renders/{render_id}.pngand/v1/chart/renders/{render_id}.svgfor 1 hour, so chart URLs returned in theX-Render-Idheader remain reachable - Popular charts are pre-warmed after each daily data update (05:00 UTC)