Get the history of prediction market odds and settled results for research

Research on prediction markets needs the odds over time and the settled result. Narwhal gives binary and scalar history by interval, a list of settled events, and a result with final and venue settlement for each event.

List the events that have settled

The events endpoint lists prediction events and filters by topic, shape, status, from, to and limit. This request asks for the most recent settled Federal Reserve decision. The status values are upcoming, trading, closed, settled and void.

curl, a settled event
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/predictions/events?topic=usa-fed&status=settled&limit=1"
Response
{
  "events": [
    {
      "event_id": "usa-fed-2026-10",
      "question": "Federal Reserve rate decision, 28 October 2026",
      "status": "settled",
      "closes_at": "2026-10-28T17:59:00Z",
      "resolves_at": "2026-10-28T18:00:00Z",
      "release_id": "ae2a77cc-3b5b-4541-a060-3bc2ea1145d9",
      "result": {
        "final": true,
        "settled_at": "2026-10-28T20:01:40Z"
      },
      "topic_id": "usa-fed",
      "shape": "scalar"
    }
  ],
  "next_cursor": null
}

A settled event carries a result summary. final is true when every venue's result is final, and settled_at is the latest venue settlement time. next_cursor is null on the last page; otherwise pass it back as cursor to get the next page.

Read the result and the last odds

The event id opens the full prediction. This response leaves out the venue rules text and keeps the hold row and the winning market of each venue.

curl, a settled scalar event
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/predictions/scalar/usa-fed-2026-10"
Response, trimmed
{
  "event_id": "usa-fed-2026-10",
  "topic_id": "usa-fed",
  "question": "Federal Reserve rate decision, 28 October 2026",
  "status": "settled",
  "closes_at": "2026-10-28T17:59:00Z",
  "resolves_at": "2026-10-28T18:00:00Z",
  "as_of": "2026-10-28T17:58:00Z",
  "result": {
    "final": true,
    "settled_at": "2026-10-28T20:01:40Z",
    "venues": [
      {
        "venue": "kalshi",
        "final": true,
        "settled_at": "2026-10-28T18:06:30Z",
        "markets": [
          {
            "market_id": "KXFEDDECISION-26OCT-H0",
            "outcome": "yes",
            "value": "1"
          }
        ]
      },
      {
        "venue": "polymarket",
        "final": true,
        "settled_at": "2026-10-28T20:01:40Z",
        "markets": [
          {
            "market_id": "2589812",
            "outcome": "Yes",
            "value": "1"
          }
        ]
      }
    ]
  },
  "outcomes": [
    {
      "key": "hold",
      "label": "No change",
      "lower": "-12.5",
      "upper": "12.5",
      "probability": "0.9622",
      "quotes": [
        {
          "venue": "kalshi",
          "probability": "0.9602",
          "market_ids": [
            "KXFEDDECISION-26OCT-H0"
          ]
        },
        {
          "venue": "polymarket",
          "probability": "0.9686",
          "market_ids": [
            "111061902544814266207267295505639408607400625795891618462682726460921782993748"
          ]
        }
      ]
    }
  ],
  "above": []
}

as_of is 17:58 UTC on 28 October 2026, one minute before the market closed, and the hold probability is 0.9622. result.venues lists each venue's markets with the settled outcome and value. Kalshi settled the KXFEDDECISION-26OCT-H0 market to yes, and the market id appears in the quotes of the hold outcome. A settled event has no stale field, since that field is sent on upcoming, trading and closed events only.

Pull the odds history

The history endpoints take a required start and end and an optional interval. They exist for scalar, binary, timing and extreme events. This request reads daily bars for the recession market and keeps two consecutive bars.

curl, daily history of a binary event
curl -s -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/predictions/binary/usa-recession-2026/history?start=2026-10-01T00:00:00Z&end=2026-10-08T00:00:00Z&interval=1d"
Response, trimmed
{
  "backfill_venue": "polymarket",
  "series": [
    {
      "key": "yes",
      "active_from": "2026-10-01T00:00:00Z",
      "bars": [
        {
          "timestamp": "2026-10-04T00:00:00Z",
          "close": "0.0850"
        },
        {
          "timestamp": "2026-10-05T00:00:00Z",
          "open": "0.0750",
          "high": "0.0750",
          "low": "0.0750",
          "close": "0.0750"
        }
      ]
    }
  ],
  "next_cursor": null
}

Every bar has a timestamp for the start of the bar and a close. The bar on 4 October has only a close, while the bar on 5 October also has open, high and low. A bar with all four values was captured live, and a close-only bar is backfill from a venue. backfill_venue names that venue, and active_from says when the series began. For a scalar event there is one series per outcome key.

Check the last odds against the result

The script reads the settled Federal Reserve event, finds the outcome whose Kalshi market settled to yes, and then walks the daily history of that outcome. It follows next_cursor until the last page, repeating the same start, end and interval.

python, odds history for a settled event
import json
import os
import urllib.parse
import urllib.request

def get(path):
    req = urllib.request.Request(
        "https://api.narwhalapi.com" + path,
        headers={"Authorization": "Bearer " + os.environ["NARWHAL_API_KEY"]},
    )
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.load(r)

def history(event_id, start, end, interval="1d"):
    bars, cursor = {}, None
    while True:
        query = {"start": start, "end": end, "interval": interval}
        if cursor:
            query["cursor"] = cursor
        page = get(f"/v1/predictions/scalar/{event_id}/history?" + urllib.parse.urlencode(query))
        for series in page["series"]:
            bars.setdefault(series["key"], []).extend(series["bars"])
        cursor = page["next_cursor"]
        if not cursor:
            return bars

event = get("/v1/predictions/scalar/usa-fed-2026-10")
print(event["status"], "final:", event["result"]["final"])

# The winning outcome: the Kalshi market that settled to yes.
yes = {m["market_id"] for v in event["result"]["venues"] if v["venue"] == "kalshi"
       for m in v["markets"] if m["outcome"] == "yes"}
winner = [o["key"] for o in event["outcomes"] for q in o["quotes"]
          if yes & set(q["market_ids"])]
print("winner:", winner)

bars = history("usa-fed-2026-10", "2026-10-21T00:00:00Z", "2026-10-29T00:00:00Z")
for bar in bars[winner[0]]:
    print(bar["timestamp"][:10], bar["close"])

It prints settled final: True, then winner: ['hold'], then the daily closes of the hold outcome from 21 October 2026 to 28 October 2026. They run from 0.8707 to 0.9622, and the last close equals the probability of the hold row in the event. That makes one row for a calibration table: the market priced the winning outcome at 0.9622 just before the decision.

To build a sample, page through /v1/predictions/events with status=settled, a topic and a from and to window on the resolution time. Run the script for each event_id and store the last pre-close probability beside the settled outcome. Use interval=1d for a study over many months and 1h for the final days before a decision.

What do as_of and stale tell a researcher?

as_of is when the venues were last checked. stale is true on an open event after more than 3 minutes without a venue check. After more than 30 minutes the response sends no probabilities, every quotes list is empty and every venue is excluded. Drop those moments from a study or mark them as missing. Settled and void events have no stale field.

When is a result final?

result.final is true once every venue's result is final. A venue's result that it has determined but not finalized can still change. If Narwhal finds that the outcome of a final result changed after a dispute or an amendment, the result carries amended_at. Check final and amended_at before you freeze a label.

What does a void event look like?

A void event has the status void in the events list. Once a venue has settled or voided its market, its entry in venues carries settlement with the value settled or void. In the Federal Reserve response above both venues show settled. Filter with status=void to find voided events, and treat them separately from settled events in a calibration sample.

How far back can I pull history, and in what steps?

The interval is 1m, 5m, 1h or 1d, and the default is 1h. One request can cover at most 2 days for 1m, 7 days for 5m, 92 days for 1h and 3,660 days for 1d. A page holds about 5,000 bars, and a page can be empty while next_cursor is not null, so keep following the cursor. See the predictions documentation for the full parameter list.

Published .

Next steps