Official Python SDK for Polymarket.
The SDK gives Python developers one coherent, workflow-oriented interface for building on Polymarket across public data, authenticated account, trading, builder attribution, and wallet workflows.
uv add polymarket-clientor:
pip install polymarket-clientSynchronous client:
from polymarket import Market, PublicClient
with PublicClient() as client:
market: Market = client.get_market(url="https://polymarket.com/event/example-market")Asynchronous client:
import asyncio
from polymarket import AsyncPublicClient, Market
async def main() -> None:
async with AsyncPublicClient() as client:
market: Market = await client.get_market(
url="https://polymarket.com/event/example-market"
)
asyncio.run(main())Custom transaction calls:
from polymarket.calls import merge_v2_call
from polymarket.types import EvmAddress
router = EvmAddress(client.environment.protocol_v2_router)
handle = client.execute_transaction(
calls=[
merge_v2_call(router=router, condition_id="0x03...", amount=1_000_000),
merge_v2_call(router=router, condition_id="0x03...", amount=2_000_000),
merge_v2_call(router=router, condition_id="0x03...", amount=500_000),
],
metadata="Merge 3 combo positions",
)
outcome = handle.wait()Batch position merges:
# Merge regular market positions by condition or market id.
handle = client.merge_multiple_positions(
positions=[
{"condition_id": condition_id_1},
{"market_id": market_id_2, "amount": "max"},
{"condition_id": condition_id_3, "amount": 500_000},
],
)
outcome = handle.wait()
# Or merge combo positions by position id. Do not mix market and combo
# requests in the same batch.
handle = client.merge_multiple_positions(
positions=[
{"position_id": combo_position_id_1},
{"position_id": combo_position_id_2, "amount": "max"},
{"position_id": combo_position_id_3, "amount": 500_000},
],
)
outcome = handle.wait()The SDK follows semantic versioning. Although minor releases on the 0.x line may include breaking changes, we aim to avoid them and, whenever possible, provide a deprecation path before removing or changing an API. Patch releases remain backward compatible except for APIs marked experimental, which may change in any release. All Perps APIs are currently experimental, including client methods, sessions, stream subscriptions, and models.
See SDK Direction for public API design principles and developer-experience decisions.
Install dependencies:
make syncRun checks:
make checkBuild package artifacts:
make buildThe Makefile is a thin convenience wrapper around uv. Running the underlying commands directly is also fine.
Unit tests run by default:
make testRun unit tests in watch mode:
make test-watchThis runs the tests once immediately, then reruns them when Python files change.
Integration tests are opt-in:
make test-integrationIntegration tests can load local secrets from a gitignored .env copied from .env.example:
cp .env.example .envSee .env.example for the supported local and CI secret names.
Tests that require credentials should use the require_env fixture so they skip when secrets are unavailable:
import pytest
@pytest.mark.integration
def test_authenticated_flow(require_env):
private_key = require_env("POLYMARKET_PRIVATE_KEY")
builder_api_key = require_env("POLYMARKET_BUILDER_API_KEY")
assert private_key
assert builder_api_keyThe SDK does not load .env files at runtime. The integration test fixture loads .env only for tests that request credentials, and existing environment variables take precedence over local .env values.
Tests that place orders, spend funds, or mutate live state must also use @pytest.mark.metered. Metered tests are skipped unless POLYMARKET_RUN_METERED_TESTS=1 is set:
import pytest
@pytest.mark.integration
@pytest.mark.metered
def test_order_lifecycle(require_env):
private_key = require_env("POLYMARKET_PRIVATE_KEY")
assert private_keyPOLYMARKET_RUN_METERED_TESTS=1 make test-integrationWe welcome bug reports, feature requests, and feedback through GitHub Issues. See CONTRIBUTING.md before proposing code changes.