FirstFlow integrates into your product from two sides and is operated from a third. A browser SDK renders experiences inside your chat UI over a realtime connection; a server SDK observes your LLM calls so FirstFlow can see the real conversation; and an MCP server lets a coding agent build and publish experiences for you. This page is the map of those pieces what each one is, the small contract it asks of your code, and where its deep reference lives.
The integration contract is deliberately small. You wrap your LLM client on the server, mount a provider plus a widget in the browser, and join the two with one id you own. Everything that decides what to show triggers, audience, schedule, frequency, the classifier, and widget composition runs server-side in FirstFlow Cloud (or your self-hosted backend), so adding a new experience never requires a client deploy.
How the pieces fit
The mechanism behind every piece on this page is the same loop. Your server SDK reports each conversation turn; the backend evaluates eligibility and walks the experience’s flow graph to a result; FirstFlow composes a WidgetTree with Anthropic (claude-sonnet-4-6) and pushes it over Socket.IO; the browser SDK renders it and reports interaction back. The browser is a passive consumer it never decides what to show, which is why you cannot trigger a widget from the client alone.
The packages map cleanly onto that loop: @firstflow/sdk is the server half (reporting turns, traces, and outcomes), @firstflow/react is the browser half (rendering and reporting activity), @firstflow/nextjs bundles both for App Router apps, @firstflow/widget-kit is the rendering core they share, and the MCP server is how an agent edits the experiences the loop runs.
Browser SDK @firstflow/react
The browser SDK is the runtime that lives in your chat UI. It opens the realtime socket, identifies the end user, renders the widgets the backend pushes, and reports activity back. Mount FirstflowProvider once above your chat and drop a FirstflowWidget where experiences should appear.
The provider takes agentId and a publishableKey (pk_live_…, browser-safe not a secret), plus an optional user and conversationId. It throws if agentId or publishableKey is empty, and identifies the user immediately from the user prop without waiting for the socket. Theme and slash commands arrive on the server’s agent.config event and apply automatically. Around the widget, useFirstflow() gives you the instance: analytics (track, identify, page), getUser/setUser, notifyActivity(), the commands array, triggerCommand(), and getConversationId(). Identity, analytics, slash commands, and theming each have their own page; realtime covers the connection itself.
Server SDK @firstflow/sdk
The server SDK wraps your existing LLM client so every conversation turn, trace, token count, and cost flows to FirstFlow. Observing the conversation server-side is also what powers user-message triggers and classifier context the backend sees the real conversation and can decide what to show. The one rule is that every observed call carries three identifiers, all required: firstflowAgentId, sessionId (which must equal the browser’s conversationId), and userId.
The wrapper strips those three fields before the request reaches OpenAI or
Anthropic. If any one is missing, the call passes through unobserved (with a
one-time warning) so partial tagging silently drops the turn from FirstFlow.
Import the wrapped client that matches your provider @firstflow/sdk/openai, /anthropic, or /aiclient for any OpenAI-compatible endpoint (Ollama, vLLM, LM Studio) or use @firstflow/sdk/langchain for LangChain. The instrumented methods are chat.completions.create, messages.create, responses.create, and embeddings.create. Alongside the wrappers, fire-and-forget helpers read FIRSTFLOW_API_KEY and never throw or block: observe() records a turn manually, trace() records nested spans, outcome() signals how a session ended, and track() / identify() send server-side analytics. The only required env var is FIRSTFLOW_API_KEY (secret, server-only); set FIRSTFLOW_API_BASE_URL when self-hosting. Observed conversations are what the dashboard turns into analytics and sessions.
Next.js wrapper @firstflow/nextjs
@firstflow/nextjs is a thin wrapper for App Router apps. Its default entry re-exports the browser SDK for client components, and its /server subpaths re-export the server helpers and pre-wrapped clients (/server/openai, /server/anthropic, /server/aiclient). Keeping server code on a separate subpath means a bundler never pulls server or OTel code into the browser. Use it when you want one dependency and Next-aware defaults; the underlying API and prop names are identical to the two SDKs above.
The widget is the visible surface, but it has no logic of its own it renders the WidgetTree the backend pushes. The connection is a Socket.IO link at path /v1/socket, authenticated with a short-lived client JWT the server SDK mints (carrying workspace, sub, and traits), and the browser joins a per-user room so only that user’s widgets arrive. User interaction emits widget.action back; flow navigation (flow.next / flow.back) is resolved server-side, which then composes and pushes the next tree. See FirstflowWidget for props and event handlers, and realtime for connecting without React.
@firstflow/widget-kit is the runtime-agnostic rendering core that both SDKs build on. It defines the WidgetTree and Block schema and a BlockRenderer that turns a tree into React components, along with state utilities and streaming helpers that heal partial JSON trees as they arrive from the model. Most apps never import it directly @firstflow/react already renders widgets and re-exports the block types. Reach for widget-kit only to build a custom renderer or render trees outside the standard widget shell.
AI Connect the MCP server
The MCP server is how a coding agent operates FirstFlow instead of integrating it. Mounted at <BACKEND_URL>/mcp over streamable HTTP and protected by OAuth 2.0 + PKCE, it lets Claude Code, Cursor, or Claude Desktop read your product context list agents, inspect experiences and audience and then create tours, surveys, announcements, and guides, edit flow steps, and publish. What each call can do depends on its scopes. Note that static API keys are rejected on /mcp (clients must use OAuth) even though they still work on the REST API. To point a plain assistant at these docs as context rather than have it operate the platform, see Use these docs with AI.
The linking id
The browser and server halves are joined by one value you own and persist: the browser’s conversationId must equal the server SDK’s sessionId. Mint it lazily omit it until the first message, then create and persist a stable id for the life of the thread and pass the same value to both sides so every observed turn and every pushed widget belong to the same conversation. The full model is in Conversations & sessions.
Next
Pick the side you’re integrating first.