Developer API — Integration Guide¶
This guide shows how to integrate the Beyond Retrieval Developer API from every major platform. The Developer API uses simple API key authentication (X-API-Key header) — no OAuth, no JWT, no SDK required.
Prerequisites
Before starting, you need an API key. An admin creates it in Developer API page and copies the key (shown once). See the Developer API Reference for full endpoint documentation.
Quick Reference¶
| Detail | Value |
|---|---|
| Base URL | https://your-domain.com/api/v1/external |
| Auth header | X-API-Key: br_key_... |
| Content-Type | application/json |
| Endpoints | POST /query, POST /chat, GET /chat/{id}/messages |
| Rate limit | Per-key, configurable (default 100/hour) |
Python¶
Install¶
Quick start¶
import httpx
API_KEY = "br_key_Oq8vceZRJobUzknlT58_3CqhS2NLAQaqKfxqoYbZW0A"
BASE_URL = "https://your-domain.com/api/v1/external"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
# RAG Query — get raw document chunks
response = httpx.post(
f"{BASE_URL}/query",
headers=HEADERS,
json={"query": "What is the refund policy?", "top_k": 5},
timeout=30,
)
data = response.json()["data"]
print(f"Found {data['total_results']} chunks")
for chunk in data["chunks"]:
print(f" [{chunk['score']:.2f}] {chunk['content'][:80]}...")
Chat with follow-up¶
import httpx
API_KEY = "br_key_..."
BASE_URL = "https://your-domain.com/api/v1/external"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
# First message — creates a new conversation
response = httpx.post(
f"{BASE_URL}/chat",
headers=HEADERS,
json={
"message": "What are the membership benefits?",
"persona": "professional",
"language": "en",
},
timeout=60,
)
data = response.json()["data"]
conversation_id = data["conversation_id"]
print(f"AI: {data['answer']}")
print(f"Conversation: {conversation_id}")
# Follow-up — same conversation
response = httpx.post(
f"{BASE_URL}/chat",
headers=HEADERS,
json={
"message": "How much does it cost?",
"conversation_id": conversation_id,
},
timeout=60,
)
data = response.json()["data"]
print(f"AI: {data['answer']}")
# Get full history
response = httpx.get(
f"{BASE_URL}/chat/{conversation_id}/messages",
headers=HEADERS,
)
messages = response.json()["data"]["messages"]
for msg in messages:
print(f" [{msg['role']}] {msg['content'][:100]}...")
Production client class¶
import httpx
from typing import Any
class BeyondRetrievalAPI:
"""Production-ready client for the Beyond Retrieval Developer API."""
def __init__(self, api_key: str, base_url: str = "https://your-domain.com"):
self.base_url = f"{base_url.rstrip('/')}/api/v1/external"
self.client = httpx.Client(
headers={"X-API-Key": api_key, "Content-Type": "application/json"},
timeout=60.0,
)
def query(self, query: str, top_k: int = 10, strategy: str = "fusion") -> dict:
"""Execute a RAG query and return matching chunks."""
r = self.client.post(
f"{self.base_url}/query",
json={"query": query, "strategy_id": strategy, "top_k": top_k},
)
r.raise_for_status()
body = r.json()
if not body["success"]:
raise RuntimeError(body.get("error", "Unknown error"))
return body["data"]
def chat(
self,
message: str,
conversation_id: str | None = None,
persona: str = "professional",
language: str = "en",
) -> dict:
"""Send a chat message and get a RAG-powered response."""
payload = {"message": message, "persona": persona, "language": language}
if conversation_id:
payload["conversation_id"] = conversation_id
r = self.client.post(f"{self.base_url}/chat", json=payload, timeout=120)
r.raise_for_status()
body = r.json()
if not body["success"]:
raise RuntimeError(body.get("error", "Unknown error"))
return body["data"]
def history(self, conversation_id: str) -> list[dict]:
"""Get conversation message history."""
r = self.client.get(f"{self.base_url}/chat/{conversation_id}/messages")
r.raise_for_status()
return r.json()["data"]["messages"]
def close(self):
self.client.close()
# Usage
api = BeyondRetrievalAPI("br_key_...")
result = api.chat("What is the main topic?")
print(result["answer"])
api.close()
Async (Python 3.11+)¶
import httpx
import asyncio
async def main():
async with httpx.AsyncClient(
headers={"X-API-Key": "br_key_...", "Content-Type": "application/json"},
timeout=60.0,
) as client:
response = await client.post(
"https://your-domain.com/api/v1/external/chat",
json={"message": "Summarize the document", "language": "de"},
)
data = response.json()["data"]
print(data["answer"])
asyncio.run(main())
JavaScript / TypeScript¶
Browser (fetch)¶
const API_KEY = "br_key_...";
const BASE_URL = "https://your-domain.com/api/v1/external";
// RAG Query
async function queryDocuments(query, topK = 5) {
const response = await fetch(`${BASE_URL}/query`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": API_KEY,
},
body: JSON.stringify({
query: query,
strategy_id: "fusion",
top_k: topK,
}),
});
const { success, data, error } = await response.json();
if (!success) throw new Error(error);
return data;
}
// Chat
async function chat(message, conversationId = null) {
const body = {
message,
persona: "professional",
language: "en",
};
if (conversationId) body.conversation_id = conversationId;
const response = await fetch(`${BASE_URL}/chat`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": API_KEY,
},
body: JSON.stringify(body),
});
const { success, data, error } = await response.json();
if (!success) throw new Error(error);
return data;
}
// Usage
const result = await chat("What are the key findings?");
console.log(result.answer);
console.log(`Citations: ${result.citations.length}`);
// Follow-up
const followUp = await chat("Tell me more", result.conversation_id);
console.log(followUp.answer);
Node.js¶
const API_KEY = "br_key_...";
const BASE_URL = "https://your-domain.com/api/v1/external";
async function beyondRetrievalChat(message) {
const response = await fetch(`${BASE_URL}/chat`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": API_KEY,
},
body: JSON.stringify({
message,
persona: "professional",
language: "en",
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const { data } = await response.json();
return data;
}
// Express.js middleware example
import express from "express";
const app = express();
app.use(express.json());
app.post("/ask", async (req, res) => {
try {
const result = await beyondRetrievalChat(req.body.question);
res.json({
answer: result.answer,
sources: result.citations.map((c) => c.file_name),
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
TypeScript types¶
interface QueryRequest {
query: string;
strategy_id?: "fusion" | "semantic" | "full_text" | "hybrid" | "hyde" | "multi_query" | "kb_explorer";
top_k?: number;
}
interface QueryChunk {
content: string;
source: string | null;
score: number | null;
metadata: Record<string, any> | null;
}
interface QueryResponse {
query: string;
strategy_id: string;
chunks: QueryChunk[];
total_results: number;
execution_time_ms: number;
}
interface ChatRequest {
message: string;
conversation_id?: string;
strategy_id?: string;
persona?: "professional" | "funny" | "mentor" | "storyteller" | "clear" | "custom";
language?: "en" | "de" | "es" | "fr" | "it" | "pt" | "nl" | "ru" | "zh" | "ja";
}
interface Citation {
citation_id: number;
content: string;
file_name: string | null;
similarity: number | null;
}
interface ChatResponse {
conversation_id: string;
answer: string;
citations: Citation[];
suggested_questions: string[];
is_cached: boolean;
execution_time_ms: number;
}
interface ChatMessage {
role: "user" | "assistant";
content: string;
created_at: string;
}
interface APIEnvelope<T> {
success: boolean;
data: T;
error: string | null;
}
cURL¶
RAG Query¶
curl -X POST "https://your-domain.com/api/v1/external/query" \
-H "Content-Type: application/json" \
-H "X-API-Key: br_key_..." \
-d '{
"query": "What is the refund policy?",
"strategy_id": "fusion",
"top_k": 5
}'
Chat¶
curl -X POST "https://your-domain.com/api/v1/external/chat" \
-H "Content-Type: application/json" \
-H "X-API-Key: br_key_..." \
-d '{
"message": "Summarize the key findings",
"persona": "professional",
"language": "en"
}'
Chat follow-up (pass conversation_id)¶
curl -X POST "https://your-domain.com/api/v1/external/chat" \
-H "Content-Type: application/json" \
-H "X-API-Key: br_key_..." \
-d '{
"message": "Can you explain point 2 in more detail?",
"conversation_id": "34f512ec-7f8a-4e6f-b8a0-38f1c143807d"
}'
Conversation history¶
curl -X GET "https://your-domain.com/api/v1/external/chat/34f512ec-7f8a-4e6f-b8a0-38f1c143807d/messages" \
-H "X-API-Key: br_key_..."
Parse response with jq¶
# Extract just the answer
curl -s -X POST "https://your-domain.com/api/v1/external/chat" \
-H "Content-Type: application/json" \
-H "X-API-Key: br_key_..." \
-d '{"message": "What is the main topic?"}' | jq -r '.data.answer'
# List source files from citations
curl -s -X POST "https://your-domain.com/api/v1/external/query" \
-H "Content-Type: application/json" \
-H "X-API-Key: br_key_..." \
-d '{"query": "pricing", "top_k": 3}' | jq -r '.data.chunks[].source'
n8n¶
Credential setup¶
- Go to Settings > Credentials > Add Credential > Header Auth
- Set:
- Name:
Beyond Retrieval API Key - Header Name:
X-API-Key - Header Value:
br_key_...
- Name:
RAG Query workflow¶
Configure an HTTP Request node:
| Setting | Value |
|---|---|
| Method | POST |
| URL | https://your-domain.com/api/v1/external/query |
| Authentication | Header Auth > Beyond Retrieval API Key |
| Body Content Type | JSON |
Body parameters:
Dynamic input
Use {{ $json.query }} to pass the query from a previous node (e.g., a Webhook trigger, Telegram message, or form submission).
Chat workflow¶
Configure an HTTP Request node:
| Setting | Value |
|---|---|
| Method | POST |
| URL | https://your-domain.com/api/v1/external/chat |
| Authentication | Header Auth > Beyond Retrieval API Key |
| Body Content Type | JSON |
Body parameters:
{
"message": "{{ $json.message }}",
"conversation_id": "{{ $json.conversation_id || '' }}",
"persona": "professional",
"language": "en"
}
Extracting the response¶
After the HTTP Request node, add a Set node to extract fields:
| Field | Expression |
|---|---|
answer | {{ $json.data.answer }} |
conversation_id | {{ $json.data.conversation_id }} |
citation_count | {{ $json.data.citations.length }} |
sources | {{ $json.data.citations.map(c => c.file_name).join(', ') }} |
Full chatbot workflow (Webhook + RAG + Response)¶
graph LR
A[Webhook Trigger] --> B[HTTP Request: Chat]
B --> C[Set: Extract answer]
C --> D[Respond to Webhook] Webhook Trigger node: - Method: POST - Path: /beyond-retrieval-chat
HTTP Request node: - URL: https://your-domain.com/api/v1/external/chat - Body: {"message": "{{ $json.body.question }}", "persona": "professional"}
Respond to Webhook node: - Response body: {{ JSON.stringify({ answer: $json.answer, sources: $json.sources }) }}
Telegram chatbot with n8n¶
graph LR
A[Telegram Trigger] --> B[HTTP Request: Chat]
B --> C[Set: Format answer]
C --> D[Telegram: Send Message] - Telegram Trigger: Listens for new messages
- HTTP Request:
POST /api/v1/external/chatwith{"message": "{{ $json.message.text }}"} - Set: Extract
{{ $json.data.answer }} - Telegram Send: Reply with the extracted answer
Slack chatbot with n8n¶
graph LR
A[Slack Trigger] --> B[HTTP Request: Chat]
B --> C[Slack: Post Message] - Slack Trigger: On new message in channel
- HTTP Request:
POST /api/v1/external/chatwith{"message": "{{ $json.text }}"} - Slack Post Message: Channel = same channel, Message =
{{ $json.data.answer }}
Error handling in n8n¶
Add an IF node after the HTTP Request to check the response:
| Condition | Value |
|---|---|
{{ $json.success }} | equals true |
True branch: Continue with the answer. False branch: Send error notification or retry.
Make.com (Integromat)¶
HTTP Module setup¶
Use the HTTP > Make a request module:
RAG Query¶
| Setting | Value |
|---|---|
| URL | https://your-domain.com/api/v1/external/query |
| Method | POST |
| Headers | X-API-Key: br_key_... |
| Body type | Raw |
| Content type | JSON (application/json) |
Request content:
Chat¶
| Setting | Value |
|---|---|
| URL | https://your-domain.com/api/v1/external/chat |
| Method | POST |
| Headers | X-API-Key: br_key_... |
| Body type | Raw |
| Content type | JSON (application/json) |
Request content:
Parsing the response¶
After the HTTP module, use the JSON > Parse JSON module with the data type set to the response body. Then access:
data.answer— the AI responsedata.conversation_id— for follow-up messagesdata.citations— array of source referencesdata.suggested_questions— suggested follow-ups
Scenario: Google Form to RAG Answer¶
graph LR
A[Google Forms: Watch] --> B[HTTP: Chat API]
B --> C[Google Sheets: Add Row] - Google Forms: Watch for new submissions
- HTTP: POST to
/api/v1/external/chatwith the form question - Google Sheets: Write the question + answer to a spreadsheet
Scenario: Email auto-responder¶
graph LR
A[Gmail: Watch] --> B[HTTP: Chat API]
B --> C[Gmail: Send Reply] - Gmail Watch: New emails matching a label/filter
- HTTP: POST to
/api/v1/external/chatwith{"message": "{{email.subject}}: {{email.body}}"} - Gmail Send: Reply with the AI answer
Zapier¶
Use the Webhooks by Zapier action (Custom Request):
| Setting | Value |
|---|---|
| Method | POST |
| URL | https://your-domain.com/api/v1/external/chat |
| Headers | X-API-Key: br_key_... and Content-Type: application/json |
| Data | {"message": "{{trigger_field}}", "persona": "professional"} |
Parse the response with the Code by Zapier step:
Power Automate (Microsoft)¶
Use the HTTP action:
| Setting | Value |
|---|---|
| Method | POST |
| URI | https://your-domain.com/api/v1/external/chat |
| Headers | X-API-Key: br_key_... and Content-Type: application/json |
| Body | {"message": "@{triggerBody()?['question']}", "language": "en"} |
Parse the response with Parse JSON using this schema:
{
"type": "object",
"properties": {
"success": { "type": "boolean" },
"data": {
"type": "object",
"properties": {
"answer": { "type": "string" },
"conversation_id": { "type": "string" },
"citations": { "type": "array" }
}
}
}
}
WordPress (PHP)¶
<?php
$api_key = "br_key_...";
$base_url = "https://your-domain.com/api/v1/external";
function beyond_retrieval_chat($message, $api_key, $base_url) {
$ch = curl_init("$base_url/chat");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: $api_key",
],
CURLOPT_POSTFIELDS => json_encode([
"message" => $message,
"persona" => "professional",
"language" => "en",
]),
CURLOPT_TIMEOUT => 60,
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code !== 200) {
throw new Exception("API error: HTTP $http_code");
}
$body = json_decode($response, true);
if (!$body["success"]) {
throw new Exception("API error: " . ($body["error"] ?? "Unknown"));
}
return $body["data"];
}
// Usage in a WordPress shortcode
function br_chatbot_shortcode($atts) {
$question = sanitize_text_field($_POST["question"] ?? "");
if (empty($question)) {
return '<form method="post"><input name="question" placeholder="Ask a question..."><button type="submit">Ask</button></form>';
}
try {
global $api_key, $base_url;
$result = beyond_retrieval_chat($question, $api_key, $base_url);
return "<div class='br-answer'><p>{$result['answer']}</p></div>";
} catch (Exception $e) {
return "<div class='br-error'>Error: {$e->getMessage()}</div>";
}
}
add_shortcode("br_chat", "br_chatbot_shortcode");
?>
Flutter / Dart¶
import 'dart:convert';
import 'package:http/http.dart' as http;
class BeyondRetrievalAPI {
final String apiKey;
final String baseUrl;
BeyondRetrievalAPI({
required this.apiKey,
this.baseUrl = "https://your-domain.com/api/v1/external",
});
Future<Map<String, dynamic>> chat(
String message, {
String? conversationId,
String persona = "professional",
String language = "en",
}) async {
final body = {
"message": message,
"persona": persona,
"language": language,
};
if (conversationId != null) body["conversation_id"] = conversationId;
final response = await http.post(
Uri.parse("$baseUrl/chat"),
headers: {
"Content-Type": "application/json",
"X-API-Key": apiKey,
},
body: jsonEncode(body),
);
if (response.statusCode != 200) {
throw Exception("API error: ${response.statusCode}");
}
final data = jsonDecode(response.body);
if (data["success"] != true) {
throw Exception("API error: ${data['error']}");
}
return data["data"];
}
}
// Usage
final api = BeyondRetrievalAPI(apiKey: "br_key_...");
final result = await api.chat("What is the main topic?");
print(result["answer"]);
Error Handling¶
All platforms should handle these error cases:
| HTTP Status | Meaning | Action |
|---|---|---|
200 | Success | Parse data from response |
401 | Invalid/expired/disabled key | Check your API key |
403 | API disabled or wrong notebook | Contact the admin |
429 | Rate limit exceeded | Wait and retry (check Retry-After header) |
500 | Server error | Retry with exponential backoff |
Rate limit handling¶
When you receive a 429 response, wait before retrying:
import time
import httpx
def chat_with_retry(message, max_retries=3):
for attempt in range(max_retries):
response = httpx.post(
f"{BASE_URL}/chat",
headers=HEADERS,
json={"message": message},
timeout=60,
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
continue
response.raise_for_status()
return response.json()["data"]
raise RuntimeError("Max retries exceeded")
async function chatWithRetry(message, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(`${BASE_URL}/chat`, {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },
body: JSON.stringify({ message }),
});
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "60");
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
continue;
}
const { data } = await response.json();
return data;
}
throw new Error("Max retries exceeded");
}
Settings Inheritance¶
The Developer API automatically inherits the notebook's Intelligence Settings when optional parameters are omitted. This means you can configure a notebook's strategy, persona, language, and retrieval weights once in the admin UI, and all external API requests will use those settings by default.
Minimal request (uses all notebook settings):
Override specific settings:
In the example above, persona and language are overridden while strategy_id and other retrieval settings are inherited from the notebook.
Zero-config integrations
For the simplest integration, omit all optional parameters. The notebook admin controls behavior entirely through the Intelligence Settings UI — no code changes needed when settings are updated.
Best Practices¶
- Store API keys securely — use environment variables, not hardcoded strings
- Set appropriate timeouts — chat responses can take 10-30 seconds for complex queries
- Reuse conversation IDs — pass
conversation_idfor multi-turn chats to get context-aware answers - Handle rate limits gracefully — implement retry logic with exponential backoff
- Use the
queryendpoint for search — if you only need document chunks without AI generation, use/queryinstead of/chat(faster, no LLM cost) - Pick the right persona —
professionalfor business apps,clearfor user-facing FAQs,mentorfor learning tools - Use language codes — set
languageto match your users' language for localized responses