Skip to content

Python SDK

The official Python SDK is published as sybilion on PyPI. It ships two layers in one package:

  • sybilion — the hand-written wrapper exposed to users (Client, ergonomic methods, pagination iterators, exceptions).
  • sybilion._api — the OpenAPI-generated low-level client. Accessible via client._api as an escape hatch for endpoints not yet wrapped.

This page documents the wrapper's idioms. For canonical use cases (forecasts, drivers, alerts, account, regions/categories), see the Features section — every example there has a Python tab.

Install

bash
pip install sybilion

Requires Python 3.10+.

Construct a client

python
import os
from sybilion import Client

# Token read from the environment automatically:
client = Client()

# Or pass it explicitly:
client = Client(token=os.environ["SYBILION_API_TOKEN"])

me = client.me()
print(me.user_id, me.available_eur_cents)

When token is omitted, Client reads SYBILION_API_TOKEN from the environment. If neither is provided it raises ValueError. Client also accepts an optional base_url to override the default API origin.

Base URL resolution

Client(base_url=...) resolves the API origin in this order:

  1. Explicit base_url argument.
  2. SYBILION_API_BASE_URL environment variable.
  3. The compiled-in default (sybilion.DEFAULT_PUBLIC_API_BASE_URL, currently https://api.sybilion.dev).

To verify the resolution at runtime:

python
from sybilion import resolve_api_base_url
print(resolve_api_base_url(None))  # what Client will use

Wrapper methods

Client exposes a method for every endpoint:

MethodEndpoint
me()GET /api/v1/me
submit_forecast(request)POST /api/v1/forecasts
get_forecast(id)GET /api/v1/forecasts/{id}
get_forecast_artifact(id, name)GET /api/v1/forecasts/{id}/artifacts/{name}
wait_forecast(job_id, *, poll_s, timeout_s)polling helper
get_drivers(request)POST /api/v1/drivers
get_alerts(*, metadata, context_enriched, ...)POST /api/v1/alerts
list_jobs(*, page, limit, ...)GET /api/v1/jobs
iter_jobs_pages(*, limit, status, ...)pagination helper
get_usage(*, page, limit, ...)GET /api/v1/usage
iter_usage_pages(*, limit, ...)pagination helper
list_categories()GET /api/v1/categories
list_regions()GET /api/v1/regions

For endpoints not yet covered, the underlying generated client is accessible as client._api (a DefaultApi instance). Method names mirror the OpenAPI operationId: api_v1_me_get, api_v1_forecasts_post, etc.

wait_forecast — poll until settled

python
submit = client.submit_forecast(body)

job = client.wait_forecast(
    submit.job_id,
    poll_s=2.0,        # seconds between polls
    timeout_s=3600.0,  # max total wait
)

print(job.status, job.eur_cents_final)
for art in job.artifacts or []:
    print(art.name, art.size)

Behaviour:

  • Polls GET /api/v1/forecasts/{id} every poll_s seconds.
  • Returns the response as soon as settled == True — works for completed, failed, and canceled jobs.
  • Raises TimeoutError if the deadline is exceeded; the job continues running on the server and you can resume polling later.

Pick poll_s based on your latency tolerance — 2–5 seconds is typical. The wrapper does not back off; if you need exponential backoff, call client.get_forecast(id) in your own loop.

get_alerts — synchronous alert detection

client.get_alerts calls POST /api/v1/alerts and returns the result immediately — no polling required.

python
import os

from sybilion import Client
from sybilion._api.models import Filters, TimeseriesMetadata

client = Client(token=os.environ["SYBILION_API_TOKEN"])

meta = TimeseriesMetadata(
    title="Brent Crude Oil Price Monthly",
    description="Monthly average Brent crude oil spot price in USD/barrel.",
    keywords=["oil", "brent", "energy", "commodity"],
)

filters = Filters(categories=[3, 7], regions=[42], limit=25)

alerts = client.get_alerts(
    metadata=meta,
    context_enriched=False,
    filters=filters,
    date_from="2024-01-01",
)
for alert in alerts:
    print(alert["name"], alert["pct_change"], alert["trending"])

Parameters:

ParameterTypeNotes
metadataTimeseriesMetadataRequired. Title, description, keywords describing the series.
context_enrichedboolRequired. Pass True if the metadata was already enriched upstream; False to let the engine enrich it.
filtersFilters | NoneOptional. Scope by category / region ids and cap item count with limit.
date_fromstr | NoneOptional. Earliest date bound for alert events (YYYY-MM-DD).
date_tostr | NoneOptional. Latest date bound for alert events (YYYY-MM-DD).

Returns a list[dict] — each dict has name, pct_change, trending, and a news[] sub-list. See POST /api/v1/alerts for the full response shape.

Pagination iterators

Two helpers walk paginated endpoints page-by-page so you don't have to track page / total_pages yourself.

python
# All usage events (newest first by default).
for page in client.iter_usage_pages(limit=100, sort="created_at", order="desc"):
    for ev in page.usage_events:
        print(ev.id, ev.eur_cents_charged, ev.created_at)

# Only completed forecast jobs.
for page in client.iter_jobs_pages(limit=100, status="completed"):
    for job in page.jobs:
        print(job.job_id, job.status, job.eur_cents_final)

Each iterator stops automatically when page == pagination.total_pages. Stop early with break.

Error handling

Wrapper methods raise a plain Exception whose message is the error field from the API response body. If the body can't be parsed, the original ApiException from the generated client is re-raised.

python
try:
    job = client.submit_forecast(body)
except Exception as exc:
    print("error:", exc)

When calling client._api.* directly, the generated client raises typed exceptions:

python
from sybilion import (
    ApiException,
    BadRequestException,
    UnauthorizedException,
    UnprocessableEntityException,
    ServiceException,
)

try:
    job = client._api.api_v1_forecasts_post(forecast_request_v1=body)
except UnprocessableEntityException as exc:
    # 422 — single validation detail in exc.body
    print("validation failed:", exc.body)
except UnauthorizedException:
    print("token rejected — refresh SYBILION_API_TOKEN")
except ApiException as exc:
    print("HTTP", exc.status, exc.reason, exc.body)
ExceptionHTTPWhen
BadRequestException400Malformed JSON, invalid query params.
UnauthorizedException401Missing or invalid token.
ForbiddenException403Authenticated but not allowed.
NotFoundException404Unknown id, or outside the visibility window.
ConflictException409Job not yet completed (artifact downloads).
UnprocessableEntityException422Validation failure — body has error + one details[].
ServiceException5xxTransient backend failure; retry with backoff.
ApiExceptionotherCatch-all base class.

Useful attributes on every ApiException: status (int), reason (str), body (raw bytes), headers (dict).

For a 402 (insufficient balance), the SDK raises ApiException. Inspect exc.status == 402 and parse exc.body for error text. Surface this as "top up balance" in your UI.

Custom configuration

The generated client uses urllib3 under the hood. To override the default timeout or connection settings, build a Configuration directly and access the generated API:

python
import os

from sybilion import resolve_api_base_url
from sybilion._api import ApiClient, Configuration, DefaultApi

cfg = Configuration(
    host=resolve_api_base_url(None),
    access_token=os.environ["SYBILION_API_TOKEN"],
)
cfg.retries = 3                   # urllib3 retry policy
cfg.connection_pool_maxsize = 10  # parallel calls

raw_api = DefaultApi(ApiClient(cfg))
me = raw_api.api_v1_me_get()

Per-call timeouts can be passed via the underlying urllib3 request — see the urllib3 docs for the full surface.

Versioning

The SDK uses SemVer independently of the API server version; minor releases stay backward-compatible, breaking changes bump the major. Pin to a tag (sybilion==0.1.0) for production builds.

See also

[email protected] · Slack · Discord (links in Community page & header icons)