Documentation Index
Fetch the complete documentation index at: https://docs.kalshi.com/llms.txt
Use this file to discover all available pages before exploring further.
This guide shows you how to make authenticated requests to the Kalshi API in three simple steps. For the full production and demo endpoint list, see API Environments and Endpoints.
Step 1: Get Your API Keys
- Log in to your Kalshi account (demo or production)
- Navigate to Account & security → API Keys
- Click Create Key
- Save both:
- Private Key: Downloaded as a
.key file
- API Key ID: Displayed on screen (looks like
a952bcbe-ec3b-4b5b-b8f9-11dae589608c)
Your private key cannot be retrieved after this page is closed. Store it securely!
Step 2: Set Up Your Request
Every authenticated request to Kalshi requires three headers:
| Header | Description | Example |
|---|
KALSHI-ACCESS-KEY | Your API Key ID | a952bcbe-ec3b-4b5b-b8f9-11dae589608c |
KALSHI-ACCESS-TIMESTAMP | Current time in milliseconds | 1703123456789 |
KALSHI-ACCESS-SIGNATURE | Request signature (see below) | base64_encoded_signature |
How to Create the Signature
The signature proves you own the private key. Here’s how it works:
-
Create a message string: Concatenate
timestamp + HTTP_METHOD + path
- Example:
1703123456789GET/trade-api/v2/portfolio/balance
- Important: Sign the full URL path from the API root, without query parameters. For
https://external-api.demo.kalshi.co/trade-api/v2/portfolio/orders?limit=5, sign /trade-api/v2/portfolio/orders.
-
Sign with your private key: Use RSA-PSS with SHA256
-
Encode as base64: Convert the signature to base64 string
Here’s the signing process in Python:
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
def sign_request(private_key, timestamp, method, path):
# Strip query parameters from path before signing
path_without_query = path.split('?')[0]
# Create the message to sign
message = f"{timestamp}{method}{path_without_query}".encode('utf-8')
# Sign with RSA-PSS
signature = private_key.sign(
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.DIGEST_LENGTH
),
hashes.SHA256()
)
# Return base64 encoded
return base64.b64encode(signature).decode('utf-8')
Step 3: Get Your Balance
Now let’s make your first authenticated request to get your account balance:
import requests
import datetime
# Set up the request
timestamp = str(int(datetime.datetime.now().timestamp() * 1000))
method = "GET"
path = "/trade-api/v2/portfolio/balance"
# Create signature (using function from Step 2)
signature = sign_request(private_key, timestamp, method, path)
# Make the request
headers = {
'KALSHI-ACCESS-KEY': 'your-api-key-id',
'KALSHI-ACCESS-SIGNATURE': signature,
'KALSHI-ACCESS-TIMESTAMP': timestamp
}
response = requests.get('https://external-api.demo.kalshi.co' + path, headers=headers)
balance = response.json()
print(f"Your balance: ${balance['balance'] / 100:.2f}")
Complete Working Example
Here’s the minimal code to get your balance:
import requests
import datetime
import base64
from urllib.parse import urlparse
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import padding
# Configuration
API_KEY_ID = 'your-api-key-id-here'
PRIVATE_KEY_PATH = 'path/to/your/kalshi-key.key'
BASE_URL = 'https://external-api.demo.kalshi.co/trade-api/v2' # or 'https://external-api.kalshi.com/trade-api/v2' for production
def load_private_key(key_path):
with open(key_path, "rb") as f:
return serialization.load_pem_private_key(f.read(), password=None, backend=default_backend())
def create_signature(private_key, timestamp, method, path):
"""Create the request signature."""
# Strip query parameters before signing
path_without_query = path.split('?')[0]
message = f"{timestamp}{method}{path_without_query}".encode('utf-8')
signature = private_key.sign(
message,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH),
hashes.SHA256()
)
return base64.b64encode(signature).decode('utf-8')
def get(private_key, api_key_id, path, base_url=BASE_URL):
"""Make an authenticated GET request to the Kalshi API."""
timestamp = str(int(datetime.datetime.now().timestamp() * 1000))
# Signing requires the full URL path from root (e.g. /trade-api/v2/portfolio/balance)
sign_path = urlparse(base_url + path).path
signature = create_signature(private_key, timestamp, "GET", sign_path)
headers = {
'KALSHI-ACCESS-KEY': api_key_id,
'KALSHI-ACCESS-SIGNATURE': signature,
'KALSHI-ACCESS-TIMESTAMP': timestamp
}
return requests.get(base_url + path, headers=headers)
# Load private key
private_key = load_private_key(PRIVATE_KEY_PATH)
# Get balance
response = get(private_key, API_KEY_ID, "/portfolio/balance")
print(f"Your balance: ${response.json()['balance'] / 100:.2f}")
Common Issues
| Problem | Solution |
|---|
| 401 Unauthorized | Check your API Key ID and private key file path |
| Signature error | Ensure timestamp is in milliseconds (not seconds) |
| Path not found | If your BASE_URL already ends with /trade-api/v2, pass only the endpoint path to the helper (e.g. /portfolio/balance, not /trade-api/v2/portfolio/balance) so the request URL is not double-prefixed |
| Signature error with query params | Sign the request path without query parameters. The examples do this with path.split('?')[0] after building the full URL path |
Next Steps
Now you can make authenticated requests! Try these endpoints (relative to BASE_URL):
/portfolio/positions - Get your positions
/portfolio/orders - View your orders
/markets - Browse available markets
For more details, see the Complete Order Lifecycle guide or explore the API Reference.