SDKs
There is no official SDK yet
No npm package, no PyPI package, nothing published by bitpull. The supported path is REST plus livekit-client - which, for a five-endpoint API, is genuinely fine.
What that means in practice
The public API is five endpoints and one dependency you would need anyway. An SDK would save you the fetch wrapper and a type file - worth having, not worth waiting for.
| Language | Official package | What to use |
|---|---|---|
| JavaScript / TypeScript | Planned | fetch for the REST calls, livekit-client for the room. Both are already in your stack. |
| Python | Planned | httpx or requests. Server-side session creation is all most Python callers need. |
| Anything else | Planned | An HTTP client. There is nothing language-specific in the API. |
If a package named after bitpull appears in a registry, it is not from bitpull. An SDK holds your API key, so an unaudited third-party package is a credential risk rather than a convenience. Verify the source before installing anything.
Write the thin client instead
Forty lines gets you the whole public surface, typed, with your own error handling. Here it is - copy it, own it, and delete it when a real SDK exists.
// bitpull.ts - the entire public API, typed. Server-side only.
const BASE = 'https://api.bitpull.ai'
export interface Session { sessionId: string; wsUrl: string; token: string; voiceId?: string }
export interface Languages {
voiceAgentKind: string
languages: string[]
defaultLanguage: string
languageConfigs: { code: string; name: string; availableVoices: { id: string; label: string }[] }[]
}
export class BitpullError extends Error {
constructor(readonly status: number, message: string) {
super(message)
this.name = 'BitpullError'
}
}
export function bitpull(apiKey: string) {
async function call<T>(path: string, init: RequestInit = {}): Promise<T> {
const res = await fetch(BASE + path, {
...init,
headers: {
Authorization: `Bearer ${apiKey}`,
...(init.body ? { 'Content-Type': 'application/json' } : {}),
...init.headers
}
})
const data = await res.json().catch(() => ({}))
if (!res.ok) {
// 429 is the one worth distinguishing: back off, do not retry immediately.
throw new BitpullError(res.status, data.message ?? data.error ?? `HTTP ${res.status}`)
}
return data as T
}
return {
health: () => call<{ status: string; timestamp: string }>('/health'),
languages: () => call<Languages>('/api/sessions/languages'),
createSession: (body: {
language?: string
voiceId?: string
systemPrompt?: string
interactionMode?: 'VOICE' | 'TEXT'
} = {}) => call<Session>('/api/sessions', { method: 'POST', body: JSON.stringify(body) }),
getSession: (id: string) => call<Session>(`/api/sessions/${encodeURIComponent(id)}`),
endSession: (id: string) =>
call<{ message: string }>(`/api/sessions/${encodeURIComponent(id)}/end`, { method: 'POST' })
}
}# bitpull.py - server-side session creation.
import os
import httpx
BASE = "https://api.bitpull.ai"
class BitpullError(RuntimeError):
def __init__(self, status: int, message: str):
super().__init__(f"{status}: {message}")
self.status = status
class Bitpull:
def __init__(self, api_key: str | None = None, timeout: float = 10.0):
self._client = httpx.Client(
base_url=BASE,
timeout=timeout,
headers={"Authorization": f"Bearer {api_key or os.environ['BITPULL_API_KEY']}"},
)
def _call(self, method: str, path: str, **kw):
res = self._client.request(method, path, **kw)
if res.is_error:
body = res.json() if res.headers.get("content-type", "").startswith("application/json") else {}
raise BitpullError(res.status_code, body.get("message") or body.get("error") or res.text)
return res.json()
def languages(self):
return self._call("GET", "/api/sessions/languages")
def create_session(self, **body):
# Every field is optional; omitted values fall back to the agent config.
return self._call("POST", "/api/sessions", json={k: v for k, v in body.items() if v is not None})
def end_session(self, session_id: str):
return self._call("POST", f"/api/sessions/{session_id}/end")What an SDK would actually need to add
Worth knowing, because it is what you have to handle yourself:
- Rate-limit awareness. The API limits requests per window. A client that backs off on
429rather than retrying immediately is the single most useful thing to build in. - Request deduplication. The platform's own client collapses concurrent identical GETs into one request. Worth copying if several parts of your app ask for the language list at once.
- Session cleanup. Making sure
/endis called even when the user closes the tab. This is the leak nobody notices until the minute count arrives. - Typed errors. Distinguishing an invalid key from an unconfigured language, both of which fail with a message string.
When a package is published under an account that is verifiably bitpull's, this page will name it and the status pills will change. The changelog is where it would be announced.