Expected Value Research

API reference

Version v1. 25 series across 15 categories.

Every endpoint is a static file behind a CDN. There is no key, no sign-up and no rate limit, and every response allows any origin, so you can fetch these straight from a browser. Requests are GET only.

Endpoints

PathReturns
/v1/catalog.jsonEverything that exists. Start here.
/v1/series/<id>.jsonOne series: observations, provenance and licences.
/v1/csv/<id>.csvThe same series as CSV. The provenance sits in # comments above the data.
/v1/composite/<category>.jsonEvery series in a category, aligned to one date axis. This is what the charts read.
/v1/figures/<category>.pdf, .svg, .pngThe category figure. Vector for a journal, 300 dpi otherwise.

Identifiers come from the catalogue. Read path and csv off a series and follow them, rather than assembling URLs yourself. That way a renamed series is a changed link, not a 403.

curl https://evresearch.co.uk/v1/catalog.json
curl https://evresearch.co.uk/v1/series/uk-bank-rate.json
curl https://evresearch.co.uk/v1/csv/uk-bank-rate.csv
curl https://evresearch.co.uk/v1/composite/bonds.json

The catalogue

FieldMeaning
versionThe API version. Currently v1.
licencesEvery licence any series refers to, keyed by id. The vocabulary for the licences list on a series.
seriesEvery series, with its date range, units, licences and links.
compositesEvery category, with the number of series in it.
gapsCategories deliberately not covered, and why. An empty shelf on purpose reads differently from an oversight.

A series

/v1/series/uk-bank-rate.json

FieldMeaning
observations[[date, value], ...], oldest first. A value may be null where the source has a gap; the date is still listed.
startFirst observation date. Currently 1694-11-01 for this series.
endLast observation date.
countNumber of observations, including nulls.
unitsWhat a value means. Never mixed within a series.
vintageThe newest last_updated across the sources. See below.
proxyTrue when the series stands in for the thing you asked for rather than measuring it.
sourcesOne entry per upstream series used, with its own licence, attribution, frequency, date range and last_updated.
splicesWhere one source hands over to the next.
checksHow far the sources disagreed where they overlapped.
licencesThe licence ids this series is published under. Look them up in the catalogue's licences.

A composite

/v1/composite/bonds.json. One category, drawn on one axis, so every series in it shares one unit.

FieldMeaning
datesThe shared date axis, as ISO strings.
xThe same axis as Unix seconds, for plotting libraries that want numbers.
series[].valuesValues aligned to dates position by position, padded with null where that series does not reach. Same length as dates, always.
series[].splicesHandover dates, so a chart can mark them.
logWhether this category is meant to be read on a log axis.
creditsThe attribution strings the sources require. Reproduce them if you publish the chart.

Reading the provenance

The point of this API is that you can tell where a number came from, so four fields are worth understanding.

vintage is the newest date any upstream source was updated. It is not the build time. Rebuilding changes nothing about what a citation refers to, which is what makes the URLs citable.

sources lists each upstream series and the date range it supplied. A long series is usually several sources end to end: a historical reconstruction, then a modern official series.

splices records each handover. Every entry has rescaled: false, and always will. Sources are joined end to end and never shifted or stretched to meet, so a step at a handover is a real disagreement between two sources and not an artefact of the join.

checks quantifies that disagreement over the range where two sources overlap: the mean and maximum difference, absolutely and as a percentage. A large number is not a bug. It is the honest measure of how much the join is asking you to accept.

The CSV

Every # line above the data carries the same provenance as the JSON, plus a citation line. The data itself is two columns, date,value.

import pandas as pd
df = pd.read_csv("https://evresearch.co.uk/v1/csv/uk-bank-rate.csv",
                 comment="#", parse_dates=["date"])

Licences

Terms belong to whoever published the data, not to us and not to the API it arrived through, so they are stated per source. There is no blanket licence over this data. A series that draws on two publishers carries both sets of terms.

IdLicenceRedistributionTerms pass through
boe-millenniumBank of England, 'A Millennium of Macroeconomic Data' series noteyesno
cc-by-4.0Creative Commons Attribution 4.0 Internationalyesno
oecdOECD Terms and Conditions, section 3yesyes
ogl-v3Open Government Licence v3.0yesno
us-public-domainUS Government work, in the public domainyesno

Where terms pass through, anyone you pass the data to is bound by them too, so you have to tell them. Each CSV says which of these applies to which source, and quotes the required attribution.

Citing

Every CSV carries a citation line built from its own vintage. It reads:

evresearch.co.uk. Bank of England policy rate (uk-bank-rate) [dataset],
data vintage 2026-07-16. https://evresearch.co.uk/v1/csv/uk-bank-rate.csv

Cite the upstream publishers as well. The attribution strings they require are in the CSV header and in credits on every composite.

Caching

Pathmax-age
/v1/catalog.json5 minutes
/v1/ everything else1 hour
/v1/figures/1 day

What v1 promises

Series identifiers and URLs are stable: an id that exists today keeps pointing at the same measurement. Fields may be added. Anything that would break a reader gets a new version under a different prefix rather than changing under you.

What is not promised is that the numbers never move. Upstream publishers revise, and a revision changes values and lifts vintage. That is why citations name the vintage.