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.

Planned No published package in any language

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.

LanguageOfficial packageWhat to use
JavaScript / TypeScriptPlannedfetch for the REST calls, livekit-client for the room. Both are already in your stack.
PythonPlannedhttpx or requests. Server-side session creation is all most Python callers need.
Anything elsePlannedAn HTTP client. There is nothing language-specific in the API.
Careful with community packages

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 whole client
// 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
# 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 429 rather 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 /end is 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 this page will change

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.