Skip to main content

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

  1. Log in to your Kalshi account (demo or production)
  2. Navigate to Account & securityAPI Keys
  3. Click Create Key
  4. 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:
HeaderDescriptionExample
KALSHI-ACCESS-KEYYour API Key IDa952bcbe-ec3b-4b5b-b8f9-11dae589608c
KALSHI-ACCESS-TIMESTAMPCurrent time in milliseconds1703123456789
KALSHI-ACCESS-SIGNATURERequest signature (see below)base64_encoded_signature

How to Create the Signature

The signature proves you own the private key. Here’s how it works:
  1. 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.
  2. Sign with your private key: Use RSA-PSS with SHA256
  3. 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

ProblemSolution
401 UnauthorizedCheck your API Key ID and private key file path
Signature errorEnsure timestamp is in milliseconds (not seconds)
Path not foundIf 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 paramsSign 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.