Browse documentation

Connect

Build your UI or bot

Connect a web UI, mobile app, Telegram bot, Discord bot, CMS, or webhook.

Flary is the durable agent backend. Your application owns the screen, bot, or channel that people use.

Headless use

Choose Agent backend in flary create. The deployed Worker provides typed HTTP, WebSocket, and SSE APIs. You do not write a route for each agent action.

Use the typed client from a trusted server:

import { flary } from "flary/client";
import type { functions } from "../worker/src/index";

const api = flary<typeof functions>({
  baseUrl: "https://my-agent.example.workers.dev",
  headers: () => ({ authorization: `Bearer ${token}` }),
});

Do not place a personal backend token in public browser code. Use your application session, a trusted server route, or the dashboard’s secure cookie.

Use the ready React console

Flary ships the same WebSocket console that the dashboard starter uses. It includes thread creation and deletion, replay, reconnect, optimistic messages, one ordered activity rail, tool details, approvals, interruption, and the composer. Assistant answers render Markdown as they stream, including headings, lists, links, tables, and fenced code blocks with a copy button. Styling is included; your app does not need Tailwind.

import { flary } from "flary/client";
import { FlaryAgentConsole } from "flary/react";
import type { functions } from "../worker/src/index";

const api = flary<typeof functions>({
  baseUrl: "/api/flary",
  headers: getApplicationSessionHeaders,
});

export function AgentPanel() {
  return <FlaryAgentConsole agent={api.support} title="Support agent" />;
}

The component does not copy the session into application storage. Flary stays the session authority. The browser stores only the newest replay cursor.

Style the outer shell or build your own view from the same hook:

import { useFlaryThread } from "flary/react";

const state = useFlaryThread({ thread });
// state.turns, state.send(), state.interrupt(), state.approve()

For a custom chat layout, use the same renderer for assistant messages:

import { FlaryMarkdown } from "flary/react";

<FlaryMarkdown streaming={turn.status === "working"}>{assistantMessage.text}</FlaryMarkdown>;

Pass the accumulated answer text on each update. streaming keeps unfinished formatting readable while tokens arrive; turn it off when the answer finishes. Code blocks include Shiki syntax highlighting, scroll horizontally, and enable copying once the answer is complete. Highlighting is included; no extra plugin or Tailwind setup is needed. Raw HTML stays disabled. Keep user messages as plain text so their input is shown exactly as entered.

The renderer includes scoped CSS by default. To share one stylesheet across a transcript, render <FlaryReactStyles /> once and pass styled={false} to each FlaryMarkdown. Override the --flary-accent, --flary-code, --flary-line, --flary-muted, and --flary-mono CSS variables to match your app. The default Flary light and dark syntax palettes follow the host’s .light / .dark class, data-theme / data-color-mode attribute, or system preference. Set colorScheme="light" or colorScheme="dark" for an explicit choice. Use shikiTheme={["github-light", "github-dark"]} to choose another pair of Shiki themes. Advanced renderers can supply Streamdown components and plugins props; a custom plugins.code replaces the built-in highlighter.

Build your own web UI

Store two values in the UI:

  • The Flary thread ID.
  • The last event cursor that the UI received.

Open the saved thread, send a message, and resume after the saved cursor:

const thread = await api.support.threads.open({ threadId });

await thread.send({
  message: form.message,
  idempotencyKey: form.requestId,
});

for await (const event of thread.stream({ after: savedCursor })) {
  render(event);
  saveCursor(event.cursor);
}

Use thread.connect() when the UI needs bidirectional WebSocket control. The connection uses one short-lived ticket, resumes from a durable cursor, and can sleep with its Thread Control Durable Object. SSE is the read-only fallback.

Build a Telegram, Discord, CMS, or webhook adapter

Flary provides the typed API and client for these integrations. The examples below describe an adapter you build in your application; Flary does not include ready-made Telegram or Discord bots.

An adapter typically:

  1. Authenticate the incoming user or service.
  2. Map the external conversation to a Flary thread ID.
  3. Send the message with an idempotency key.
  4. Save the newest durable cursor.
  5. Deliver public events back to the channel.

Do not store model or tool credentials in the channel. The Flary Worker resolves them after it validates the tenant identity.

Read Threads and realtime clients for all thread controls and HTTP API and events for route-level integration.