Guides
Practical guides for using Brain
Guides
Migrating from Cloudflare AutoRAG
Brain is API-compatible with Cloudflare AutoRAG. To migrate:
1. Change the base URL
- const BASE_URL = "https://api.cloudflare.com/client/v4/accounts/ACCOUNT_ID/autorag/rags/RAG_NAME"
+ const BASE_URL = "https://brain.aistack.run/api/v1"Or keep using Cloudflare-style URLs (Brain proxies them):
https://brain.aistack.run/api/v1/accounts/ACCOUNT_ID/autorag/rags/RAG_NAME/search2. Replace the API key
Replace your Cloudflare API token with a Brain API key created in the dashboard.
3. Verify responses
The response envelope is identical:
{ "success": true, "result": { ... }, "errors": [], "messages": [] }Folder-Based Filtering
Organize documents by folder and filter searches:
# Upload to a folder
curl -X POST https://brain.aistack.run/api/v1/documents \
-H "Authorization: Bearer $KEY" \
-F "file=@guide.md" \
-F "filename=docs/guide.md"
# Filter search to that folder
curl -X POST https://brain.aistack.run/api/v1/search \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"query":"...", "filters": {"type":"and","filters":[{"key":"folder","value":"docs/"}]}}'Streaming AI Responses
Use "stream": true for real-time streaming responses. The SSE stream emits several event types:
| Event Type | Description |
|---|---|
search_results | Matched documents (sent first) |
thinking | Model reasoning tokens (if supported) |
text | Generated answer text (streamed incrementally) |
steps | Internal step trace (if include_steps: true) |
error | Error message (if generation fails) |
[DONE] | End of stream |
const res = await fetch("https://brain.aistack.run/api/v1/ai-search", {
method: "POST",
headers: {
"Authorization": "Bearer " + apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({ query: "...", stream: true }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
for (const line of text.split("\n")) {
if (!line.startsWith("data: ")) continue;
const payload = line.slice(6);
if (payload === "[DONE]") break;
const data = JSON.parse(payload);
switch (data.type) {
case "search_results":
console.log("Sources:", data.data);
break;
case "thinking":
process.stderr.write(data.text); // reasoning
break;
case "text":
process.stdout.write(data.text); // answer
break;
case "error":
console.error("Error:", data.error);
break;
}
}
}TypeScript Integration
Basic Setup
const API_BASE = "https://brain.aistack.run/api/v1";
const API_KEY = "rag_your_api_key";
async function request(path: string, options: RequestInit = {}) {
const res = await fetch(`${API_BASE}${path}`, {
...options,
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
...options.headers,
},
});
if (!res.ok) {
const error = await res.json();
throw new Error(error.errors?.[0]?.message ?? res.statusText);
}
return res.json();
}Upload a Document
async function uploadDocument(filename: string, content: string) {
return request("/documents", {
method: "POST",
body: JSON.stringify({ filename, content }),
});
}
await uploadDocument("docs/guide.md", "# Getting Started\n...");Search Documents
interface SearchResult {
success: boolean;
result: {
object: "vector_store.search_results.page";
search_query: string;
data: Array<{
file_id: string;
filename: string;
score: number;
content: Array<{ id: string; type: string; text: string }>;
}>;
has_more: boolean;
next_page: string | null;
};
}
async function search(query: string, maxResults = 10): Promise<SearchResult> {
return request("/search", {
method: "POST",
body: JSON.stringify({ query, max_num_results: maxResults }),
});
}
const results = await search("How do I deploy?");
for (const doc of results.result.data) {
console.log(`${doc.filename} (score: ${doc.score})`);
for (const chunk of doc.content) {
console.log(` ${chunk.text.slice(0, 100)}...`);
}
}AI Search
interface AISearchResult {
success: boolean;
result: {
object: "vector_store.search_results.page";
search_query: string;
response: string;
data: Array<{
file_id: string;
filename: string;
score: number;
content: Array<{ id: string; type: string; text: string }>;
}>;
has_more: boolean;
next_page: string | null;
};
}
async function aiSearch(query: string): Promise<AISearchResult> {
return request("/ai-search", {
method: "POST",
body: JSON.stringify({ query }),
});
}
const answer = await aiSearch("What is Brain?");
console.log(answer.result.response);Streaming AI Search
async function aiSearchStream(
query: string,
onText: (text: string) => void,
onThinking?: (text: string) => void,
onSources?: (data: Array<{ file_id: string; filename: string; score: number }>) => void,
) {
const res = await fetch(`${API_BASE}/ai-search`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query, stream: true }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
for (const line of text.split("\n")) {
if (!line.startsWith("data: ")) continue;
const payload = line.slice(6);
if (payload === "[DONE]") return;
const data = JSON.parse(payload);
switch (data.type) {
case "search_results":
onSources?.(data.data);
break;
case "thinking":
onThinking?.(data.text);
break;
case "text":
onText(data.text);
break;
case "error":
throw new Error(data.error);
}
}
}
}
await aiSearchStream(
"Explain the architecture",
(text) => process.stdout.write(text),
(thinking) => process.stderr.write(thinking),
(sources) => console.log("Sources:", sources.map(s => s.filename)),
);Python Integration
Basic Setup
import requests
API_BASE = "https://brain.aistack.run/api/v1"
API_KEY = "rag_your_api_key"
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}Upload a Document
# Upload from file
with open("guide.md", "rb") as f:
res = requests.post(
f"{API_BASE}/documents",
headers={"Authorization": f"Bearer {API_KEY}"},
files={"file": f},
data={"filename": "docs/guide.md"},
)
print(res.json())
# Upload from string
res = requests.post(
f"{API_BASE}/documents",
headers=headers,
json={
"filename": "docs/guide.md",
"content": "# Getting Started\n...",
},
)
print(res.json())Search Documents
res = requests.post(
f"{API_BASE}/search",
headers=headers,
json={
"query": "How do I deploy?",
"max_num_results": 10,
},
)
data = res.json()
for doc in data["result"]["data"]:
print(f'{doc["filename"]} (score: {doc["score"]:.2f})')
for chunk in doc["content"]:
print(f' {chunk["text"][:100]}...')AI Search
res = requests.post(
f"{API_BASE}/ai-search",
headers=headers,
json={"query": "What is Brain?"},
)
data = res.json()
print(data["result"]["response"])Streaming AI Search
import json
res = requests.post(
f"{API_BASE}/ai-search",
headers=headers,
json={"query": "Explain the architecture", "stream": True},
stream=True,
)
for line in res.iter_lines(decode_unicode=True):
if not line.startswith("data: "):
continue
payload = line[6:]
if payload == "[DONE]":
break
event = json.loads(payload)
if event.get("type") == "search_results":
for source in event["data"]:
print(f"Source: {source['filename']} (score: {source['score']:.2f})")
elif event.get("type") == "thinking":
print(f"[thinking] {event['text']}", end="", flush=True)
elif event.get("type") == "text":
print(event["text"], end="", flush=True)
elif event.get("type") == "error":
print(f"\nError: {event['error']}")Filtering by Folder
res = requests.post(
f"{API_BASE}/search",
headers=headers,
json={
"query": "deployment steps",
"filters": {
"type": "and",
"filters": [{"key": "folder", "value": "guides/"}],
},
},
)
print(res.json())