Developers

A free API for US macro data

MacroRadar publishes a free, keyless JSON API — plus CSV export and an MCP server — for the standard US macroeconomic indicators and for MacroRadar's own derived model outputs: the macro regime read, recession probability, and sentiment composite. Every observation is vintage-dated and every model output is walk-forward validated, so the same point-in-time discipline that runs the site is available to anyone building on top of it. Attribution back to MacroRadar is requested wherever the data is used.

Quickstart

The base URL is https://www.macroradar.io/api/v1. No API key, no auth, and no rate-limit registration — every response is CORS-open and cached for one hour. Every payload is wrapped in the same envelope: { data, meta: { source, attribution, license, docs, generated_at } }, and errors follow { error, meta }.

Full catalog of tracked series

curl https://www.macroradar.io/api/v1/catalog

Latest unemployment rate reading, with percentiles

curl https://www.macroradar.io/api/v1/series/UNRATE/latest

Full CPI history as CSV, with percentiles and z-scores

curl "https://www.macroradar.io/api/v1/series/CPIAUCSL?derived=true&format=csv"

Endpoint reference

All paths below are relative to https://www.macroradar.io/api/v1. The full machine-readable contract is in the OpenAPI spec at /openapi.json.

MethodPathDescription
GET/catalogAll tracked series (id, slug, name, category, frequency, units, description, license status, chart URL) plus the list of model endpoints. Ambiguous price series carry a price_basis object naming what the number is and the near neighbour it is not — our Brent is the EIA spot price, not the ICE front-month future.
GET/series/{id}Observations for one series, oldest first. id is the FRED series id (e.g. CPIAUCSL) or the site's url slug (e.g. inflation-rate). Params: limit (default 5000, max 50000), derived=true (adds percentile_10y, percentile_30y, and z_score per date), format=csv (CSV download with an attribution footer).
GET/series/{id}/latestThe most recent observation for one series, plus its current 10-year and 30-year percentiles and z-score.
GET/regimeThe current multi-dimensional macro regime read — a MacroRadar model output.
GET/regime/historyWalk-forward regime probability history: each point comes from a model refit on observations up to that date; input series are as later revised.
GET/recessionThe current 6-month US recession probability. Add ?history=true for the full walk-forward history.
GET/sentimentThe current 0–100 MacroRadar sentiment composite. Add ?history=true for the causal daily history.
GET/whats-movingEvery tracked series' latest move ranked by how unusual it is against that series' own history, with a checkable sentence per row.
GET/episodesDerived episode spans (curve inversions, volatility stress, bear markets, rate cycles) plus the open spans ranked by running duration. Param: series (id or slug).
GET/event-windowsFOMC decision-day windows for the tracked market series, with the latest decision's moves ranked within each series' record. Param: series.
GET/calendarThe consolidated schedule and recent record: releases, FOMC meetings and communications, ECB/BoE/BoJ decisions, US trade actions. Params: days_ahead (max 120), days_back (max 60), or from/to for an explicit range (max 1100 days). Every event carries four timestamps — when it happened, when the source published it, when this record first held it, and when the record's copy was last revised.
GET/series/{id}/as-of?as_of=YYYY-MM-DDWhat a series read on a past date, from the vintage archive — not what it reads now. Economic statistics are revised, so June 2008 CPI was one number in September 2008 and is another today. Answers distinguish known_at from not_yet_published (the number did not exist yet) and no_archive (we hold no vintage), because only the middle one is a claim about the world. Add period=YYYY-MM-DD for one observation's full revision history: first print, republication count, and where it stands now.
GET/log?date=YYYY-MM-DDOne day's macro record: events, episode transitions, decision-day moves ranked as of that date, and cited annotations. Episode and decision records reach decades back.
GET/policy-regimesSanctions, tariff and export-control programmes as spans, each dated from a cited instrument. Durations are exact; action counts are bounded by the returned record_starts date.
GET/studies?study=SLUGConditional history: every past window where stated macro conditions held together, and what followed each. Call without ?study to list them. Returns matching windows with dates, the distribution of outcomes and the sample size — and deliberately no probability, expected value or forecast, because none of those are recomputable from the record. `recompute.licensed_series` names any input this API withholds.
POST/studiesAn ad-hoc conditional-history question over any series the catalog carries — the same engine as the GET form, asked a question nobody has published yet. Body: { conditions: [{ kind: "threshold"|"percentile"|"direction", expr, comparator, ... }], measure: expr, horizonPeriods, range: { start, end }, dedupeWithin?, vintage: { mode: "latest" } | { mode: "as_of", date } }, where expr is { kind: "series", seriesId, transforms? } or a binary combination. At most 8 conditions. Refuses 400 for a malformed body or an address the grammar cannot parse; 422 for an incompatible unit (a bare threshold on an index level), an unregistered series, or a sample below the thin-sample floor — always named, never a silent guess. `method` in the response is the compiled recompute plan, including which series' dates the conditions are aligned onto. Example: {"conditions":[{"kind":"threshold","expr":{"kind":"series","seriesId":"T10Y2Y"},"comparator":"below","value":0}],"measure":{"kind":"series","seriesId":"UNRATE"},"horizonPeriods":12,"range":{"start":"1976-06-01","end":"2026-01-01"},"vintage":{"mode":"latest"}}.
POST/exposureA user-supplied long-only basket's conditional history: the same question as POST /studies, asked of a weighted basket instead of one series. Body: { basket: [{ series_id, weight }], conditions: [...same shape as /studies], horizonPeriods, range, dedupeWithin?, vintage }. A basket takes at most 10 members, all level-class (a yield or spread refuses — a basket of rates is not a portfolio), all the same observation frequency, no duplicates, weights finite and strictly positive and summing to 1 within 1e-6. A matched window's basket outcome is the weighted sum of each member's own percent change, rebalanced to the stated weights at the window's start — no drift inside a window, no compounding across them. Every refusal is named: a percent-unit member, a mixed-frequency basket, a duplicate member, a weight that does not sum to 1, an unregistered series, a thin sample. A member this API may display but not redistribute has its own start/end value withheld (null) in the response; the derived basket_change is published either way, and `recompute.licensed_series` names what was withheld. API-only, never archived — a basket is caller-specific. Example: {"basket":[{"series_id":"DCOILWTICO","weight":0.6},{"series_id":"DTWEXBGS","weight":0.4}],"conditions":[{"kind":"threshold","expr":{"kind":"series","seriesId":"T10Y2Y"},"comparator":"below","value":0}],"horizonPeriods":60,"range":{"start":"1990-01-01","end":"2026-01-01"},"vintage":{"mode":"latest"}}.
GET/catalogEvery series with its semantic layer: concept (what it measures), geography, publisher, seasonality, the publisher's first observation, and whether it is ever restated. Series are also grouped by concept. Read this before composing an expression — /compute refuses to combine two levels of different concepts, and INDPRO and PCEPI are both "Index 2017=100".
GET/compute?expr=EXPRESSIONCompute any expression the grammar can express, and get back the numbers WITH the plan that produced them. FEDFUNDS~minus~CPIAUCSL.yoy is the real policy rate; CPILFESL.yoy.zscore_120 is core inflation standardised against its own decade. Transforms after a dot: yoy, mom, diff_N, lag_N, annualized_N, zscore_N, percentile_N, movingAverage_N, index_YYYY-MM-DD. Operators between two expressions: plus, minus, times, over. No percent-encoding needed. Refusals are answers and say what would have been wrong — an as_of date is refused rather than answered from the revised record, and a non-redistributable input returns 451.
GET/claimsThe claims ledger: every kind of quantitative sentence on the site with its rule and recompute steps.

Licensing, honestly

Most of the catalog is US-government-produced economic data, which is public domain. Those series are served in full through /series/{id} and its CSV export, with no restriction beyond the attribution request above.

Ten of the 35 tracked series are hosted on FRED by permission of a private owner, and that owner's license does not extend to redistributing the raw values through a third-party API. Those ten remain viewable as interactive charts on the site, and their catalog entries carry full metadata, but calling /series/{id} or /series/{id}/latest for one of them returns HTTP 451 with a license_note and a chart_url pointing at the chart instead of a value.

Series IDOwner
UMCSENTUniversity of Michigan Surveys of Consumers
VIXCLSCboe
MORTGAGE30USFreddie Mac Primary Mortgage Market Survey
BAMLH0A0HYM2ICE Data Indices / BofA
CSUSHPINSAS&P Dow Jones Indices (Case-Shiller)
BAA10YMoody's
NASDAQCOMNasdaq
SP500S&P Dow Jones Indices
SHILLER_CAPERobert Shiller dataset (research use with attribution)
SP500_DIV_YIELDRobert Shiller dataset (research use with attribution)

MacroRadar's own model outputs — the macro regime read, recession probability, and sentiment composite — are free to use with attribution, same as the public-domain series. They are historical indicators describing what the models compute from public data, not investment advice, and never a recommendation to buy, sell, or hold any security.

Fair use

There is no API key and no hard rate limit, which relies on callers being reasonable. Every response is cached for one hour at the edge, so polling more often than that returns the same data and buys nothing. Please cache on your end too, and link back to macroradar.io wherever the data appears — that attribution is what keeps this free for everyone.

MCP server

@macroradar/mcp is a zero-dependency Node stdio server that wraps this API for Claude Desktop, Claude Code, and other Model Context Protocol clients, so an assistant can look up an indicator or the current regime read directly instead of you copying values by hand. Add it to a client's MCP config as follows:

{
  "mcpServers": {
    "macroradar": {
      "command": "npx",
      "args": ["-y", "@macroradar/mcp"]
    }
  }
}

The package is published on npm as @macroradar/mcp — the config above works as-is with any MCP client that can run npx. It ships sixteen tools, a thin wrapper over this same API, so the licence rule above applies to it unchanged: every value returned over a redistributable series may be quoted in public and verified for free against this API. Licensed series never leave the raw level unmarked — series-level tools return metadata and a chart link instead of a value, and derived-statistic tools return their computed percentile or rank with a note that the underlying level is withheld, the same wording /api/v1/ranked uses for its value_note.