Build with the API

Chat & conversations

Run Theo turns from your backend, streaming or JSON, with persistent conversations.

Modes

POST /chat runs a Theo turn with tools: Theo can create flowcharts, whiteboards, notes, presentations, sheets, boards, documents, images, video, and music from a plain-English prompt, each surfacing as an artifact on the response. Two launch modes ship in v1:

Modes
fast
default
Snappy responses for everyday turns.
think
deeper reasoning
Slower, more deliberate reasoning for complex asks.
Request body
message
string
The prompt (shorthand for a single user message).
messages
array (max 40)
Alternative: explicit user/assistant history.
conversationId
string
Continue an existing thread; prior history loads server-side, so send only the new message.
mode
fast | think
stream
boolean (default true)
true = SSE stream; false = one assembled JSON response.

Streaming (default)

The endpoint streams the Vercel AI SDK UI-message SSE protocol (the same wire format the OpenCharts app consumes), so useChatcan render it directly. The request body is the API's own shape (see above), so map it in the transport:

React (AI SDK v5+)
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";

const { messages, sendMessage } = useChat({
  transport: new DefaultChatTransport({
    // Your server proxy: forwards to opencharts.com and attaches the key.
    api: "/api/theo-chat",
    prepareSendMessagesRequest: ({ messages }) => ({
      body: {
        messages: messages
          .filter((m) => m.role === "user" || m.role === "assistant")
          .map((m) => ({
            role: m.role,
            content: m.parts
              .filter((p) => p.type === "text")
              .map((p) => p.text)
              .join(""),
          })),
      },
    }),
  }),
});

Or read the SSE stream directly: the exact event vocabulary is documented in the next section.

Keys stay server-side

Never expose your API key in a browser. Proxy the stream through your own backend and attach the key there.

SSE events

The stream is standard Server-Sent Events: Content-Type: text/event-stream plus the protocol marker header x-vercel-ai-ui-message-stream: v1. Each event is data: <json> with a type discriminator, and the stream ends with the terminator data: [DONE].

Event types
start
lifecycle
The turn started (carries the messageId).
start-step / finish-step
lifecycle
Bracket each model step; a turn with tool calls runs multiple steps.
text-start / text-delta / text-end
text
Assistant text. Concatenate `delta` values that share the same `id`.
reasoning-start / reasoning-delta / reasoning-end
text
Optional reasoning traces (think mode).
tool-input-start / tool-input-delta / tool-input-available
tools
A tool call being assembled; `tool-input-available` carries the final `toolName` + `input`.
tool-output-available
tools
The tool result: artifacts arrive here as `output` (see the next section).
finish
lifecycle
The turn is complete (carries `finishReason`).
error
lifecycle
Terminal error (`errorText`).
example stream (abridged)
data: {"type":"start","messageId":"msg_0"}

data: {"type":"start-step"}

data: {"type":"text-start","id":"txt_0"}

data: {"type":"text-delta","id":"txt_0","delta":"Here is your onboarding funnel"}

data: {"type":"tool-input-available","toolCallId":"call_1","toolName":"create_flowchart","input":{"title":"SaaS onboarding funnel"}}

data: {"type":"tool-output-available","toolCallId":"call_1","output":{"type":"flowchart","title":"SaaS onboarding funnel","projectId":"6870f3...","projectType":"chart","openUrl":"https://www.opencharts.com/editor/6870f3..."}}

data: {"type":"text-end","id":"txt_0"}

data: {"type":"finish-step"}

data: {"type":"finish","finishReason":"stop"}

data: [DONE]

Non-streaming

Pass stream: false for a single assembled JSON response with the message, its artifacts, and token usage:

curl https://www.opencharts.com/api/v1/chat \
  -H "Authorization: Bearer $OPENCHARTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Create a flowchart of a SaaS onboarding funnel",
    "mode": "fast",
    "stream": false
  }'

Conversations

Artifacts & async jobs

Every tool result surfaces as an artifact: on JSON responses under message.artifacts, on streams as the output of a tool-output-availableevent. Project-backed artifacts carry the created project's id plus an absolute openUrl deep link into the OpenCharts editor:

artifact
{
  "type": "flowchart",
  "title": "SaaS onboarding funnel",
  "projectId": "6870f3...",
  "projectType": "chart",
  "openUrl": "https://www.opencharts.com/editor/6870f3..."
}
Editor deep links (openUrl formats)
flowchart / whiteboard
/editor/{projectId}
notes
/notes/{projectId}
presentation
/presentations/{projectId}
sheet
/sheets/{projectId}
code build
/code-editor/{codeProjectId}
social_board
/boards/{boardId}
conversation
/chat/{conversationId}
The thread itself, in the in-app sidebar.

When Theo dispatches long-running work mid-chat (video, music), the artifact instead carries an apiJobId (type-prefixed) and pollUrl on its data. Poll it at GET /jobs/{id} or register a webhook and skip polling entirely. The full artifact schema ships in the API reference and the OpenAPI document (ChatArtifact).

Tools & credits

The API surface runs a curated tool set focused on creation and research; account-sensitive tools (email sending, project deletion, database access) are excluded by design. Chat consumes AI credits with the same metering Theo uses in-app; check the balance with GET /account/credits.