Skip to main content

Official alpha Python SDK for the Drazill Prediction Market API

Project description

drazill

Official alpha Python SDK for the Drazill Prediction Market API. The SDK is MIT licensed while the broader monorepo has its own repository license.

Status: source-available (this repo) — registry publish pending. The drazill package is generated and tested but is not yet published to PyPI. Install it from source (below) for now; the pip install drazill commands throughout this README start working once the owner runs the release pipeline with registry tokens (see ../PUBLISHING.md).

Installation

Until the package is published, install from the monorepo checkout:

pip install -e sdks/python
# with WebSocket support:
pip install -e "sdks/python[websocket]"

Once published to PyPI (registry publish pending), this becomes:

pip install drazill
# with WebSocket support:
pip install drazill[websocket]

Requirements: Python 3.9+

Quick Start

from drazill import DrazillClient

client = DrazillClient(api_key="<DRAZILL_API_KEY>")

# List active markets
result = client.markets.list_markets(status="ACTIVE", limit=10)
for market in result.items:
    print(f"{market.title} - Volume: ${market.total_volume}")

Runnable quickstart examples

Two self-contained scripts under examples/ run end-to-end against staging (safe test data, no real money):

Script What it does Scopes
quickstart_markets.py List open markets → outcomes/prices → live order book → recent trades (read-only) markets:read
quickstart_trade.py Fee-inclusive quote → place a small market BUY → print the execution receipt markets:read, orders:write (fees:read optional)
pip install drazill                # or, from the monorepo: pip install -e sdks/python
export DRAZILL_API_KEY="sk_...your staging key..."
export DRAZILL_BASE_URL="https://staging.drazill.com/api/v1"   # optional; this is the default
python examples/quickstart_markets.py
python examples/quickstart_trade.py                            # DRAZILL_QUOTE_ONLY=1 to preview only

Issuing the demo key. Create a scoped key in the developer portal at /developers/api-keys (the secret is shown once — copy it). Grant only the scopes the script needs. The trade script spends test balance, so fund the key's account first via the staging "Confirm test deposit" flow or the test-balance faucet. Never commit a key — pass it through the environment.

Environment variables both scripts honor: DRAZILL_API_KEY (required), DRAZILL_BASE_URL (defaults to staging). The trade script also honors DRAZILL_MARKET_SLUG, DRAZILL_OUTCOME_ID, DRAZILL_ORDER_QTY (default 1 share), and DRAZILL_QUOTE_ONLY.

API coverage & method naming

Public API surface (generated): 65 resource groups · 460 operations · 864 types. Regenerated from docs/api/openapi.v1.json by sdks/generate_sdks.py; kept honest by the DX-19 drift gate.

The SDK mirrors the entire public API contract — every resource group covering markets, orders, wallet, users, comments, watchlist, notifications, webhooks, disputes, moderation, rewards, compliance, chat, copy-trading, lessons, market intelligence, paper trading, sports, crypto, rank, parlays, conditional/bracket/TWAP/trailing-stop orders, price alerts, referrals, api-keys, security, geo, fees, and more.

Resources and methods are generated from the OpenAPI contract, so each method name matches the API operation it calls (for example GET /markets becomes client.markets.list_markets() and POST /orders becomes client.orders.place_order(...)). Path parameters are positional, query parameters are keyword-only, and request bodies are passed as the first positional argument (a typed model or a plain dict):

client.markets.get_market("market-uuid")                 # path param
client.markets.list_markets(status="ACTIVE", limit=20)    # query params
client.orders.place_order({"market_id": "m", ...})        # request body

Every method exists in both sync (DrazillClient) and async (AsyncDrazillClient) clients.

Maintainers: the resource/type modules are produced by sdks/generate_sdks.py from docs/api/openapi.v1.json. Regenerate with python3 scripts/api/generate_openapi.py && python3 sdks/generate_sdks.py.

Authentication

Create an API key from your Drazill dashboard (requires a registered account).

# API key authentication (recommended)
client = DrazillClient(api_key="<DRAZILL_API_KEY>")

# Bearer token authentication (for Cognito tokens)
client = DrazillClient(bearer_token="eyJhbGciOi...")

Configuration

client = DrazillClient(
    api_key="<DRAZILL_API_KEY>",
    # Omit to use the default; see "Which environment?" below.
    base_url="https://staging.drazill.com/api/v1",
    timeout=10.0,   # seconds
    max_retries=3,   # retry on 5xx and 429
)

Which environment?

This SDK talks to staging by default — test data, test money, no real funds at risk. That is deliberate: production is not a separate live environment yet, so the default stays on staging until the release that ships alongside it.

The base URL resolves in this order:

  1. the base_url= argument, if you pass one;
  2. the DRAZILL_BASE_URL environment variable;
  3. https://staging.drazill.com/api/v1 (the default).

The realtime URL resolves the same way via DRAZILL_WS_URL, defaulting to wss://staging.drazill.com/ws. client.ws() derives its URL from whatever the client resolved, so the two can never disagree.

export DRAZILL_BASE_URL="http://localhost:8000/api/v1"   # e.g. a local backend

You can point at any environment explicitly, but no environment other than staging is ever selected for you.

Async Client

from drazill import AsyncDrazillClient

async with AsyncDrazillClient(api_key="<DRAZILL_API_KEY>") as client:
    markets = await client.markets.list_markets(status="ACTIVE")
    print(f"Found {markets.total} markets")

Request signing (optional, FP-107)

If your key was created with signing_required, every request must carry an HMAC signature. Sign "<unix_ts>.<METHOD>.<path>." + body with the signing secret returned once at key creation, and send X-Drazill-Timestamp + X-Drazill-Signature: v1=<hex>:

import hashlib
import hmac
import time


def drazill_signature(secret: str, method: str, path: str, body: bytes = b"") -> dict:
    ts = int(time.time())
    signed = f"{ts}.{method.upper()}.{path}.".encode() + body
    digest = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return {"X-Drazill-Timestamp": str(ts), "X-Drazill-Signature": f"v1={digest}"}

For safe automated testing, create a key with the env:paper scope — real-money actions are blocked and you target the paper-trading endpoints.

Markets

# List markets with filters
result = client.markets.list_markets(
    status="ACTIVE",
    q="bitcoin",
    sort_by="total_volume",
    limit=20,
)

# Get a single market
market = client.markets.get_market("market-uuid")
market = client.markets.get_market_by_slug("will-bitcoin-reach-100k")

# Featured and trending
featured = client.markets.get_featured_markets()
trending = client.markets.get_trending_markets()
for_you = client.markets.get_for_you_markets()

# Categories, tags, stats
categories = client.markets.list_categories()
tags = client.markets.get_popular_tags()
stats = client.markets.get_market_stats("market-uuid")

# Trades and price history
trades = client.markets.get_market_trades("market-uuid")
history = client.markets.get_market_price_history("market-uuid", interval="1h")

Orders

# Place a limit order (body may be a dict or a drazill.types model)
order = client.orders.place_order({
    "market_id": "market-uuid",
    "outcome_id": "outcome-uuid",
    "side": "BUY",
    "type": "LIMIT",
    "price": "0.65",
    "quantity": "10",
})

# Place a market order and ask for an execution receipt
order = client.orders.place_order(
    {"market_id": "m", "outcome_id": "o", "side": "BUY", "type": "MARKET", "quantity": "10"},
    include_receipt=True,
)

# List your orders / open orders
result = client.orders.list_my_orders(status="OPEN")
open_orders = client.orders.list_open_orders()

# Cancel
client.orders.cancel_order("order-uuid")
client.orders.cancel_all_orders()

# Order book and price impact
book = client.orders.get_order_book("outcome-uuid")
impact = client.orders.get_price_impact("outcome-uuid", side="BUY", quantity="100")

# Pre-trade quote and your trade history
quote = client.orders.preview_order_quote({"outcome_id": "o", "side": "BUY", "quantity": "10"})
my_trades = client.orders.get_my_trades()

Wallet

wallet = client.wallet.get_wallet()
print(f"Balance: ${wallet.balance} CAD")

portfolio = client.wallet.get_portfolio_summary()
positions = client.wallet.list_positions()
txs = client.wallet.list_transactions(type="TRADE_BUY", limit=50)

# Payments
methods = client.wallet.list_payment_methods()
client.wallet.create_deposit({"amount": "100.00", "payment_method_id": "pm_x"})
client.wallet.create_withdrawal({"amount": "50.00"})

# Analytics
analytics = client.wallet.get_portfolio_analytics()
pnl = client.wallet.get_pnl_today()

Users

me = client.users.get_my_profile()
client.users.update_my_profile({"bio": "Prediction market enthusiast"})
stats = client.users.get_my_stats()

profile = client.users.get_user_profile("username")
leaders = client.users.get_leaderboard()

client.users.follow_user("username")
client.users.unfollow_user("username")
results = client.users.search_users(q="john")

# Avatar upload (multipart) — pass file bytes or an (filename, bytes, content-type) tuple
with open("avatar.png", "rb") as fh:
    client.users.upload_avatar(fh.read())

Comments, Watchlist & Notifications

# Comments
comments = client.comments.get_comments("market-uuid")
client.comments.create_comment({"market_id": "market-uuid", "content": "Interesting!"})
client.comments.toggle_like_comment("comment-uuid")

# Watchlist
items = client.watchlist.get_watchlist()
client.watchlist.add_to_watchlist("market-uuid")
result = client.watchlist.toggle_watchlist("market-uuid")
print(f"Watchlisted: {result.is_watchlisted}")

# Notifications
notifs = client.notifications.get_notifications()
count = client.notifications.get_notification_count()
client.notifications.mark_notifications_read({"notification_ids": ["notification-uuid"]})
client.notifications.mark_all_notifications_read()

Pagination

List endpoints return a typed page object (items, total, page, pages); cursor endpoints (suffixed _cursor) return items plus next_cursor. Rather than hand-roll the loop, use the exported iterators — pass a bound resource method and its params and iterate items directly:

from drazill import iter_cursor, iter_offset

# Cursor pagination (the platform's canonical signed-cursor scheme)
for market in iter_cursor(client.markets.list_markets_cursor, limit=50):
    print(market.title)

# Offset pagination
for market in iter_offset(client.markets.list_markets, status="ACTIVE", limit=50):
    print(market.title)

Use aiter_cursor / aiter_offset with async for on the async client. Each iterator fetches one page at a time and stops when the data runs out.

Webhooks

Create API keys with webhooks:read and webhooks:write scopes before using the webhook management API.

from drazill import construct_webhook_event, verify_webhook_signature

# Register an endpoint. The whsec_ secret is returned only once.
endpoint = client.webhooks.create_webhook({
    "url": "https://example.com/drazill/webhooks",
    "subscribed_events": ["order.filled", "trade.executed", "wallet.deposit.completed"],
})
print(endpoint.secret)

# Send sandbox deliveries without mutating market/order/wallet/reward state.
client.webhooks.send_webhook_test_event(endpoint.id)
client.webhooks.create_sandbox_webhook_event({
    "endpoint_id": endpoint.id,
    "event_type": "order.filled",
    "data": {"order_id": "00000000-0000-0000-0000-000000000000"},
})

# Inspect and replay failed deliveries.
failures = client.webhooks.list_endpoint_deliveries(endpoint.id, status="FAILED")
for delivery in failures.items:
    client.webhooks.replay_webhook_delivery(delivery.id)

# Verify an incoming request before parsing.
is_valid = verify_webhook_signature(raw_body, headers, endpoint.secret)
if not is_valid:
    raise ValueError("Invalid Drazill webhook signature")

event = construct_webhook_event(raw_body, headers, endpoint.secret)
print(event.type, event.data)

WebSocket (Real-Time Data)

Requires pip install drazill[websocket].

import asyncio
from drazill import DrazillClient

client = DrazillClient(api_key="<DRAZILL_API_KEY>")
ws = client.ws()

async def stream():
    await ws.connect()
    await ws.subscribe("orderbook:OUTCOME_UUID")
    await ws.subscribe("trades:MARKET_UUID")

    async for event in ws.listen():
        event_type = event.get("type")
        channel = event.get("channel", "")
        data = event.get("data", {})
        if event_type == "orderbook_update":
            print(f"Order book update on {channel}: {data}")
        elif event_type == "trade":
            print(f"Trade on {channel}: price={data.get('price')}")

asyncio.run(stream())

Available Channels

Channel Description
orderbook:{outcome_id} Live order book depth for an outcome
trades:{market_id} Trade executions on a market
prices:{outcome_id} Price tick updates
market:{market_id} Market status changes and resolution
user:{user_id} Private notifications and order fills (requires auth)

Error Handling

from drazill import (
    ApiError,
    AuthenticationError,
    RateLimitError,
    ValidationError,
    NetworkError,
)

try:
    order = client.orders.place_order({...})
except ValidationError as e:
    print(f"Validation errors: {e.errors}")
except AuthenticationError:
    print("Bad credentials")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after}s")
except ApiError as e:
    print(f"API error {e.status}: {e.code} - {e.message}")
except NetworkError as e:
    print(f"Network error: {e.message}")

Type Safety

All responses are Pydantic models with full type hints. Generated request/ response models live under drazill.types:

from drazill.types import models

result = client.markets.list_markets(status="ACTIVE")   # typed page object
market: models.MarketListResponse = result.items[0]
print(market.title)  # IDE autocomplete works here

Context Manager

# Sync
with DrazillClient(api_key="<DRAZILL_API_KEY>") as client:
    markets = client.markets.list_markets()

# Async
async with AsyncDrazillClient(api_key="<DRAZILL_API_KEY>") as client:
    markets = await client.markets.list_markets()

License

MIT

Project details


Download files

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

Source Distribution

drazill-0.2.0.tar.gz (122.2 kB view details)

Uploaded Source

Built Distribution

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

drazill-0.2.0-py3-none-any.whl (135.8 kB view details)

Uploaded Python 3

File details

Details for the file drazill-0.2.0.tar.gz.

File metadata

  • Download URL: drazill-0.2.0.tar.gz
  • Upload date:
  • Size: 122.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for drazill-0.2.0.tar.gz
Algorithm Hash digest
SHA256 7057ac606b786ee98a2c45f71a92c2fce597f3e3cb929d5af5d4a113e6e762d4
MD5 b7405a998adea820dca130b82a1f5c87
BLAKE2b-256 34337a1d29145a7703eb12b9728b15cee0762bcba59a78fa100901291e910cfe

See more details on using hashes here.

File details

Details for the file drazill-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: drazill-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 135.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for drazill-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 516d5a00734b01195195d0c9632bdfdcaf6f5df21c661bd9be0ebaf2dc9faa7c
MD5 70e4fed64147c0f5ff778bafc8a52db5
BLAKE2b-256 fad825909d2e44ecc3e17d75cffe6a1769a39dae212e1fa0a300561ae11732e9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page