Five endpoints carry the entire public surface. This page documents each one, what it returns, and where the boundary of the public API actually runs.
Base URL https://api.bitpull.ai · JSON · Bearer auth
Where this reference comes from
bitpull does not publish an OpenAPI document. This page was written by reading the platform's own API client and its published documentation, and it is kept to what is verifiable there. If an endpoint is not on this page, treat it as not public - not as undocumented but supported. The canonical product documentation lives at bitpull.ai/docs.
Authentication
Every endpoint on this page except /health expects your agent API key as a bearer token:
Authorization: Bearer <API_KEY>
The key and its secret are in the dashboard under Agent → Website/Deploy → API access, and both can be regenerated there. The secret is server-side only - it does not belong in a bundle, a repository or a script tag. For the browser, use the hosted widget, which only ever carries the key.
Rate limiting
The API limits requests per window; the platform's own client is written around a budget of roughly 180 requests per window and deduplicates in-flight GETs to stay inside it. Cache anything that rarely changes - the language list above all - and back off rather than retry immediately after a 429.
Errors
Non-2xx responses carry a JSON body with a message or error string. Read the status code first and the body second: an invalid key and an unconfigured language look identical if you only log the text.
Endpoints
GET/healthno auth
Availability check. Needs no credential, which makes it the right target for uptime monitoring and for confirming reachability from a new network before you start debugging keys.
terminal
curl https://api.bitpull.ai/health
200 - fields
status string state of the API
timestamp string ISO 8601 time of the response
GET/api/sessions/languagesagent key
The languages this agent is configured for, with the voices available for each and the default. It is not a global capability list - an agent configured for German and English returns two entries, and asking for a third language when creating a session will fail.
200 - fields
voiceAgentKind string which voice agent backs this tenant
languages string[] configured language codes
languageConfigs object[] per language: code, name, availableVoices[]
defaultLanguage string used when a session omits language
Cache this. It changes when someone edits the agent, not between requests, and it is the easiest way to spend your rate limit for nothing.
POST/api/sessionsagent key
Creates a conversation and returns the connection details for it. Every body field is optional - omit them all and the agent's configured defaults apply.
request body
language string? one of the configured language codes
voiceId string? voice for this session
systemPrompt string? overrides the agent prompt for this session only
interactionMode 'VOICE' | 'TEXT'? spoken or text-only
200 - fields
sessionId string id of the session
wsUrl string LiveKit WebSocket URL
token string access token for the LiveKit room
voiceId string? the voice actually used
A per-session prompt override is genuinely useful - passing the signed-in customer's context, for instance. It is also a direct injection point if any part of it comes from user input. Build it server-side from values you control, never from a query string.
GET/api/sessions/:sessionIdagent key
Reads an existing session by id. Returns the same shape as the create call, so a reconnecting client can recover its room credentials without starting a new conversation.
Ends a running session. Call it when the conversation is over instead of only dropping the connection client-side - a browser tab closing is not a clean end, and sessions that are never ended are the usual cause of a minute count nobody can explain.
Mints a room token and returns it with the WebSocket URL. Accepts an optional language. Used by clients that manage the room connection themselves rather than going through the session lifecycle above.
Two further endpoints are reachable with the agent key and are used by the product's own clients. They are listed for completeness; their response contracts are not documented anywhere public, so build on them only if you are prepared for them to change.
Endpoint
Purpose
GET /api/sessions/config/:roomName
Configuration for a room, addressed by room name rather than session id.
POST /api/sessions/transcription
Posts a transcript line - role is USER or ASSISTANT, plus content and either sessionId or roomName. Used by clients that render their own transcript and want it persisted with the session.
The account API is not an integration API
A second, much larger surface exists behind a user token - the JWT the dashboard holds after login. It covers agents, prompts, phone numbers, team members, conversation history, analytics, subscriptions and outbound calls.
It is not documented here as something to integrate against, and that is a deliberate call rather than an omission. Those endpoints authenticate a person, not an application: there is no scoping, no service account and no published stability guarantee. Storing a user's login token on a server to call them is building on someone's session.
Area
Examples
Credential
Agents & prompts
/api/tenants/:id, /api/tenants/:id/prompts
user token
Conversation history
/api/tenants/:id/sessions, …/messages
user token
Phone numbers
/api/tenants/:id/numbers
user token
Outbound calls
/api/tenants/:id/outbound-calls · Idempotency-Key
user token
Calendar
/api/tenants/:id/calendar-integrations/…
user token
Capabilities
/api/capabilities
user token
If you need programmatic agent management, that is a product conversation with bitpull.ai, not a request this reference can answer.
The conversation itself
POST /api/sessions hands you a wsUrl and a token. Everything after that is a LiveKit room, and the livekit-client package is what talks to it.
join the room
import { Room } from 'livekit-client'
// 1. Create the session on your server - the API key never reaches the browser.
const { wsUrl, token, sessionId } = await fetch('/api/agent/session').then((r) => r.json())
// 2. Join the room.
const room = new Room()
await room.connect(wsUrl, token)
// 3. Play the agent, publish the microphone.
room.on('trackSubscribed', (track) => track.attach())
await room.localParticipant.setMicrophoneEnabled(true)
// 4. End it properly when the user leaves - not just room.disconnect().
window.addEventListener('pagehide', () => {
navigator.sendBeacon('/api/agent/end', JSON.stringify({ sessionId }))
})
In TEXT mode the same room carries text streams instead of audio - no microphone permission needed.