Developers
Trading
Open and close leveraged positions, place and cancel limit orders, and set take-profit triggers.
Open a position
POST /polymarket/positions/open
{
"userAddress": "0x…",
"tokenId": "7239…", // YES or NO token (decimal string)
"conditionId": "0xabcd…", // bytes32 hex from /polymarket/markets
"outcome": "YES", // "YES" | "NO" — UPPERCASE
"usdcAmount": "100.50", // user margin in dollars (decimal string)
"leverage": "3", // optional, "1" if omitted; "1"–"100"
"slug": "btc-2026", // optional; auto-registers an unknown market
// Auth — pick one:
"sessionToken": "…", // skips signature
// — or —
"signature": "0x…", // OpenPositionIntent
"nonce": 12,
"deadline": 1700000000
}
outcome controls which side is bought. With outcome: "NO" plus the YES tokenId, the backend buys the NO complement of that market. Most integrators should set tokenId to whichever side they want and outcome to match — match outcome to the outcome field of the market row for tokenId (and complementOutcome for complementTokenId).
leverage = 1 opens without a loan and never liquidates. leverage > 1 borrows (leverage − 1) × usdcAmount from the lending pool and posts shares as isolated collateral; the position carries a liquidationPrice that the engine monitors continuously.
The current binding leverage cap can be lower than market.maxLeverage because of orderbook depth or bid/ask spread. Either pre-check via /polymarket/order-preview or be prepared for HTTP 400 "Maximum leverage is Nx" and retry with a smaller value. The backend clamps to whatever cap is binding right now; it never silently rounds down.
Response
{
"positionId": 789,
"tokenId": "7239…",
"outcome": "YES",
"sharesAmount": "0", // populated on transition to OPEN
"entryPrice": "0",
"liquidationPrice": "0",
"status": "PENDING",
}
Lifecycle
PENDING → OPENING → OPEN (happy path)
PENDING → OPENING → FAILED (CLOB rejected — funds auto-recovered, loan auto-repaid)
Poll status (the owner's trading session, as on GET …/take-profit below):
GET /polymarket/positions/{positionId}/status?userAddress=0x…
x-trading-session: <sessionToken>
→ {
"id": 789, "status": "OPEN",
"entryPrice": "0.62", "sharesAmount": "162.10", "liquidationPrice": "0.45",
"currentPrice": "0.60", "pnlUsd": "-3.24",
"distanceToLiquidation": "0.150000", "pctToLiquidation": "60.0"
}
Typical end-to-end latency (PENDING → OPEN): single-digit seconds. On a FAILED terminal state, read the position once with GET /polymarket/positions/{id}?userAddress=0x… (same header) — errorMessage describes the CLOB rejection. Both reads answer 404 for a position the session's wallet does not own.
Close a position
POST /polymarket/positions/{positionId}/close
{
"userAddress": "0x…",
"sessionToken": "…", // OR signature/nonce/deadline
"signature": "0x…", // ClosePositionIntent over { userAddress, positionId, nonce, deadline }
"nonce": 7,
"deadline": 1700000000
}
→ { "positionId": 789, "exitPrice": "0.61", "sharesAmount": "0", "status": "CLOSING" }
The backend market-sells the entire position, auto-repays any loans (including positions that merged loans together), and credits remaining equity to the Deposit Wallet. The position transitions OPEN → CLOSING → CLOSED. There is no liquidation penalty on a voluntary close, though a close-side trading fee applies (see Fees).
When the market is resolved, close is automatic via on-chain CTF redemption (no orderbook needed). The backend handles winning legs at $1 and losing legs at $0 without integrator intervention; the position transitions to CLOSED once the redemption batch lands.
If the position is being liquidated, the close call errors with 409 — wait for the executor to finish.
Limit orders
The order-first API places resting limit orders that fund a position when they fill. The order's nominal margin × leverage is locked the moment the order is placed (loan issued, USDC locked in the Deposit Wallet); cancelling unwinds the loan.
POST /polymarket/orders
{
"userAddress": "0x…",
"tokenId": "7239…",
"outcome": "YES", // "YES" | "NO"
"leverage": 3, // number (not string)
"marginUsdc": 100.50, // number, dollars
"limitPrice": 0.60, // number, [0, 1)
"slug": "btc-2026", // optional
"conditionId": "0xabcd…", // optional but recommended
"sessionToken": "…" // REQUIRED for placement (no signed variant yet)
}
→ {
"order": {
"id": 999, "userAddress": "0x…", "tokenId": "7239…",
"side": "LONG", "clobSide": "BUY",
"leverage": "3", "margin": "100.50",
"limitPrice": "0.60",
"sharesRequested": "500.00", "sharesFilled": "0",
"avgFillPrice": null, "status": "OPEN",
"loanId": 456, "positionId": null,
"createdAt": "…", "updatedAt": "…"
}
}
Order lifecycle: OPEN → PARTIALLY_FILLED → FILLED (or CANCELLED). Once filled (fully or partially), a position row appears with the order's loanId attached.
Cancel a limit order
POST /polymarket/orders/{orderId}/cancel
{
"userAddress": "0x…",
"sessionToken": "…" // OR signature/nonce/deadline (CancelOrderIntent)
}
→ { "success": true }
List own orders
GET /polymarket/orders/{userAddress}[?statuses=OPEN,PARTIALLY_FILLED]
GET /polymarket/orders/{orderId}/status?userAddress=0x…
Take-profit
POST /polymarket/positions/{id}/take-profit { userAddress, sessionToken, takeProfitPrice: "0.85" }
GET /polymarket/positions/{id}/take-profit?userAddress=… (sessionToken in header or query)
DELETE /polymarket/positions/{id}/take-profit { userAddress, sessionToken }
takeProfitPrice is the YES-equivalent exit price (i.e. always the price of the side held). The backend monitors and auto-closes when the price crosses. Requires a trading session — no signed variant.
