Skip to main content

parlayx

Official Python client for the ParlayX public API, a single trading interface over aggregated prediction markets.

Documentation

Full guides, authentication, and API reference live at docs.parlayx.com.

Install

pip install parlayx

Requires Python 3.11 or newer. Requests are signed with your Ed25519 key, so the client runs server-side: the private key stays in your own process and only the key id, a timestamp, and the signature go on the wire.

Usage

Set your credentials in the environment:

export PARLAYX_KEY_ID="3f7c1b2a-9d4e-4a61-b8f0-2c5d7e91a4b3"
export PARLAYX_PRIVATE_KEY_HEX="a3f1...9c20"  # 64 hex characters
from parlayx import ParlayX

client = ParlayX()

who = client.whoami()
competitions = client.list_competitions(sport="spt_baseball")
balance = client.kalshi.get_balance()

PARLAYX_PRIVATE_KEY_HEX is your key's 32-byte seed as 64 lowercase hex characters.

Pass either credential directly to take it from somewhere else, such as a secret manager (vault below is your own client). An argument always wins over the environment, and each resolves on its own, so the key id can be inline while the private key stays out of your source:

client = ParlayX(private_key_hex=vault.read("parlayx/private-key"))

A credential that is neither passed nor exported raises ValueError naming the variable it wanted, at construction rather than on your first request. Only an omitted argument falls back to the environment: an argument you did pass is used as given, and a blank one is rejected rather than replaced, so a lookup of your own that returned "" cannot end up signing as whatever account the environment happens to hold.

Orders, positions and balances are venue-scoped, because the venues address markets differently:

from parlayx import KalshiOrderRequest

order = client.kalshi.submit_order(
    KalshiOrderRequest(
        ticker="KXMLBGAME-26SEP01-NYY",
        side="YES",
        action="BUY",
        count=10,
        price_cents=45,
    )
)
print(order.order_id, order.status)

Fields accept either spelling: price_cents or priceCents. The wire always carries the API's own camelCase.

Each of client.polymarket and client.kalshi carries the same seven methods: submit_order, list_orders, get_order, cancel_order, get_order_fills, list_positions and get_balance.

Idempotency

submit_order attaches a fresh idempotency key per call, so retrying a call that failed submits a second order. Pass your own key to make a specific retry safe:

client.kalshi.submit_order(order, idempotency_key="my-retry-key")

Errors

Every non-2xx response raises RequestError, carrying a stable code, the HTTP status, a message and the parsed body:

from parlayx import ApiErrorCode, RequestError

try:
    client.polymarket.get_balance()
except RequestError as error:
    if error.code == ApiErrorCode.insufficient_balance:
        ...

A RATE_LIMITED refusal also carries retry_after_seconds, read from the Retry-After header. It is None when the header is absent or unreadable, so keep a fallback:

import time

try:
    balance = client.polymarket.get_balance()
except RequestError as error:
    if error.code != "RATE_LIMITED":
        raise
    time.sleep(error.retry_after_seconds or 1)
    balance = client.polymarket.get_balance()

Pagination

Order listings are cursored. The paginators follow nextPageToken to exhaustion:

from parlayx import paginate_kalshi_orders

for order in paginate_kalshi_orders(client, status="OPEN"):
    print(order.order_id, order.status)

Positions and fills are not cursored and return their full result in one call.

Async

AsyncParlayX is the same surface with await, and apaginate_kalshi_orders and apaginate_polymarket_orders are the async paginators:

from parlayx import AsyncParlayX

async with AsyncParlayX() as client:
    who = await client.whoami()

Market data stream

The stream is a separate import, with nothing extra to install:

from parlayx.stream import StreamClient

It authenticates with the same signing key, re-signing the handshake on every connect, and replays your subscriptions across reconnects so you subscribe once:

from parlayx.stream import StreamClient

stream = StreamClient()

async with stream:
    await stream.subscribe([{"venue": "polymarket", "tokenId": "97840505..."}])
    async for frame in stream:
        if frame.type == "snapshot":
            print(frame.seq, frame.bids[:3], frame.asks[:3])
        elif frame.type == "delta":
            for change in frame.changes:
                ...  # size 0 removes the level, anything else sets it

channels defaults to ["book"]; pass ["book", "trade"] for the trade tape as well.

Connection lifecycle is reported through callbacks rather than frames, because it describes the connection rather than the market:

StreamClient(
    on_reconnecting=lambda event: log.info("reconnecting, attempt %s", event.attempt),
    on_terminated=lambda event: log.warning("stream stopped: %s", event.reason),
    on_unparseable=lambda raw: log.warning("dropped an unrecognised frame: %s", raw),
)

A terminated stream does not reconnect; build a new client to resume.

One inconsistency worth knowing

Discovery listings report venue in uppercase (KALSHI, POLYMARKET), while the order and streaming surfaces take it lowercase. The client passes the value through unchanged in both directions rather than quietly rewriting it.

License

MIT. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

parlayx-1.1.0.tar.gz (29.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

parlayx-1.1.0-py3-none-any.whl (35.6 kB view details)

Uploaded Python 3

File details

Details for the file parlayx-1.1.0.tar.gz.

File metadata

  • Download URL: parlayx-1.1.0.tar.gz
  • Upload date:
  • Size: 29.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for parlayx-1.1.0.tar.gz
Algorithm Hash digest
SHA256 facba21cbeaa61cc8bca2252589726fc72659f4b5d7603763c1bd89d26d0822c
MD5 5c3c3bdeed2811e3e553a9e3eeedc9aa
BLAKE2b-256 6d9040d60484b42d3cb5c1228e7d42b2c260fc00b1650f4c713d268769c53f7e

See more details on using hashes here.

Provenance

The following attestation bundles were made for parlayx-1.1.0.tar.gz:

Publisher: release-python-sdk.yml on parlayx/aggregator

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file parlayx-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: parlayx-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 35.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for parlayx-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1757b6a3188916e99a03609a9bef3335e6bc90e2ebb8901e45f0d6537bf3af65
MD5 1c087585dedf0f9c506b3b12e9fb3f75
BLAKE2b-256 ba9f763a3f72eedaa038a7a7487a17e0b5b42bec50bfcbb9cb74360086373873

See more details on using hashes here.

Provenance

The following attestation bundles were made for parlayx-1.1.0-py3-none-any.whl:

Publisher: release-python-sdk.yml on parlayx/aggregator

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 files

1.0.0

2 files

0.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page