Official Python SDK for the Lumify agent-ready sports intelligence API (schedules, live scores, odds, betting splits, and AI bet intelligence).
Project description
lumify-sdk
Official Python client for Lumify, the agent-ready sports intelligence API — schedules, live scores, odds, line movement, public betting splits, and AI bet intelligence across MLB, NFL, NCAAF, NBA, NHL, tennis, and soccer.
Status: v0.1.0 — the data-plane slice (sports/seasons/events/teams/players, SSE streaming, webhooks, agent onboarding). Same data as the REST API, the TypeScript SDK, and the MCP server — this SDK is the typed REST path, not a third implementation. Synchronous today; an async client (
AsyncLumify) is the planned 0.2.0 follow-up.
Install
pip install lumify-sdk
Requires Python 3.8+. Zero runtime dependencies — the transport is stdlib
urllib and signatures use stdlib hmac/hashlib.
Quick start
import os
from lumify import Lumify
client = Lumify(api_key=os.environ["LUMIFY_API_KEY"])
sports = client.sports.list()
event = client.events.get(12345, include_odds=True, include_intelligence=True)
print(event["status"], event.get("intelligence"))
Create a key at https://lumify.ai/api-keys or programmatically via
client.agent.keys.create().
Resources
client.sports.list() # client.seasons.list()
client.events.list(**filters) # .get(id, include_odds=?, include_intelligence=?, bookmaker=?)
client.events.batch_get(event_ids) # up to 25 ids in one round-trip; see below
client.events.query(text, limit=None) # natural-language search, e.g. "live nfl games today"
client.events.odds(id) # .odds_history(id) / .score(id) / .intelligence(id) / .splits(id)
client.events.stream(id) # SSE iterator of live score updates
client.events.paginate(**filters) # .iterate(**filters) — cursor pagination helpers
client.teams.list(**filters) # .get(id)
client.players.list(**filters) # .get(id) / .events(id, **filters)
client.webhooks.create(url=...) # .list() / .delete(id) / .verify(secret, header, raw_body)
client.agent.keys.create() # .list() / .revoke(id)
client.agent.credits.get() # .list_packs() / .topup(pack_id)
Every method maps 1:1 to a REST endpoint and returns the same JSON shape you'd
get from curl (a plain dict, typed as the matching model in
lumify.models) — see https://lumify.ai/docs/reference for full field docs.
Pagination
List endpoints are cursor-paginated (after_id/limit, max 100). Use the
iterator helpers instead of tracking cursors by hand:
for event in client.events.iterate(sport="nfl", status="scheduled"):
print(event["id"], event["starts_at"])
# Or page-by-page (events pages use "events", not "data"):
for page in client.events.paginate(sport="nfl", limit=50):
print(len(page.get("events", [])), "events, next cursor:", page.get("next_after_id"))
Standalone helpers paginate() / iterate_items() are also exported for
custom fetchers.
Batch event lookup
Already have a list of event ids (e.g. from client.events.list()) and want
full detail for each? batch_get fetches up to 25 in a single round-trip
instead of one GET per event:
result = client.events.batch_get([101, 102, 999999999], include_odds=True)
print(result["total"], "found;", result["not_found"], "missing")
for event in result["events"]:
print(event["id"], event["status"])
Duplicate ids are billed once; ids that don't exist are returned under
not_found and cost nothing — credits are the sum of each event's normal
GET /v1/events/{id} cost, with the same billing-fairness rules (unavailable
odds/intelligence stay free).
Natural-language search
query maps free text to the same filters client.events.list() accepts —
sport, status, and date/date-range — using a small, deterministic, rule-based
mapper (not an LLM call). Costs 1 credit, same as list():
result = client.events.query("live nfl games today", limit=5)
print(result["interpreted"]) # {"sport": "nfl", "status": "inprogress", "date": "2026-07-15", ...}
print(result["equivalent_request"]) # "GET /v1/events?sport=nfl&status=inprogress&date=2026-07-15"
print(result["unrecognized_terms"]) # words that didn't map to a filter
for event in result["events"]:
print(event["id"], event["status"])
Live score streaming (SSE)
for evt in client.events.stream(event_id):
if evt.event == "score":
print(evt.data["status"], evt.data.get("clock"))
if evt.event == "done":
break
Cheaper than polling client.events.score(id) — the server only emits on
change, plus periodic keep-alives, and closes when the event finishes.
Webhooks
sub = client.webhooks.create(url="https://you.example.com/hooks/lumify")
# sub["signing_secret"] ("whsec_...") is returned once — store it.
# In your webhook handler, verify against the *raw* request body:
client.webhooks.verify(signing_secret, request.headers["Lumify-Signature"], raw_body)
verify() raises WebhookSignatureError (bad format, signature mismatch, or a
stale/replayed timestamp) — treat that as "reject with 4xx", not a crash.
Errors
Every non-2xx response raises a typed subclass of LumifyError — switch on
err.code (the stable machine-readable slug), not str(err):
from lumify import NotFoundError, RateLimitError, ValidationError
try:
client.events.get(999999999)
except NotFoundError:
...
except RateLimitError as err:
print("retry after", err.retry_after, "s")
except ValidationError as err:
print(err.field_errors)
AuthenticationError (401), PaymentError (402), PermissionError (403, has
.upgrade_url on sport-scope denials), NotFoundError (404), ValidationError
(422, has .field_errors), RateLimitError (429, has .retry_after from the
envelope or Retry-After header), APIError (5xx), and ConnectionError
(network/timeout, never reached the server) all extend LumifyError
(.code, .status, .doc_url, .request_id).
GET requests are automatically retried (default: 2 attempts) with exponential
backoff on 429/5xx/network failures, honoring Retry-After. Non-idempotent
requests (POST/webhook & key creation) are never auto-retried. Configure with
max_retries / timeout on the Lumify constructor.
Credits and rate limits
Every successful response carries X-Credits-Used, X-Credits-Remaining, and
X-RateLimit-* headers. Read them via get_meta() (attached out-of-band on the
returned object, so it never pollutes json.dumps or iteration):
from lumify import get_meta
odds = client.events.odds(event_id)
meta = get_meta(odds)
print(meta.credits_used, meta.rate_limit_remaining)
Queries for data that isn't available yet (e.g. odds not yet posted) return
credits_used == 0 — you're never charged for a "not available" read.
Sync with REST, MCP, and the OpenAPI contract
This SDK's response types (lumify/models.py) are generated from the same
filtered OpenAPI slice the TypeScript SDK uses
(clients/lumify-sdk/openapi/openapi.sdk.json, produced by
scripts/export_openapi_sdk.py at the repo root), not hand-maintained. CI fails
if the models are stale, so the two SDKs can't disagree with each other or drift
from what the API returns. A shared cross-surface probe matrix
(tests/fixtures/agent_contract.json) additionally asserts this SDK builds the
exact REST request that REST and MCP agree on. The client ergonomics (this
README's resource shape, pagination/SSE/webhook helpers) are hand-written.
python scripts/export_openapi_sdk.py # regenerate the schema slice
python clients/lumify-sdk-python/scripts/gen_models.py # regenerate the Python models
Development
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
pytest
License
MIT © 2026 Lumify AI
Related
- Docs: https://lumify.ai/docs
- TypeScript SDK:
@lumifyai/sdk - MCP server (for AI agent runtimes):
@lumifyai/mcp
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 lumify_sdk-0.1.0.tar.gz.
File metadata
- Download URL: lumify_sdk-0.1.0.tar.gz
- Upload date:
- Size: 30.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b6d875cdadb59fbb3a768c8a1806b5bcc3ff73e746e4199b93f6ce1b798cf3e
|
|
| MD5 |
682fa3deaff1480e357b9ac2c3561584
|
|
| BLAKE2b-256 |
aee7d4fd03c1eabe351f4353f973f25abe62fcb2f27927f956bfb0e768c5d8b6
|
File details
Details for the file lumify_sdk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: lumify_sdk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 28.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7171bd9f9cc6d135418687585e6cabb4307f5a26141cd5bed0e85742199098a3
|
|
| MD5 |
bb39089b40384b48bdf7c031033fde38
|
|
| BLAKE2b-256 |
c7bbf755ac1d3ea0365a37edb1e6709a233b3ebef552a8f374a09534c9be3f84
|