Polymarket API 错误:每个错误码逐一解读
Polymarket 的错误消息很简略,而凌晨 3 点让你的机器人崩溃的,通常恰恰是文档里根本没写的那些。这是一份完整参考:有文档的错误、没文档的错误,以及每一个实际该怎么处理。
每个 Polymarket CLOB API 错误返回的格式都一样:一个只有 error 字段的 JSON 对象。这种一致性值得称赞,但当你想弄清楚自己的订单为什么凭空消失时,它毫无帮助。
{
"error": "<message>"
}本页负责解读它们。内容包括有文档的 CLOB 错误、状态码及其真实原因,以及官方文档里找不到的部分:人们在生产环境中实际遇到、而 Polymarket 从未写下来的错误。
每条有文档的消息也都有自己的页面,介绍原因和是否应重试,列在 Polymarket 错误索引中。
状态码速查
| 状态码 | 含义 | 常见原因 |
|---|---|---|
400 | Bad Request | 参数无效、载荷格式错误、违反业务逻辑 |
401 | Unauthorized | API 密钥缺失或无效、HMAC 签名错误、时间戳过期 |
404 | Not Found | 市场不存在、订单未找到、代币 ID 无法识别 |
425 | Too Early | 撮合引擎正在重启:带退避重试 |
429 | Too Many Requests | 超出速率限制:指数退避 |
500 | Internal Server Error | 服务器意外错误:带退避重试 |
503 | Service Unavailable | 交易所暂停,或被仅取消 / 仅挂单模式阻止 |
会误导你的状态码改写
CLOB API 会根据消息内容改写状态码。任何包含 "not found" 的错误消息都返回 404,包含 "unauthorized" 的返回 401,包含 "context canceled" 的返回 400,无论原始状态码是什么。如果某个状态码看起来与消息矛盾,原因就在这里。根据消息文本而不是数字来分支处理错误。
401:认证错误
Unauthorized/Invalid api key
被搜索最多的 Polymarket 错误。你的 API 密钥缺失、过期或无效。实际上它几乎从来不是密钥真的有问题,而是以下情况之一:
- 你只发送了部分凭证。Polymarket 的 L2 认证需要 key、secret 和 passphrase 三者一起。常见的失败是设置了
POLY_API_KEY和POLY_PASSPHRASE,却没设置 secret:客户端发送了一个格式正确但无法签名的请求。 - 你的系统时钟发生了漂移。签名中包含时间戳,时钟偏差超过几秒,产生的签名就会被服务器判定为无效。这是人们最后才找到的原因,而在虚拟机和容器上它常见得令人沮丧。
- 你派生密钥所用的钱包与你交易所用的钱包不同。密钥与创建它的签名者地址绑定。
- 你在需要 L2 的认证端点上使用了 L1 请求头,或者反过来。
Invalid L1 Request headers
你的 L1 认证请求头(EIP-712 签名组)格式错误,或者签名与你声称的地址不匹配。检查你签名用的私钥是否与请求头中的地址一致,以及你的域分隔符是否与所在的链匹配(Polygon,链 ID 137)。对错误链 ID 有效的签名仍然是有效签名,只是对这个服务器无效。
Could not create api key / Could not derive api key!
这些就是“polymarket unable to generate api credentials”这类搜索背后的错误。两者都是在创建或派生密钥失败时由认证端点返回的。GET /auth/derive-api-key 返回 Could not derive api key! 通常意味着该签名者还没有密钥:derive 返回的是已有的密钥,它不会创建新密钥。先用 POST /auth/api-key 创建密钥,之后运行时再派生。
先派生再创建的模式
大多数生产环境的机器人不存储密钥,而是先尝试 derive,失败后再回退到 create。这在多次重启之间是幂等的,并能避免积累孤立的密钥。认证端点的限制是每 10 秒 100 次请求,所以你本来也不希望每次启动都创建一个密钥。
400:订单拒绝错误
当一个订单通过了传输层校验但违反了业务规则时,会返回这些错误。它们出现在 POST /order 和 POST /orders 的响应主体中。
| 错误消息 | 含义 |
|---|---|
the order signer address has to be the address of the API KEY | 签署订单的密钥与订单声称的签署密钥不一致。你在混用凭证,通常是之前某个钱包留下的过时密钥。 |
the order owner has to be the owner of the API KEY | 订单中的 maker 地址与 API 密钥所属的地址不匹配。 |
order {id} is invalid. Price ({price}) breaks minimum tick size rule: {tick} | 你的价格不在市场的价格网格上。调用 GET /tick-size 并按它取整,不要假设是 0.01。 |
order {id} is invalid. Size ({size}) lower than the minimum: {min} | 订单数量低于市场最小下单量。 |
not enough balance / allowance | pUSD 余额不足,或者你从未给交易所合约授权额度。两者都是必需的。 |
invalid post-only order: order crosses book | 你的仅挂单订单会立即吃掉流动性。重新定价,让它挂在订单簿上。 |
order couldn't be fully filled. FOK orders are fully filled or killed. | 全部成交否则取消(FOK)订单发现流动性不足。整笔订单被拒绝。 |
no orders found to match with FAK order. | 立即成交并取消剩余(FAK)订单完全没有找到可匹配的订单。至少需要一次匹配。 |
invalid expiration | 过期时间戳已过去或格式错误。 |
order {id} is invalid. Duplicated. | 同一订单被提交了两次。 |
order canceled in the CTF exchange contract | 订单已在链上被取消。 |
'{address}' address banned | 该地址被禁止交易。 |
'{address}' address in closed only mode | 该地址只能平掉现有仓位。 |
tick size 按市场设置,而且会变
最常见的静默订单拒绝就是违反 tick size。tick size 因市场而异,并且可能随市场成熟而收紧:上周有效的价格,今天未必有效。按市场获取 GET /tick-size 并据此取整,而不是硬编码一个价格网格。
批量订单的失败方式不同
在 POST /orders 中,单笔订单的失败会以单独的 errorMsg 字段返回在 200 响应数组内部,而不是作为顶层错误。200 并不意味着你批量中的每笔订单都被接受了。遍历数组并检查每一项。
425:Too Early
这是一个不常见的状态码,因为在网络上的其他地方很少用到,所以容易让人困惑。在 Polymarket 上,它意味着撮合引擎正在重启。你的请求恰好在交易所恢复的窗口期到达。
这个错误是真正暂时性的,也真正可以安全重试,可以说是平台上最适合重试的错误。退避后再试;不要把它当作失败,也不要对单次出现发出告警。
503:交易所处于受限模式
Polymarket 可以让交易所进入受限模式。这些不是你这一侧的故障,在模式解除之前,重试多少次相同的请求都没用。
| 消息 | 你还能做什么 |
|---|---|
Trading is currently disabled. Check polymarket.com for updates | 什么都做不了。不接受任何订单,包括撤单。 |
Trading is currently cancel-only. New orders are not accepted, but cancels are allowed. | 撤销现有订单。不能开新单。 |
post-only mode: only post-only orders and cancels are allowed | 撤单,或下 postOnly: true 的订单。吃单会被拒绝。 |
仅挂单模式是需要妥善处理的那一个,因为它会准确告诉你什么时候再来。响应中包含 code: "post_only_mode" 和 retry_after_seconds,同样的延迟也会在 Retry-After 响应头中发送。
{
"error": "post-only mode: only post-only orders and cancels are allowed",
"code": "post_only_mode",
"retry_after_seconds": 79
}仅取消模式可能困住做市机器人
如果你的机器人的关闭流程是先重新报价再撤单,仅取消模式会拒绝重新报价,你的机器人可能会陷入循环而不是平仓。处理 503 时先撤单,然后再决定是否重新入场。
文档里没有的错误
大部分真实的调试时间都花在这里。下面这些没有出现在 Polymarket 的错误码参考中,因为它们根本不是来自 CLOB 应用:它们来自边缘层、网络或你的客户端库。知道是哪一层产生了错误,你就知道该去哪里找。
“The server was unable to process your request”
文档中没有、却被搜索最多的 Polymarket 错误。这是一条边缘层消息,而不是 CLOB API 错误:当请求从未到达应用,或者从未从应用干净地返回时,你就会收到它。实际中常见的原因是:
- 边缘层在路由之前就拒绝了格式错误的请求:载荷过大、请求头有误,或者该路由不支持的 HTTP 方法。
- 服务重新部署或负载较高时的暂时性上游故障。
- 持续被限流。由于 Cloudflare 是通过排队而不是拒绝来限流的,严重超出限制的客户端,请求可能会在边缘层超时,而不是返回一个干净的
429。
把它当作可带退避重试的错误,但如果它是持续出现而不是偶尔出现,先检查你的请求速率(见速率限制参考),再检查你的载荷大小。
403 Forbidden
来自 Polymarket 的 403 通常不是认证问题,这正是它让人困惑的地方。它一般是边缘层在拦截请求。常见的触发原因包括:交易端点的地域限制、数据中心和 VPN 的 IP 段被质询,以及边缘层期望来自客户端的请求缺少请求头或请求头不像浏览器。
如果 403 只在你的云虚拟机上出现,而在你的笔记本上不出现,那是 IP 信誉问题,而不是代码问题。如果它出现在交易端点而不出现在市场数据端点,那是地域问题。
SSL、TLS 和证书错误
“polymarket ssl error”、“polymarket certificate error” 和 “polymarket https error” 几乎总是客户端的问题。Polymarket 的证书没有问题;有问题的是你和它们之间的某个环节。按顺序检查:过时的 CA 证书包(在旧的 Docker 镜像和冻结的 Python 环境中非常常见)、替换成自己证书的企业 TLS 检查代理,以及偏差大到让证书看起来尚未生效或已过期的系统时钟。
# 1. Does TLS work at all, outside your app's HTTP client?
curl -sS -o /dev/null -w "%{http_code} %{ssl_verify_result}\n" \
https://clob.polymarket.com/ok
# 2. Is your CA bundle current? (Python)
python3 -c "import certifi, os; print(certifi.where()); print(os.path.getmtime(certifi.where()))"
# 3. Is your clock right? Certificate validation depends on it.
date -u“Service not ready”
一条启动状态消息:服务已经能应答,但还不能提供服务。在功能上把它当作 425 处理:退避并重试。它应该在几秒内消失,而且你无法从客户端修复它。
“Order manager not ready”
一条来自撮合引擎的启动状态消息,与 425 Too Early 是近亲。服务可以访问,但订单管理层还没有完全启动,所以它会接受你的连接,却拒绝你的订单。它是暂时性的,可以安全地带退避重试;但要把它当作暂停提交几秒的信号,而不是猛烈重试,因为重启通常意味着订单簿即将在你脚下变化。
如果它持续出现而不是阵发性出现,在调试你自己的代码之前先查看 Polymarket 的状态:客户端没有什么可修的。
“Couldn't get a quote”
通常意味着你请求定价的那一方没有挂单流动性,而不是有什么东西坏了。在临近到期、流动性很薄的 Up/Down 市场上,一方经常会被清空:一旦 Up 已成定局,就没人会以任何价格卖出 Up,所以报价请求什么都返回不了。我们自己的数据表明这种情况很常见:在一组 BTC 5 分钟市场样本中,8,510 个中有 1,454 个在到期前 60 秒有一方的卖单簿为空。
把它当作“没有市场”而不是错误来处理:跳过这笔交易,不要在紧密循环中重试。如果你需要知道你交易的某个系列中这种情况发生得有多频繁,市场数据页面按时间周期展示了流动性。
客户端库异常
像 PolyApiException[status_code=None, error_message=request exception!] 这样的错误来自 Python 客户端,而不是服务器。status_code=None 是关键线索:从未收到任何 HTTP 响应,所以这是连接层面的失败(DNS、TLS、超时或 socket 断开),以库异常的形式表现出来。把它当作网络问题来调试,而不是 API 问题。同样的思路也适用于 relayer 客户端中的 BuilderApiKeyCreds invalid authorization 401,它意味着传给客户端的 builder 凭证没有被接受;请重新派生,而不是复用缓存的那一套。
一套能处理以上所有情况的重试策略
实际的结论是,Polymarket 的错误分为三类,而把它们混为一谈正是机器人脆弱的原因:直接重试、在特定延迟后重试,以及永不重试。
| 类别 | 状态码 / 消息 | 处理方式 |
|---|---|---|
| 暂时性 | 425、500、Service not ready、连接错误 | 指数退避 + 随机抖动后重试 |
| 被限流 | 429 | 按 Retry-After 重试;如果没有,带随机抖动退避 |
| 模式受限 | 503 仅挂单 | 休眠 retry_after_seconds,然后重试 |
| 模式受限 | 503 仅取消 / 已禁用 | 停止开仓;如允许则撤单;缓慢轮询 |
| 终止性 | 400 校验错误、401、404 | 不要重试:修正请求或凭证 |
type Decision =
| { retry: true; delayMs: number }
| { retry: false; reason: string };
function classify(status: number, body: { error?: string; code?: string; retry_after_seconds?: number }, attempt: number): Decision {
const msg = (body.error ?? "").toLowerCase();
const backoff = Math.min(2 ** attempt * 250, 8000);
const jittered = Math.random() * backoff;
// Never retry — the request itself is wrong.
if (status === 401) return { retry: false, reason: "auth: check key, secret, passphrase and clock drift" };
if (status === 404) return { retry: false, reason: "not found" };
if (status === 400) return { retry: false, reason: `validation: ${body.error}` };
// Mode-restricted — the server tells you when to come back.
if (status === 503 && body.code === "post_only_mode") {
return { retry: true, delayMs: (body.retry_after_seconds ?? 60) * 1000 };
}
if (status === 503) return { retry: false, reason: "exchange paused or cancel-only" };
// Transient — safe to retry.
if (status === 425 || status === 429 || status >= 500) return { retry: true, delayMs: jittered };
if (msg.includes("service not ready")) return { retry: true, delayMs: jittered };
return { retry: false, reason: `unhandled status ${status}` };
}记录消息,按类别告警
按原始错误字符串告警只会产生噪音,因为 Polymarket 的消息中嵌入了订单 ID 和价格。按类别告警:终止性 400 激增说明你发布了一个 bug;429 激增说明你需要放慢速度;425 激增说明 Polymarket 正在重启,你什么都不用做。
靠调试解决不了的错误
有些看起来像错误的东西,其实只是不受支持。Polymarket 没有历史订单簿端点,所以根本不存在调用方式错误的问题。已结算市场的订单簿返回 404,是因为市场结算后 Polymarket 就不再提供它,即使你的代币 ID 完全正确。
如果你撞上了这些墙,你不是在调试一个集成,而是缺少一个数据源。PolyTest 在 Polymarket 加密货币 Up/Down 市场运行期间记录时点快照,带完整订单簿深度,所以结算后历史数据依然存在。我们自己的错误参考介绍了 PolyTest API 的错误格式,相比之下它们刻意设计得很无聊。
常见问题
- Polymarket 上的 "Unauthorized/Invalid api key" 是什么意思?
- 它表示你的 API 密钥缺失、过期或无效,但实际原因通常是以下四种之一:你发送的凭证不完整(key、secret 和 passphrase 都是必需的);你的系统时钟漂移到足以让请求签名失效;你派生密钥所用的钱包与你交易所用的钱包不同;或者你在需要 L2 请求头的地方发送了 L1 请求头。
- Polymarket 错误 425 是什么?
- HTTP 425 Too Early 表示 Polymarket 的撮合引擎正在重启,而你的请求恰好在那个窗口期到达。它是暂时性的,可以安全地用指数退避重试。单次出现的 425 不值得告警。
- 为什么 Polymarket 提示 "The server was unable to process your request"?
- 这是一条边缘层消息,而不是有文档的 CLOB API 错误,意味着请求从未干净地到达应用或从应用返回。常见原因是在路由前被拒绝的格式错误请求、暂时性的上游故障,或者持续被限流:由于 Polymarket 是通过排队而不是拒绝来限流的,严重超限的客户端看到的可能是边缘层超时,而不是干净的 429。
- 为什么我从 Polymarket API 收到 403?
- 来自 Polymarket 的 403 通常是边缘层在拦截请求,而不是认证失败。常见触发原因包括交易端点的地域限制、数据中心或 VPN 的 IP 段被质询,以及缺少类似浏览器的请求头。如果它在云虚拟机上出现而在你的笔记本上不出现,那是 IP 信誉问题;如果它出现在交易端点而不出现在市场数据端点,那是地域问题。
- 如何修复 "the order signer address has to be the address of the API KEY"?
- 签署你订单的密钥与订单声称使用的密钥不一致。这意味着你在混用凭证,通常是之前某个钱包缓存下来的过时 API 密钥。为你实际交易所用的钱包重新派生 API 凭证,并确保订单中的 maker 地址与拥有该密钥的地址一致。
- Polymarket 上的 "order manager not ready" 是什么意思?
- 它是一条来自撮合引擎的启动状态消息:服务可以访问,但订单管理层还没有完全启动,所以它接受连接却拒绝订单。它是暂时性的,与 HTTP 425 Too Early 密切相关。带退避重试,但要暂停提交几秒而不是猛烈重试,因为重启通常意味着订单簿即将变化。如果持续出现,那是 Polymarket 一侧的问题,而不是客户端 bug。
- 为什么 Polymarket 提示无法获取报价(couldn't get a quote)?
- 几乎总是因为你请求定价的那一方没有挂单流动性,而不是有什么东西坏了。在临近到期、流动性很薄的 Up/Down 市场上,一方经常会被清空:一旦 Up 已成定局,就没人再卖 Up。在一组 BTC 5 分钟市场样本中,8,510 个中有 1,454 个在到期前 60 秒有一方的卖单簿为空。把它当作“没有市场”,跳过这笔交易,而不是循环重试。
- 应该自动重试 Polymarket API 错误吗?
- 只重试一部分。对 425、429、500 系列和连接错误,使用指数退避加随机抖动重试。对 503 仅挂单模式,按响应提供的 retry_after_seconds 值等待后重试。永远不要重试 400 校验错误、401 认证错误或 404:它们需要修正请求或凭证,重试只会浪费速率限制额度。
获取 Polymarket 不保留的历史数据
PolyTest 在 Polymarket 加密货币 Up/Down 市场运行期间实时记录:240M+ 条快照,包含 8 档订单簿深度、亚秒级时间戳,并保留已结算的市场。从 3 天免费试用开始。
继续阅读
Polymarket API 速率限制:完整参考
Polymarket 把它的速率限制分散在好几个页面上,分属两套完全不同的系统,而且使用的是几乎没人预料到的 10 秒窗口。这里把它们全部整理成一组表格,并说明超出限制时实际会发生什么。
如何构建 Polymarket 交易机器人
大多数 Polymarket 机器人教程都基于已经不存在的 SDK。这是一条最新的端到端路径:2026 年该安装什么、认证实际如何运作、数据缺口在哪里,以及在第一周就让机器人挂掉的失效模式。
2026 年的 Polymarket Python SDK(以及如何迁移出 py-clob-client)
如果你的代码还在从 py_clob_client 导入 ClobClient,那它就建立在一个已被取代的包之上,AI 助手为你写的大部分 Polymarket 代码也是如此。下面介绍当前的 SDK 以及如何迁移。