接口文档

两类资源:/v1/access 告诉你 Key 能下载什么;/v1/days 列出日期并给出每条流的文件。全部是带 API Key 的普通 HTTPS 请求;文件是 gzip 压缩的 JSON Lines,每条数据流每个 UTC 日一个文件。

快速开始

  1. 用邮箱登录,在定价页购买一期或订阅。
  2. 在账户页复制你的 API Key。
  3. 调用接口:
bash
export POLYTICKTAPE_KEY=ptt_your_key_here

# What your key can reach
curl -s -H "Authorization: Bearer $POLYTICKTAPE_KEY" https://polyticktape.com/v1/access

# Download one file and check it
curl -fL -H "Authorization: Bearer $POLYTICKTAPE_KEY" -o btc-twap60-2026-08-14.jsonl.gz \
  https://polyticktape.com/v1/days/2026-08-14/twap60
shasum -a 256 btc-twap60-2026-08-14.jsonl.gz   # Linux: sha256sum
gzip -dc btc-twap60-2026-08-14.jsonl.gz | head -3

接口地址为 https://polyticktape.com。所有日期都是 UTC 日,格式 YYYY-MM-DD;数据从 2026-08-14 开始。

用 AI 接入

AI 助手和编程智能体(Claude、ChatGPT、Cursor、Copilot 等)可以替你完成接入。我们准备了专门写给 AI 看的文档:网站提供什么、每个接口怎么用、出错怎么处理,以及一个实际跑通过的下载脚本。让你的 AI 助手先读这些文档,例如:

提示词
先阅读 https://polyticktape.com/llms.txt,然后用 Poly Tick Tape 的 API 把 2026-09-01 到 2026-09-07 的 BTC TWAP60 文件下载到 ./data,并核对 SHA-256。我的 API Key 在环境变量 POLYTICKTAPE_KEY 里。
  • /llms.txt——Poly Tick Tape 是什么、价格,以及 AI 应该怎样协助你。
  • /docs/api.md——本接口文档的 Markdown 版,写给 AI 看。
  • /llms-full.txt——以上两份合在一个文件里,适合只读取单个文档的工具。

API Key 请放在环境变量 POLYTICKTAPE_KEY 里,不要直接贴进对话。如果你的 Key 不覆盖所需日期,AI 会告诉你需要购买哪一期,或者订阅可以覆盖。

认证

每个请求都在 Authorization 请求头里带上 Key:

Authorization: Bearer ptt_your_key_here
  • 每个账户一把 Key。购买和订阅续费都不会更换 Key。只有在账户页点重置 Key才会换成新 Key,旧 Key 立即失效(之后返回 401 key_revoked)。
  • Key 只能放在请求头里,不接受放在网址中(网址会留在日志和浏览器历史里)。如果工具只能打开一个网址,先用 /v1/days/{date}/{stream}?as=json 拿到签名下载链接。
  • Key 仅限本人使用。以任何方式使用过 Key(包括在账户页下载)之后,订单不再支持退款。

GET /v1/access

返回这把 Key 当前能下载的范围:日期区间 ranges(仍在下载期内的期与订阅窗口合并后的结果)、已购买的期、订阅状态和今日下载次数。每一期带 expiresAt(7 天下载期的截止时间,Unix 秒;进行中的期在发完之前为 null)和 active(过了截止时间变为 false)。

GET /v1/access

{
  "streams": ["price", "twap60"],
  "saleStart": "2026-08-14",
  "lastPublished": "2026-12-20",
  "firstDay": "2026-08-14",
  "lastDay": "2026-12-20",
  "ranges": [
    { "from": "2026-08-14", "to": "2026-09-12" },
    { "from": "2026-10-22", "to": "2026-12-20" }
  ],
  "periods": [
    { "period": 1, "expiresAt": 1798099200, "active": true }
  ],
  "subscription": {
    "plan": "monthly",
    "status": "active",
    "active": true,
    "currentPeriodEnd": 1798761600,
    "cancelAtPeriodEnd": false,
    "graceUntil": null,
    "window": { "from": "2026-10-22", "to": "2026-12-20" }
  },
  "quota": { "dailyDownloads": 300, "usedToday": 12 }
}

lastPublished 是网站上最新的日期。没有订阅时 subscription 为 null;订阅无效时 window 为 null。时间戳均为 Unix 秒。

GET /v1/days

列出一个日期范围内的每一天。每天按数据流各给一个对象:文件名、大小、行数、coverage、SHA-256 和下载地址。

参数说明
from必填。范围的第一天。
to范围的最后一天,含当天(默认等于 from)。每次最多 92 天。
streamsprice、twap60 或 price,twap60(默认两者都要)。

from 为必填。想要最新一天,先从 /v1/access 读 lastPublished。

GET /v1/days?from=2026-08-14&streams=twap60

{
  "from": "2026-08-14",
  "to": "2026-08-14",
  "streams": ["twap60"],
  "days": [
    {
      "date": "2026-08-14",
      "twap60": {
        "name": "btc-twap60-2026-08-14.jsonl.gz",
        "bytes": 2411945,
        "records": 75392,
        "coverage": 0.8726,
        "sha256": "bef953289d716b3360d5afdd30db4902edf8849097f3291add330abb81605415",
        "url": "https://polyticktape.com/v1/days/2026-08-14/twap60"
      }
    }
  ],
  "missing": []
}

范围内不能下载的数据流不会让请求失败,而是列在 missing 里:not_entitled(不在你的访问范围内)或 not_published(还没发布,或当天没有数据)。两条流都拿不到的日期只出现在这里。

"missing": [
  { "date": "2026-09-13", "stream": "price", "reason": "not_entitled" },
  { "date": "2026-10-12", "stream": "price", "reason": "not_published" }
]

coverage 表示当天 86,400 秒中有数据的比例。

GET /v1/days/{date}/{stream}

stream 为 price 或 twap60。返回 302,指向一个 2 分钟内有效、无需 Key 的文件链接;支持 Range,下载中断可续传。请用 curl -L(Python requests 会自动跟随)。保存的文件名为 btc-{stream}-{date}.jsonl.gz;链接本身就以这个文件名结尾,所以 curl -LO 和 wget 也会用这个名字保存。

GET /v1/days/2026-08-14/twap60

HTTP/1.1 302 Found
Location: https://polyticktape.com/blob/eyJkIjo…/btc-twap60-2026-08-14.jsonl.gz
X-Ptt-Sha256: bef953289d716b3360d5afdd30db4902edf8849097f3291add330abb81605415

加上 ?as=json 则以 JSON 返回链接:

GET /v1/days/2026-08-14/twap60?as=json

{
  "url": "https://polyticktape.com/blob/eyJkIjo…/btc-twap60-2026-08-14.jsonl.gz",
  "expiresAt": 1786752300,
  "name": "btc-twap60-2026-08-14.jsonl.gz",
  "sha256": "bef953289d716b3360d5afdd30db4902edf8849097f3291add330abb81605415",
  "bytes": 2411945,
  "records": 75392
}

每调用一次计 1 次下载;随后访问返回的链接不重复计数。

文件格式

  • 文件名:btc-price-YYYY-MM-DD.jsonl.gz 和 btc-twap60-YYYY-MM-DD.jsonl.gz。
  • gzip 压缩,每行一个 JSON 对象,UTF-8 编码,每行以 \n 结尾。
  • 一个文件是一个 UTC 日:ts 从 00:00:00 到 23:59:59。按 ts 升序排列,每秒最多一行。
  • 没有报价的秒就没有对应的行。/v1/days 里每天的 coverage 就是 86,400 秒中有行的比例。
  • 每天的数据在次日 UTC 05:00 前发布,之后不再改动。下载后可用 sha256 校验。
字段类型含义
ts int 报价所属的秒(Unix 秒)
p string 全精度价格——比较和判定请用这个
v float 同一价格的浮点数,方便画图和计算
lag int 接收延迟(毫秒):recv_ms − feed_ts_ms
feed_ts_ms int 报价时间(毫秒)
server_ts_ms int | null 上游服务器发出时间(毫秒),上游未提供时为 null
recv_ms int 采集端收到的时间(毫秒)
btc-twap60-2026-08-14.jsonl.gz 的前两行
{"ts":1786665600,"p":"63416.048159164624535552","v":63416.048159164624,"lag":2636,"feed_ts_ms":1786665600000,"server_ts_ms":1786665602338,"recv_ms":1786665602636}
{"ts":1786665602,"p":"63416.671282124873007104","v":63416.67128212487,"lag":1945,"feed_ts_ms":1786665602000,"server_ts_ms":1786665603584,"recv_ms":1786665603945}

比较和结算请用 p:它是字符串,保留了全部位数。解析时请用十进制类型(Python 的 Decimal),不要转成浮点数。v 是同一个值的浮点形式,用于画图和统计。

Key 的访问范围

  • 单次购买:从 2026-08-14 起每 30 天为一期——第 1 期是 2026-08-14 → 2026-09-12,第 2 期是 2026-09-13 → 2026-10-12,依此类推。买下的期付款后 7 天内可下载,过期后需要重新购买。进行中的期买下后,每发布一天就解锁一天,7 天从这一期最后一天发布时起算。每笔购买各算各的,再买别的期不会延长之前的期限。
  • 订阅:最近 60 个已发布日,每发布一天窗口向前移动一天。续费扣款失败时,Stripe 重试期间还能继续使用 1 天;之后窗口关闭,直到扣款成功。
  • 两者都有时,可以下载两者的并集。/v1/access 中的 ranges 就是这个并集。

请求超出访问范围时返回 403 not_entitled,hint 会说明原因(该期未购买或下载期已过、订阅已结束、扣款失败)以及怎么处理。

Python

下载一个日期范围:已有的文件跳过,每个文件都用 SHA-256 校验:

download.py — pip install requests
import hashlib
import os
import pathlib

import requests

BASE = "https://polyticktape.com"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['POLYTICKTAPE_KEY']}"


def download(date_from, date_to, streams="price,twap60", out="data"):
    r = session.get(f"{BASE}/v1/days", params={"from": date_from, "to": date_to, "streams": streams})
    r.raise_for_status()
    listing = r.json()
    files = [day[s] for day in listing["days"] for s in listing["streams"] if s in day]
    for f in files:
        path = pathlib.Path(out) / f["name"]
        if path.exists() and hashlib.sha256(path.read_bytes()).hexdigest() == f["sha256"]:
            continue  # already downloaded; don't spend quota on it again
        body = session.get(f["url"]).content  # follows the redirect to a short-lived signed link
        if hashlib.sha256(body).hexdigest() != f["sha256"]:
            raise ValueError(f"checksum mismatch: {f['name']}")
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_bytes(body)
        print("saved", path)
    for m in listing["missing"]:
        print("missing", m["date"], m["stream"], m["reason"])


download("2026-08-14", "2026-08-20")

用 pandas 读取。一定要传 dtype=False,否则 pandas 会把 p 转成浮点数,精确位数就丢了:

pandas
import pandas as pd

df = pd.read_json("data/btc-twap60-2026-08-14.jsonl.gz", lines=True, dtype=False, convert_dates=False)
df["time"] = pd.to_datetime(df["ts"], unit="s", utc=True)
# ts, lag and the *_ms columns are integers, p is a string, v is a float
polars
import gzip

import polars as pl

with gzip.open("data/btc-twap60-2026-08-14.jsonl.gz") as f:
    df = pl.read_ndjson(f.read())
df = df.with_columns(pl.from_epoch("ts", time_unit="s").dt.replace_time_zone("UTC").alias("time"))
# p stays a String column: exact decimal digits

核对每一局

每局 5 分钟,从一个能被 300 整除的起始秒开始,到下一局的起始秒结束。结束秒的 TWAP60 大于或等于起始秒的 TWAP60 为涨(Up),否则为跌(Down)。每天最后一局在次日 00:00:00 结束,所以需要同时加载第二天的文件。

rounds.py
import gzip
import json
from datetime import datetime, timezone
from decimal import Decimal


def twap60(path):
    with gzip.open(path, "rt") as f:
        return {row["ts"]: Decimal(row["p"]) for row in map(json.loads, f)}


# The last round of a day ends at 00:00 the next day, so load both files.
twap = {**twap60("data/btc-twap60-2026-08-14.jsonl.gz"), **twap60("data/btc-twap60-2026-08-15.jsonl.gz")}

day = int(datetime(2026, 8, 14, tzinfo=timezone.utc).timestamp())
for start in range(day, day + 86_400, 300):
    open_, close = twap.get(start), twap.get(start + 300)
    if open_ is None or close is None:
        print(start, "no TWAP60 row at a boundary second")
        continue
    print(start, "Up" if close >= open_ else "Down", open_, close)

如果采集器在某个边界秒没有 TWAP60 记录,文件里也就没有这一行,脚本会把这一局单独报告出来,而不是去猜。这些局怎么处理(跳过,或者采用 Polymarket 公布的结果)由你决定;文件里不会有任何替代值。

错误

错误以 JSON 返回,包含固定的 code、可读的 message,有时还有 hint:

HTTP/1.1 403 Forbidden

{
  "error": {
    "code": "not_entitled",
    "message": "Your key does not cover 2026-09-20.",
    "hint": "Buy period 2 or subscribe at https://polyticktape.com/pricing/."
  }
}
HTTPcode场景
400bad_request缺少 from、日期格式错误、streams 里有未知的数据流、范围超过 92 天。
401unauthorized没有带 Key,或 Key 不存在。
401key_revokedKey 已被重置。请到账户页复制当前的 Key。
403not_entitled该日期不在你的访问范围内,原因见 hint。
404not_found未知的数据流或路径。
404not_published该日还没有发布(次日 UTC 05:00 前发布)。
410link_expired签名链接已超过 2 分钟、已被下载 10 次,或对应的 Key 已被重置。请重新调用 /v1/days/{date}/{stream}。
429quota_exceeded已达到每日下载次数上限。Retry-After 给出距 UTC 00:00 的秒数。
429rate_limited短时间内请求太多。等待 Retry-After 秒后放慢速度继续。
503downloads_paused下载暂时维护中,请稍后重试。
500internal服务端出错。请稍后重试;持续出现请联系我们。

限制

  • 每个账户每个 UTC 日最多下载 300 次(每天有两个文件)。/v1/access 和 /v1/days 列表不计次数。
  • 签名下载链接 2 分钟后过期,最多可下载 10 次(足够断点续传)。
  • 每把 Key 每 10 秒最多 60 次 /v1 请求。请逐个下载文件,不要并发。
  • /v1/days 每次最多查询 92 天。
  • 每个文件下载一次保存好即可:已发布的文件不会再改动。