Skip to main content

Placing Orders

POST /v1/orders
Place a market or limit order. Requires trade permission.
Polymarket-compatible execution model: On Polymarket, all orders are limit orders — “market orders” are just limit orders with FOK time-in-force at a marketable price. PolySimulator mirrors this exactly: market orders fill at the best available price (BUY at best ask, SELL at best bid), and the price field acts as a required worst-price limit for slippage protection.

Market Orders

Executed immediately at the best available price — BUY at the best ask, SELL at the best bid — matching how Polymarket fills market orders.

Worst-Price Limit (Required)

The price field is required on market orders and sets the worst price you’ll accept. This is identical to Polymarket — there are no “blind” market orders.
  • BUY: the order won’t fill above your price (you won’t overpay)
  • SELL: the order won’t fill below your price (you won’t undersell)
curl -X POST https://api.polysimulator.com/v1/orders \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-bot-order-001" \
  -d '{
    "market_id": "0xabc123...",
    "side": "BUY",
    "outcome": "Yes",
    "quantity": "10",
    "order_type": "market",
    "price": "0.68"
  }'

FAK (Fill-and-Kill) Market Order

Use time_in_force: "FAK" for partial fills — buy what’s available up to your quantity, cancel the rest. This is Polymarket’s FAK order type.
{
  "market_id": "0xabc123...",
  "side": "BUY",
  "outcome": "Yes",
  "quantity": "10",
  "order_type": "market",
  "price": "0.68",
  "time_in_force": "FAK"
}
Polymarket migration tip: FAK and IOC are equivalent in PolySimulator. If your Polymarket bot uses FAK, it works here unchanged. If you omit time_in_force, market orders default to FOK (Fill-or-Kill) — the entire order fills or is cancelled.

Limit Orders

Queued and filled by the background matching engine (~1s polling cycle).

GTC (Good-Til-Cancel)

Persists until filled, cancelled by the user, or the market closes.
{
  "market_id": "0xabc123...",
  "side": "BUY",
  "outcome": "Yes",
  "quantity": "10",
  "order_type": "limit",
  "price": "0.55",
  "time_in_force": "GTC"
}

FOK (Fill-or-Kill)

All-or-nothing immediate fill — matches Polymarket’s FOK order type. If the full quantity can’t fill at the limit price, the entire order is cancelled.
{
  "market_id": "0xabc123...",
  "side": "BUY",
  "outcome": "Yes",
  "quantity": "10",
  "order_type": "limit",
  "price": "0.55",
  "time_in_force": "FOK"
}

IOC (Immediate-or-Cancel)

Attempts to fill on the next matching cycle (~1s). Any unfilled quantity is automatically cancelled — the order never persists in the book.
{
  "market_id": "0xabc123...",
  "side": "BUY",
  "outcome": "Yes",
  "quantity": "10",
  "order_type": "limit",
  "price": "0.55",
  "time_in_force": "IOC"
}
When to use IOC: Sniping a specific price level without the risk of stale orders sitting in the book. If the current market price is worse than your limit, the order cancels immediately.
Fill conditions:
  • BUY limit: Fills when market ask ≤ your limit price. Funds reserved upfront.
  • SELL limit: Fills when market bid ≥ your limit price. Shares reserved upfront.

Request Fields

FieldTypeRequiredDescription
market_idstringYesPolymarket condition_id
sidestringYesBUY or SELL
outcomestringYesOutcome label: Yes, No, or custom
quantitystringYesNumber of shares as decimal string
order_typestringNomarket (default) or limit
pricestringYesFor limit orders: the limit price (0.01–0.99). For market orders: worst-price limit — required (Polymarket-style slippage protection)
time_in_forcestringNoGTC (default for limit), FOK (default for market), FAK, or IOC
client_order_idstringNoIdempotency key

Time in Force

ValueDescriptionPolymarket Equivalent
GTCGood-till-Cancelled — persists until filled, cancelled, or expired. Overridden to FOK for market orders.GTC
FOKFill-or-Kill — all-or-nothing immediate fill (default for market orders)FOK
FAKFill-and-Kill — fill available quantity, cancel remainder (Polymarket term)FAK
IOCImmediate-or-Cancel — same behavior as FAK (PolySimulator alias)FAK

Response

{
  "order_id": 42,
  "status": "FILLED",
  "order_type": "market",
  "side": "BUY",
  "outcome": "Yes",
  "price": "0.65",
  "quantity": "10",
  "notional": "6.50",
  "fee": "0",
  "filled_at": "2026-02-06T12:00:45Z",
  "price_source": "clob_book",
  "slippage_bps": 15,
  "account_balance": "993.50",
  "position": {
    "market_id": "0xabc123...",
    "outcome": "Yes",
    "quantity": "10",
    "avg_entry_price": "0.65",
    "status": "OPEN"
  }
}

Fill Diagnostics

Every market order includes transparency metadata:
FieldDescription
price_sourceWhere the price came from: clob_book, clob_midpoint, gamma_api, redis_cache, matching_engine (limit order fill)
slippage_bpsActual slippage from expected mid-price in basis points

Idempotency

Use the Idempotency-Key header or client_order_id field to prevent duplicate executions on retries:
curl -X POST https://api.polysimulator.com/v1/orders \
  -H "Idempotency-Key: my-bot-2026-02-06-001" \
  ...
If the same key is submitted twice, the second request returns the result of the first execution without creating a new order.
Include a timestamp or sequence number in your idempotency key to make debugging easier: "bot-alpha-20260206-001"

Error Handling

StatusError CodeMeaning
400PRICE_REQUIREDMarket orders require a price field (worst-price limit)
400INSUFFICIENT_BALANCENot enough funds for this trade
400MARKET_CLOSEDMarket has resolved or is no longer accepting orders
400INVALID_QUANTITYQuantity must be a positive decimal string
401INVALID_API_KEYAPI key is invalid, expired, or revoked
403PERMISSION_DENIEDKey lacks trade permission
404MARKET_NOT_FOUNDUnknown market_id
409LIMIT_PRICE_NOT_METMarket price exceeds your worst-price limit
409IDEMPOTENCY_CONFLICTSame idempotency key with different order parameters
429RATE_LIMITEDToo many requests — check Retry-After header
resp = requests.post(
    f"{BASE_URL}/v1/orders",
    headers={"X-API-Key": API_KEY, "Idempotency-Key": idempotency_key},
    json=order_payload,
)

if resp.status_code == 200:
    order = resp.json()
    print(f"Order {order['order_id']}: {order['status']} @ {order['price']}")
elif resp.status_code == 400:
    err = resp.json()
    if err.get("error") == "PRICE_REQUIRED":
        print("Add a 'price' field — market orders require a worst-price limit")
    elif err.get("error") == "INSUFFICIENT_BALANCE":
        print("Not enough funds — current balance too low")
    else:
        print(f"Order rejected: {err.get('message', err)}")
elif resp.status_code == 409:
    err = resp.json()
    if err.get("error") == "LIMIT_PRICE_NOT_MET":
        print(f"Price moved beyond your limit — retry with a wider price")
    else:
        print(f"Conflict: {err.get('message', err)}")
elif resp.status_code == 429:
    retry_after = int(resp.headers.get("Retry-After", 1))
    time.sleep(retry_after)

Next Steps