Polymarket API Errors: Every Code, Decoded
Polymarket's error messages are terse, and the ones that break your bot at 3am are usually the ones missing from the documentation entirely. Here is the full reference — documented errors, undocumented ones, and what to actually do about each.
Every Polymarket CLOB API error comes back in the same shape — a JSON object with a single error field — which is admirably consistent and completely unhelpful when you are trying to work out why your order just vanished.
{
"error": "<message>"
}This page decodes them. It covers the documented CLOB errors, the status codes and their real causes, and — the part you will not find in the official docs — the errors people actually hit in production that Polymarket has never written down.
Status code quick reference
| Status | Meaning | Common causes |
|---|---|---|
400 | Bad Request | Invalid parameters, malformed payload, business logic violation |
401 | Unauthorized | Missing or invalid API key, bad HMAC signature, expired timestamp |
404 | Not Found | Market doesn't exist, order not found, token ID not recognised |
425 | Too Early | Matching engine is restarting — retry with backoff |
429 | Too Many Requests | Rate limit exceeded — back off exponentially |
500 | Internal Server Error | Unexpected server error — retry with backoff |
503 | Service Unavailable | Exchange paused, or blocked by cancel-only / post-only mode |
401 — authentication errors
Unauthorized/Invalid api key
The single most-searched Polymarket error. Your API key is missing, expired, or invalid. In practice it is almost never a genuinely bad key — it is one of these:
- You are sending only part of the credential set. Polymarket L2 auth needs the key, the secret and the passphrase together. A common failure is populating
POLY_API_KEYandPOLY_PASSPHRASEbut leaving the secret unset — the client sends a well-formed but unsignable request. - Your system clock has drifted. The signature includes a timestamp, and a clock more than a few seconds off produces a signature the server rejects as invalid. This is the cause people find last and it is depressingly common on VMs and containers.
- You derived the key against a different wallet than you are trading from. Keys are bound to the signer address that created them.
- You are hitting an authenticated endpoint with L1 headers where L2 is expected, or vice versa.
Invalid L1 Request headers
Your L1 authentication headers — the EIP-712 signature set — are malformed, or the signature does not match the address you claim to be. Check that you are signing with the same private key as the address in the header, and that your domain separator matches the chain you are on (Polygon, chain ID 137). A signature valid for the wrong chain ID is still a valid signature — just not for this server.
Could not create api key / Could not derive api key!
These are the errors behind the search "polymarket unable to generate api credentials". Both are returned from the auth endpoints when key creation or derivation fails. Could not derive api key! on GET /auth/derive-api-key usually means no key exists yet for that signer — derive returns an existing key, it does not create one. Create the key first with POST /auth/api-key, then derive it on subsequent runs.
400 — order rejection errors
These are returned when an order passes transport validation but fails business rules. They appear in the response body of POST /order and POST /orders.
| Error message | What it means |
|---|---|
the order signer address has to be the address of the API KEY | The key signing the order is not the key the order says signed it. You are mixing credentials — usually a stale key from a previous wallet. |
the order owner has to be the owner of the API KEY | The maker address in the order does not match the address the API key belongs to. |
order {id} is invalid. Price ({price}) breaks minimum tick size rule: {tick} | Your price is not on the market's tick grid. Call GET /tick-size and round to it — do not assume 0.01. |
order {id} is invalid. Size ({size}) lower than the minimum: {min} | Order size below the market minimum. |
not enough balance / allowance | Insufficient pUSD balance, or you never granted the exchange contract an allowance. Both are required. |
invalid post-only order: order crosses book | Your post-only order would take liquidity immediately. Reprice so it rests. |
order couldn't be fully filled. FOK orders are fully filled or killed. | Fill-or-Kill found insufficient liquidity. The whole order is rejected. |
no orders found to match with FAK order. | Fill-and-Kill found no match at all. At least one match is required. |
invalid expiration | Expiration timestamp is in the past or malformed. |
order {id} is invalid. Duplicated. | The same order was submitted twice. |
order canceled in the CTF exchange contract | The order was canceled on-chain. |
'{address}' address banned | The address is banned from trading. |
'{address}' address in closed only mode | The address may only close existing positions. |
425 — Too Early
An unusual status code that confuses people because it is so rarely used elsewhere on the web. On Polymarket it means the matching engine is restarting. Your request arrived during the window where the exchange is coming back up.
This one is genuinely transient and genuinely safe to retry — it is arguably the most retry-appropriate error on the platform. Back off and try again; do not treat it as a failure and do not alert on a single occurrence.
503 — the exchange is in a restricted mode
Polymarket can put the exchange into restricted modes. These are not failures on your side and no amount of retrying the same request will help until the mode lifts.
| Message | What you can still do |
|---|---|
Trading is currently disabled. Check polymarket.com for updates | Nothing. No orders accepted, including cancels. |
Trading is currently cancel-only. New orders are not accepted, but cancels are allowed. | Cancel existing orders. You cannot open new ones. |
post-only mode: only post-only orders and cancels are allowed | Cancel, or place orders with postOnly: true. Taking orders are rejected. |
Post-only mode is the one to handle properly, because it tells you exactly when to come back. The response includes code: "post_only_mode" and retry_after_seconds, and the same delay is sent in the Retry-After header.
{
"error": "post-only mode: only post-only orders and cancels are allowed",
"code": "post_only_mode",
"retry_after_seconds": 79
}Errors that are not in the documentation
This is where most real debugging time goes. The following do not appear in Polymarket's error code reference because they do not originate from the CLOB application at all — they come from the edge, the network, or your client library. Knowing which layer produced an error tells you where to look.
"The server was unable to process your request"
The most-searched Polymarket error that is not in the docs. This is an edge-layer message rather than a CLOB API error — it is what you get when the request never reached, or never cleanly returned from, the application. In practice the usual causes are:
- A malformed request the edge rejects before routing — an oversized payload, a bad header, or an invalid HTTP method for the route.
- A transient upstream failure while the service is redeploying or under load.
- Sustained rate limiting. Because Cloudflare throttles by queueing rather than rejecting, a badly over-limit client can end up with requests that time out at the edge instead of returning a clean
429.
Treat it as retryable with backoff, but if it is persistent rather than occasional, check your request rate first — see the rate limits reference — and then your payload size.
403 Forbidden
A 403 from Polymarket is usually not an authentication problem, which is what makes it confusing. It is generally the edge blocking the request. The common triggers are geographic restrictions on trading endpoints, datacenter and VPN IP ranges being challenged, and missing or non-browser-like headers on requests that the edge expects to come from a client.
If a 403 appears only from your cloud VM but not from your laptop, it is an IP reputation issue, not a code issue. If it appears on trading endpoints but not on market data endpoints, it is geographic.
SSL, TLS and certificate errors
"polymarket ssl error", "polymarket certificate error" and "polymarket https error" are almost always client-side. Polymarket's certificates are fine; something between you and them is not. Check, in order: an outdated CA bundle (very common in old Docker images and frozen Python environments), a corporate TLS-inspecting proxy substituting its own certificate, and a system clock so far off that certificates appear not-yet-valid or expired.
# 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"
A startup-state message: the service is up enough to answer but not ready to serve. Functionally treat it like 425 — back off and retry. It should clear in seconds, and it is not something you can fix from the client side.
Client library exceptions
Errors like PolyApiException[status_code=None, error_message=request exception!] come from the Python client, not the server. status_code=None is the tell: no HTTP response was ever received, so this is a connection-level failure — DNS, TLS, timeout, or a dropped socket — surfacing as a library exception. Debug it as a network problem, not an API problem. The same reasoning applies to BuilderApiKeyCreds invalid authorization 401 in the relayer client, which means the builder credentials passed to the client were not accepted; re-derive them rather than reusing a cached set.
A retry policy that handles all of this
The practical takeaway is that Polymarket errors fall into three buckets, and conflating them is what makes bots fragile: retry blindly, retry after a specific delay, and never retry.
| Category | Codes / messages | Action |
|---|---|---|
| Transient | 425, 500, Service not ready, connection errors | Retry with exponential backoff + jitter |
| Rate limited | 429 | Retry after Retry-After; if absent, backoff with jitter |
| Mode-restricted | 503 post-only | Sleep retry_after_seconds, then retry |
| Mode-restricted | 503 cancel-only / disabled | Stop opening; cancel if permitted; poll slowly |
| Terminal | 400 validation, 401, 404 | Do not retry — fix the request or the credentials |
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}` };
}Errors you cannot debug your way out of
Worth naming the limits of all this. Some things that look like errors are simply not supported: there is no historical order book endpoint that returns 500s because you called it wrong — it does not exist. Resolved markets return 404 not because your token ID is malformed but because they are gone from the public feeds.
If you are hitting those walls, you are not debugging an integration — you are missing a data source. PolyTest records point-in-time snapshots of Polymarket crypto Up/Down markets as they run, with full order book depth, so the history is there after resolution. Our own error reference covers the PolyTest API's error shapes, which are deliberately boring by comparison.
Frequently asked questions
- What does "Unauthorized/Invalid api key" mean on Polymarket?
- It means your API key is missing, expired, or invalid — but in practice the cause is usually one of four things: you are sending an incomplete credential set (the key, secret and passphrase are all required), your system clock has drifted enough to invalidate the request signature, you derived the key against a different wallet than you are trading from, or you are sending L1 headers where L2 headers are expected.
- What is Polymarket error 425?
- HTTP 425 Too Early means Polymarket's matching engine is restarting and your request arrived during that window. It is transient and safe to retry with exponential backoff. A single 425 is not worth alerting on.
- Why does Polymarket say "The server was unable to process your request"?
- This is an edge-layer message rather than a documented CLOB API error, meaning the request never cleanly reached or returned from the application. The usual causes are a malformed request rejected before routing, a transient upstream failure, or sustained rate limiting — because Polymarket throttles by queueing rather than rejecting, a badly over-limit client can see edge timeouts instead of clean 429s.
- Why am I getting a 403 from the Polymarket API?
- A 403 from Polymarket is usually the edge blocking the request rather than an authentication failure. Common triggers are geographic restrictions on trading endpoints, datacenter or VPN IP ranges being challenged, and missing browser-like headers. If it happens from a cloud VM but not your laptop, it is IP reputation; if it happens on trading endpoints but not market data endpoints, it is geographic.
- How do I fix "the order signer address has to be the address of the API KEY"?
- The key signing your order does not match the key the order claims was used. This means you are mixing credentials — typically a stale API key cached from a previous wallet. Re-derive your API credentials for the wallet you are actually trading from and make sure the maker address in the order matches the address that owns the key.
- Should I retry Polymarket API errors automatically?
- Only some. Retry 425, 429, 500-series and connection errors with exponential backoff plus jitter. Retry 503 post-only mode after the retry_after_seconds value the response provides. Never retry 400 validation errors, 401 auth errors or 404s — those need a fixed request or fixed credentials, and retrying just burns rate limit budget.
Get the historical data Polymarket does not keep
PolyTest records Polymarket crypto Up/Down markets as they run — 90M+ snapshots with 8 levels of order book depth, sub-second timestamps, and resolved markets preserved. Free tier, no card.
Keep reading
Polymarket API Rate Limits: The Complete Reference
Polymarket publishes its rate limits across several pages, in two completely different systems, using a 10-second window almost nobody expects. This is all of it in one table set — plus what actually happens when you cross a limit.
How to Build a Polymarket Trading Bot
Most Polymarket bot tutorials are built on SDKs that no longer exist. This is the current path end to end — what to install in 2026, how auth actually works, where the data gaps are, and the failure modes that kill bots in week one.
The Polymarket Python SDK in 2026 (and Migrating off py-clob-client)
If your code still imports ClobClient from py_clob_client, it is built on a superseded package — and so is most of the Polymarket code an AI assistant will write for you. Here is the current SDK and how to move.