L1 authentication uses the wallet’s private key to sign an EIP-712 message used in the request header. It proves ownership and control over the private key. The private key stays in control of the user and all trading activity remains non-custodial.Used for:
L2 uses API credentials (apiKey, secret, passphrase) generated from L1 authentication. These are used solely to authenticate requests made to the CLOB API. Requests are signed using HMAC-SHA256.Used for:
Cancel or get user’s open orders
Check user’s balances and allowances
Post user’s signed orders
Even with L2 authentication headers, methods that create user orders still
require the user to sign the order payload.
While we highly recommend using our provided clients to handle signing and authentication, the following is for developers who choose NOT to use our Python or TypeScript clients.Create API Credentials
POST https://clob.polymarket.com/auth/api-key
Derive API Credentials
GET https://clob.polymarket.com/auth/derive-api-key
Required L1 headers:
Header
Description
POLY_ADDRESS
Polygon signer address
POLY_SIGNATURE
CLOB EIP-712 signature
POLY_TIMESTAMP
Current UNIX timestamp
POLY_NONCE
Nonce (default: 0)
The POLY_SIGNATURE is generated by signing the following EIP-712 struct:
EIP-712 Signing Example
const domain = { name: "ClobAuthDomain", version: "1", chainId: chainId, // Polygon Chain ID 137};const types = { ClobAuth: [ { name: "address", type: "address" }, { name: "timestamp", type: "string" }, { name: "nonce", type: "uint256" }, { name: "message", type: "string" }, ],};const value = { address: signingAddress, // The Signing address timestamp: ts, // The CLOB API server timestamp nonce: nonce, // The nonce used message: "This message attests that I control the given wallet",};const sig = await signer._signTypedData(domain, types, value);
domain = { "name": "ClobAuthDomain", "version": "1", "chainId": chainId, # Polygon Chain ID 137}types = { "ClobAuth": [ {"name": "address", "type": "address"}, {"name": "timestamp", "type": "string"}, {"name": "nonce", "type": "uint256"}, {"name": "message", "type": "string"}, ]}value = { "address": signingAddress, # The signing address "timestamp": ts, # The CLOB API server timestamp "nonce": nonce, # The nonce used "message": "This message attests that I control the given wallet",}sig = signer.sign_typed_data(domain, types, value)
The POLY_SIGNATURE for L2 is an HMAC-SHA256 signature created using the user’s API credentials secret value. Reference implementations can be found in the TypeScript and Python clients.
When initializing the L2 client, you must specify your wallet signatureType and the funder address which holds the funds:
Signature Type
Value
Description
EOA
0
Standard Ethereum wallet (MetaMask). Funder is the EOA address and will need POL to pay gas on transactions.
POLY_PROXY
1
Existing Polymarket proxy wallet flow, commonly used by users who logged in via Magic Link email/Google.
GNOSIS_SAFE
2
Existing Gnosis Safe wallet flow. Existing Safe users can continue using this type.
POLY_1271
3
Deposit wallet flow for new API users. The funder is the deposit wallet address and orders are validated through ERC-1271.
New API users should use deposit wallets with POLY_1271. Existing Safe and
Proxy users are unaffected and can keep using their current funder address and
signature type. See the Deposit Wallet Guide for
setup details.
Your wallet’s private key is incorrect or improperly formatted.Solutions:
Verify your private key is a valid hex string (starts with “0x”)
Ensure you’re using the correct key for the intended address
Check that the key has proper permissions
Error - NONCE_ALREADY_USED
The nonce you provided has already been used to create an API key.Solutions:
Use deriveApiKey() with the same nonce to retrieve existing credentials
Or use a different nonce with createApiKey()
Error - Invalid Funder Address
Your funder address is incorrect or doesn’t match your wallet.Solution: Check your Polymarket profile address at polymarket.com/settings.If it does not exist or user has never logged into Polymarket.com, deploy it first before creating L2 authentication.
Lost both credentials and nonce
Unfortunately, there’s no way to recover lost API credentials without the nonce. You’ll need to create new credentials:
// Create fresh credentials with a new nonceconst newCreds = await client.createApiKey();// Save the nonce this time!