BillKit/Docs Console
SDK

Python

Sync and async clients over httpx, with typed errors and cursor auto-pagination.

v0.4.0 Python 3.11+ Beta Source

Install

pip install billkit-eu

Or uv add billkit-eu. Requires Python 3.11 or newer, and pulls in httpx.

The distribution is billkit-eu because the bare billkit name on PyPI belongs to an unrelated project. The import name is unaffected: import billkit.

Initialise

Two clients, same surface. BillKit is synchronous; AsyncBillKit is the same methods with await and an async context manager. Omit api_key and it reads BILLKIT_API_KEY.

python
from billkit import BillKit

client = BillKit(
  api_key="bk_test_...",
  base_url="https://api.billkit.eu",
  timeout=30.0,
)
python
from billkit import AsyncBillKit

async with AsyncBillKit(api_key="bk_test_...") as client:
  customer = await client.customers.create(email="ada@example.com")

Making calls

One attribute per resource family: client.customers, products, prices, checkout_sessions, one_shot_payments, subscriptions, refunds, disputes, webhook_endpoints, events, tenant, coupons, tax_rates, invoices, audit_logs, payments, billing_portal_sessions.

Arguments are keyword-only and named exactly as the API names them. Every method returns the decoded JSON body as a dict, so nothing is lost in translation on its way to your own models.

python
price = client.prices.create(
  product_id="prod_9XKp2vQ1",
  amount_cents=999,
  currency="EUR",
  interval="month",
  trial_days=14,
)
print(price["id"])

Auto-pagination

iter() walks the has_more and starting_after cursor protocol. Paging is forward-only.

python
for customer in client.customers.iter():
  print(customer["id"])

for event in client.events.iter(type="subscription.created", page_size=100):
  handle(event)

Idempotency and retries

Connection errors, 5xx and short Retry-After 429s are retried with jittered backoff. Every mutating call carries an auto-generated Idempotency-Key, so a retried write replays the first response instead of repeating it. Pass idempotency_key= to make that hold across process restarts.

python
from billkit import BillKit, RetryPolicy

client = BillKit(
  api_key="bk_test_...",
  retry_policy=RetryPolicy(max_attempts=5, max_retry_after_seconds=10.0),
)

Errors

Everything inherits from BillKitError: APIConnectionError, APIError, ServerError, AuthenticationError, PermissionError, ResourceMissingError, InvalidRequestError, ConflictError, RateLimitError. The class is picked from the error envelope’s type, falling back to the HTTP status when the envelope does not name one. Each carries status_code, message, code, param and request_id.

python
from billkit import BillKitError, RateLimitError, ResourceMissingError

try:
  client.customers.retrieve("cus_gone")
except ResourceMissingError:
  return None
except RateLimitError as exc:
  retry_in(exc.retry_after)
except BillKitError as exc:
  raise RuntimeError(f"BillKit {exc.status_code}: {exc.message}") from exc

Webhooks

python
from billkit import WebhookSignature, WebhookVerificationError

try:
  event = WebhookSignature.verify(
      payload=request.body,  # raw bytes, never the parsed dict
      signature_header=request.headers.get("BillKit-Signature"),
      secret=os.environ["BILLKIT_WEBHOOK_SECRET"],
  )
except WebhookVerificationError:
  return Response(status_code=400)

if event["type"] == "subscription.created":
  provision(event["data"])

Constant-time HMAC compare and a five-minute timestamp tolerance for replay protection. Pass tolerance_seconds= to change it.

Logging

The SDK owns one logger, logging.getLogger("billkit"), with a NullHandler on it. It never calls basicConfig, never sets a level, and never touches a logger it does not own.

python
import logging

logging.basicConfig()
logging.getLogger("billkit").setLevel(logging.DEBUG)

DEBUG gives one line per attempt and per response; WARNING one per retry. Your API key, request and response bodies and the query string are never logged.

httpx logs its own request line at INFO, full URL included, and list filters put customer emails in query strings. So when you opt this SDK in, it raises the httpx and httpcore loggers to WARNING, but only if you have not set a level on them yourself and only for the client it created.