Blade AI ChatbotIntegration API
Streaming AI agent · SSE · Tool calling

Integrate the Blade AI Chatbot into any product

A single streaming HTTP endpoint powers the whole conversation: the model thinks, calls tools on the shared MCP server, dispatches actions back to your UI, and streams tokens to the user in real time. No SDK, no bespoke protocol — just POST and text/event-stream.

Get started → Browse OpenAPI

What this service does

The Blade AI Chatbot Integration API is a thin, opinionated gateway that sits between your frontend and a LangGraph-based deepagents runtime. Your client posts a single message; the server orchestrates the model, calls MCP tools, dispatches UI actions back to you, remembers the conversation, and streams everything over Server-Sent Events.

Real-time streaming

Token-by-token SSE. Show output as the model produces it — no polling, no websockets.

Tool calling

MCP tools run server-side. UI tools are dispatched to your client as structured actions.

Memory built in

Pass a stable conversation_id and the agent remembers previous turns.

Swap models freely

OpenRouter or Ollama backends, switchable per session, project, or globally at runtime.

Quick start

The only endpoint you strictly need is the chat one. Send a JSON message, read an SSE stream, render tokens as they arrive.

1. Base URL

In production, put this API behind your own gateway/domain. Locally the server listens on port 8100.

Base URL
# Local dev
http://localhost:8100

# Production (behind your gateway)
https://your-gateway.example.com

2. Fire your first message

cURL
curl -N -X POST http://localhost:8100/utils/ai-agent/chat \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Hello, what can you do?",
    "conversation_id": "demo-session-1"
  }'

The -N flag disables cURL buffering so you can watch the SSE stream arrive line by line.

That's the whole integration surface. Everything else on this page is either an SSE event you may want to handle, or an admin knob you can tune from the dashboard.

Chat endpoint

MethodPathContentResponse
POST /utils/ai-agent/chat application/json text/event-stream

Request body

message string required
The end user's message. Must be non-empty.
conversation_id string optional
Stable identifier for the chat session — typically a client-generated UUID (crypto.randomUUID()). Reuse the same value across turns so the agent recalls prior context. Omit to make each turn stateless. Never reuse the same id across different end users — they would share history.
project_id string optional
Identifier for the project/workspace the user is in. Used as a scope key for provider overrides. Not validated against any external system.
url string optional
The URL of the screen the user is currently viewing. The agent uses it to infer context (e.g. which file is open).
layers array<{id, name}> optional
Currently visible layers of the open drawing / document. Lets the model resolve human names to real IDs when calling UI tools.

Minimal request

JSON
{
  "message": "Summarize the current document",
  "conversation_id": "5b2f0b6c-08a2-4b4b-9c3d-3e0f8a5b6c7d",
  "project_id": "proj_42",
  "url": "https://app.example.com/viewer/pdf?baseFileId=abc"
}

SSE event schema

Every line the server sends looks like data: {...}\n\n — standard Server-Sent Events. Each JSON payload carries a type field so you can dispatch on it.

Event typeFieldsWhat to do
token content: string chunk Append to the assistant bubble as it arrives.
tool_start tool: MCP tool name Optionally show a "thinking / searching…" indicator.
tool_call tool, params, requiresConfirm The agent wants your UI to perform an action. Dispatch it in your app.
table Structured tabular data Render as a table (optional — falls back to text if ignored).
done Turn finished. Close the stream / re-enable input.

Sample stream

SSE
data: {"type":"token","content":"Sure, "}

data: {"type":"token","content":"let me open page 3."}

data: {"type":"tool_call","tool":"go_to_page","params":{"pageNumber":3},"requiresConfirm":false}

data: {"type":"done"}

Client examples

Three ready-to-paste snippets that consume the SSE stream and dispatch UI actions.

JavaScript (fetch stream)
// Works in modern browsers, Node 18+, and Deno.
async function chat(message, conversationId, onEvent) {
  const res = await fetch("/utils/ai-agent/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ message, conversation_id: conversationId }),
  });

  const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
  let buffer = "";

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += value;

    let idx;
    while ((idx = buffer.indexOf("\n\n")) >= 0) {
      const frame = buffer.slice(0, idx).trim();
      buffer = buffer.slice(idx + 2);
      if (!frame.startsWith("data:")) continue;
      const event = JSON.parse(frame.slice(5).trim());
      onEvent(event);
    }
  }
}

// Usage
const convoId = crypto.randomUUID();
chat("Hello!", convoId, (evt) => {
  if (evt.type === "token")    appendToBubble(evt.content);
  if (evt.type === "tool_call") dispatchUiAction(evt.tool, evt.params);
  if (evt.type === "done")     enableInput();
});
Python (httpx)
import json, uuid, httpx

def chat(message: str, convo_id: str):
    payload = {"message": message, "conversation_id": convo_id}
    url = "http://localhost:8100/utils/ai-agent/chat"
    with httpx.stream("POST", url, json=payload, timeout=None) as r:
        for line in r.iter_lines():
            if not line or not line.startswith("data:"):
                continue
            event = json.loads(line[5:].strip())
            if event["type"] == "token":
                print(event["content"], end="", flush=True)
            elif event["type"] == "tool_call":
                print(f"\n[ui-action] {event['tool']} {event['params']}")
            elif event["type"] == "done":
                print()

chat("Hello!", str(uuid.uuid4()))
cURL
curl -N -X POST http://localhost:8100/utils/ai-agent/chat \
  -H "Content-Type: application/json" \
  -d '{
    "message": "What tools do you have?",
    "conversation_id": "demo-1"
  }'
Don't use EventSource. The browser EventSource API only supports GET requests, so it can't send the JSON body. Use fetch with a streaming reader as shown above.

Conversations & memory

The server persists turn history in MongoDB, keyed by conversation_id. On every request it feeds the last few turns back into the model as context.

  • Generate a fresh UUID per user session with crypto.randomUUID() and reuse it for every turn in that session.
  • Two different users must never share the same conversation_id — they would see each other's history.
  • Conversations are automatically deleted after a configurable idle period (TTL), so old sessions don't linger.
  • Omit conversation_id entirely to make a turn stateless (nothing is stored, nothing is recalled).

UI tools

When the agent decides a UI action would help, it emits a tool_call SSE event. Your frontend is responsible for actually performing the action — navigating, highlighting, toggling a layer, whatever your app supports.

You declare the available UI tools once via POST /utils/ai-agent/ui-tools. Each tool is an OpenAI-style function schema. The list is stored server-side and picked up on the next turn.

POST /utils/ai-agent/ui-tools
{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "go_to_page",
        "description": "Jump the PDF viewer to a specific page in the open drawing.",
        "parameters": {
          "type": "object",
          "properties": {
            "pageNumber": { "type": "integer", "description": "Page to navigate to" }
          },
          "required": ["pageNumber"]
        }
      }
    }
  ]
}

Then, in your SSE handler, listen for events with type: "tool_call" and route evt.tool + evt.params into your action registry:

JavaScript
const ACTIONS = {
  go_to_page: ({ pageNumber }) => viewer.jumpTo(pageNumber),
  highlight:   ({ id })         => viewer.flash(id),
};

function dispatchUiAction(name, params) {
  const fn = ACTIONS[name];
  if (fn) fn(params);
  else    console.warn("unknown UI tool", name);
}
Tool schema management is in the admin dashboard. Open /adminUI Tools to edit the list from a UI instead of hand-crafting POST bodies.

Provider & model

The agent runs on either OpenRouter (any OpenAI-compatible model) or a self-hosted Ollama server. You can switch model globally, or override it per project or per conversation — useful for A/B tests or giving certain users access to a stronger model.

MethodPathPurpose
GET /utils/ai-agent/provider Inspect the effective provider & model.
POST /utils/ai-agent/provider Override provider & model at global / project / session scope.

Resolution order (highest wins): sessionprojectglobal → environment default.

POST /utils/ai-agent/provider
{
  "provider": "openrouter",
  "model":    "anthropic/claude-sonnet-4.5",
  "scope":    "project",
  "scope_id": "proj_42"
}

Provider & API-key configuration also lives in the admin dashboard so you don't have to redeploy to change models.

Health & operations

MethodPathWhat it returns
GET /health MongoDB reachability + overall status (200 healthy / 503 degraded).
GET /docs Interactive Swagger UI for every endpoint.
GET /redoc ReDoc reference for the same schema.
GET /admin Password-protected dashboard: provider config, MCP endpoint, UI tools, live test chat.

CORS

Browser origins are whitelisted via the WEB_URL environment variable (comma-separated) or a regex in CORS_ORIGIN_REGEX. Use WEB_URL=* in local dev if you need any origin.

Timeouts

Chat requests get a generous 10-minute ceiling because a single agent turn can involve several tool round-trips. All other endpoints use the default REQUEST_TIMEOUT.

You're ready to integrate. The only thing your product has to know is POST /utils/ai-agent/chat. Everything else — models, tools, MCP configuration — can be tuned from /admin without touching your client code.