Docs

With a key in hand, three commands put a file on your disk. The first block below is copy-pasteable as-is.

What outcometick is: The Chainlink settlement feeds and order books of Polymarket and Predict.fun crypto Up/Down markets, plus Polymarket’s trade prints — collected continuously, checksummed, and downloadable by API. 158 complete UTC days archived since 2026-06-08 (Predict.fun from 2026-06-13). Polymarket 7 assets, Predict.fun 3 assets. Subscriptions from $10/month for one venue.

Pricing · Free sample (Polymarket) · Free sample (Predict.fun) · Backtest a strategy

Sixty seconds

Replace the key on line 1 with your own (shown once on the page right after payment); paste the rest as-is.

# 1. your key, from the page you saw right after paying
export OT_KEY="ck_a1b2c3d4e5f6.xxxxxxxxxxxxxxxxxxxxxxxx"

# 2. what can I get?
curl -s -H "Authorization: Bearer $OT_KEY" \
  https://outcometick.com/v1/meta

# 3. find a file — with no date filter you get the newest day your key reaches
curl -s -H "Authorization: Bearer $OT_KEY" \
  "https://outcometick.com/v1/files?asset=btc&dataset=prices"

# 4. download it — every file in that listing carries its own url
URL=$(curl -s -H "Authorization: Bearer $OT_KEY" \
  "https://outcometick.com/v1/files?asset=btc&dataset=prices" \
  | jq -er '.files[0].url')

curl -L -H "Authorization: Bearer $OT_KEY" "$URL" -o btc-prices.csv.gz

The -L in step 4 matters: the download endpoint answers with a 302 to a short-lived object-storage URL. Files come straight from storage — they never pass through our server.

One line in Python

pandas, DuckDB and wget cannot attach a header to a URL, so the download endpoint also accepts ?api_key=. Be aware it puts the key in browser history and proxy logs — prefer the header wherever you can set one.

import pandas as pd, requests

KEY  = "ck_a1b2c3d4e5f6.xxxxxxxxxxxxxxxxxxxxxxxx"
auth = {"Authorization": f"Bearer {KEY}"}

r = requests.get("https://outcometick.com/v1/files", headers=auth,
                 params={"asset": "btc", "dataset": "prices"})

# the download endpoint also takes ?api_key=, for clients that cannot set a header.
# The URL no longer ends in .gz, so name the compression; full_accuracy_value is
# a 1e18 fixed-point integer wider than int64, so keep it as text.
df = pd.read_csv(r.json()["files"][0]["url"] + "?api_key=" + KEY,
                 compression="gzip", dtype={"full_accuracy_value": str})
print(df.head())

The same URL works in polars and DuckDB, and the JSONL datasets (markets, book, trades) load the same way. The arguments are not decoration: without them pandas cannot read the file at all, and pandas and DuckDB silently turn 1e18 fixed-point values and 76-digit token ids into floats.

import duckdb, pandas as pd, polars as pl, requests

KEY  = "ck_a1b2c3d4e5f6.xxxxxxxxxxxxxxxxxxxxxxxx"
auth = {"Authorization": f"Bearer {KEY}"}

def newest(dataset):
    r = requests.get("https://outcometick.com/v1/files", headers=auth,
                     params={"venue": "polymarket", "asset": "btc", "dataset": dataset})
    return r.json()["files"][0]["url"] + "?api_key=" + KEY

# CSV — settlement prices, here; klines read the same way
prices = newest("prices")
pl.read_csv(prices)
# types= only for prices / twap30s / twap60s: it errors on a file without the column
duckdb.sql(f"SELECT * FROM read_csv('{prices}', types={{'full_accuracy_value': 'VARCHAR'}})").df()

# JSONL — markets, book, trades: ids and prices are strings, keep them that way
markets = newest("markets")
pd.read_json(markets, lines=True, compression="gzip", dtype=False)
pl.read_ndjson(markets)
duckdb.sql(f"SELECT * FROM read_json('{markets}')").df()

Every file also comes as Parquet: add format=parquet to the listing. Same rows, already typed, so no compression or precision arguments are needed.

import duckdb, polars as pl, requests

KEY  = "ck_a1b2c3d4e5f6.xxxxxxxxxxxxxxxxxxxxxxxx"
auth = {"Authorization": f"Bearer {KEY}"}

# format=parquet lists the Parquet copy of each file: the same rows, already typed
r = requests.get("https://outcometick.com/v1/files", headers=auth, params={
    "venue": "polymarket", "asset": "btc", "dataset": "book", "format": "parquet"})
url = r.json()["files"][0]["url"] + "?api_key=" + KEY

pl.read_parquet(url)
duckdb.sql(f"SELECT * FROM read_parquet('{url}')").df()

Comparing the two platforms? With format=parquet, dataset=markets_unified lists a second copy of each markets file in one shape for both: one row per market, times in milliseconds, prices as exact decimals and the outcome spelled out, so a single query reads Polymarket and Predict.fun together. The columns are on the schemas page.

Would rather not download the files at all? The same archive can be replayed against a strategy in a sealed sandbox, with fills matched against the depth that was really there — see the strategy SDK reference

From an AI coding tool (MCP)

The npm package includes an MCP server, so Claude Code, Cursor or any MCP client can look up coverage, list and download files (sha256-verified), quote a backtest, validate a strategy locally, follow your runs and, with a smart-money key (OT_SMART_KEY), download the smart-money lists. It runs on your machine and needs Node 24; your keys stay in its local config. It never submits a backtest, because that spends credits.

# Claude Code — every key is optional; public tools work without any
claude mcp add outcometick \
  -e OT_KEY="ck_a1b2c3d4e5f6.xxxxxxxxxxxxxxxxxxxxxxxx" \
  -e OT_SMART_KEY="ck_f6e5d4c3b2a1.xxxxxxxxxxxxxxxxxxxxxxxx" \
  -e OT_BACKTEST_KEY="bt_a1b2c3d4e5f6.xxxxxxxxxxxxxxxxxxxxxxxx" \
  -- npx -y -p outcometick ot mcp

Cursor, Claude Desktop and other MCP clients take the same server as JSON:

{
  "mcpServers": {
    "outcometick": {
      "command": "npx",
      "args": ["-y", "-p", "outcometick", "ot", "mcp"],
      "env": { "OT_KEY": "ck_a1b2c3d4e5f6.xxxxxxxxxxxxxxxxxxxxxxxx" }
    }
  }
}

Or use the remote server at https://outcometick.com/v1/mcp, with nothing to install. In claude.ai or ChatGPT, add it as a custom connector: you are sent to an outcometick page to paste your keys, and the connector receives a token that stops working when the key is revoked or expires. To see or disconnect the apps connected with your key, use https://outcometick.com/connections. Clients that can send headers can pass the key directly. Remotely, download_file and smart_download return a short-lived signed link together with its sha256, and check_strategy takes the files' contents instead of a directory.

# Remote: nothing to install. The key goes in a header…
claude mcp add --transport http outcometick https://outcometick.com/v1/mcp \
  --header "Authorization: Bearer ck_a1b2c3d4e5f6.xxxxxxxxxxxxxxxxxxxxxxxx"

# …or leave --header out and sign in with your keys in the browser (OAuth).
# Public tools only, no key: https://outcometick.com/v1/mcp/public

The HTTP API is also described in OpenAPI 3.1, for code generators and agents: https://outcometick.com/v1/openapi.json. Operations that spend money or credits are marked x-requires-human-confirmation.

Common tasks

One day, one asset, one interval

Every filter is optional. Use date for a single day, or from / to for a range. None of these examples names a day: they read it out of /v1/meta, which answers with the window your own key reaches.

# the newest day your key reaches
DAY=$(curl -s -H "Authorization: Bearer $OT_KEY" \
  https://outcometick.com/v1/meta | jq -r .lastDay)

# every BTC 5-minute order book for that day
curl -s -H "Authorization: Bearer $OT_KEY" \
  "https://outcometick.com/v1/files?date=$DAY&asset=btc&interval=5m&dataset=book"

Everything for 5min markets: books and settlement

The settlement streams (prices / twap30s / twap60s) are continuous and carry no interval of their own, so interval=5m leaves them out. Add none to get both in one call — settling a 5min market takes twap60s, and the book alone will not do it.

DAY=$(curl -s -H "Authorization: Bearer $OT_KEY" \
  https://outcometick.com/v1/meta | jq -r .lastDay)

# everything a 5-minute market needs: the 5m slices AND the settlement streams
curl -s -H "Authorization: Bearer $OT_KEY" \
  "https://outcometick.com/v1/files?date=$DAY&asset=btc&interval=5m,none"

A month of settlement prices, verified on the way in

Each file carries a sha256 identical to the archive manifest. A match proves you have exactly what we published — not merely that the transfer succeeded.

#!/usr/bin/env bash
# a month of BTC settlement prices, downloaded and checksum-verified
set -euo pipefail
export OT_KEY="ck_a1b2c3d4e5f6.xxxxxxxxxxxxxxxxxxxxxxxx"

# 30 days back from the newest day you can reach, clamped to the oldest —
# one call covers at most 92 days, and your window may be shorter than either
read -r FROM TO < <(curl -s -H "Authorization: Bearer $OT_KEY" \
  https://outcometick.com/v1/meta \
  | jq -r '((.lastDay | strptime("%Y-%m-%d") | mktime) - 29*86400
            | strftime("%Y-%m-%d")) as $back
           | "\(if $back < .firstDay then .firstDay else $back end) \(.lastDay)"')

curl -s -H "Authorization: Bearer $OT_KEY" \
  "https://outcometick.com/v1/files?asset=btc&dataset=prices&from=$FROM&to=$TO" \
| jq -r '.files[] | "\(.url) \(.name) \(.sha256)"' \
| while read -r url name sha; do
    curl -sL -H "Authorization: Bearer $OT_KEY" "$url" -o "$name"
    echo "$sha  $name" | sha256sum -c -
  done

Straight into one DataFrame

import pandas as pd, requests
from datetime import date, timedelta

KEY  = "ck_a1b2c3d4e5f6.xxxxxxxxxxxxxxxxxxxxxxxx"
BASE = "https://outcometick.com"
auth = {"Authorization": f"Bearer {KEY}"}

meta  = requests.get(f"{BASE}/v1/meta", headers=auth).json()
last  = date.fromisoformat(meta["lastDay"])
first = max(date.fromisoformat(meta["firstDay"]), last - timedelta(days=6))

# every 1-minute kline for BTC over the last week your key reaches
r = requests.get(f"{BASE}/v1/files", headers=auth, params={
    "asset": "btc", "dataset": "klines", "interval": "1m",
    "from": first.isoformat(), "to": last.isoformat(),
})
files = r.json()["files"]

df = pd.concat(
    pd.read_csv(f["url"] + "?api_key=" + KEY, compression="gzip") for f in files
).sort_values("ts_ms")
print(len(df), "rows")

Reference

Polymarket and Predict.fun data download APIEvery filter, both response shapes, and what each status code actually means.
File schemasEvery column and field, per dataset and per venue. The two venues do not share one.
How a market settlesWhich stream decides the outcome, and how to recompute one yourself.
Cancel your subscriptionEnter the email you paid with on the billing page and we send you a sign-in link that lists your subscriptions. Cancelling takes effect at the end of the paid period and the key works until then; card changes and invoices are there too. Range purchases and stablecoin payments are one-off, so there is nothing to cancel.

Guides

Fees, queue position and markoutsTwo simple strategies replayed on the free sample day, and what taker fees, book depth and queueing do to them.
Grading markets on the right settlement priceHow many outcomes flip when a backtest grades with the wrong Chainlink stream, and how early the result shows up.