Future Index

Future Index API

Anonymous, read-only, and versioned under /v1. No key, no account, and no write path — every endpoint below is a GET anyone can make.

Contract version 1.0.0 · base URL https://api.futureindex.app · the same document as OpenAPI

Using the API

Published by the API and read back here, so this is the policy the host answering you is running under rather than a copy of it.

What this API is

Anonymous, read-only, versioned under /v1. No key, no account, no write path.

Every value is published with the timestamps behind it and a freshness state, and a stale value is flagged rather than served as current. There is no single composite score anywhere in this contract: the World Pulse is a four-component vector, and components on different scales are never mixed into one number.

A missing component narrows a response rather than failing it. forecast is null for a contract nothing has calculated yet, a pulse component is null with a stated reason where nothing could be measured, and a series simply omits the buckets that hold no observation — a gap is a gap, never an interpolated line.

The response envelope

Every data response is { data, meta }. data is the payload; meta carries generated_at, api_version, freshness_state, a stale flag and the oldest observed_at the payload rests on.

meta.freshness_state is the worst state across everything in the payload, and a payload resting on no observations reports outage rather than livenothing to report and everything is current are opposite claims, and the second one would render a failed layer as healthy.

Errors take one shape everywhere, including the ones no handler anticipated: { error: { code, message, request_id } }, with details added only on bad_request. Branch on code, which is a closed list; the message is for a human and may be reworded. A 4xx describes your request and says what was wrong with it; a 5xx is about us and deliberately says nothing beyond the request id, which is in the log with the specifics under it.

Every expectations figure belongs to one source

An event in this API is one source's contract, identified by that source and the source's own id for it. It is never an aggregate, a consensus, an average, or a match across venues: this system does not compare contracts between sources and computes no cross-source probability at all.

The source therefore travels on the event object itself rather than on the metric or behind a relation, and every figure below is that source's view of that source's question:

  • event.source — the venue the contract is listed on. Two events with similar titles from different sources are two questions, not two readings of one.
  • event.latest — the newest raw value the source published, with no confidence attached, because a confidence would be a number nobody calculated.
  • forecast.probability — that one source's latest probability, unadjusted. Not a market consensus and not comparable across sources without doing that work yourself.
  • forecast.movements / forecast.volatility — change and realized variability in that one source's own series.
  • forecast.confidence — how well-supported that source's figure is. It is independent of the value: a dramatic move at low confidence stays low confidence.
  • forecast.source_contributions — always exactly one entry, which is the shape of the guarantee rather than a coincidence of the current data.
  • method_version — resolve it against /v1/methodology to get the constants and boundaries the figure was computed under.

Rate limits and caching

The budgets below are a rate to design around rather than a number to discover as a 429. A poller on a five-minute loop spends twelve requests an hour and a desktop widget refreshing every quarter of an hour spends four, so both fit with room to spare; a server that answers each of its own readers with one upstream request of its own does not fit at any polling interval, and the shortfall grows with its audience rather than with its schedule. Fetch once, cache for as long as the headers below say the answer stays good, and serve your readers from that.

Per client IP, refused with 429 and a Retry-After. RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset are on every limited response, so a client need not count for itself:

  • 60 per hour — every public read, including one that ends in an error.
  • 20 per hour — history endpoints, in addition to the read budget rather than instead of it.
  • 30 per hour — search, also in addition to the read budget.
  • About 10 per minute — a burst ceiling, enforced at the CDN in front of this API rather than by the application, so a 429 from it carries Retry-After but none of the RateLimit-* headers above. Pace yourself under this figure rather than probing for the exact threshold: it is the rate to design for, the enforced value is at least this generous, and neither is a budget to sit against.
  • Responses are publicly cacheable with stale-while-revalidate and stale-if-error: about 60s for summaries and the pulse, five to thirty minutes for history by range, and 1h for the methodology and the source registry. Honour them — a polling loop tighter than the cache window spends your hour to receive the same body.

Versioning: what may change without notice

The major version is in the path. /v1 will not change shape underneath you in any of the ways listed under breaking below; those arrive as /v2, at which point /v1 enters the deprecation procedure rather than stopping.

info.version is the contract's own semantic version: minor for the additive changes below, patch for wording. It is not the running build — that is /v1/status's version, which changes on every deploy.

Additive, and shipped whenever the product needs it. Write your client so that none of these breaks it:

  • a new endpoint, a new tag, or a new optional query parameter;
  • a new property on a response object — so ignore properties you do not recognise rather than rejecting the response;
  • a new value in a response enum, including a new error.code, a new source id, and a new signal layer — so have a default branch;
  • a new section, constant or boundary in /v1/methodology, which is generated from the method and grows with it.

Versioning: what counts as breaking

These do not happen inside a major version. A build that would ship one fails CI against a committed baseline of the published surface, so the guarantee is a test rather than a habit:

  • removing or renaming an endpoint, a response field, or a query parameter;
  • making an optional parameter required, or narrowing what an existing one accepts;
  • removing a value from a request enum, or a documented response status;
  • changing a field's type, or the envelope any response is wrapped in;
  • changing what an existing field means while its name and type stand still — the one item on this list no test can see. A changed meaning ships as a new field, and the old one is deprecated on the procedure below.

Deprecation

Nothing published is removed without notice. An endpoint on its way out is first marked deprecated in this document, keeps answering unchanged for at least 90 days, and only then leaves — and the build refuses to remove an operation the baseline does not already record as deprecated, so the order cannot be skipped by accident. The interval is an obligation we state; the ordering is one the pipeline enforces.

A deprecated field behaves the same way: it keeps its type and keeps being populated for the notice period, with its replacement documented beside it.

There is no announcement channel to subscribe to. GET /v1/openapi.json is the notice — diff it, or read deprecated on the operations you call.

The shape of every response

Two shapes, and every endpoint answers in one of them. Neither is fetched from anywhere — they are properties of the contract rather than of this page, so they are here whether or not the reference below could be read.

A successful read

The payload under data, and the answer's own currency under meta.

Read meta.freshness_state before you render a number: it is the worst state across everything in the payload, and a payload resting on no observations reports outage rather than live.

{
  "data": { … },
  "meta": {
    "generated_at": "2026-08-09T12:00:00Z",
    "api_version": "v1",
    "freshness_state": "live | stale | outage",
    "stale": false,
    "observed_at": "2026-08-09T11:58:00Z"
  }
}

A failure

One shape everywhere, including the failures no handler anticipated.

Branch on code, which is a closed list; message is for a person and may be reworded. A 4xx describes your request and says what was wrong with it; a 5xx says only the request id, because the specifics are about us and are in our log under that id.

{
  "error": {
    "code": "bad_request | not_found | rate_limited | internal",
    "message": "…",
    "request_id": "…",
    "details": [ { "path": "query.cursor", "message": "…" } ]
  }
}

Build something with it

A shell line, a desktop widget and a chat bot, each in one file and each reading the API answering right now — an example whose endpoint or fields this document does not publish is left out rather than shown.

None of them prints a sample of its own output, for the reason the response shapes above carry no sample values: a reading pasted into a page is a number nobody can check and is wrong within the hour. What they print is whatever the API says when you run them.

The world in one line

One request, four readings, curl and jq.

Every reading in the World Conditions group with the plain-language band the API published beside it. A reading nothing could be measured for is null and is skipped rather than printed as a zero — on these scales a zero is a measurement.

One request per run. What that costs you

curl -s "https://api.futureindex.app/v1/pulse" \
  | jq -r '.data.world_conditions
           | to_entries[]
           | select(.value != null)
           | "\(.key): \(.value.value) — \(.value.state)"'

A Raycast script command

The four readings on one line, refreshed in the launcher.

Save it as world-conditions.sh in a directory Raycast is configured to scan, chmod +x it, and it appears in the root search with its output rendered inline.

@raycast.mode inline is what makes the line itself the result rather than something a reader has to run; @raycast.refreshTime is what makes it a thing that is simply there. The same script is a working xbar or SwiftBar plugin with the Raycast comments removed — a menu-bar plugin is a program that prints one line, which is what this is.

One request every fifteen minutes — four an hour, whatever else is running. What that costs you

#!/usr/bin/env bash
# @raycast.schemaVersion 1
# @raycast.title World conditions
# @raycast.mode inline
# @raycast.refreshTime 15m
# @raycast.packageName Future Index
# @raycast.icon 🌍

curl -s "https://api.futureindex.app/v1/pulse" \
  | jq -r '[.data.world_conditions
            | to_entries[]
            | select(.value != null)
            | "\(.key) \(.value.value | round)"]
           | join("  ·  ")'

A Telegram bot, under thirty lines

Long-polls Telegram, answers /conditions, and caches so its audience does not cost anything.

Node 22 or newer and no dependencies at all: fetch and long polling are both in the platform. Put a token from @BotFather in TELEGRAM_TOKEN and run it with node bot.mjs.

The cache is the example rather than an optimisation of it. A budget is per address, and every user of a bot shares the bot's address — so a bot that fetched per message would cost one request per reader and would stop working at exactly the point it became popular. One upstream request a minute serves any number of them, and the answer is not a minute old in any way that matters: the pulse recomputes on its own schedule and the response is cacheable for about as long.

At most one request a minute, however many people ask. What that costs you

// bot.mjs — node >= 22, no dependencies. Token from @BotFather.
const TELEGRAM = `https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}`;

// One upstream request a minute however many people ask: the hourly budget is
// per address, and every user of this bot shares its address.
let cached = { at: 0, text: "" };

async function conditions() {
  if (Date.now() - cached.at < 60_000) return cached.text;
  const { data } = await (await fetch("https://api.futureindex.app/v1/pulse")).json();
  cached = {
    at: Date.now(),
    text: Object.entries(data.world_conditions)
      .filter(([, reading]) => reading !== null)
      .map(([name, r]) => `${name}: ${Math.round(r.value)} — ${r.state}`)
      .join("\n"),
  };
  return cached.text;
}

let offset = 0;
for (;;) {
  const updates = await fetch(`${TELEGRAM}/getUpdates?timeout=50&offset=${offset}`);
  for (const update of (await updates.json()).result) {
    offset = update.update_id + 1;
    if (!update.message?.text?.startsWith("/conditions")) continue;
    await fetch(`${TELEGRAM}/sendMessage`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ chat_id: update.message.chat.id, text: await conditions() }),
    });
  }
}

Endpoints

Every endpoint below is read back from the API answering right now, so this list is what is deployed rather than what was written down. The machine-readable form is https://api.futureindex.app/v1/openapi.json, which is the same document this page is rendered from.

Pulse

The World Pulse: four components — outlook, attention, sentiment, uncertainty — published as a vector with no composite scalar. Outlook measures movement in expectations, never their direction: there is no improving or worsening here.

GET/v1/pulse

The World Pulse — four figures, never a total

Outlook measures how much expectations moved, not whether they improved: there is no direction in it and no level. Each component may be null on its own, with the reason in absent. Nothing in this payload combines the four.

  • category query, optionalAccepts one of politics, economy, conflict, technology, ai, crypto, climate, energy, health, sports, entertainment.
  • region query, optionalAccepts one of europe, asia-pacific, north-america, middle-east, africa, latin-america.

Answers 200, 400, 429

curl -s "https://api.futureindex.app/v1/pulse"

Changes

What changed today, ranked across layers. Movements of different kinds are normalised before they are ranked and each card names the layer it came from.

GET/v1/changes

What changed, ranked

The largest movements across expectations, attention, discussion sentiment and context over the window, ranked by importance, how far each departed from its own subject's history, whether another layer moved with it, confidence and freshness. Magnitudes are compared as deviations from each subject's own baseline and never as raw values, so a probability change and a comment count are never added together; a change whose layer produced no deviation is excluded rather than ranked on its face. Coverage volume is capped absolutely, so an occurrence many outlets carried cannot outrank a measured movement. At most 200 candidates per layer are considered; considered and excluded say what the ranking saw.

  • window query, optionalAccepts one of 1h, 24h, 7d.
  • limit query, optional
  • category query, optionalAccepts one of politics, economy, conflict, technology, ai, crypto, climate, energy, health, sports, entertainment.
  • region query, optionalAccepts one of europe, asia-pacific, north-america, middle-east, africa, latin-america.

Answers 200, 400, 429

curl -s "https://api.futureindex.app/v1/changes"

Cross signals

Observed relationships between layers on one topic — attention preceding a forecast move, coverage without discussion. These state coincidence and never causation.

GET/v1/cross-signals

Relationships detected between independent signals

Observations about how a topic's layers moved relative to each other. Each detection states what was measured and when; none of them states a cause.

  • topic query, optional
  • pattern query, optionalAccepts one of expectation_attention_confirmation, attention_leads_expectation, expectation_without_attention, media_without_public_attention, community_sentiment_divergence.
  • hours query, optional
  • limit query, optional
  • category query, optionalAccepts one of politics, economy, conflict, technology, ai, crypto, climate, energy, health, sports, entertainment.
  • region query, optionalAccepts one of europe, asia-pacific, north-america, middle-east, africa, latin-america.

Answers 200, 400, 429

curl -s "https://api.futureindex.app/v1/cross-signals"

Events

Forecast contracts. Each is one source's contract, never an aggregate across sources — see the single-source section of the description.

GET/v1/events

Forecast contracts, closing soonest first

Each entry is one source's contract, never an aggregate across sources. Paged with an opaque cursor; hand next_cursor back verbatim.

  • source query, optional
  • status query, optionalAccepts one of open, closed, resolved, void.
  • limit query, optional
  • cursor query, optional

Answers 200, 400, 429

curl -s "https://api.futureindex.app/v1/events"

GET/v1/events/{slug}

One forecast contract

One source's contract and that source's current view of it. Nothing here is combined with, compared to, or ranked against another source's question.

  • slug path, required

Answers 200, 400, 404, 429

curl -s "https://api.futureindex.app/v1/events/{slug}"

GET/v1/events/{slug}/context

What was reported on this contract's subjects

Clustered occurrences on the subjects this contract is linked to, newest first. Each entry is one occurrence however many outlets carried it, and states the number of independent origins behind it rather than a number of articles. An entry may state that this contract's probability moved close to the same time; nothing in this payload states that either produced the other.

  • days query, optional
  • limit query, optional
  • slug path, required

Answers 200, 400, 404, 429

curl -s "https://api.futureindex.app/v1/events/{slug}/context"

GET/v1/events/{slug}/history

One contract's series over a window

Bucketed to at most 500 points. Each point is the last observation in its bucket — never an average and never interpolated. A bucket with no observation is absent, so a source outage reads as a gap. Observations are stored at source cadence for 32 days and as daily totals for 400; window=1y is therefore answered at day resolution and no finer. Always read resolution_seconds rather than assuming one — it is the width the series was actually bucketed at.

  • window query, optionalAccepts one of 1h, 24h, 7d, 30d, 90d, 1y.
  • metric query, optionalAccepts one of probability, spread, liquidity, volume.
  • slug path, required

Answers 200, 400, 404, 429

curl -s "https://api.futureindex.app/v1/events/{slug}/history"

Topics

Persistent subjects, their per-layer metrics and their contracts. A topic's contracts are listed and never combined into one figure.

GET/v1/topics

Topics, by editorial importance, by name, or by how active they are

Each entry carries whatever of the four public metrics has been computed for it. A topic with none is published with an empty list rather than omitted — that is the state of every topic until the metric passes reach it. order selects between three stored columns: the seeded editorial importance, the collation over the canonical name, and activity, the trending score over the last 24 hours. All three page by keyset, and a cursor is refused by any ordering other than the one that issued it. ordering says which produced the page, whether it is the whole index or a bounded selection, and — under activity — which method version computed the ranking and when. A null computed_at under activity means no ranking has been computed, in which case every subject scores zero and the page falls back to the editorial order.

  • category query, optionalAccepts one of politics, economy, conflict, technology, ai, crypto, climate, energy, health, sports, entertainment.
  • region query, optionalAccepts one of europe, asia-pacific, north-america, middle-east, africa, latin-america.
  • order query, optionalAccepts one of importance, name, activity.
  • limit query, optional
  • cursor query, optional

Answers 200, 400, 429

curl -s "https://api.futureindex.app/v1/topics"

GET/v1/topics/{slug}

One topic, its metrics and the contracts linked to it

Contracts are listed side by side with their own questions and their own probabilities. Nothing here is an average across them: in MVP an event is one source's contract and this product computes no consensus. contracts selects between the editorial ordering over every linked contract and two orderings over §18.1's figures — the size of the last 24 hours' movement, and the probability itself. Both of those are selections: a contract with no computed metric carries neither figure and is absent from them rather than ranked last. contracts_ordering says which of the three produced the list, how many contracts it could place, and the cap it was drawn against.

  • contracts query, optionalAccepts one of importance, moving, likely.
  • slug path, required

Answers 200, 400, 404, 429

curl -s "https://api.futureindex.app/v1/topics/{slug}"

GET/v1/topics/{slug}/changes

What changed on this subject, ranked

The same ranking as /v1/changes over one subject's own candidates: its linked contracts' movements, its attention and discussion buckets, and the occurrences reported on it. Magnitudes are compared as deviations from each subject's own baseline and never as raw values. considered is this subject's candidate count and is often small, which is what makes it worth reading beside the feed.

  • window query, optionalAccepts one of 1h, 24h, 7d.
  • limit query, optional
  • slug path, required

Answers 200, 400, 404, 429

curl -s "https://api.futureindex.app/v1/topics/{slug}/changes"

GET/v1/topics/{slug}/context

What was reported on this subject

Clustered occurrences linked to this subject. Each entry is one occurrence however many outlets carried it, and states the number of independent origins behind it rather than a number of articles. Ordered newest first by default, or by how many independent origins reported it with order=most_reported; either way the ordering is applied before the page is capped, and the ordering that was applied is echoed in the response. Nothing in this payload relates an occurrence to any figure this product publishes about the subject.

  • days query, optional
  • limit query, optional
  • order query, optionalAccepts one of newest, most_reported.
  • slug path, required

Answers 200, 400, 404, 429

curl -s "https://api.futureindex.app/v1/topics/{slug}/context"

GET/v1/topics/{slug}/history

One topic metric's series over a window

Bucketed to at most 500 points. Each point is the last computed value in its bucket — never an average and never interpolated. A bucket with no value is absent, so a period nothing was computed reads as a gap.

  • window query, optionalAccepts one of 1h, 24h, 7d, 30d, 90d, 1y.
  • metric query, optionalAccepts one of outlook, attention, sentiment, uncertainty, turbulence, disagreement.
  • slug path, required

Answers 200, 400, 404, 429

curl -s "https://api.futureindex.app/v1/topics/{slug}/history"

Discovery

Movers, trending topics, narrative divergence and the category view.

GET/v1/categories

Each launch category's four figures, its top contract and what moved in it

The four figures are the same World Pulse calculation the hero publishes, run over the subjects in one category — so a category selected on both is described identically by each. A category with fewer measured subjects than the method's minimum publishes no figure and is still listed with its subject count: one subject wearing a category's name is not a category. Computed as the response is built and stored nowhere, so a figure here carries no deltas and no history.

  • window query, optionalAccepts one of 1h, 24h, 7d.
  • category query, optionalAccepts one of politics, economy, conflict, technology, ai, crypto, climate, energy, health, sports, entertainment.
  • region query, optionalAccepts one of europe, asia-pacific, north-america, middle-east, africa, latin-america.

Answers 200, 400, 429

curl -s "https://api.futureindex.app/v1/categories"

GET/v1/movers

The topics whose figure for one metric moved most over a window

One metric per request. Ranked by the size of the change and never by how well founded it is: confident is published on every row as a warning and has no effect on the order. On the metrics the method lists as rise-only — attention, whose anomaly is calibrated on its upper tail alone — a fall is not listed at all; on the rest the direction does not affect the order. A change smaller than the method's threshold is absent rather than listed as small.

  • category query, optionalAccepts one of politics, economy, conflict, technology, ai, crypto, climate, energy, health, sports, entertainment.
  • region query, optionalAccepts one of europe, asia-pacific, north-america, middle-east, africa, latin-america.
  • metric query, optionalAccepts one of outlook, attention, sentiment, uncertainty, turbulence, disagreement.
  • window query, optionalAccepts one of 1h, 24h, 7d.
  • limit query, optional

Answers 200, 400, 429

curl -s "https://api.futureindex.app/v1/movers"

GET/v1/narrative-divergence

Subjects where coverage and discussion, or two communities, differ materially

Both sides are required: one side reporting is an absence rather than a disagreement. Each card states which side is ahead and nothing about why — the relationship is what was observed, and a reason would be a claim this product does not make.

  • window query, optionalAccepts one of 1h, 24h, 7d.
  • category query, optionalAccepts one of politics, economy, conflict, technology, ai, crypto, climate, energy, health, sports, entertainment.
  • region query, optionalAccepts one of europe, asia-pacific, north-america, middle-east, africa, latin-america.

Answers 200, 400, 429

curl -s "https://api.futureindex.app/v1/narrative-divergence"

GET/v1/trending

Subjects several independent kinds of source are unusually busy with

Scored from five inputs with fixed shares that never redistribute, so a subject only news sources have covered reaches the coverage share and stops. The score carries the parts it was built from, how many kinds of source were moving, and what the concentration discount did to it.

  • window query, optionalAccepts one of 1h, 24h, 7d.
  • category query, optionalAccepts one of politics, economy, conflict, technology, ai, crypto, climate, energy, health, sports, entertainment.
  • region query, optionalAccepts one of europe, asia-pacific, north-america, middle-east, africa, latin-america.

Answers 200, 400, 429

curl -s "https://api.futureindex.app/v1/trending"

Sources

The source registry as published: what each source is, how it is attributed, and what its terms permit. Attribution here is required to be displayed, not advisory.

GET/v1/sources

Every source in the registry, with the permission it runs under

The registry loaded by the running build. Resolve the ids that appear in a metric's coverage against this.

Takes no parameters.

Answers 200, 429

curl -s "https://api.futureindex.app/v1/sources"

Methodology

How every published number is calculated, generated from the versioned method the calculation itself reads. method_version on a metric resolves here.

GET/v1/methodology

How every published number is calculated

Generated from the same versioned method the calculation uses, so it cannot describe something other than what ran. method_version matches the version on every metric this method produced.

Takes no parameters.

Answers 200, 429

curl -s "https://api.futureindex.app/v1/methodology"

Operations

Service state. Documented so a deployment is checkable, not part of the data contract.

GET/v1/status

Service state and the running build

Takes no parameters.

Answers 200

curl -s "https://api.futureindex.app/v1/status"

Attribution

Every response names the sources behind it, and several of those sources require their attribution — and in some cases a link back — to be displayed wherever their data is shown.

The source registry publishes the exact string for each, and it is the string to render: shortening it in a style pass is a change to a licence rather than to a label.

How every number is calculatedis published on the same terms — generated from the versioned method the calculation reads, with each figure's method_version resolving to the constants it was computed under.