Skip to main content

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

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

lumify_sdk-0.1.0.tar.gz (30.9 kB view details)

Uploaded Source

Built Distribution

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

lumify_sdk-0.1.0-py3-none-any.whl (28.5 kB view details)

Uploaded Python 3

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

Hashes for lumify_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 4b6d875cdadb59fbb3a768c8a1806b5bcc3ff73e746e4199b93f6ce1b798cf3e
MD5 682fa3deaff1480e357b9ac2c3561584
BLAKE2b-256 aee7d4fd03c1eabe351f4353f973f25abe62fcb2f27927f956bfb0e768c5d8b6

See more details on using hashes here.

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

Hashes for lumify_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7171bd9f9cc6d135418687585e6cabb4307f5a26141cd5bed0e85742199098a3
MD5 bb39089b40384b48bdf7c031033fde38
BLAKE2b-256 c7bbf755ac1d3ea0365a37edb1e6709a233b3ebef552a8f374a09534c9be3f84

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