PASS WITH FIXESTested on Claude Code (Claude Sonnet 5.5), , result: PASS WITH FIXES.We built this bot from the prompt and ran it in paper mode against live public Polymarket US data (41 tests passing). Preview, live orders and signing were mocked with a fake HTTP session: no real keys, orders or money. The prompt below includes the fixes from that run.
Short answer:You can build a Polymarket US bot with Claude Code by having it write a bot that runs in paper mode by default: it simulates fills locally against the live public order book and can validate orders with POST /v1/order/preview without placing them. Polymarket US has no public demo exchange, so a local paper ledger, hard position limits, a kill switch and a locked live mode are the safe way to start.
Polymarket US has no public demo exchange for retail API users, so paper mode here means: simulate fills locally against the live public order book, and optionally call POST /v1/order/preview (body wrapped as {"request": order}) to validate an order without placing it.
Market data comes from https://gateway.polymarket.us with no key. Live orders go to POST https://api.polymarket.us/v1/orders.
Order fields: marketSlug, intent (ORDER_INTENT_BUY_LONG, ORDER_INTENT_SELL_LONG, ORDER_INTENT_BUY_SHORT, ORDER_INTENT_SELL_SHORT), type (ORDER_TYPE_LIMIT/ORDER_TYPE_MARKET), price: {value, currency: "USD"}, quantity, tif (e.g. TIME_IN_FORCE_IMMEDIATE_OR_CANCEL), and manualOrderIndicator, which the docs say is required (MANUAL_ORDER_INDICATOR_AUTOMATIC for a bot).
There's no client order id on the order API, so idempotency has to be local: record each order before sending and never auto-retry POST /v1/orders after a timeout.
price.value always refers to the YES (long) side, even when buying NO, and must be between 0.01 and 0.99. An out-of-range order still gets an order ID but never fills.
Read each market's orderPriceMinTickSize and minimumTradeQty before ordering. Don't assume them.
Cancel everything: POST /v1/orders/open/cancel.
DifficultyIntermediate
Time2–3 hours
Cost$0 API · your AI tool subscription
Works withClaude Code, Cursor, ChatGPT / Codex
What will you build?
A Polymarket US bot skeleton that watches markets you choose, runs your rule, and records simulated fills in a local paper ledger. A live mode exists but is locked behind a flag, a typed confirmation and hard limits.
Who is it for?
Builders who want a real bot structure (data → rule → risk check → order) without risking money on day one.
Not for:Anyone looking for a strategy that makes money. We don't provide one, and nobody honest can promise one.
What prompt do you paste into your AI coding tool?
Works in Claude Code, Cursor, Codex/ChatGPT, Grok or any agent that can write files. Paste it into an empty folder. Read what it builds before you run anything.
Build a Polymarket US trading bot in Python 3.11+ (requests, cryptography,
pytest only) that runs in PAPER MODE by default. Use only endpoints documented
at https://docs.polymarket.us. If you are unsure an endpoint or field exists,
ask me instead of guessing.
Read CLAUDE.md (the BotCookery safety rules) first and follow it. If you are
not allowed to write under .claude/, tell me and I will approve it; do not
work around it.
Architecture (one file per module):
- config.py : loads config.toml (markets = list of slugs, poll_seconds,
target_price) and limits.json (max_contracts_per_order,
max_open_exposure_usd, max_daily_loss_usd,
max_orders_per_minute). Fail closed if a limit is missing.
- data.py : public market data from https://gateway.polymarket.us, no keys:
GET /v1/markets?active=true&closed=false&limit=20 (listing),
GET /v1/market/slug/{slug} (response {"market": {...}}; read
orderPriceMinTickSize and minimumTradeQty; cache 5 min),
GET /v1/markets/{slug}/book (response
{"marketData": {"bids":[{"px":{"value"},"qty"}],"offers":[...],
"state"}}; px.value and qty are strings; prices are YES-side),
GET /v1/markets/{slug}/bbo.
Bounded exponential backoff on 429/5xx.
- strategy.py : ONE placeholder rule I will replace: "if best YES offer <=
target_price from config, propose buying the minimum quantity
of YES". Return an order body dict or None, plus a one-line
reason. No ML, no "AI picks".
- risk.py : check_order() enforcing every limit in limits.json plus: price
within 0.01-0.99 AND on the market's tick, quantity a multiple
of minimumTradeQty, no YES+NO buys on the same market
(self-match), and a kill switch (file KILL exists or BOT_KILL=1
=> no orders). Unit-test every rule.
- paper.py : local ledger (paper_ledger.json). Fill conservatively against
the current book: buys lift offers at or below my limit, sells
hit bids at or above it, at the displayed price, never more
than displayed size, walking levels. NO intents trade the
inverse (BUY_SHORT fills against YES bids at cost 1 - bid).
Taker fee = theta * qty * p * (1-p), theta from
https://docs.polymarket.us/fees (0.0695 standard), rounded to
$0.01 with banker's rounding (round half to even). Test against
the fee page's worked examples. Track cash, positions, realized
and unrealized PnL (mark at the bid).
- broker.py : LIVE adapter for https://api.polymarket.us. Ed25519: the secret
key from env is base64; the first 32 decoded bytes are the
seed. Headers X-PM-Access-Key, X-PM-Timestamp (ms, within 30 s
of server time), X-PM-Signature = base64(sign(timestamp +
METHOD + path)), e.g. path "/v1/portfolio/positions"; the body
is not signed. Methods: preview_order (POST /v1/order/preview,
body wrapped as {"request": order}), create_order
(POST /v1/orders), cancel_all (POST /v1/orders/open/cancel,
body {"slugs": []}), positions (GET /v1/portfolio/positions),
balances (GET /v1/account/balances).
Order body: marketSlug, type ORDER_TYPE_LIMIT, price
{value, currency "USD"} (ALWAYS the YES price: buying NO at
0.83 sends 0.17), quantity (number), tif
TIME_IN_FORCE_IMMEDIATE_OR_CANCEL, intent, and
manualOrderIndicator MANUAL_ORDER_INDICATOR_AUTOMATIC.
- bot.py : main loop. Default --paper. --preview calls the preview
endpoint with my key but never places orders (exit with a
clear message if keys are missing). --live requires the flag,
keys, AND typing "I accept the risk"; refuse if stdin is not a
terminal. Every loop: kill-switch check -> data -> strategy ->
risk -> paper/preview/live -> log to logs/decisions.jsonl.
- cancel_all.py : one command that calls cancel-all in live mode.
Rules:
- Keys only from env vars (POLYMARKET_KEY_ID, POLYMARKET_SECRET_KEY).
.env.example with blanks; .env in .gitignore; never print keys.
- Bounded exponential backoff on 429/5xx for reads and cancels. A "Global Rate
Limit Exceeded" reject on an order is a latency stopgap, not a rate limit:
log it, don't retry.
- The order API has no client order id, so do idempotency locally: record a
key (hash of order + nonce) in sent_orders.json BEFORE sending, never
re-send a recorded key, and never auto-retry POST /v1/orders after a
timeout or 5xx (outcome unknown; check open orders instead).
- pytest tests for risk.py (each rule), paper.py fills (partial, NO side,
sells, fee examples), the YES-price inversion, the live-mode gate, and
broker signing (verify with the public key) using a fake HTTP session.
Show me the file plan first. Then build it. Then give me the exact commands
to run it in paper mode.
How do you build it, step by step?
Set up the kitchen. New folder, git init, then add the Safety Pack: save the rules file as CLAUDE.md, add the .gitignore, and add .claude/settings.json with the deny rules so Claude Code can't read your .env or key files. Claude Code will ask permission before writing under .claude/; approve it.
Start Claude Code in the folder and paste the prompt. Review the file plan before it writes code. If it proposes any endpoint you can't find on docs.polymarket.us, push back.
Fill in config.toml. Pick 1–3 market slugs from GET https://gateway.polymarket.us/v1/markets?active=true&closed=false&limit=20. Without closed=false, the list can include markets that have already closed. Set a target price for the placeholder rule.
Set tiny limits in limits.json, for example 1 contract per order, $5 max exposure, $2 max daily loss, 3 orders a minute.
Run paper mode with python bot.py --paper. Watch logs/decisions.jsonl. Every line should show the data it saw, what the rule proposed, and whether risk said yes or no. Some markets quote below $0.01 (their tick can be 0.001); the risk check refuses those because the exchange only accepts 0.01–0.99, so a "price outside 0.01-0.99" line is the guardrail working, not a bug.
Test the kill switch. While it's running, touch KILL. The next loop should log "kill switch on" and place nothing. Delete the file to resume.
Replace the placeholder rule with your own logic. Keep it simple and explainable. The bot should be able to tell you in one sentence why it wanted each order.
Optional preview mode. Once you have a verified Polymarket US account and API key, run python bot.py --preview. It calls POST /v1/order/preview with the order wrapped as {"request": order} to validate it and show expected fills, and it places nothing. Without keys, --preview should exit with a clear message.
Only after days of clean paper logs would you even consider --live, with limits you're fine losing entirely. The typed confirmation is there to make you stop and think.
How do you test it in paper or demo mode first?
Level 1: local paper ledger (no account needed).paper.py simulates fills against the real public book. Be conservative: fill at the ask for buys and the bid for sells, never better, and never more than the displayed size. Mark open positions at the bid, so a fresh position shows a loss equal to the spread. Real fills are often worse than paper fills because of latency, queue position and fees, so treat paper profits with suspicion.
Fees belong in the simulation. The taker fee is Θ × contracts × p × (1 − p) with Θ = 0.0695 on standard markets, rounded to the cent with banker's rounding (round half to even). Have the AI write tests against the worked examples on the fee schedule; in our test run, that check caught a floating-point rounding bug.
Level 2: order preview (needs a key, places nothing).POST /v1/order/preview checks your parameters against the exchange (tick size, minimum quantity, price validity) and returns what it expects would happen. It's the best way to catch "valid in my head, rejected by the exchange" bugs before going live.
What to review before you go further:
At least a week of decisions.jsonl with no surprise orders.
Every rejected order has a clear reason.
Paper PnL includes fees.
What safety checks does it need before real money?
Tick every box before the bot touches real money. The full rules file, .gitignore and pre-commit hook are in the Safety Pack.
Key handling: API keys and private-key files live in environment variables or a secrets manager. Never in code, prompts, chat logs, screenshots or git. .env and *.pem are in .gitignore, and your AI tool is blocked from reading them (see the Safety Pack).
Position limits: hard caps in config (max contracts per order, max total exposure, max daily loss). The bot refuses any order above them, and the caps are checked in code, not just in the prompt.
Kill switch: one command or one file (touch KILL) stops new orders immediately, and a second command cancels every resting order through the venue's cancel-all endpoint.
Paper first: paper or demo mode is the default. Live trading needs an explicit --live flag and a typed confirmation, and you only flip it after days of clean paper logs.
price.value is always the YES side. Buying NO at 0.83 means sending 0.17. Write a unit test for this; it's the most common way these bots buy the wrong thing.
Read orderPriceMinTickSize and minimumTradeQty from the market response. Don't hardcode them.
Send manualOrderIndicator: MANUAL_ORDER_INDICATOR_AUTOMATIC on every bot order. The docs mark the field as required.
Never auto-retry POST /v1/orders after a timeout or 5xx. There's no client order id, so a retry can double your order. Check open orders instead.
Don't place a YES buy and a NO buy at complementary prices on the same market. Polymarket US treats that as a self-match.
Prefer the markets WebSocket over polling the book. Polling burns your 25 req/s limit (shared per IP) fast.
Endpoints and rules were checked against these pages on Oct 19, 2026. APIs change, so if something here disagrees with the official docs, trust the docs and tell us.
Frequently asked questions
Can Claude Code build a Polymarket trading bot?
Yes. Claude Code can write the whole structure (data, strategy, risk checks, broker adapter) from a good prompt. It can't make the strategy profitable, and it will make mistakes, so run it in paper mode and read the code, especially the parts that touch keys and orders.
Does Polymarket US have a paper trading or demo mode?
There's no public retail demo exchange documented. The practical approach is a local paper ledger that simulates fills against the live public order book, plus the POST /v1/order/preview endpoint, which validates an order and shows expected fills without placing it.
Can I run a Polymarket bot from the US?
US residents should use Polymarket US, the CFTC-regulated exchange, which requires identity verification before you can create API keys. International Polymarket is close-only for the US, and using a VPN to get around that breaks its terms.
Should I buy a Polymarket bot instead of building one?
Be careful. Many "bots for sale" are scams or repackaged public GitHub code, and any repo you run can hide code that steals keys. If you use someone else's code, audit it first. See our GitHub audit recipe.
The Daily Bot Recipe
One new build recipe, one safety tip, and API changes worth knowing about. Free. No picks, ever.
A copy-paste AI prompt and step-by-step guide to the Polymarket US API: public market data with no key, then authenticated reads. Includes the international Gamma API, read-only, with a geoblock check.
An honest guide to building a trading bot with Claude, Claude Code or Claude AI: where it helps, where it fails, and a safe Kalshi-demo starter prompt with a rules file, deny settings, limits and a kill switch.
Build a personal alert monitor that compares prices for the same event on Kalshi and Polymarket US and flags gaps after fees. Alerts only, no auto-trading, with an honest look at why most "arbitrage bots" don't work.