Skip to content

API keys

Manage RSA API keys programmatically — list, create with a BYO public key, have the server mint a fresh pair, or delete.

Auth required throughout (you need an existing key to manage keys).

Quick reference

Method Endpoint
list(*, fcm_subtrader_id=None) GET /api_keys
create(*, name, public_key, scopes=None, subaccount=None, fcm_subtrader_id=None) POST /api_keys
generate(*, name, scopes=None, subaccount=None, fcm_subtrader_id=None, key_type=None) POST /api_keys/generate
delete(api_key) DELETE /api_keys/{api_key}

Subaccount-scoped keys (spec v3.23.0)

Pass subaccount=<0-63> to create() / generate() to restrict a key to a single subaccount. Omit it (the default) for an account-wide key. The value is bounded to 0-63 client-side; ApiKey.subaccount echoes it back on list() (None for account-wide keys).

FCM-bound keys

FCM members can pass fcm_subtrader_id="{user_id}_{suffix}" instead of subaccount to bind a key to a single FCM subtrader. The two are mutually exclusive. A bound key is the institution's trading credential for that subtrader (FIX + margin WebSocket) and is denied on every REST endpoint. list(fcm_subtrader_id=...) filters to keys bound to that subtrader. Create/generate may return warning when the subtrader has no initial-margin cap.

List

resp = client.api_keys.list()
for key in resp.api_keys:
    print(key.api_key, key.name, key.scopes, key.created_ts)

Server-minted pair (generate)

The simplest path — Kalshi mints the keypair, you store the private key once:

resp = client.api_keys.generate(name="ci-bot-2026", scopes=["read", "write"])
private_pem = resp.private_key.get_secret_value()   # SecretStr — see warning
print(resp.api_key_id)                               # the key id
# Persist private_pem somewhere safe; you will not see it again.

key_type is "rsa" or "ed25519". Omit it and the server mints RSA, which is what KalshiAuth can sign with. An Ed25519 private key (key_type="ed25519", PKCS#8 PEM) is returned the same way, but this SDK's request signer is RSA-PSS only — it cannot authenticate calls with that key. resp.key_type echoes the algorithm when the server sends it.

private_key is a SecretStr — and you only see it once

resp.private_key is a pydantic.SecretStr. print(resp.private_key) will print **********, not the key. Use .get_secret_value() to extract the PEM, and store it before the response goes out of scope. Kalshi cannot retrieve a server-minted private key after this call.

Scopes

Scope Allows
read All GET endpoints
write All write endpoints — requires read

The server default when you omit scopes is ["read", "write"].

BYO public key (create)

If you'd rather mint the keypair yourself (better for HSM / KMS workflows), generate locally and upload only the public half:

from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_pem = key.public_key().public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo,
).decode()

resp = client.api_keys.create(name="self-minted", public_key=public_pem)
print(resp.api_key.api_key)

The private half never touches Kalshi.

Rotate a key

# 1) Mint the replacement.
new = client.api_keys.generate(name="prod-bot-2026-q2")
new_pem = new.private_key.get_secret_value()

# 2) Swap your application's credentials over to `new`.

# 3) Once you've confirmed the new key works:
client.api_keys.delete("old-key-id")

Delete is not server-idempotent

delete(api_key) propagates a 404 as KalshiNotFoundError when the key has already been revoked or never existed. The SDK does not swallow it — the caller owns safe-retry idempotency:

from kalshi.errors import KalshiNotFoundError

try:
    client.api_keys.delete(key_id)
except KalshiNotFoundError:
    pass  # already revoked — idempotent

Reference

kalshi.resources.api_keys.ApiKeysResource

ApiKeysResource(transport: SyncTransport)

Bases: SyncResource

Sync API keys resource.

All endpoints require authentication. create takes a caller-minted RSA public key; generate has Kalshi mint a pair and returns the private key once (see :class:GenerateApiKeyResponse). generate may request key_type="ed25519"; :class:kalshi.auth.KalshiAuth signs with RSA-PSS only.

generate

generate(
    *,
    request: GenerateApiKeyRequest,
    extra_headers: dict[str, str] | None = None
) -> GenerateApiKeyResponse
generate(
    *,
    name: str,
    scopes: list[str] | None = ...,
    subaccount: int | None = ...,
    fcm_subtrader_id: str | None = ...,
    key_type: ApiKeyTypeLiteral | None = ...,
    extra_headers: dict[str, str] | None = None
) -> GenerateApiKeyResponse
generate(
    *,
    request: GenerateApiKeyRequest | None = None,
    name: str | None = None,
    scopes: list[str] | None = None,
    subaccount: int | None = None,
    fcm_subtrader_id: str | None = None,
    key_type: ApiKeyTypeLiteral | None = None,
    extra_headers: dict[str, str] | None = None
) -> GenerateApiKeyResponse

Mint a key pair. key_type defaults server-side to rsa.

The SDK signer (:class:kalshi.auth.KalshiAuth) is RSA-PSS only; an ed25519 private key returned here cannot authenticate REST calls through this client.

kalshi.resources.api_keys.AsyncApiKeysResource

AsyncApiKeysResource(transport: AsyncTransport)

Bases: AsyncResource

Async API keys resource.

generate async

generate(
    *,
    request: GenerateApiKeyRequest,
    extra_headers: dict[str, str] | None = None
) -> GenerateApiKeyResponse
generate(
    *,
    name: str,
    scopes: list[str] | None = ...,
    subaccount: int | None = ...,
    fcm_subtrader_id: str | None = ...,
    key_type: ApiKeyTypeLiteral | None = ...,
    extra_headers: dict[str, str] | None = None
) -> GenerateApiKeyResponse
generate(
    *,
    request: GenerateApiKeyRequest | None = None,
    name: str | None = None,
    scopes: list[str] | None = None,
    subaccount: int | None = None,
    fcm_subtrader_id: str | None = None,
    key_type: ApiKeyTypeLiteral | None = None,
    extra_headers: dict[str, str] | None = None
) -> GenerateApiKeyResponse

Async :meth:ApiKeysResource.generate. RSA-PSS signer only.