接口文档
两类资源:/v1/access 告诉你 Key 能下载什么;/v1/days 列出日期并给出每条流的文件。全部是带 API Key 的普通 HTTPS
请求;文件是 gzip 压缩的 JSON Lines,每条数据流每个 UTC 日一个文件。
快速开始
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 天。 |
streams | price、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 | 采集端收到的时间(毫秒) |
{"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 校验:
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 转成浮点数,精确位数就丢了:
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 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 结束,所以需要同时加载第二天的文件。
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/."
}
} | HTTP | code | 场景 |
|---|---|---|
| 400 | bad_request | 缺少 from、日期格式错误、streams 里有未知的数据流、范围超过 92 天。 |
| 401 | unauthorized | 没有带 Key,或 Key 不存在。 |
| 401 | key_revoked | Key 已被重置。请到账户页复制当前的 Key。 |
| 403 | not_entitled | 该日期不在你的访问范围内,原因见 hint。 |
| 404 | not_found | 未知的数据流或路径。 |
| 404 | not_published | 该日还没有发布(次日 UTC 05:00 前发布)。 |
| 410 | link_expired | 签名链接已超过 2 分钟、已被下载 10 次,或对应的 Key 已被重置。请重新调用 /v1/days/{date}/{stream}。 |
| 429 | quota_exceeded | 已达到每日下载次数上限。Retry-After 给出距 UTC 00:00 的秒数。 |
| 429 | rate_limited | 短时间内请求太多。等待 Retry-After 秒后放慢速度继续。 |
| 503 | downloads_paused | 下载暂时维护中,请稍后重试。 |
| 500 | internal | 服务端出错。请稍后重试;持续出现请联系我们。 |
限制
- 每个账户每个 UTC 日最多下载 300 次(每天有两个文件)。
/v1/access和/v1/days列表不计次数。 - 签名下载链接 2 分钟后过期,最多可下载 10 次(足够断点续传)。
- 每把 Key 每 10 秒最多 60 次
/v1请求。请逐个下载文件,不要并发。 /v1/days每次最多查询 92 天。- 每个文件下载一次保存好即可:已发布的文件不会再改动。