全部文章
API 参考Polymarket API调试CLOB

Polymarket API 错误:每个错误码逐一解读

Polymarket 的错误消息很简略,而凌晨 3 点让你的机器人崩溃的,通常恰恰是文档里根本没写的那些。这是一份完整参考:有文档的错误、没文档的错误,以及每一个实际该怎么处理。

阅读约 12 分钟

每个 Polymarket CLOB API 错误返回的格式都一样:一个只有 error 字段的 JSON 对象。这种一致性值得称赞,但当你想弄清楚自己的订单为什么凭空消失时,它毫无帮助。

{
  "error": "<message>"
}

本页负责解读它们。内容包括有文档的 CLOB 错误、状态码及其真实原因,以及官方文档里找不到的部分:人们在生产环境中实际遇到、而 Polymarket 从未写下来的错误。

每条有文档的消息也都有自己的页面,介绍原因和是否应重试,列在 Polymarket 错误索引中。

状态码速查

状态码含义常见原因
400Bad Request参数无效、载荷格式错误、违反业务逻辑
401UnauthorizedAPI 密钥缺失或无效、HMAC 签名错误、时间戳过期
404Not Found市场不存在、订单未找到、代币 ID 无法识别
425Too Early撮合引擎正在重启:带退避重试
429Too Many Requests超出速率限制:指数退避
500Internal Server Error服务器意外错误:带退避重试
503Service Unavailable交易所暂停,或被仅取消 / 仅挂单模式阻止

会误导你的状态码改写

CLOB API 会根据消息内容改写状态码。任何包含 "not found" 的错误消息都返回 404,包含 "unauthorized" 的返回 401,包含 "context canceled" 的返回 400,无论原始状态码是什么。如果某个状态码看起来与消息矛盾,原因就在这里。根据消息文本而不是数字来分支处理错误。

401:认证错误

Unauthorized/Invalid api key

被搜索最多的 Polymarket 错误。你的 API 密钥缺失、过期或无效。实际上它几乎从来不是密钥真的有问题,而是以下情况之一:

  1. 你只发送了部分凭证。Polymarket 的 L2 认证需要 key、secret 和 passphrase 三者一起。常见的失败是设置了 POLY_API_KEY 和 POLY_PASSPHRASE,却没设置 secret:客户端发送了一个格式正确但无法签名的请求。
  2. 你的系统时钟发生了漂移。签名中包含时间戳,时钟偏差超过几秒,产生的签名就会被服务器判定为无效。这是人们最后才找到的原因,而在虚拟机和容器上它常见得令人沮丧。
  3. 你派生密钥所用的钱包与你交易所用的钱包不同。密钥与创建它的签名者地址绑定。
  4. 你在需要 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 / allowancepUSD 余额不足,或者你从未给交易所合约授权额度。两者都是必需的。
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 天免费试用开始。

继续阅读