About
Public API
Programmatic access to indicators, your watchlist, and your dashboards. API tokens require Research or Pro. Authenticate with a personal access token (PAT) minted in Account > Security.
Last reviewed: 2026-05-21.
Quickstart
- Sign in, open Account > Security, mint a token, copy the plaintext. The token is shown once and cannot be recovered later.
- Set it as
MBM_PATin your shell or app. - Make a request:
curl
curl -H "Authorization: Bearer $MBM_PAT" \ "https://macrobymark.com/api/v1/observations?slug=labor.unemployment.rate&country=US&from=2020-01-01"
Node / TypeScript
const response = await fetch(
"https://macrobymark.com/api/v1/observations?slug=labor.unemployment.rate&country=US",
{ headers: { Authorization: `Bearer ${process.env.MBM_PAT}` } }
);
if (!response.ok) throw new Error(`API error ${response.status}`);
const { observations } = await response.json();
console.log(observations.slice(-12)); // last 12 monthly printsPython
import os, requests
response = requests.get(
"https://macrobymark.com/api/v1/observations",
params={"slug": "labor.unemployment.rate", "country": "US"},
headers={"Authorization": f"Bearer {os.environ['MBM_PAT']}"},
timeout=10,
)
response.raise_for_status()
print(response.json()["observations"][-12:]) # last 12 monthly printsA free /api/v1/platform-stats endpoint is unauthenticated and serves the public scale snapshot for journalists and integrators — useful as a smoke test before you mint a token.
Authentication
Every request includes the Authorization header:
Authorization: Bearer mbm_pat_<prefix>_<secret>
Tokens are scoped. A token without the required scope returns 403 with code pat_forbidden. A missing or invalid token returns 401 with code pat_unauthorized.
Rate limits
Each endpoint is rate-limited at 60 requests per minute per source IP. Research includes 1,000 API reads per rolling 30-day window; Pro includes 10,000. Exceeding a limit returns 429 with Retry-After and X-RateLimit-* headers. The bucket is process-local in test environments and durable in production (Supabase RPC).
Public API
GET /api/v1/indicators
Query normalized indicator metadata for dashboards, watchlists, and classroom pipelines without scraping product pages.
Request
curl https://macrobymark.com/api/v1/indicators?topic=gdp&limit=3 \
-H "Authorization: Bearer mbm_pat_demo_demo"Sample response
{
"indicators": [
{
"slug": "gdp.real",
"displayName": "Real GDP",
"topicSlug": "gdp",
"frequencyHint": "Q",
"unitHint": "Index"
},
{
"slug": "prices.cpi.headline",
"displayName": "Headline CPI",
"topicSlug": "prices",
"frequencyHint": "M",
"unitHint": "Index"
}
],
"pagination": { "total": 128, "returned": 2, "limit": 3 }
}GET /api/v1/indicators
Required scope: indicators:read
Reads from the canonical indicator catalog (the same source that powers the marketing site's indicator routing). Filters apply server-side. No DB read.
Query parameters: topic (string), q (full-text fragment), limit (1-200, default 100).
{
"indicators": [
{
"slug": "real-gdp",
"displayName": "Real GDP",
"definition": "...",
"topic": "growth",
"parentSlug": null,
"frequencyHint": "quarterly",
"methodologyUrl": "https://..."
}
],
"count": 1,
"totalCount": 47,
"query": { "topic": null, "q": null, "limit": 100 }
}GET /api/v1/observations
Required scope: indicators:read
Returns the observation series for one canonical indicator slug. Optional country preference, date window, frequency assertion (no resampling — must match source frequency), and a row limit.
Query parameters: slug (required), country (ISO-3166-1 alpha-2), from, to (ISO date, inclusive), freq (daily | weekly | monthly | quarterly | annual; must match source), limit.
{
"slug": "unemployment-rate",
"series": {
"id": "labor.unemployment.headline",
"name": "Unemployment Rate",
"source": "bls",
"sourceAgency": "U.S. Bureau of Labor Statistics",
"sourceId": "LNS14000000",
"frequency": "monthly",
"unit": "percent",
"dataStatus": "live",
"lastFetchedAt": "2026-05-19T13:30:00Z",
"country": { "code": "US", "name": "United States" }
},
"query": {
"country": "US", "from": "2020-01-01", "to": null, "freq": null, "limit": null
},
"count": 64,
"observations": [
{ "date": "2020-01-01", "value": 3.6 }
],
"compliance": { "source": "bls", "surface": "api", "decision": "allowed" },
"generatedAt": "2026-05-20T11:30:00Z"
}The endpoint enforces redistribution compliance per provider. A series whose provider blocks the api surface returns 451 with a structured compliance payload.
GET /api/v1/revisions
Required scope: indicators:read
Returns the revision history (vintages, restatement tags, or platform snapshots — see /about/trust for the per-provider capability matrix) for one canonical indicator slug as of a target vintageDate.
Rate limit on this endpoint is tighter (30 req/min/IP) because vintage assembly is more expensive than observation reads.
Query parameters: slug (required), country, vintageDate (defaults to today).
GET /api/v1/releases
Required scope: indicators:read
Live release calendar. Each event carries scheduleSourceKind (source-native official / API / provider calendar / static curated / heuristic / unknown), so consumers can filter on schedule-authority. When a release has an attached snapshot, the response embeds releaseSnapshot with actual / expected / surprise / previous values and an expectationSource label.
Query parameters: from, to, country (ISO-3166-1), importance (high | medium | watch), limit (1-500, default 250).
{
"count": 12,
"totalCount": 480,
"matchedCount": 12,
"query": { "from": "2026-05-01", "to": "2026-05-31", "country": "US", "importance": "high", "limit": 250 },
"releases": [
{
"id": "evt-...",
"date": "2026-05-13", "day": "Tue", "time": "08:30",
"title": "CPI - May",
"importance": "high",
"indicatorId": "prices.cpi.headline",
"scheduleSourceKind": "source_native_official",
"scheduleSourceUrl": "https://...",
"releaseSnapshot": {
"actualValue": "2.7%", "expectedValue": "2.6%",
"surpriseValue": "+0.1pp", "expectationSource": "modeled"
}
}
],
"compliance": { "surface": "public", "filteredCount": 0 },
"generatedAt": "2026-05-20T11:30:00Z"
}GET /api/v1/watchlist
Required scope: watchlist:read
{
"watchlist": {
"indicatorIds": ["gdp.real", "prices.cpi.headline"],
"createdAt": "2026-04-12T...",
"updatedAt": "2026-05-02T..."
}
}PUT /api/v1/watchlist
Required scope: watchlist:write
Body:
{ "indicatorIds": ["gdp.real", "labor.unemployment.headline"] }Up to 1000 ids. The server normalizes the list (dedupes, trims, drops empties) before storage. The complete new list replaces the previous one; this is upsert, not append.
GET /api/v1/dashboards
Required scope: dashboards:read
Supports limit (1-100) and offset (0 or higher).
{
"dashboards": [
{
"id": "...",
"title": "Recession watch",
"description": "...",
"gridCols": 4,
"isPublic": false,
"layout": [...],
"createdAt": "...",
"updatedAt": "..."
}
],
"pagination": {
"total": 7,
"returned": 1,
"limit": 50,
"offset": 0,
"nextOffset": null
}
}Dashboard write surface (PUT, DELETE) is not in v1. Coming in a follow-up release.
What is not in v1
- Account management (password, email, MFA, passkey).
- Billing operations.
- Admin endpoints.
- Lab runs / forecasts.
- Dashboard write surface (PUT, DELETE).
The scope set is pinned at the schema check constraint and the typed ALLOWED_SCOPES tuple in code; a token cannot acquire access to surfaces that are not on the list.
GET /api/v1/platform-stats (unauthenticated)
Public-safe snapshot of platform scale: total series count, provider count, country count, addressable indicator-country page count, plus a provider breakdown. No authentication required — this is the surface journalists, buyers, and integrators can use to independently verify our public claims.
The response includes an HMAC-SHA256 signature over the payload when PLATFORM_STATS_HMAC_SECRET is configured server-side. The signature lets you save a snapshot now and later prove "at time T, the platform's own infrastructure asserted X" without having to trust us unilaterally.
{
"schemaVersion": "platform-stats-public/v1",
"generatedAt": "2026-05-20T11:30:00.000Z",
"asOf": "2026-05-20T11:00:00.000Z",
"sourceOfTruth": "tiger",
"isFallback": false,
"isDegraded": false,
"totalDiscoverySeries": 2900000,
"providerCount": 16,
"countryCount": 27,
"trackedIndicatorCount": 23381,
"providerBreakdown": [
{ "provider": "fred", "seriesCount": 582000 }
],
"signing": {
"signature": "<64-char hex sha256-hmac>",
"signatureAlgorithm": "sha256-hmac",
"signedAt": "2026-05-20T11:30:00.000Z"
},
"verification": {
"algorithm": "sha256-hmac",
"instructions": "Compute sha256-hmac of canonical-JSON-stringify(payload) + '\n' + signing.signedAt, using the platform-disclosed key."
}
}Edge-cached: s-maxage=60, stale-while-revalidate=600. Stats refresh roughly hourly server-side; a one-minute edge freshness is the right trade-off for an unauthenticated surface.
When the signing secret is absent (preview / dev), signature is null and signatureAlgorithm is "none". The numbers themselves remain accurate; only third-party signature verification is unavailable.
Error shape
All errors return JSON in this shape:
{ "error": "<human readable>", "code": "<machine readable>" }Common codes:
pat_unauthorized-- 401, missing or invalid token.pat_forbidden-- 403, scope mismatch.pat_service_unavailable-- 503, server-side config issue.watchlist_invalid_body-- 400, schema validation failed.
Operational policies for the API (incident response, retention, subprocessors) are documented at /about/trust.