What running a bot does not solve

A bot fixes speed and consistency. Rate limits, a missing rehearsal, a field that stops arriving, a partial fill and a paused market are not coding problems.

A bot supplies speed and consistency. It inherits everything else from the venue: published rate limits per endpoint class, whether a rehearsal environment exists at all, whether breaking changes arrive with a changelog, what happens to the unfilled half of an order, and what becomes of a resting order when a market is paused, clarified or resolved. None of those is a coding problem. Each is read out of the documentation, or discovered afterwards.

There is a point in most people's first automated strategy where the remaining problem looks like engineering. The analysis is done, the entries are defined, and what is left is that a human cannot sit at a screen at 03:00 and cannot place four orders in the same second. A bot fixes both of those, completely and on the first day.

It fixes nothing else, and the things it does not fix are not in your code. They are in the venue: how often you may ask, whether you can practise, what shape the answers arrive in, how much of an order you actually get, and what a market is permitted to do to an order you have already placed. Every one of those is written down somewhere in a venue's own API documentation, and every one of them is a question a reader can settle in an afternoon before the first live run rather than in the ten minutes after something has gone quiet.

Everything below is read from venue documentation on 21 September 2026 and dated. None of it has been run against a funded account by this site. Four doc sets are used throughout because the point is the spread between them: what one venue publishes, another does not, and the shape of the answer differs even where both publish one.

How it works

Split what a trading process does into two halves. The first half is yours: what to trade, at what price, in what size, on what signal. The second half belongs to the venue, and your code does not get a vote in it. A bot is an improvement to the first half only, and most of the expensive surprises live in the second.

Rate limits are per endpoint class, and a cancel is not an order

The first thing to learn is that "requests per second" is not a unit. It becomes one only once you know what it is counted against, and the four venues read for this page count against four different things.

Polymarket US publishes the most granular table. On its exchange API the limits are per participant firm and per method, with the documentation stating that each method has its own bucket and that "Methods displaying the same limit do not share that allowance". Order entry (InsertOrder) and cancel-replace run from 30 requests per second at Tier 1 to 400 at Tier 4. Plain CancelOrder runs from 90 per second to 1,200 — three times the order-entry allowance at every tier. Batch methods are flat at 50 requests per second across all tiers, carry up to 20 operations per request, and each batch request "counts once toward that method's rate limit, regardless of the number of operations in the request".

Read endpoints on the same API are quoted in requests per minute, not per second, and the numbers are small: ListInstruments and ListSymbols at 6 per minute, GetOrderBook, GetBBO, SearchOrders, SearchExecutions and SearchTrades at 12 per minute each. The retail API is simpler and much flatter — a global 20 requests per second per API key across all endpoints, and 20 per second per IP for public unauthenticated calls.

The tiers are not a plan you buy. They are earned from trailing-30-day contract volume share: Tier 2 at 0.125% to earn and 0.10% to maintain, Tier 3 at 0.50% and 0.225%, Tier 4 at 1.50% and 1.20%. Dropping below the maintain threshold does not cut your limit immediately — the documentation gives 30 days to climb back before a move down. Read that as what it is: the limit your process ran comfortably inside last quarter is a variable, and it moves with your own activity.

Adjacent counts by organisation and by two budgets at once. The free public tier is 50 requests per 10 seconds keyed by client IP at the edge; Pro is 30 requests per minute and 20,000 per day; Premium is 180 per minute and 150,000 per day, with the daily budget resetting at 00:00 UTC. On top of that sits a concurrency cap — four simultaneous requests per organisation, with a request waiting up to two seconds for a slot before it is refused. The sentence worth copying into your notes is the one that closes off the obvious workaround: "Every API key and authenticated session in an organization draws from the same budgets. Creating more keys does not increase throughput."

Manifold publishes one number and one sentence: "There is a rate limit of 500 requests per minute per IP. Please don't use multiple IP addresses to circumvent this limit."

Limitless publishes the mechanism and not the figure. Its API reference states that the API enforces rate limits, that exceeding one returns 429 Too Many Requests, that you should respect a Retry-After header when present and otherwise back off from one second and double, and that high-frequency workloads should queue outgoing orders rather than fire them concurrently. For the actual ceiling it points you at a support address. That is a legitimate design and it has a consequence for you: there is no number to code against, so the only safe posture is a queue with a configurable rate you can turn down.

Four venues, four scopes — per firm per method, per organisation, per IP, per API key. A client library that ships one rate_limit setting is making an assumption on your behalf.

What a retry loop does at the wrong moment

A retry loop is the first thing anybody writes and the last thing anybody reads. Two failure modes recur, and both come from treating the rejection as a single kind of event.

The first is retrying something that will never succeed. Limitless states it directly: "Never retry 400 or 401 — those indicate a bad request or invalid credentials and will not succeed on retry." Adjacent draws the same line between two status codes that a loop written around "if it failed, try again" cannot tell apart: a 429 means a budget ran out and will refill, whereas a request from an organisation with no paid plan is refused up front with 403, and "Unlike a 429, retrying does not help: nothing resets until the organization holds a plan."

The second is more interesting, because it is the inverse error — backing off when backing off is wrong. Polymarket US applies a five-second latency stopgap to inbound orders: an order received but not processed within five seconds is rejected "to protect you from a bad fill at a stale price". The documentation then warns, in its own bold, that these rejects "carry the message Global Rate Limit Exceeded, but they are not an actual rate limit. You do not need to throttle your traffic in response to them. Treat them as a transient latency reject, not a signal to back off."

Read what that does to an ordinary bot. The order never reached the book, so nothing was filled and nothing is resting. The correct response is to re-price against the current book and resubmit. A process that matches on the string "Rate Limit Exceeded" instead sleeps, doubles, sleeps again — and comes back to a market that has moved, having done nothing during exactly the interval it was written to cover. The stopgap applies to new orders and to modifications via cancel/replace; "Pure cancels are not affected — a standalone cancel is never rejected by this stopgap", and "You can always cancel an order before you have received an acknowledgement, and even before it has been processed". Cancelling is the one primitive that stays reachable when everything else is being rejected, which makes it the right thing to build your safety path around.

Limitless adds a third shape to the same lesson: a 425 Too Early is documented as either a receive-window failure or a maintenance block, distinguished by a code in the body, and the two want different handling — "Re-stamp and retry only for receive-window failures; refresh maintenance status for maintenance blocks." One status, two responses, and only the body tells you which.

There is also a cost to getting this wrong that is not measured in missed fills. Polymarket US lists "Automated retry loops without backoff" among the patterns that "may result in temporary or permanent restrictions on your API credentials", alongside polling for data available via streaming and requesting the same unchanged data repeatedly.

The rehearsal you may not have

"I have tested it" means two very different things, and which one it means is decided by the venue rather than by you.

Limitless states the harder case in one paragraph, and it is worth quoting whole because the second sentence is the part that is actually useful: "There is no sandbox, testnet, mock mode, or Base Sepolia deployment — all integrations run against production with real USDC. To rehearse a flow without significant capital, place small live orders (e.g. the minimum order size for a CLOB market) on a low-volume market."

That is a venue telling you plainly that your integration test is a live trade. The advice is sound and it is also an admission of what a live rehearsal cannot reach: it exercises authentication, signing, submission, the response shape and your own reconciliation, and it does not exercise a deep book, your own size moving a price, or any of the rejection paths you will only meet under load.

Polymarket US is the other case. Its exchange API runs two environments — a preprod at api.preprod.polymarketexchange.com and a prod at api.prod.polymarketexchange.com, each with its own auth domain, each exposing the same /v1/health check — and the documented integration progression is preprod first, then prod, with credentials requested per environment from an onboarding address. So a rehearsal exists here, on the institutional surface, by application. Note the detail that will bite a long-running process in either environment: "Tokens must be refreshed every 3 minutes."

Manifold sits between the two, publishing a dev websocket endpoint beside the production one.

The question to settle before you write a line is therefore not "how do I test this" but "what does this venue let me test, and what will I only ever find out live". The answer changes which parts of the strategy you are willing to automate first.

The API you are running against is not the API you read

Three of the four doc sets read for this page carry a dated changelog. That is the good case, and it is still not a promise that nothing moves under you.

Manifold versions by path prefix and is explicit about what lies outside it: there is a /v0 surface, and a set of internal endpoints that "are not preceeded by /v0 and are even more subject to sudden changes than the official API endpoints". Its changelog is short and dated, and it does record removals — the 30 October 2024 entry removes an endpoint outright. Elsewhere a documented endpoint is marked deprecated in favour of a more versatile replacement and left in place, which is the kinder pattern and also the one that lets a process keep running on a surface nobody is maintaining.

Limitless shows deprecation happening inside a field rather than to an endpoint. Its order event frames carry a timestamp that the documentation marks "Deprecated — kept for backward compatibility", whose meaning is now source-dependent, beside three additive semantic timestamps. The guidance is blunt: "Do not substitute one for another", and "any missing semantic timestamp arrives as null rather than a value copied from another field". Nothing was renamed. The name is identical, the field still arrives, and what it means depends on which frame you are looking at.

Adjacent's changelog contains the clearest worked example of how this actually breaks a running process, and it is not a rename either. On 27 August 2026, GET /api/v1/events began returning total and total_pages as null when a search parameter is set, with callers directed to page using has_next instead. The changelog explains why — the old total was bounded by the search's internal candidate window rather than the matched set, so it under-reported broad queries and changed as you paged through the same results — and the change is unambiguously a correction. It is also a field that used to be an integer and is now sometimes nothing. A loop built as a range over total_pages raises on the next run, which is the lucky outcome; one that coerces a missing value to zero iterates zero times, returns an empty result set, and reports success.

The same changelog is full of the more common variant, which is a field that simply stops arriving: latest_price "is omitted when the rate's last print is older than seven days", volume "is omitted when the print did not record it", event volume and open_interest are omitted "when no child market reports a value" while "A measured zero still serializes as 0". Read those three together and you have the actual failure mode: code that indexes the key directly raises, and code that reaches for it defensively with a default of zero turns "nobody has priced this in a week" into "this is worth nothing" — and then trades on it.

So the honest answer to "what happens to my process when a field is renamed" is that a rename is the rare case and the one you notice. The common case is a field that becomes nullable, becomes conditional, or changes what it counts, and that one is silent.

The half of the order that did not fill

Polymarket US documents its fill model in four numbered lines and then works the example, and it is the clearest statement of this in the sector. Every order is processed as a marketable limit order. If enough size is available your order fills entirely; if not, "you receive a partial fill", and "The unfilled portion stays on the order book as an open order until it is filled or canceled". The worked case: buy 1,000 Yes contracts when the best ask is $0.52, and if only 600 are available at that price, "you receive a partial fill for 600 contracts. The remaining 400 stay on the order book at $0.52 as an open order until they are filled or canceled. Your execution price reflects only the portion that filled."

Manifold describes the same outcome on a hybrid book: an order that crosses "would fill partially or completely depending on current unfilled limit bets and the AMM's liquidity. Any remaining portion of the bet not filled would remain to be matched against in the future."

The default on both is that the remainder is a live order you now own. Three branches go wrong, and all three are what a first draft does:

  1. Treat the order as filled. Your own position book now disagrees with the venue's. Anything sized off it — a hedge leg, a stop, a max-exposure check — is sized against a fill that partly did not happen.
  2. Resubmit the whole order. You now have the original remainder and a fresh order resting. The moment liquidity returns you are filled at roughly double the size you intended, at a price you chose for the smaller one.
  3. Forget it. It fills four hours later, at your price, into a market your strategy has since changed its mind about. Nothing failed; nothing logged an error.

On Limitless there is a further step between submission and fill that a synchronous mental model has no room for. Some markets apply a taker delay, and on those the order endpoint "returns right away with execution.settlementStatus: "DELAYED" and an eligibleAt timestamp instead of blocking until settlement". The fill is then observed on the order-event stream as a provisional MATCHED frame followed by a terminal MINED or FAILED, and the documentation is explicit that the timeline is not bounded by eligibleAt: "Maintenance mode can postpone delayed fills beyond eligibleAt; keep the order open in your integration until a terminal event arrives." Maker orders sent postOnly are never delayed, and a market's current delay is readable as settings.takerDelayMs on the market response.

The consequence is worth stating flatly: on those markets a 200 from the order endpoint is not a fill, and "did my order fill" is not a question the submit response answers. It is answered by the stream, correlated back by client order id. The same changelog adds the matching constraint on the immediate-or-cancel types — "FAK and FOK orders can only be cancelled while they wait out a taker delay. On markets without one they complete immediately and cannot be cancelled."

The states your code has no branch for

The fills are the part people think about. The states below are the part that has no branch at all, because the code was written while a market was open and normal.

A scheduled pause. Polymarket US "operates nearly 24/7, with a recurring weekly maintenance window every Thursday from 2:00–4:00 AM ET".

An unscheduled one. From the same page: "Trading may be paused without prior notice to protect market participants or ensure system stability. Service resumes once operational integrity is restored." Nothing about that arrives as an exception in your process. Prices stop moving, the fills stop, and a mean-reversion signal reading a frozen book is reading a number that means something different from what it meant an hour earlier.

A rewording. Polymarket US posts clarifications to a market's rules — additional context that "specif[ies] how the existing rules should be understood, resolve ambiguous language, and ensure markets resolve according to the intended criteria", and which, the page says, "do not change the market question". Two things follow for an automated position. Before a clarification lands, the documentation says "Liquidity may be reduced", "Spreads may widen" and "Volatility can increase when there is uncertainty" — so the thin book your sizing logic just met may be a signal about the rules rather than about the market. And when it lands: "When a clarification is posted, Polymarket US may cancel resting orders so they are not executed under clarified rules."

That last sentence deserves its own line. The exchange can cancel your resting orders, for a reason that is neither an error nor about you, and a reconciliation routine that counts only its own cancels will find a discrepancy it has no explanation for. The right design is to treat the venue's order state as authoritative and your own as a cache.

A resolution you were not watching for. Limitless emits a marketResolved event carrying the winning outcome and a resolutionDate, both to lifecycle subscribers and automatically to anyone already subscribed to that market's room, so there is no separate subscription to remember. What happens to an order still resting at that moment is not stated on that page — which is exactly the sort of gap to settle with the venue before a funded run rather than during one. Who decides that outcome, and against what source, is who decides the outcome.

A resolution that comes back. Manifold documents a POST /unresolve endpoint that "Unresolves a market". Resolution is therefore not necessarily terminal, and a process that closes its books on a resolution event and never reads that market again has no branch for the market reopening underneath it.

A market that ended while its successor trades. Limitless separates a market's instance slug from its stable slug, with a dedicated endpoint to resolve the stable one and an include parameter that returns successor-round data. A recurring market therefore has a new instance each round. A bot holding a hard-coded instance slug keeps fetching a market that has ended, gets a valid 200 every time, sees no new trades, and concludes the market is quiet. There is no error anywhere in that sequence.

The one thing this page does not cover

Authentication, key custody and what signing software can do with your account are a subject of their own and a different set of documents. Where your key lives when software trades for you is that page, and it is the one to read first if the bot you are about to run asks for a private key.

What it costs

Most of what this page describes has no fee attached. The costs that do exist are worth naming with their units, because two of them are priced in something other than money.

  • A rate-limit tier is priced in volume, not in dollars. On Polymarket US's exchange API, Tier 2 is earned at 0.125% of trailing-30-day exchange-wide contract volume and maintained at 0.10%; Tier 4 at 1.50% and 1.20%. You cannot buy the headroom, and if your own volume falls you have 30 days before the limit follows it down.
  • A rehearsal on a venue without one costs a live trade. Limitless's own suggestion is a minimum-size order on a low-volume market, which means the fee schedule applies to your test suite. What a fill is actually charged, in the four incompatible units venues publish it in, is what a trade actually costs.
  • Reading is metered, and the read budgets are what decide your architecture. ListInstruments and ListSymbols at 6 requests per minute and GetOrderBook at 12 make a polling design impossible rather than merely inefficient — and the documentation's own remedy is free: "Streaming connections don't count against the REST rate limit." The catalogue's streaming feeds collection is the set of cards where that option exists.
  • A daily budget is spent, not merely hit. Adjacent's Pro tier is 30 requests per minute and 20,000 per day, organisation-wide, resetting at 00:00 UTC. An agent stuck in a retry loop at nine in the morning does not pause; it spends the rest of the day's allowance, for every process in the organisation, on nothing.
  • A partial fill costs the spread on the remainder, plus whatever a half-sized position did while a hedge leg was sized for a whole one. Why the book was thin in the first place is where liquidity comes from.
  • And a retry loop can cost the credential. Polymarket US reserves temporary or permanent restrictions on API credentials for sustained over-limit traffic and automated retry loops without backoff.

What you can do about it

Each of these is an afternoon at most, and each is cheaper before the first funded run than after it.

Read the venue's rate-limit page before you read the client library's. Write down one word per limit: is it per IP, per API key, per organisation, or per firm per method? That word decides whether a second process doubles your headroom or halves it. Adjacent's documentation answers it explicitly — more keys do not buy more throughput — and most do not.

Budget cancels separately from orders, and build the safety path on cancel. Polymarket US gives cancels three times the order-entry allowance at every tier, exempts standalone cancels from the latency stopgap, and states that you can cancel before you have received an acknowledgement or before the order has been processed. That combination is a design instruction: whatever else is failing, "cancel everything" should be the path that still works.

Branch on the status code and the documented reason field, never on a message string. The Polymarket US latency stopgap is the case that proves it — a reject reading Global Rate Limit Exceeded that the venue tells you not to back off for. Where a venue gives you a structured reason, use it: Limitless returns 409 Conflict with reason: "ORDER_RELEASE_IN_PROGRESS" and retryable: true on a cancel that races a release, and a field called retryable exists so that your loop does not have to guess.

Treat a submit response as an intent and reconcile from the venue's own stream. Subscribe to the order and position feeds, correlate by your own client order id, and let the venue's view of your open orders be authoritative. Every venue read for this page pushes you the same way for the same reason, and on the delayed-settlement markets it is not an optimisation but the only correct reading.

Give the remainder a named branch. Every order path needs an explicit case for "partially filled, remainder resting", and the decision — cancel the rest, leave it, or replace it at a new price — belongs in the strategy, written down, before the first time it happens. The default of having no branch is branch three above: it fills later, at your price, for reasons that have expired.

Rehearse in whatever rehearsal exists, and write down what it does not reach. If the venue runs a preprod, ask for credentials and use it. If it does not, do what Limitless suggests — minimum size, low-volume market, live — and then list explicitly what that did not test: depth, your own size moving the price, and every rejection path that only appears under load. A rehearsal you have characterised is worth more than one you trust.

Subscribe to market lifecycle events and write the four branches. Paused, clarified, resolved, un-resolved. A bot with no branch for a paused market does not stop; it keeps reading a frozen price as though it were a live one. A bot with no branch for a clarification treats an exchange-initiated cancel as a bug in its own reconciliation.

Resolve recurring markets through whatever stable identifier the venue offers, rather than pinning the instance you looked at on the day you wrote the code. A hard-coded slug for a weekly market fails by returning valid, empty, successful responses forever.

Pin a version, and read the changelog on a schedule you actually keep. Where a venue publishes one — Limitless, Adjacent and Manifold all do — put it on a calendar, because the entries that matter to you will not be the ones labelled breaking. They will be the ones that add a null, drop a key when a value is unavailable, or change what a count counts.

Assert on the shape you receive, and fail loudly rather than defaulting. The common failure is not a renamed field, it is a missing one. A default of zero for an absent price turns "not priced recently" into "priced at nothing", and that is a value your strategy will happily act on. Validate the response against the shape you expect, and make an unexpected one stop the process rather than feed it.

Ask the key question separately. What the bot can do with your account, and whether you can take that permission back, is not on this page: where your key lives is. The rest of the maintenance and liveness questions about the clients themselves — whether a library still speaks the venue's current API at all — are on the trading clients and bots page.

And then the part no amount of this fixes. Automation executes a strategy; it does not supply one. Every item above makes a process lose less to mechanics, and none of them make a process profitable, because none of them touch the question of whether the positions were worth taking. A strategy with no edge, run by hand, loses slowly enough to notice. The same strategy automated loses at whatever frequency the rate limits allow, in the small hours, correctly, and exactly as designed.

Tools this bears on

Cards in the catalogue where what is above changes the decision.

  • py-clob-client

    Polymarket's own Python CLOB client - archived, and declared non-functional by its README.

    FreeFree tierOpen source

  • kalshi-python

    Kalshi's own generated Python client - closed-source, and unreleased since September 2025.

    FreeFree tier

  • limitless-sdk

    Limitless Exchange's own async Python SDK - CLOB and NegRisk orders, WebSocket, MIT.

    FreeFree tierOpen source

  • Polymarket US Python SDK

    Official SDK for Polymarket US - installs as polymarket-us, unreleased since January.

    FreeFree tierOpen source

FAQ

Which venues in this catalogue have a sandbox?

It has to be checked per venue, and the honest answers span the whole range. Limitless states plainly that there is no sandbox, testnet or mock mode and that every integration runs against production with real USDC. Polymarket US runs a preprod environment for its exchange API, on separate auth domains, with credentials requested per environment. Manifold publishes a dev websocket endpoint beside the production one.

What should a bot do when it gets a 429?

Read the body, not the message string. Adjacent names the budget that blocked you and sends a Retry-After header; a 403 from Adjacent means the organisation holds no plan and retrying never helps. Polymarket US asks you to stop, wait at least a second and back off exponentially. Limitless says never to retry a 400 or a 401 at all.

Does a 200 response mean my order filled?

Not on every venue. On a Limitless market with a taker delay, the order endpoint returns immediately with a settlement status of DELAYED and an eligibleAt timestamp, and the fill is observed on the order-event stream as a provisional match followed by a terminal mined or failed frame. The documentation asks you to keep the order open in your integration until a terminal event arrives.

Can the exchange cancel my resting orders?

Yes, and not only for a fault of yours. Polymarket US documents that when a clarification is posted to a market's rules it may cancel resting orders so they are not executed under the clarified reading. That is a normal, documented event rather than an error, and a process that assumes an order it placed is still on the book will be wrong about its own position.

Sources

  1. Rate Limits (Retail API) Polymarket US, read
  2. Rate Limits (Trader Guide) Polymarket US, read
  3. Environments Polymarket US, read
  4. Trading Hours Polymarket US, read
  5. Market Clarification Polymarket US, read
  6. Order Types Polymarket US, read
  7. For Developers Limitless Exchange, read
  8. API Reference, Rate limits Limitless Exchange, read
  9. Order Events Limitless Exchange, read
  10. Error Handling & Retry, Python SDK Limitless Exchange, read
  11. Changelog Limitless Exchange, read
  12. Manifold API Manifold, read
  13. Rate limits Adjacent, read
  14. Changelog Adjacent, read

The catalogue next door

This page is background, not a listing. The products it bears on are in Trading Clients, SDKs & Bots, each filled in against the same schema, with the fields to narrow it yourself.

Last updated . Corrected in place: this is a reference page, not a dated post.