Skip to content

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

pip install httpx

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

  1. Go to Settings > Credentials > Add Credential > Header Auth
  2. Set:
    • Name: Beyond Retrieval API Key
    • Header Name: X-API-Key
    • Header Value: br_key_...

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:

{
  "query": "{{ $json.query }}",
  "strategy_id": "fusion",
  "top_k": 10
}

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]
  1. Telegram Trigger: Listens for new messages
  2. HTTP Request: POST /api/v1/external/chat with {"message": "{{ $json.message.text }}"}
  3. Set: Extract {{ $json.data.answer }}
  4. 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]
  1. Slack Trigger: On new message in channel
  2. HTTP Request: POST /api/v1/external/chat with {"message": "{{ $json.text }}"}
  3. 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:

{
  "query": "What is the pricing?",
  "strategy_id": "fusion",
  "top_k": 5
}

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:

{
  "message": "Summarize the document",
  "persona": "professional",
  "language": "en"
}

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 response
  • data.conversation_id — for follow-up messages
  • data.citations — array of source references
  • data.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]
  1. Google Forms: Watch for new submissions
  2. HTTP: POST to /api/v1/external/chat with the form question
  3. 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]
  1. Gmail Watch: New emails matching a label/filter
  2. HTTP: POST to /api/v1/external/chat with {"message": "{{email.subject}}: {{email.body}}"}
  3. 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:

const response = JSON.parse(inputData.response);
return { answer: response.data.answer };

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):

{"query": "What is the refund policy?"}

Override specific settings:

{
  "message": "What is the refund policy?",
  "persona": "clear",
  "language": "de"
}

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

  1. Store API keys securely — use environment variables, not hardcoded strings
  2. Set appropriate timeouts — chat responses can take 10-30 seconds for complex queries
  3. Reuse conversation IDs — pass conversation_id for multi-turn chats to get context-aware answers
  4. Handle rate limits gracefully — implement retry logic with exponential backoff
  5. Use the query endpoint for search — if you only need document chunks without AI generation, use /query instead of /chat (faster, no LLM cost)
  6. Pick the right personaprofessional for business apps, clear for user-facing FAQs, mentor for learning tools
  7. Use language codes — set language to match your users' language for localized responses