Chatbase API: Integration Guide for Developers
Chatbase ships two REST API versions. API v1 is the original integration path; API v2 is a modern redesign with structured error codes, cursor-based pagination, Server-Sent Events (SSE) streaming, and a cleaner response contract. This guide covers v2 exclusively — it is the forward path for all new integrations in 2026. By the end, you will know how to authenticate, send streaming chat messages, paginate through conversation history, handle rate limits, and feed fresh website content scraped with Apify into your Chatbase knowledge base.
Prerequisites: API v2 requires a Standard plan or above. Free and Hobby accounts cannot use the API. Check the pricing guide to evaluate upgrading.
Getting Your API Key
- Log into the Chatbase dashboard.
- Navigate to Workspace Settings → API Keys.
- Click Create API Key and copy the generated key immediately — it is only shown once.
- Store the key in a secrets manager or environment variable. Never commit it to a repository or expose it in client-side code.
Every API key grants full write access to its workspace. Treat it with the same care as a database password.
Authentication
All v2 endpoints (except GET /api/v2/health) require a Bearer token in the Authorization header:
Authorization: Bearer YOUR_API_KEY
The base URL for all v2 requests is:
https://www.chatbase.co/api/v2
Quick auth test
curl -X GET 'https://www.chatbase.co/api/v2/health' \
-H 'Authorization: Bearer YOUR_API_KEY'
A 200 OK response confirms your key and plan are valid.
Available Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v2/health | Health check (no auth required) |
POST | /api/v2/agents/{agentId}/chat | Send a message, receive a response |
POST | /api/v2/agents/{agentId}/conversations/{conversationId}/retry | Retry the last message in a conversation |
GET | /api/v2/agents/{agentId}/conversations | List all conversations (paginated) |
GET | /api/v2/agents/{agentId}/conversations/{conversationId} | Get a single conversation |
GET | /api/v2/agents/{agentId}/conversations/{conversationId}/messages | List messages in a conversation |
GET | /api/v2/agents/{agentId}/users/{userId}/conversations | List conversations for a user |
POST | /api/v2/agents/{agentId}/conversations/{conversationId}/tool-result | Submit a client action result |
PATCH | /api/v2/agents/{agentId}/conversations/{conversationId}/messages/{messageId}/feedback | Submit message feedback |
Getting your Agent ID
In the Chatbase dashboard, select your AI agent, go to Settings → General, and copy the Agent ID field.
Sending Your First Chat Message
The simplest non-streaming request:
curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"message": "What is your return policy?",
"stream": false
}'
Successful response:
{
"data": {
"id": "msg_abc123",
"role": "assistant",
"parts": [
{
"type": "text",
"text": "Our return policy allows returns within 30 days of purchase..."
}
],
"metadata": {
"userMessageId": "msg_xyz789",
"conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"userId": null,
"finishReason": "stop",
"usage": { "credits": 2 }
}
}
}
The conversationId in the response metadata is the key to multi-turn conversations. Pass it back in subsequent requests to maintain context.
Multi-turn conversation
curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"message": "What if the item is damaged?",
"conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"stream": false
}'
Omit conversationId to start a new conversation thread.
Streaming Responses with SSE
For production UIs, streaming delivers a far better perceived response time — characters arrive as the model generates them rather than after the full response is ready. Chatbase v2 streaming uses Server-Sent Events over a regular HTTP response with Content-Type: text/event-stream.
Set "stream": true (this is the default) in the request body:
// Node.js streaming example
const response = await fetch(
`https://www.chatbase.co/api/v2/agents/${agentId}/chat`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
message: "Summarize your product features",
stream: true,
conversationId: existingConversationId, // omit for new conversation
}),
}
);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let conversationId = null;
while (true) {
const { done, value } = await reader.read();
if (done) break;
const lines = decoder.decode(value, { stream: true }).split("\n");
for (const line of lines) {
if (!line.trim() || line === "data: [DONE]") continue;
const event = JSON.parse(line);
switch (event.type) {
case "text-delta":
// Stream text to your UI
process.stdout.write(event.delta);
break;
case "message-metadata":
conversationId = event.conversationId;
console.log(`\nCredits used: ${event.usage.credits}`);
break;
case "error":
console.error("Stream error:", event.errorText);
break;
}
}
}
SSE event types
| Event type | Description |
|---|---|
message-start | Signals the start of a new message; includes messageId |
text-delta | A text chunk from the model; concatenate event.delta to build the full response |
tool-input-available | A client action has been invoked with full input object |
tool-output-available | Result of a tool call with full output object |
start-step / finish-step | Step lifecycle markers for multi-step reasoning |
finish | Stream complete; finishReason is stop or tool-calls |
message-metadata | Chatbase metadata: conversationId, userId, finishReason, usage.credits |
error | Error from the model or API; check errorText |
The stream terminates with data: [DONE].
Cursor-Based Pagination
List endpoints (conversations, messages) use cursor-based pagination. Cursors are opaque, base64-encoded strings — treat them as tokens, not parse them.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
cursor | string | — | Cursor from a previous response; omit to start from the beginning |
limit | integer | 20 | Items per page. Range: 1–100 |
Response shape
{
"data": [...],
"pagination": {
"cursor": "eyJpZCI6Im1zZ19hYmMxMjMifQ==",
"hasMore": true,
"total": 347
}
}
pagination.cursor is null and hasMore is false when you have reached the last page.
Paginating through all conversations
import requests
def fetch_all_conversations(agent_id: str, api_key: str) -> list:
conversations = []
cursor = None
base_url = f"https://www.chatbase.co/api/v2/agents/{agent_id}/conversations"
headers = {"Authorization": f"Bearer {api_key}"}
while True:
params = {"limit": 100}
if cursor:
params["cursor"] = cursor
response = requests.get(base_url, headers=headers, params=params)
response.raise_for_status()
payload = response.json()
conversations.extend(payload["data"])
pagination = payload["pagination"]
if not pagination["hasMore"]:
break
cursor = pagination["cursor"]
return conversations
Note on message pagination: The messages endpoint paginates backward from the newest message. The first page contains the most recent messages; passing cursor fetches the next older page. Within each page, messages are returned in chronological (oldest → newest) order.
Rate Limiting
The API enforces 100 requests per 10-second sliding window, scoped per API key and IP address.
Every response includes these rate limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests in the window (100) |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp (ms) when the window resets |
Retry-After | Seconds to wait before retrying (only on 429 responses) |
Handling 429 responses
import time
import requests
from requests.exceptions import HTTPError
def chat_with_backoff(agent_id, api_key, message, max_retries=5):
url = f"https://www.chatbase.co/api/v2/agents/{agent_id}/chat"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = {"message": message, "stream": False}
for attempt in range(max_retries):
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 2 ** attempt))
print(f"Rate limited. Retrying in {retry_after}s...")
time.sleep(retry_after)
continue
response.raise_for_status()
return response.json()
raise Exception("Max retries exceeded")
Always log the x-request-id header from failed requests — it is required when contacting Chatbase support about an error.
Piping Apify-Scraped Content into Chatbase
One of the most powerful developer workflows is combining Apify's web scraping infrastructure with Chatbase's knowledge base to keep your AI agent current with live website content. This is the foundation of any solid data-for-AI RAG pipeline.
The workflow
- Scrape your documentation, product pages, or help center using Apify's Website Content Crawler
- Clean the output — strip navigation, footers, and boilerplate
- Format as plain text or Markdown
- Upload to Chatbase via the Sources API
Step 1: Run the Apify scraper
Using the Apify API to trigger a crawl:
import requests
APIFY_API_TOKEN = "your_apify_token"
actor_id = "apify~website-content-crawler"
run_input = {
"startUrls": [{"url": "https://docs.yoursite.com"}],
"maxCrawlDepth": 3,
"maxCrawlPages": 100,
"outputFormats": ["markdown"],
}
response = requests.post(
f"https://api.apify.com/v2/acts/{actor_id}/runs",
json=run_input,
params={"token": APIFY_API_TOKEN},
)
run = response.json()
run_id = run["data"]["id"]
print(f"Crawl started: {run_id}")
See the full scrape website content guide for crawler configuration options.
Step 2: Retrieve the scraped content
import time
def wait_for_run(run_id: str, token: str) -> list:
while True:
status_resp = requests.get(
f"https://api.apify.com/v2/actor-runs/{run_id}",
params={"token": token},
)
status = status_resp.json()["data"]["status"]
if status == "SUCCEEDED":
break
time.sleep(5)
dataset_resp = requests.get(
f"https://api.apify.com/v2/actor-runs/{run_id}/dataset/items",
params={"token": token, "format": "json"},
)
return dataset_resp.json()
Step 3: Upload to Chatbase
Chatbase's source management API (available on Standard+) allows programmatic addition of text content to your agent's knowledge base. Concatenate the cleaned page content and send it as a text source:
def upload_text_to_chatbase(agent_id: str, api_key: str, text_content: str, source_name: str):
url = f"https://www.chatbase.co/api/v1/update-chatbot-data"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = {
"chatbotId": agent_id,
"sourceText": text_content,
}
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json()
# Build the corpus from Apify output
pages = wait_for_run(run_id, APIFY_API_TOKEN)
corpus = "\n\n---\n\n".join(
page.get("markdown", page.get("text", ""))
for page in pages
if page.get("markdown") or page.get("text")
)
upload_text_to_chatbase(agent_id=CHATBASE_AGENT_ID, api_key=CHATBASE_API_KEY, text_content=corpus, source_name="docs-crawl")
This pattern keeps your chatbot synchronized with your latest documentation on a schedule. Run the crawler weekly (or on every deployment), push the updated corpus to Chatbase, and trigger a retrain. On the Standard plan with auto-retrain enabled, the retraining step can happen automatically.
Best Practices
Scope credit consumption. Use economical models (1 credit/response) for straightforward FAQ lookups and reserve Claude Opus or GPT-5 tier models for complex queries that benefit from deeper reasoning. Track usage in Workspace → Usage to catch unexpected model consumption patterns.
Persist conversation IDs. Store the conversationId from the first API response in your session layer. Passing it on follow-up messages gives the agent context from the entire session, dramatically improving multi-turn accuracy.
Stream in production. Non-streaming responses for long answers can take 3–8 seconds to return. SSE streaming delivers the first characters within ~300ms and sustains a perceptibly faster UI experience.
Separate API keys per environment. Create distinct API keys for development, staging, and production workspaces. This isolates credit consumption and prevents test traffic from polluting production analytics.
Log x-request-id headers. Every response includes a unique request ID. Capture it in your application logs. When something goes wrong, this ID is the fastest path to resolution with Chatbase support.
Ready to start building? Create your Chatbase account and get your API key from the Standard plan. If you want to evaluate Chatbase against developer-first alternatives like Botpress, see the Chatbase vs Botpress deep dive.
The Chatbase API v2 requires the Standard plan or above. Free and Hobby plan accounts will receive a 403 Forbidden error even with a valid API key. API v1 has the same restriction on Standard+.
The API enforces 100 requests per 10-second sliding window, scoped per API key and IP address. When you hit the limit, you receive a 429 status code with a Retry-After header indicating how many seconds to wait before retrying.
Capture the conversationId from the first API response's metadata object and pass it in the conversationId field of all subsequent requests to the same conversation thread. Omitting conversationId starts a fresh conversation with no prior context.
Yes. Set stream: true in the request body (it is the default) to receive responses as Server-Sent Events. Each line in the stream is a newline-delimited JSON object with a type field. The text-delta event carries text chunks; message-metadata carries the final conversationId and credit usage.
Yes. The Chatbase source management API (Standard plan+) accepts text content uploads to update an agent's knowledge base programmatically. This is the foundation for automated pipelines that pull fresh content from Apify crawls and push it directly into Chatbase without manual dashboard updates.
List endpoints return a pagination object with cursor, hasMore, and total fields. Pass the cursor value from one response as the cursor parameter in the next request to fetch the next page. When hasMore is false, you have reached the end of the list. Cursors are opaque tokens — do not decode or construct them manually.
