SDK & Client Libraries
No official SDKs yet — but the OpenAPI spec makes generating one straightforward.
Generate a Client
Python (openapi-generator)
pip install openapi-generator-cli
openapi-generator generate \
-i https://api.bill.sh/openapi.json \
-g python \
-o ./billing-client-python \
--additional-properties=packageName=billing_client
TypeScript/Node.js
npx @openapitools/openapi-generator-cli generate \
-i https://api.bill.sh/openapi.json \
-g typescript-fetch \
-o ./billing-client-ts
Go
openapi-generator generate \
-i https://api.bill.sh/openapi.json \
-g go \
-o ./billing-client-go \
--additional-properties=packageName=billingclient
Manual HTTP Client Pattern
Until official SDKs ship, here’s a minimal Python client that wraps requests with authentication, error handling, and idempotency support:
"""
billing_client.py — Minimal billing platform HTTP client
"""
from __future__ import annotations
import uuid
import hashlib
import requests
from typing import Any
class BillingClient:
"""Minimal client for the billing platform REST API."""
def __init__(self, admin_token: str, base_url: str = "https://api.bill.sh"):
self.base_url = base_url.rstrip("/")
self._session = requests.Session()
self._session.headers.update({
"Authorization": f"Bearer {admin_token}",
"Content-Type": "application/json",
})
def _idempotency_key(self, method: str, path: str, body: dict) -> str:
"""Generate a deterministic idempotency key from the request."""
canonical = f"{method}:{path}:{sorted(body.items())}"
return hashlib.sha256(canonical.encode()).hexdigest()[:32]
def get(self, path: str, params: dict | None = None) -> Any:
resp = self._session.get(f"{self.base_url}{path}", params=params)
resp.raise_for_status()
return resp.json()
def post(self, path: str, body: dict, idempotency_key: str | None = None) -> Any:
key = idempotency_key or self._idempotency_key("POST", path, body)
headers = {"Idempotency-Key": key}
resp = self._session.post(f"{self.base_url}{path}", json=body, headers=headers)
resp.raise_for_status()
return resp.json()
def delete(self, path: str) -> None:
resp = self._session.delete(f"{self.base_url}{path}")
resp.raise_for_status()
# --- Convenience methods ---
def create_customer(self, display_name: str, email: str,
currency: str = "USD", **kwargs) -> dict:
return self.post("/admin/v1/customers", {
"display_name": display_name,
"email": email,
"currency": currency,
**kwargs,
})
def create_subscription(self, customer_id: str, plan_id: str,
currency: str = "USD", **kwargs) -> dict:
return self.post("/v1/subscriptions", {
"customer_id": customer_id,
"plan_id": plan_id,
"currency": currency,
**kwargs,
}, idempotency_key=f"sub-{customer_id}-{plan_id}")
def send_event(self, event_type: str, customer_id: str,
subscription_id: str, properties: dict,
timestamp: str | None = None) -> dict:
import datetime
event_id = f"evt-{uuid.uuid4()}"
return self.post("/v1/events", {
"id": event_id,
"event_type": event_type,
"customer_id": customer_id,
"subscription_id": subscription_id,
"timestamp": timestamp or datetime.datetime.utcnow().isoformat() + "Z",
"properties": properties,
}, idempotency_key=event_id)
def get_usage(self, subscription_id: str) -> dict:
return self.get(f"/v1/subscriptions/{subscription_id}/usage")
def bill_subscription(self, subscription_id: str) -> None:
self.post(f"/admin/v1/subscriptions/{subscription_id}/bill", {},
idempotency_key=f"bill-{subscription_id}")
def finalize_invoice(self, invoice_id: str) -> dict:
return self.post(f"/admin/v1/invoices/{invoice_id}/finalize", {},
idempotency_key=f"finalize-{invoice_id}")
# Usage example
if __name__ == "__main__":
client = BillingClient(admin_token="bsk_live_your_key_here")
# Create a customer and subscribe them
customer = client.create_customer("Acme Corp", "billing@acme.com")
subscription = client.create_subscription(
customer_id=customer["id"],
plan_id="your-plan-id",
trial_days=14,
)
print(f"Customer {customer['id']} subscribed: {subscription['id']}")
# Send a usage event
result = client.send_event(
event_type="api.request",
customer_id=customer["id"],
subscription_id=subscription["id"],
properties={"model": "gpt-4o", "input_tokens": 512, "output_tokens": 128},
)
print(f"Event accepted: {result['event_id']}")
TypeScript Minimal Client
/**
* billing-client.ts — Minimal TypeScript client
*/
export class BillingClient {
private baseUrl: string;
private token: string;
constructor(token: string, baseUrl = "https://api.bill.sh") {
this.baseUrl = baseUrl;
this.token = token;
}
private headers(extra: Record<string, string> = {}): HeadersInit {
return {
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json",
...extra,
};
}
async get<T>(path: string, params?: Record<string, string>): Promise<T> {
const url = new URL(`${this.baseUrl}${path}`);
if (params) Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
const resp = await fetch(url.toString(), { headers: this.headers() });
if (!resp.ok) throw new Error(`GET ${path} failed: ${resp.status} ${await resp.text()}`);
return resp.json() as Promise<T>;
}
async post<T>(path: string, body: object, idempotencyKey?: string): Promise<T> {
const extra = idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {};
const resp = await fetch(`${this.baseUrl}${path}`, {
method: "POST",
headers: this.headers(extra),
body: JSON.stringify(body),
});
if (!resp.ok) throw new Error(`POST ${path} failed: ${resp.status} ${await resp.text()}`);
return resp.json() as Promise<T>;
}
async createCustomer(displayName: string, email: string, currency = "USD") {
return this.post<{ id: string }>("/admin/v1/customers", {
display_name: displayName, email, currency, account_type: "Organization",
});
}
async subscribe(customerId: string, planId: string, opts?: { trial_days?: number }) {
return this.post<{ id: string; status: string }>("/v1/subscriptions", {
customer_id: customerId, plan_id: planId, currency: "USD", ...opts,
}, `sub-${customerId}-${planId}`);
}
async sendEvent(customerId: string, subscriptionId: string,
eventType: string, properties: Record<string, unknown>) {
const id = `evt-${crypto.randomUUID()}`;
return this.post<{ accepted: boolean; event_id: string }>("/v1/events", {
id, event_type: eventType, customer_id: customerId,
subscription_id: subscriptionId,
timestamp: new Date().toISOString(), properties,
}, id);
}
}
What’s in the OpenAPI Spec
The spec at https://api.bill.sh/openapi.json includes:
- All request/response schemas with field descriptions
- Auth requirements per endpoint
- Idempotency header documentation
- Error response formats
The Interactive API Explorer is powered by the same spec.