Kalshi API Integration Guide
How to authenticate with Kalshi's trading API using RSA-PSS signatures
Table of Contents
1. The Authentication Problem
Most trading APIs use OAuth2 or JWT tokens. Kalshi doesn't. Their API uses RSA-PSS digital signatures to authenticate every single request.
This means:
- There's no "login" endpoint that returns a token
- You must cryptographically sign each request individually
- The signature proves you possess the private key
- Standard HTTP libraries won't work out of the box
2. How Kalshi Authentication Works
For each API request, you need to:
- Create a message to sign:timestamp + method + path
Example:
1704398400000GET/trade-api/v2/portfolio/fills - Sign the message using your private key with RSA-PSS (SHA-256)
- Base64-encode the signature
- Add three headers to your HTTP request:
KALSHI-ACCESS-KEY: Your public API key IDKALSHI-ACCESS-TIMESTAMP: Unix timestamp in millisecondsKALSHI-ACCESS-SIGNATURE: Base64-encoded signature
💡 Technical Details
Signature Algorithm: RSA-PSS with SHA-256 hashing
Timestamp Format: Unix epoch in milliseconds (13 digits)
3. Step-by-Step Implementation
Step 1: Get API Credentials
Log into Kalshi via a browser (not the mobile app) and navigate to Account & Security and then scroll down to API Keys and select Create Key. Nickname it, select either Read-only or Read and Write, then copy both the API key ID and private key file and save them somewhere safe.
Step 2: Load Your Private Key
Store your private key securely as an environment variable or secret. Do NOT commit it to version control.
# Store in .env file KALSHI_API_KEY_ID=your-key-id-here KALSHI_PRIVATE_KEY="-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAo... -----END PRIVATE KEY-----"
Step 3: Create Signature Function
Implement a function that takes the HTTP method and path, generates a timestamp, creates the message string, signs it with RSA-PSS, and returns the required headers.
Step 4: Sign Each Request
Call your signature function before every API request and include the headers. The signature is only valid for that specific request (timestamp + method + path).
Step 5: Test Your Integration
Start with a simple GET request to /trade-api/v2/portfolio/balance. If you get authentication errors, verify your key format and message construction.
4. Code Examples
Python Example
Using the cryptography library:
import os
import time
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
import requests
# Load credentials
API_KEY_ID = os.environ['KALSHI_API_KEY_ID']
PRIVATE_KEY_PEM = os.environ['KALSHI_PRIVATE_KEY']
BASE_URL = 'https://trading-api.kalshi.com'
# Parse private key
private_key = serialization.load_pem_private_key(
PRIVATE_KEY_PEM.encode(),
password=None
)
def create_signature(method: str, path: str):
"""Generate Kalshi API signature headers"""
timestamp = str(int(time.time() * 1000))
message = f"{timestamp}{method}{path}"
# Sign with RSA-PSS
signature = private_key.sign(
message.encode(),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
# Base64 encode
signature_b64 = base64.b64encode(signature).decode()
return {
'KALSHI-ACCESS-KEY': API_KEY_ID,
'KALSHI-ACCESS-TIMESTAMP': timestamp,
'KALSHI-ACCESS-SIGNATURE': signature_b64
}
# Example: Get portfolio balance
method = 'GET'
path = '/trade-api/v2/portfolio/balance'
headers = create_signature(method, path)
response = requests.get(f"{BASE_URL}{path}", headers=headers)
print(response.json())Node.js/TypeScript Example
Using Node's built-in crypto module:
import crypto from 'crypto';
import axios from 'axios';
const API_KEY_ID = process.env.KALSHI_API_KEY_ID!;
const PRIVATE_KEY_PEM = process.env.KALSHI_PRIVATE_KEY!;
const BASE_URL = 'https://trading-api.kalshi.com';
function createSignature(method: string, path: string) {
const timestamp = Date.now().toString();
const message = `${timestamp}${method}${path}`;
// Sign with RSA-PSS
const signature = crypto.sign(
'sha256',
Buffer.from(message),
{
key: PRIVATE_KEY_PEM,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN,
}
);
// Base64 encode
const signatureB64 = signature.toString('base64');
return {
'KALSHI-ACCESS-KEY': API_KEY_ID,
'KALSHI-ACCESS-TIMESTAMP': timestamp,
'KALSHI-ACCESS-SIGNATURE': signatureB64,
};
}
// Example: Get portfolio fills
async function getPortfolioFills() {
const method = 'GET';
const path = '/trade-api/v2/portfolio/fills';
const headers = createSignature(method, path);
const response = await axios.get(`${BASE_URL}${path}`, { headers });
return response.data;
}
getPortfolioFills().then(data => console.log(data));5. Common Pitfalls
❌ Using JWT Instead of RSA-PSS
Kalshi does NOT use JWT tokens. You must use RSA-PSS signature algorithm with SHA-256. Libraries like jsonwebtoken will not work.
❌ Modifying the Private Key Format
Use the private key exactly as downloaded. Do not strip newlines, remove headers/footers, or reformat it. The key should include -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----.
❌ Wrong Message Format
The message MUST be exactly: timestamp + method + path. No spaces, no separators, uppercase method. Path must include query parameters if present.
❌ Timestamp Format
Timestamp must be Unix epoch in milliseconds (13 digits), not seconds (10 digits). Use Date.now() in JavaScript or int(time.time() * 1000) in Python.
6. Quick Reference
API Base URL
https://trading-api.kalshi.comRequired Headers
- KALSHI-ACCESS-KEY: <your-api-key-id>
- KALSHI-ACCESS-TIMESTAMP: <unix-ms>
- KALSHI-ACCESS-SIGNATURE: <base64-signature>
Message Format
timestamp + method + pathSignature Algorithm
RSA-PSS with SHA-256Salt Length
PSS.MAX_LENGTH (auto-calculate from key)Ready to analyze your Kalshi trades?
AfterTrade imports your trading history and provides AI-powered coaching feedback to help you improve your performance and find your edge.
Get Started Free →