Audience: developers. This is a hands-on, copy-paste guide. For eligibility, agreements, and the business setup, see ISVs / IBs and Partner Onboarding .
This guide walks you through your first authenticated order on Polymarket US. By the end you’ll have:
Authenticated with the API as your Firm
Listed available instruments
Checked a balance
Placed and cancelled a limit order
Prerequisites: Complete Partner Onboarding to receive your Client ID and register your public key. This guide uses pre-production (api.preprod.polymarketexchange.com).
Step 1: Authenticate
Authentication uses Private Key JWT — you sign a JWT with your private key and exchange it for a short-lived access token. The full mechanism, claims, and key rotation are covered in the Authentication guide; the complete, runnable token code is in the full script at the bottom of this page.
Once you have a token, set your auth header and confirm your identity with GET /v1/whoami :
import requests
BASE_URL = "https://api.preprod.polymarketexchange.com"
headers = {
"Authorization" : f "Bearer { access_token } " , # from the auth guide
"Content-Type" : "application/json" ,
}
resp = requests.get( f " { BASE_URL } /v1/whoami" , headers = headers)
resp.raise_for_status()
print ( f "Authenticated as: { resp.json() } " )
Expected response:
{
"user" : "firms/ISV-Participant-Acme/users/admin" ,
"userDisplayName" : "Your Company" ,
"firm" : "ISV-Participant-Acme" ,
"firmDisplayName" : "Acme Trading" ,
"firmType" : "FIRM_TYPE_PARTICIPANT"
}
Step 2: List available instruments
Find instruments to trade with the Reference Data API (POST /v1/refdata/instruments):
resp = requests.post(
f " { BASE_URL } /v1/refdata/instruments" ,
headers = headers,
json = { "tradable_filter" : "TRADABLE_FILTER_TRADABLE" , "page_size" : 5 },
)
resp.raise_for_status()
instruments = resp.json().get( "instruments" , [])
for inst in instruments:
print ( f " { inst[ 'symbol' ] } : { inst.get( 'description' , 'N/A' ) } " )
Cache each instrument’s price_scale — you need it to convert prices to integers when placing orders.
Step 3: Check a balance
Confirm available funds with the Positions API (POST /v1/positions/balance):
resp = requests.post( f " { BASE_URL } /v1/positions/balance" , headers = headers, json = {})
resp.raise_for_status()
print ( f "Available balance: { resp.json() } " )
Step 4: Place a limit order
Submit a buy order with the Trading API (POST /v1/trading/orders):
import uuid
symbol = "tec-nfl-sbw-2026-02-08-kc" # e.g. Kansas City Chiefs to win Super Bowl 2026
price_scale = 1000 # from instrument metadata (Step 2)
order = {
"clord_id" : str (uuid.uuid4()), # your unique order ID
"symbol" : symbol,
"side" : "SIDE_BUY" ,
"type" : "ORDER_TYPE_LIMIT" ,
"time_in_force" : "TIME_IN_FORCE_GOOD_TILL_CANCEL" ,
"order_qty" : 10 ,
"price" : int ( 0.50 * price_scale), # $0.50 as integer
}
resp = requests.post( f " { BASE_URL } /v1/trading/orders" , headers = headers, json = order)
if resp.status_code == 200 :
result = resp.json()
print ( f "Order placed: { result[ 'order' ][ 'id' ] } ( { result[ 'order' ][ 'state' ] } )" )
else :
print ( f "Order failed: { resp.text } " )
Expected response:
{
"order" : {
"id" : "ord_abc123" ,
"clord_id" : "your-uuid" ,
"symbol" : "tec-nfl-sbw-2026-02-08-kc" ,
"side" : "SIDE_BUY" ,
"state" : "ORDER_STATE_NEW" ,
"order_qty" : 10 ,
"price" : 5000 ,
"leaves_qty" : 10 ,
"cum_qty" : 0
}
}
Streaming-first. To track order and fill updates in production, subscribe to the order stream rather than polling. REST search (below) is for one-off checks and backfill.
Step 5: Check your order
Look up open orders with the Report API (POST /v1/report/orders/search):
resp = requests.post(
f " { BASE_URL } /v1/report/orders/search" ,
headers = headers,
json = { "symbols" : [symbol], "state_filter" : "ORDER_STATE_FILTER_OPEN" },
)
resp.raise_for_status()
for order in resp.json().get( "orders" , []):
print ( f " { order[ 'id' ] } : { order[ 'side' ] } { order[ 'order_qty' ] } @ { order[ 'price' ] / price_scale :.2f} " )
Step 6: Cancel your order
Cancel with the Trading API (POST /v1/trading/orders/cancel):
resp = requests.post(
f " { BASE_URL } /v1/trading/orders/cancel" ,
headers = headers,
json = { "order_id" : "ord_abc123" }, # from Step 4
)
print ( "Order cancelled!" if resp.status_code == 200 else f "Cancel failed: { resp.text } " )
Complete example
A single runnable script — including the full token exchange (see the Authentication guide for an explanation of each claim):
#!/usr/bin/env python3
"""Polymarket US partner quickstart — place your first order."""
import jwt
import uuid
import time
import requests
from cryptography.hazmat.primitives import serialization
# Configuration (from Partner Onboarding)
AUTH_DOMAIN = "pmx-preprod.us.auth0.com"
CLIENT_ID = "your_client_id"
AUDIENCE = "https://api.preprod.polymarketexchange.com"
PRIVATE_KEY_PATH = "private_key.pem"
BASE_URL = "https://api.preprod.polymarketexchange.com"
def get_access_token ():
with open ( PRIVATE_KEY_PATH , "rb" ) as f:
private_key = serialization.load_pem_private_key(f.read(), password = None )
now = int (time.time())
claims = {
"iss" : CLIENT_ID ,
"sub" : CLIENT_ID ,
"aud" : f "https:// { AUTH_DOMAIN } /oauth/token" ,
"iat" : now,
"exp" : now + 300 ,
"jti" : str (uuid.uuid4()),
}
assertion = jwt.encode(claims, private_key, algorithm = "RS256" )
response = requests.post(
f "https:// { AUTH_DOMAIN } /oauth/token" ,
json = {
"client_id" : CLIENT_ID ,
"client_assertion_type" : "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" ,
"client_assertion" : assertion,
"audience" : AUDIENCE ,
"grant_type" : "client_credentials" ,
},
)
response.raise_for_status()
return response.json()[ "access_token" ]
def main ():
token = get_access_token()
headers = { "Authorization" : f "Bearer { token } " , "Content-Type" : "application/json" }
# Verify identity
resp = requests.get( f " { BASE_URL } /v1/whoami" , headers = headers)
resp.raise_for_status()
print ( f "Logged in as: { resp.json() } " )
# List instruments
resp = requests.post(
f " { BASE_URL } /v1/refdata/instruments" ,
headers = headers,
json = { "tradable_filter" : "TRADABLE_FILTER_TRADABLE" , "page_size" : 5 },
)
resp.raise_for_status()
instruments = resp.json().get( "instruments" , [])
if not instruments:
print ( "No instruments available. Exiting." )
return
symbol = instruments[ 0 ][ "symbol" ]
price_scale = instruments[ 0 ].get( "price_scale" , 1000 )
print ( f "Using: { symbol } (price_scale= { price_scale } )" )
# Place order
order = {
"clord_id" : str (uuid.uuid4()),
"symbol" : symbol,
"side" : "SIDE_BUY" ,
"type" : "ORDER_TYPE_LIMIT" ,
"time_in_force" : "TIME_IN_FORCE_GOOD_TILL_CANCEL" ,
"order_qty" : 10 ,
"price" : int ( 0.50 * price_scale),
}
resp = requests.post( f " { BASE_URL } /v1/trading/orders" , headers = headers, json = order)
if resp.status_code != 200 :
print ( f "Order failed: { resp.text } " )
return
order_id = resp.json()[ "order" ][ "id" ]
print ( f "Order placed: { order_id } " )
# Cancel order
resp = requests.post(
f " { BASE_URL } /v1/trading/orders/cancel" ,
headers = headers,
json = { "order_id" : order_id},
)
print ( "Order cancelled!" if resp.status_code == 200 else f "Cancel failed: { resp.text } " )
if __name__ == "__main__" :
main()
Required packages:
pip install PyJWT cryptography requests
Next steps
Authentication Token refresh, key rotation, and scopes.
Funding How participant accounts are funded (coming soon) .
gRPC Streaming Real-time order, position, and market data.
Trading API Full order-entry endpoint reference.