Brain by AIStack

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/search

2. 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 TypeDescription
search_resultsMatched documents (sent first)
thinkingModel reasoning tokens (if supported)
textGenerated answer text (streamed incrementally)
stepsInternal step trace (if include_steps: true)
errorError 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)}...`);
  }
}
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);
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())

On this page