Your first API call
Start a chat with a question over HTTP, stream Bilby's answer as it's written, then follow up in the same chat.
4 min read · Updated 31 Oct 2026 · For developers
Before you begin
BILBY_API_KEY.curl, or Python 3 with the requests package.workspaceId ready. Step 1 shows how to find it.How a call works
Asking Bilby takes two requests. The first sends your question and returns at once with a
streamUrl. The second follows that URL and receives the answer as it's written, because Bilby may
search and read for a minute or more first.
Every request goes to https://pulse.bilby.ai/api/v1 with your key in the Authorization header.
Step 1: Find your organization
A key scoped to one organization works there without saying so: skip this step. Otherwise, list the organizations your key can reach:
curl https://pulse.bilby.ai/api/v1/workspaces \
-H "Authorization: Bearer $BILBY_API_KEY"{
"items": [{ "id": "8d1e…", "name": "Acme Research", "slug": "acme-research" }],
"total": 1
}The API calls an organization a workspace. Use its id as workspaceId: in the body when you start a
chat, and as a ?workspaceId= query on the requests after it.
Step 2: Ask a question
POST /conversations with a message starts a chat and admits the first answer:
curl -X POST https://pulse.bilby.ai/api/v1/conversations \
-H "Authorization: Bearer $BILBY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "What changed in Malaysian data protection rules this year? Cite government sources."}'Bilby replies 202 Accepted:
{
"ok": true,
"conversationId": "3f0c…",
"status": "accepted",
"runId": "…",
"streamUrl": "/api/v1/conversations/3f0c…/stream?runId=…"
}| Body field | Required | What it does |
|---|---|---|
message | Yes, to get an answer | Your question. Without it, you get 201 and an empty chat |
workspaceId | For keys with several organizations | The organization to ask in |
title | No | The chat's name in Bilby. Bilby writes one if you leave it out |
agentId | No | One of the organization's agents. The default agent otherwise |
reasoningEffort | No | auto, low, medium, high or max |
attachments | No | Up to 10 files uploaded first with POST /attachments/init |
Step 3: Stream the answer
Follow streamUrl on the same host. The answer arrives as server-sent events, in the AI SDK's UI
message stream format (x-vercel-ai-ui-message-stream: v1). Each event is a data: line of JSON. The
text of the answer comes in text-delta events, in their delta field, and the stream ends with
data: [DONE].
curl -N "https://pulse.bilby.ai/api/v1/conversations/3f0c…/stream?runId=…" \
-H "Authorization: Bearer $BILBY_API_KEY"data: {"type":"start", …}
data: {"type":"text-delta","id":"…","delta":"Malaysia amended its Personal Data Protection Act"}
data: {"type":"text-delta","id":"…","delta":" in 2024, and the changes took effect in stages…"}
data: {"type":"finish", …}
data: [DONE]Other events carry Bilby's working steps, such as the searches it runs. Skip the ones you don't need.
The same in Python, printing the answer as it arrives:
import json
import os
import requests
API = "https://pulse.bilby.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['BILBY_API_KEY']}"}
started = requests.post(
f"{API}/conversations",
headers=HEADERS,
json={"message": "What changed in Malaysian data protection rules this year? Cite government sources."},
)
started.raise_for_status()
turn = started.json()
with requests.get(f"https://pulse.bilby.ai{turn['streamUrl']}", headers=HEADERS, stream=True) as stream:
for line in stream.iter_lines(decode_unicode=True):
if not line or not line.startswith("data: "):
continue
data = line.removeprefix("data: ")
if data == "[DONE]":
break
event = json.loads(data)
if event["type"] == "text-delta":
print(event["delta"], end="", flush=True)Note
With a key for several organizations, add &workspaceId= and the organization's id to the
streamUrl before you follow it.
If the connection drops, request the streamUrl again with &cursor= and the number of events you
already have, or send a Last-Event-ID header, and the stream picks up from there. Once the answer
is finished, the stream returns 204 No Content.
Step 4: Read the whole chat
GET /conversations/:id returns the chat with its messages, including the finished answer and its
sources. Use it after a 204, or whenever you want the full text at once.
curl https://pulse.bilby.ai/api/v1/conversations/3f0c… \
-H "Authorization: Bearer $BILBY_API_KEY"While an answer is running, activeRun gives its runId and streamUrl. When it's null, the
answer is complete.
Step 5: Follow up
POST /conversations/:id sends a follow-up in the same chat. Bilby keeps the history, so send only
the new message:
curl -X POST https://pulse.bilby.ai/api/v1/conversations/3f0c… \
-H "Authorization: Bearer $BILBY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: digest-2026-10-31" \
-d '{"message": "Which of these changes affect transfers of data abroad?"}'It returns 202 with a new streamUrl, as in Step 2. An Idempotency-Key makes a retried request
safe: the same key won't send the message twice.
To stop an answer that's still running, POST /conversations/:id/abort. What Bilby wrote so far is
kept.
When something goes wrong
Errors come back as JSON with a message in error.message.
| Status | Meaning |
|---|---|
400 | The body is missing something, such as an empty message, or a key for several organizations sent no workspaceId |
401 | The key is wrong, expired or revoked |
402 | Your allowance is used up. The message says when it resets |
403 | The key isn't scoped to that organization, or you're no longer a member |
404 | No such chat, or not one of yours |
409 | An answer is already running in this chat. Wait for it, or abort it |
Tip
Sometimes Bilby asks a few multiple-choice questions before it answers. They show in
pendingQuestions on GET /conversations/:id. Answer them with
POST /conversations/:id/tool-outputs, and Bilby carries on.
Try it in Bilby
Make a key, then run Step 2.
The chat you start appears in Bilby too, so you can read the answer there.

