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
drazillpackage is generated and tested but is not yet published to PyPI. Install it from source (below) for now; thepip install drazillcommands 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.jsonbysdks/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.pyfromdocs/api/openapi.v1.json. Regenerate withpython3 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:
- the
base_url=argument, if you pass one; - the
DRAZILL_BASE_URLenvironment variable; 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
Release history Release notifications | RSS feed
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7057ac606b786ee98a2c45f71a92c2fce597f3e3cb929d5af5d4a113e6e762d4
|
|
| MD5 |
b7405a998adea820dca130b82a1f5c87
|
|
| BLAKE2b-256 |
34337a1d29145a7703eb12b9728b15cee0762bcba59a78fa100901291e910cfe
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
516d5a00734b01195195d0c9632bdfdcaf6f5df21c661bd9be0ebaf2dc9faa7c
|
|
| MD5 |
70e4fed64147c0f5ff778bafc8a52db5
|
|
| BLAKE2b-256 |
fad825909d2e44ecc3e17d75cffe6a1769a39dae212e1fa0a300561ae11732e9
|