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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
facba21cbeaa61cc8bca2252589726fc72659f4b5d7603763c1bd89d26d0822c
|
|
| MD5 |
5c3c3bdeed2811e3e553a9e3eeedc9aa
|
|
| BLAKE2b-256 |
6d9040d60484b42d3cb5c1228e7d42b2c260fc00b1650f4c713d268769c53f7e
|
Provenance
The following attestation bundles were made for parlayx-1.1.0.tar.gz:
Publisher:
release-python-sdk.yml on parlayx/aggregator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
parlayx-1.1.0.tar.gz -
Subject digest:
facba21cbeaa61cc8bca2252589726fc72659f4b5d7603763c1bd89d26d0822c - Sigstore transparency entry: 2747536365
- Sigstore integration time:
-
Permalink:
parlayx/aggregator@5919164853b88519d19a9f47d05e08cff0ba8a70 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/parlayx
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-python-sdk.yml@5919164853b88519d19a9f47d05e08cff0ba8a70 -
Trigger Event:
workflow_dispatch
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1757b6a3188916e99a03609a9bef3335e6bc90e2ebb8901e45f0d6537bf3af65
|
|
| MD5 |
1c087585dedf0f9c506b3b12e9fb3f75
|
|
| BLAKE2b-256 |
ba9f763a3f72eedaa038a7a7487a17e0b5b42bec50bfcbb9cb74360086373873
|
Provenance
The following attestation bundles were made for parlayx-1.1.0-py3-none-any.whl:
Publisher:
release-python-sdk.yml on parlayx/aggregator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
parlayx-1.1.0-py3-none-any.whl -
Subject digest:
1757b6a3188916e99a03609a9bef3335e6bc90e2ebb8901e45f0d6537bf3af65 - Sigstore transparency entry: 2747536853
- Sigstore integration time:
-
Permalink:
parlayx/aggregator@5919164853b88519d19a9f47d05e08cff0ba8a70 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/parlayx
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-python-sdk.yml@5919164853b88519d19a9f47d05e08cff0ba8a70 -
Trigger Event:
workflow_dispatch
-
Statement type: