The Polymarket Exchange API uses Private Key JWT authentication. You sign a JWT with your private key, exchange it for an access token, then include that token in every API request.
CRITICAL: Access tokens must be refreshed every 3 minutes. Access tokens have a short expiration. Your application MUST implement automatic token refresh before expiration to maintain uninterrupted API and streaming connections.
Authentication Flow
Authentication Configuration
Auth Domains
Environment Auth Domain Development pmx-dev01.us.auth0.comPre-production pmx-preprod.us.auth0.comProduction pmx-prod.us.auth0.com
During onboarding, youβll provide your public key and receive your client_id and audience values.
Step 1: Create Client Assertion JWT
Create a JWT with these claims, signed with your private key using RS256:
{
"iss" : "YOUR_CLIENT_ID" ,
"sub" : "YOUR_CLIENT_ID" ,
"aud" : "https://pmx-preprod.us.auth0.com/oauth/token" ,
"iat" : 1703270400 ,
"exp" : 1703270700 ,
"jti" : "unique-uuid-per-request"
}
Step 2: Request Access Token
Exchange your signed JWT for an access token:
curl --request POST \
--url "https://pmx-preprod.us.auth0.com/oauth/token" \
--header "content-type: application/json" \
--data '{
"client_id": "YOUR_CLIENT_ID",
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": "YOUR_SIGNED_JWT_ASSERTION",
"audience": "YOUR_API_AUDIENCE",
"grant_type": "client_credentials"
}'
Response
{
"access_token" : "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIs..." ,
"token_type" : "Bearer" ,
"expires_in" : 180
}
The expires_in value is in seconds. With a 3-minute (180 second) expiration, you must refresh tokens frequently.
Step 3: Use Token in API Requests
Include the access token in the Authorization header for every request.
gRPC Streaming
Include the token in gRPC metadata:
import grpc
from polymarket.v1 import marketdatasubscription_pb2_grpc
# Create secure channel
credentials = grpc.ssl_channel_credentials()
channel = grpc.secure_channel( 'grpc-api.preprod.polymarketexchange.com:443' , credentials)
# Create stub
stub = marketdatasubscription_pb2_grpc.MarketDataSubscriptionAPIStub(channel)
# Include access token in metadata
metadata = [
( 'authorization' , f 'Bearer { access_token } ' )
]
# Make streaming call with metadata
response_stream = stub.CreateMarketDataSubscription(request, metadata = metadata)
The metadata key must be authorization (lowercase). Include Bearer prefix before the token.
Token Refresh Strategy
Since tokens expire every 3 minutes, implement automatic refresh:
import jwt
import uuid
import time
import requests
from cryptography.hazmat.primitives import serialization
class TokenManager :
def __init__ ( self , auth0_domain , client_id , audience , private_key_path ):
self .auth0_domain = auth0_domain
self .client_id = client_id
self .audience = audience
self .private_key_path = private_key_path
self .token = None
self .expires_at = None
def _load_private_key ( self ):
with open ( self .private_key_path, 'rb' ) as f:
return serialization.load_pem_private_key(f.read(), password = None )
def _create_client_assertion ( self ):
private_key = self ._load_private_key()
now = int (time.time())
claims = {
"iss" : self .client_id,
"sub" : self .client_id,
"aud" : f "https:// { self .auth0_domain } /oauth/token" ,
"iat" : now,
"exp" : now + 300 ,
"jti" : str (uuid.uuid4()),
}
return jwt.encode(claims, private_key, algorithm = "RS256" )
def get_token ( self ):
# Refresh if token is missing or expires within 30 seconds
if not self .token or time.time() >= self .expires_at - 30 :
self ._refresh_token()
return self .token
def _refresh_token ( self ):
assertion = self ._create_client_assertion()
response = requests.post(
f "https:// { self .auth0_domain } /oauth/token" ,
json = {
"client_id" : self .client_id,
"client_assertion_type" : "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" ,
"client_assertion" : assertion,
"audience" : self .audience,
"grant_type" : "client_credentials"
}
)
response.raise_for_status()
data = response.json()
self .token = data[ "access_token" ]
self .expires_at = time.time() + data[ "expires_in" ]
# Usage
token_manager = TokenManager(
auth0_domain = "pmx-preprod.us.auth0.com" ,
client_id = "YOUR_CLIENT_ID" ,
audience = "YOUR_API_AUDIENCE" ,
private_key_path = "/path/to/private_key.pem"
)
# Always use get_token() - it handles refresh automatically
token = token_manager.get_token()
Required packages:
pip install PyJWT cryptography requests
Handling Authentication Errors
Common Authentication Errors
Error Code Description Solution invalid_clientJWT signature verification failed Verify private key matches registered public key invalid_client_assertionMalformed JWT or wrong claims Check JWT claims (iss, sub, aud, exp, jti) UNAUTHENTICATEDAccess token invalid or expired Refresh token and retry PERMISSION_DENIEDToken valid but lacks required scopes Check user permissions with support UNAVAILABLECannot reach authentication service Check network connectivity, retry with backoff
Key Rotation
You can rotate your keys without downtime:
Generate a new key pair
Submit the new public key to us
We add the new key (both old and new work during transition)
Update your systems to use the new private key
Notify us to remove the old public key
Next Steps
Authentication Setup Complete onboarding guide with key generation
Market Data Streaming Learn how to stream market data
Order Streaming Subscribe to order updates
Error Handling Handle errors and implement reconnection