Skip to main content
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

EnvironmentAuth Domain
Developmentpmx-dev01.us.auth0.com
Pre-productionpmx-preprod.us.auth0.com
Productionpmx-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.

REST API

curl "https://traderapi.us-east-1.privatelink.preprod.polymarketexchange.com/v1/accounts/whoami" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "accept: application/json"

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 CodeDescriptionSolution
invalid_clientJWT signature verification failedVerify private key matches registered public key
invalid_client_assertionMalformed JWT or wrong claimsCheck JWT claims (iss, sub, aud, exp, jti)
UNAUTHENTICATEDAccess token invalid or expiredRefresh token and retry
PERMISSION_DENIEDToken valid but lacks required scopesCheck user permissions with support
UNAVAILABLECannot reach authentication serviceCheck network connectivity, retry with backoff

Key Rotation

You can rotate your keys without downtime:
  1. Generate a new key pair
  2. Submit the new public key to us
  3. We add the new key (both old and new work during transition)
  4. Update your systems to use the new private key
  5. Notify us to remove the old public key

Next Steps