Skip to main content

API Keys

API keys are the primary authentication mechanism for the PolySimulator API. Each key is tied to a user account, has configurable permissions, and can be revoked instantly.
Closed beta (ongoing). API key issuance is cohort-gated. New keys are issued to approved cohorts only, so POST /v1/keys/bootstrap (first key) and POST /v1/keys (additional keys) return a 403 for callers who aren’t yet admitted — CLOSED_BETA for free / waitlisted accounts, or API_PRO_COMING_SOON for paying Pro / Pro+ accounts that don’t yet have a cohort grant:
HTTP/1.1 403 Forbidden
X-Polysim-Code: CLOSED_BETA
Content-Type: application/json

{"error": "API access is in closed beta. New keys are issued to approved cohorts only. Apply via the waitlist; we'll email you when a cohort opens."}
Branch on the X-Polysim-Code response header (the body’s error is the human message) and surface a “join the waitlist” prompt. While the beta is closed, every non-admitted caller — including paying Pro / Pro+ — gets CLOSED_BETA; API_PRO_COMING_SOON appears only once self-serve issuance is enabled. Apply via the waitlist at polysimulator.com/api-trading. See Authentication → Closed Beta for the full lifecycle, including the separate runtime ACCESS_RESTRICTED allowlist gate on already-issued keys.

Create a Key

curl -X POST https://api.polysimulator.com/v1/keys \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-trading-bot",
    "permissions": ["read", "trade"],
    "tier": "pro"
  }'
Free-tier keys are read-only. A free key requesting trade returns 403 TIER_REQUIRES_UPGRADE — so a ["read", "trade"] key needs a paid tier (pro / pro_plus / enterprise). Omit permissions to take the per-tier default (free["read"], paid → ["read", "trade"]).
Response (201 Created):
{
  "id": 1,
  "raw_key": "ps_live_kJ9mNx2pQrStUvWxYz01Ab3CdEfGhI4j...",
  "key_prefix": "ps_live_kJ9mNx2p",
  "name": "my-trading-bot",
  "rate_limit_tier": "pro",
  "permissions": ["read", "trade"],
  "created_at": "2026-02-06T12:00:00Z"
}
The raw_key field is shown exactly once. Store it securely — it cannot be retrieved again. Only the SHA-256 hash is stored in the database.
FieldDescription
idNumeric key ID (used for revocation)
raw_keyFull API key — save this immediately
key_prefixFirst 16 chars (used for identification in listings)
nameYour label for this key
rate_limit_tierfree, pro, pro_plus, or enterprise (see GET /v1/keys/tiers)
permissionsArray of granted permissions
created_atISO 8601 timestamp

List Keys

Returns all keys for your account. Only prefixes are shown — never the full key.
curl -H "X-API-Key: $API_KEY" https://api.polysimulator.com/v1/keys
[
  {
    "id": 1,
    "key_prefix": "ps_live_kJ9mNx2p",
    "name": "my-trading-bot",
    "permissions": ["read", "trade"],
    "rate_limit_tier": "pro",
    "is_active": true,
    "created_at": "2026-02-06T12:00:00Z",
    "expires_at": null
  }
]

Revoke a Key

Permanently deactivates a key. This action cannot be undone.
curl -X DELETE -H "X-API-Key: $API_KEY" \
  https://api.polysimulator.com/v1/keys/1
{
  "message": "Key revoked successfully"
}

Key Limits

ConstraintValue
Max keys per user5
Key formatps_live_<64 hex chars>
StorageSHA-256 hash only
ExpirationNot set at creation — expires_at is server-managed (populated on a rotated key’s old half during its 24h overlap, and on beta keys as their cutoff). Use POST /v1/keys/{id}/rotate for short-lived keys.

Error Handling

StatusEndpointMeaning
201POST /v1/keysKey created — save raw_key immediately
400POST /v1/keysMax 5 keys reached, or unknown tier
400POST /v1/keys/bootstrapYou already have key(s) — use POST /v1/keys with X-API-Key
401AnyInvalid, expired, or deactivated API key
401POST /v1/keys/bootstrapInvalid or expired Supabase JWT
403POST /v1/keys, POST /v1/keys/bootstrapCLOSED_BETA — key issuance is in closed beta (the default for every non-admitted caller, including paying Pro / Pro+). The API_PRO_COMING_SOON variant appears only once self-serve issuance is enabled. Branch on the X-Polysim-Code response header.
# Robust key creation with error handling
resp = requests.post(
    f"{BASE_URL}/v1/keys",
    headers={"X-API-Key": API_KEY},
    json={"name": "my-bot", "permissions": ["read", "trade"]},
)

if resp.status_code == 201:
    key_data = resp.json()
    print(f"Save this key NOW: {key_data['raw_key']}")
elif resp.status_code == 400:
    err = resp.json()
    print(f"Cannot create key: {err.get('message', err.get('error'))}")
elif resp.status_code == 401:
    print("API key is invalid or expired — re-authenticate")
elif resp.status_code == 403:
    # Key issuance is closed-beta gated; branch on the stable machine
    # code in the `X-Polysim-Code` header (the body's `error` is the
    # human message).
    code = resp.headers.get("X-Polysim-Code")
    if code == "CLOSED_BETA":
        # Your account isn't in an admitted cohort yet. Apply via the
        # waitlist at https://polysimulator.com/api-trading.
        print("Closed beta — apply at https://polysimulator.com/api-trading")
    elif code == "API_PRO_COMING_SOON":
        # Paying Pro / Pro+ without a cohort grant — API access is
        # rolling out in cohorts. Check /account/billing.
        print("Pro API access is rolling out — see /account/billing")
    elif code == "TIER_REQUIRES_UPGRADE":
        # Free tier is read-only: requesting ["read", "trade"] without a
        # paid tier is rejected. Omit "trade" or upgrade first.
        print("Free tier cannot create trade-scoped keys — upgrade or drop 'trade'")
    elif code == "INSUFFICIENT_PERMISSION":
        print("Key lacks the required permission for this endpoint")
    else:
        print(f"403 [{code}]: {resp.json().get('error')}")
else:
    print(f"Unexpected error: {resp.status_code}")

Next Steps