Programmatic access to the same indices the site publishes: the catalog, the LLM Market Outlook and its history, the daily calibration rollup, and the smart-consensus council with its weights and accuracy series. Every endpoint below is deployed and public.
The live indices · How the numbers are built · Plans and pricing
Every endpoint sits under one versioned base URL and returns JSON. Response fields are camelCase throughout — expectedMove, not expected_move — including inside nested objects. Query parameters stay literal and lower-case: symbol, fh, days, slug.
GET https://marketmania.ai/api/v1/indices/outlook/?symbol=BTC&fh=4h → 200 application/json
Every timestamp inside a payload — generatedAt, ts, latestTs — is ISO-8601 in UTC, for example 2026-08-04T09:01:00+00:00. Dates written for people, in this reference and across the site, are rendered en-GB instead, for example 4 Aug 2026: UTC for machines, en-GB for readers.
The indices are public by design. There is no key, no header and no account needed to read any endpoint on this page: they are anonymous, identical-for-everyone reads of a table a scheduled job has already materialised. A 401 from one of these paths would be a bug, not a state to handle.
Responses are page-cached for 60 seconds, so polling faster than that returns the same bytes. Requests share the public benchmarks rate limit; exceeding it returns 429. The indices are recomputed on a schedule, not per request — the freshest data any caller can get is the latest slot, which each payload states in generatedAt and latestTs.
| Path | Parameters | Returns |
|---|---|---|
| GET /indices/ | slug? | Catalog: which indices exist, the live symbol and horizon vocabularies, and how fresh each index is. |
| GET /indices/outlook/ | symbol? fh? | Latest row per horizon for symbol, the 30-day scorecard, and every instrument's latest row at fh. |
| GET /indices/outlook/history/ | symbol? fh? days? | Slot-granular ts, sentiment, label series. days defaults to 30 and is clamped to 365. |
| GET /indices/calibration/ | symbol? | The latest daily calibration row for the scope. No horizon parameter — one row spans all five. |
| GET /indices/smart-consensus/ | symbol? fh? | Latest council call, the weights behind it, the 30-day daily accuracy series and the scorecard. |
symbol is one of ALL, BTC, ETH, SOL, BNB, XRP and defaults to ALL. fh is one of 1h, 4h, 1d, 1w, 1m and defaults to 4h. Both vocabularies are also published by the catalog endpoint, which is the safe thing to build a client against.
{
"generatedAt": "2026-08-04T10:09:42.165465+00:00",
"variant": "v0",
"symbols": ["ALL", "BTC", "ETH", "SOL", "BNB", "XRP"],
"horizons": ["1h", "4h", "1d", "1w", "1m"],
"indices": [
{ "slug": "outlook", "description": "…", "latestTs": "2026-08-04T09:01:00+00:00" },
{ "slug": "calibration", "description": "…", "latestTs": "2026-08-03T00:00:00+00:00" },
{ "slug": "smart", "description": "…", "latestTs": "2026-08-04T09:01:00+00:00" },
{ "slug": "smart_daily", "description": "…", "latestTs": "2026-08-03T00:00:00+00:00" }
]
}A latestTs of null means the index has published no row at all yet. The two daily slugs carry a UTC midnight, not a live slot: they are rollups of a closed day, so the newest complete row is normally yesterday's.
GET /api/v1/indices/outlook/?symbol=ALL&fh=4h
{
"generatedAt": "2026-08-04T10:09:39.929001+00:00",
"symbol": "ALL",
"fh": "4h",
"horizons": [ { …row per horizon… } ],
"scorecard": [
{ "fh": "1h", "n": 465, "hitPct": 49.2473, "strongN": 358, "strongHitPct": 49.7207 }
],
"acrossInstruments": [ { …row per instrument at fh… } ]
}Arrays above are abridged. horizons holds one row per horizon for symbol; acrossInstruments holds one row per instrument at fh, ALL first. Each item carries its own ts, because a quiet coin's latest row can be older than its neighbours'.
| Field | Type | Meaning |
|---|---|---|
| fh | string | Forecast horizon, one of 1h / 4h / 1d / 1w / 1m. |
| symbol | string | Instrument, or ALL for the pooled reading. |
| ts | string | Slot instant, ISO-8601 UTC. |
| sentiment | number | null | Weighted directional score, −100 … +100. |
| label | string | Bullish / Bearish / Sideways at the ±20 band. |
| consensus | number | null | Weight on the winning side, 0–100. |
| shares | object | null | Weighted share per side: bull, sideways, bear. |
| expectedMove | number | null | Weighted median take-profit distance over the directional forecasts, in percent. |
| conviction | number | null | Per-model z-scored confidence as 50 + 25·mean(z). An index score centred on 50, NOT a percentage — with z clipped to ±3 the range is [−25, 125]. |
| disagreement | number | null | Normalised entropy of the three shares, 0–100. |
| nForecasts | number | Forecasts behind the row. |
| nModels | number | Distinct models behind the row. |
| resolved | boolean | True once the horizon has closed. |
| realizedMovePct | number | null | Realised move over the horizon, in percent. |
| realizedDir | number | null | +1 / −1 / 0 once resolved, else null. |
GET /api/v1/indices/outlook/history/?symbol=BTC&fh=4h&days=30
{ "symbol": "BTC", "fh": "4h", "days": 30, "n": 178,
"points": [ { "ts": "…", "sentiment": -3.3869, "label": "Sideways" } ] }GET /api/v1/indices/calibration/?symbol=ALL
{ "symbol": "ALL", "ts": "2026-08-03T00:00:00+00:00", "date": "2026-08-03",
"gap": -16.8013, "stated": 63.8365, "actual": 47.0352, "n": 10962,
"buckets": [ { "bucket": "50-60", "lo": 50, "hi": 60, "n": 2262,
"stated": 56.9089, "actual": 47.7454, "gap": -9.1636 } ],
"byModel": [ { "model": "claude-fable-5", "n": 1467, "stated": 60.4901,
"actual": 46.9666, "gap": -13.5235, "discrimination": -2.045 } ],
"byHorizon": [ { "fh": "1h", "n": 6682, "stated": 63.7206,
"actual": 48.683, "gap": -15.0376 } ],
"below50": { "n": 0, "stated": null, "actual": null } }| Field | Type | Meaning |
|---|---|---|
| symbol | string | Scope of the row: ALL or one instrument. |
| ts | string | null | Row slot — a UTC midnight. Null when no row exists yet. |
| date | string | null | The closed UTC day the window ends on, YYYY-MM-DD. |
| gap | number | null | n-weighted actual minus stated, in percentage points. Null, never 0, when the window holds no bucketed call. |
| stated | number | null | n-weighted mean stated confidence. |
| actual | number | null | n-weighted mean realised accuracy. |
| n | number | Bucketed population: settled directional calls stated at 50 or above. |
| buckets[] | array | Five 10-point bands: bucket, lo, hi, n, stated, actual, gap. |
| byModel[] | array | model, n, stated, actual, gap, discrimination (null below 30 window rows). |
| byHorizon[] | array | fh, n, stated, actual, gap. Absent horizons are omitted. |
| below50 | object | Sub-50-confidence calls: n, stated, actual. Excluded from buckets. |
GET /api/v1/indices/smart-consensus/?symbol=ALL&fh=4h
{ "generatedAt": "…", "symbol": "ALL", "fh": "4h",
"latest": { "ts": "2026-08-04T08:01:00+00:00", "score": -2.8571, "label": "Bearish",
"majorityScore": -2.8571, "nForecasts": 70, "nModels": 7,
"resolved": false, "realizedMovePct": null, "realizedDir": null,
"weights": [ { "model": "claude-fable-5", "weight": 0.1429 } ] },
"weights": [ { "model": "claude-fable-5", "weight": 0.1429 } ],
"accuracy": [ { "date": "2026-07-17", "ts": "…", "smartHitPct": 50.0,
"majorityHitPct": 50.0,
"bestModel": { "model": "qwen-3.7-max", "hitPct": 63.8298, "n": 47 },
"n": 12 } ],
"scorecard": [ { "fh": "4h", "n": 115, "hitPct": 47.8261,
"strongN": 91, "strongHitPct": 46.1538 } ] }| Field | Type | Meaning |
|---|---|---|
| latest | object | null | The most recent council call for the scope, or null. |
| latest.score | number | null | Weighted council score, −100 … +100. |
| latest.label | string | Side of the council call. This is sign(score) — there is NO ±20 dead band on this index. |
| latest.majorityScore | number | null | The same vote, unweighted. |
| weights[] | array | model and weight (0–1, summing to 1) behind the current call. |
| accuracy[] | array | Daily rollups: date, ts, smartHitPct, majorityHitPct, bestModel, n. A day appears once its forecasts have resolved. |
| scorecard[] | array | fh, n, hitPct, strongN, strongHitPct over the resolved council calls of the trailing 30 days. |
| Status | When |
|---|---|
| 200 | Success — including the "no row for this scope yet" case, which is an empty payload rather than an error. |
| 400 | An unknown symbol, fh or slug. Window sizes are clamped rather than rejected, so days never 400s. |
| 429 | Public rate limit exceeded. Back off and retry. |
GET /api/v1/indices/outlook/?fh=2h
→ 400
{ "errors": { "fh": ["unknown horizon '2h', must be one of ['1h', '4h', '1d', '1w', '1m']"] },
"error": "fh: unknown horizon '2h', must be one of ['1h', '4h', '1d', '1w', '1m']" }Both keys are always emitted: errors is the field-keyed shape the rest of the site reads, error is the flat one-liner. Neither consumer needs a special case.
The following are planned and are listed here so that their absence is explicit rather than implied. None of them exists on /api/v1/indices/ today, and nothing above depends on them.
| Planned | Status |
|---|---|
| API keys and per-key quotas | In development. The indices are open and unauthenticated in the meantime. |
| CSV responses (?format=csv) | In development. Every endpoint above is JSON only today. |
| Webhooks on index updates | In development. Poll the catalog's latestTs for freshness. |
| Embeddable widgets | In development. |
| Bulk dataset exports | In development — see Data. |
| Analyze API (programmatic AI market reports) | In development. |
| MCP server for assistants | In development — see Plugins. |