The two SDKs, realtime widget, MCP server, and widget-kit that integrate FirstFlow into an AI agent product.
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.
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.
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.
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.
import { OpenAI } from "@firstflow/sdk/openai";const client = new OpenAI(); // reads FIRSTFLOW_API_KEY from envconst res = await client.chat.completions.create({ firstflowAgentId: "agt_…", sessionId, // == browser conversationId userId, model: "gpt-4o", messages,});
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.
@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.
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 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.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"FirstFlow","colors":{"primary":"#171717","light":"#FAFAFA","dark":"#000000"},"logo":{"light":"https://mintcdn.com/firstflow/9x5VMZzjDzvDo9rV/logo/wordmark-light.svg?fit=max\u0026auto=format\u0026n=9x5VMZzjDzvDo9rV\u0026q=85\u0026s=b0da7cdb29a0300450bdcb0b1eb37970","dark":"https://mintcdn.com/firstflow/9x5VMZzjDzvDo9rV/logo/wordmark-dark.svg?fit=max\u0026auto=format\u0026n=9x5VMZzjDzvDo9rV\u0026q=85\u0026s=f3ba2f1868883f5a78c9d0e359dc9bb1"},"favicon":"https://mintcdn.com/firstflow/OLAIWeysZaw-6_3n/logo.svg?fit=max\u0026auto=format\u0026n=OLAIWeysZaw-6_3n\u0026q=85\u0026s=f3ee6ce5c23c50204a1149d621c3e113","appearance":{"default":"dark"},"background":{"decoration":"grid","color":{"light":"#FCFCFC","dark":"#0A0A0A"}},"navbar":{"links":[{"href":"https://github.com/firstflowdev/firstflow-oss","label":"GitHub"}],"primary":{"type":"button","label":"Get Started","href":"https://app.firstflow.app"}},"contextual":{"options":["copy","view","chatgpt","claude"],"display":"header"},"styling":{"latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"firstflow","hasAudioNarration":false,"actualSubdomain":"firstflow","customDomains":["docs.firstflow.app"],"gitSource":{"type":"github","owner":"firstflowdev","repo":"docs","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6a2779f48b2885c05a20b230","collectionId":"57da1514-10b8-4e2b-b40c-c13141b65774","schemaVersion":"v3"},"entitlements":{},"buildId":"6a2779f48b2885c05a20b230:success","clientVersion":"0.0.4021","searchFilterCounts":[{"language":"default","tag":"Docs","count":30},{"language":"default","tag":"SDKs","count":19},{"language":"default","tag":"Self-Hosting","count":18},{"language":"default","tag":"Browser SDK","count":10},{"language":"default","tag":"Server SDK","count":8},{"language":"default","tag":"Core Concepts","count":7},{"language":"default","tag":"Features","count":7},{"language":"default","tag":"AI Connect (MCP)","count":5},{"language":"default","tag":"Get Started","count":4},{"language":"default","tag":"Integrations","count":4}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"pause-universal-search-reconcile","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"per-site-search-delta","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-private-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"features/platform","slug":"/features/platform","pageMetadata":{"title":"Developer platform","description":"The two SDKs, realtime widget, MCP server, and widget-kit that integrate FirstFlow into an AI agent product.","href":"/features/platform"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"The two SDKs, realtime widget, MCP server, and widget-kit that integrate FirstFlow into an AI agent product.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,em:`em`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,..._provideComponents(),...props.components},{Card,CardGroup,CodeBlock,Heading,Mermaid,Warning}=_components;if(!Card)_missingMdxReference(`Card`,true);if(!CardGroup)_missingMdxReference(`CardGroup`,true);if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Mermaid)_missingMdxReference(`Mermaid`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[`FirstFlow integrates into your product from two sides and is operated from a third. A `,_jsx(_components.a,{href:`/react/overview`,children:`browser SDK`}),` renders experiences inside your chat UI over a realtime connection; a `,_jsx(_components.a,{href:`/server/overview`,children:`server SDK`}),` observes your LLM calls so FirstFlow can see the real conversation; and an `,_jsx(_components.a,{href:`/mcp/overview`,children:`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.`]}),`\n`,_jsxs(_components.p,{children:[`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 `,_jsx(_components.em,{children:`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.`]}),`\n`,_jsx(Heading,{level:`2`,id:`how-the-pieces-fit`,children:`How the pieces fit`}),`\n`,_jsxs(_components.p,{children:[`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 `,_jsx(_components.code,{children:`WidgetTree`}),` with Anthropic (`,_jsx(_components.code,{children:`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.`]}),`\n`,_jsx(Mermaid,{chart:`flowchart TD\n A[\"Host app LLM chat turn\"] -->|\"observe(): firstflowAgentId + sessionId + userId\"| B[\"@firstflow/sdk (server)\"]\n B -->|\"POST /v1/conversations/:id/messages (202)\"| C[\"Backend: ConversationRouterService\"]\n C -->|\"triggers · audience · classifier · flow walk\"| D[\"WidgetCompositionService claude-sonnet-4-6\"]\n D -->|\"widget.show { tree } over Socket.IO\"| E[\"@firstflow/react (browser)\"]\n E -->|\"BlockRenderer → DOM\"| F[\"User sees \u0026 interacts\"]\n F -->|\"widget.action → flow.next / flow.back (server-side)\"| C\n G[\"Coding agent via MCP\"] -.->|\"create / edit / publish experiences\"| C\n\n style A fill:#fde68a,stroke:#b45309,color:#1c1917\n style B fill:#bbf7d0,stroke:#15803d,color:#1c1917\n style C fill:#e9d5ff,stroke:#7e22ce,color:#1c1917\n style D fill:#e9d5ff,stroke:#7e22ce,color:#1c1917\n style E fill:#bae6fd,stroke:#0369a1,color:#1c1917\n style F fill:#fbcfe8,stroke:#be185d,color:#1c1917\n style G fill:#fed7aa,stroke:#c2410c,color:#1c1917`}),`\n`,_jsxs(_components.p,{children:[`The packages map cleanly onto that loop: `,_jsx(_components.code,{children:`@firstflow/sdk`}),` is the server half (reporting turns, traces, and outcomes), `,_jsx(_components.code,{children:`@firstflow/react`}),` is the browser half (rendering and reporting activity), `,_jsx(_components.code,{children:`@firstflow/nextjs`}),` bundles both for App Router apps, `,_jsx(_components.code,{children:`@firstflow/widget-kit`}),` is the rendering core they share, and the MCP server is how an agent edits the experiences the loop runs.`]}),`\n`,_jsxs(Heading,{level:`2`,id:`browser-sdk-@firstflow/react`,children:[`Browser SDK `,_jsx(_components.code,{children:`@firstflow/react`})]}),`\n`,_jsxs(_components.p,{children:[`The `,_jsx(_components.a,{href:`/react/overview`,children:`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 `,_jsx(_components.a,{href:`/react/provider`,children:_jsx(_components.code,{children:`FirstflowProvider`})}),` once above your chat and drop a `,_jsx(_components.a,{href:`/react/widget`,children:_jsx(_components.code,{children:`FirstflowWidget`})}),` where experiences should appear.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`11`,language:`tsx`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`tsx`,children:_jsxs(_components.code,{language:`tsx`,numberOfLines:`11`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` { `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`FirstflowProvider`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`FirstflowWidget`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` } `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`from`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` \"@firstflow/react\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#808080`},children:`\u003c`}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#4EC9B0`},children:`FirstflowProvider`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#9CDCFE`},children:` agentId`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"agt_…\"`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#9CDCFE`},children:` publishableKey`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"pk_live_…\"`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#9CDCFE`},children:` user`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`{`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`{ `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`id:`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` user`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`id`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`email:`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` user`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`email`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`traits:`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` { `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`plan:`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` user`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`plan`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` } }`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`}`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#9CDCFE`},children:` conversationId`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`{`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`conversationId`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`}`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#808080`},children:`>`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#808080`},children:` \u003c`}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#4EC9B0`},children:`YourChat`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#808080`},children:` />`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#808080`},children:` \u003c`}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#4EC9B0`},children:`FirstflowWidget`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#808080`},children:` />`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#808080`},children:`\u003c/`}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#4EC9B0`},children:`FirstflowProvider`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#808080`},children:`>`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`The provider takes `,_jsx(_components.code,{children:`agentId`}),` and a `,_jsx(_components.code,{children:`publishableKey`}),` (`,_jsx(_components.code,{children:`pk_live_…`}),`, browser-safe not a secret), plus an optional `,_jsx(_components.code,{children:`user`}),` and `,_jsx(_components.code,{children:`conversationId`}),`. It throws if `,_jsx(_components.code,{children:`agentId`}),` or `,_jsx(_components.code,{children:`publishableKey`}),` is empty, and identifies the user immediately from the `,_jsx(_components.code,{children:`user`}),` prop without waiting for the socket. Theme and slash commands arrive on the server’s `,_jsx(_components.code,{children:`agent.config`}),` event and apply automatically. Around the widget, `,_jsx(_components.a,{href:`/react/use-firstflow`,children:_jsx(_components.code,{children:`useFirstflow()`})}),` gives you the instance: `,_jsx(_components.code,{children:`analytics`}),` (`,_jsx(_components.code,{children:`track`}),`, `,_jsx(_components.code,{children:`identify`}),`, `,_jsx(_components.code,{children:`page`}),`), `,_jsx(_components.code,{children:`getUser`}),`/`,_jsx(_components.code,{children:`setUser`}),`, `,_jsx(_components.code,{children:`notifyActivity()`}),`, the `,_jsx(_components.code,{children:`commands`}),` array, `,_jsx(_components.code,{children:`triggerCommand()`}),`, and `,_jsx(_components.code,{children:`getConversationId()`}),`. Identity, `,_jsx(_components.a,{href:`/react/analytics`,children:`analytics`}),`, `,_jsx(_components.a,{href:`/react/commands`,children:`slash commands`}),`, and `,_jsx(_components.a,{href:`/react/theming`,children:`theming`}),` each have their own page; `,_jsx(_components.a,{href:`/react/realtime`,children:`realtime`}),` covers the connection itself.`]}),`\n`,_jsxs(Heading,{level:`2`,id:`server-sdk-@firstflow/sdk`,children:[`Server SDK `,_jsx(_components.code,{children:`@firstflow/sdk`})]}),`\n`,_jsxs(_components.p,{children:[`The `,_jsx(_components.a,{href:`/server/overview`,children:`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: `,_jsx(_components.code,{children:`firstflowAgentId`}),`, `,_jsx(_components.code,{children:`sessionId`}),` (which `,_jsx(_components.strong,{children:`must equal`}),` the browser’s `,_jsx(_components.code,{children:`conversationId`}),`), and `,_jsx(_components.code,{children:`userId`}),`.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`11`,language:`typescript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`typescript`,children:_jsxs(_components.code,{language:`typescript`,numberOfLines:`11`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` { `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`OpenAI`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` } `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`from`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` \"@firstflow/sdk/openai\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` client`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` new`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` OpenAI`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(); `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// reads FIRSTFLOW_API_KEY from env`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` res`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` await`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` client`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`chat`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`completions`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`create`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`({`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` firstflowAgentId:`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` \"agt_…\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` sessionId`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// == browser conversationId`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` userId`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` model:`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` \"gpt-4o\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` messages`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`});`})}),`\n`]})})}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[`The wrapper strips those three fields before the request reaches OpenAI or\nAnthropic. If any one is missing, the call passes through `,_jsx(_components.strong,{children:`unobserved`}),` (with a\none-time warning) so partial tagging silently drops the turn from FirstFlow.`]})}),`\n`,_jsxs(_components.p,{children:[`Import the wrapped client that matches your provider `,_jsx(_components.a,{href:`/server/wrap-llm-client`,children:_jsx(_components.code,{children:`@firstflow/sdk/openai`})}),`, `,_jsx(_components.a,{href:`/server/wrap-llm-client`,children:_jsx(_components.code,{children:`/anthropic`})}),`, or `,_jsx(_components.a,{href:`/server/wrap-llm-client`,children:_jsx(_components.code,{children:`/aiclient`})}),` for any OpenAI-compatible endpoint (Ollama, vLLM, LM Studio) or use `,_jsx(_components.a,{href:`/server/langchain`,children:_jsx(_components.code,{children:`@firstflow/sdk/langchain`})}),` for LangChain. The instrumented methods are `,_jsx(_components.code,{children:`chat.completions.create`}),`, `,_jsx(_components.code,{children:`messages.create`}),`, `,_jsx(_components.code,{children:`responses.create`}),`, and `,_jsx(_components.code,{children:`embeddings.create`}),`. Alongside the wrappers, fire-and-forget helpers read `,_jsx(_components.code,{children:`FIRSTFLOW_API_KEY`}),` and never throw or block: `,_jsx(_components.a,{href:`/server/observe`,children:_jsx(_components.code,{children:`observe()`})}),` records a turn manually, `,_jsx(_components.a,{href:`/server/traces`,children:_jsx(_components.code,{children:`trace()`})}),` records nested spans, `,_jsx(_components.a,{href:`/server/outcomes`,children:_jsx(_components.code,{children:`outcome()`})}),` signals how a session ended, and `,_jsxs(_components.a,{href:`/server/analytics`,children:[_jsx(_components.code,{children:`track()`}),` / `,_jsx(_components.code,{children:`identify()`})]}),` send server-side analytics. The only required env var is `,_jsx(_components.code,{children:`FIRSTFLOW_API_KEY`}),` (secret, server-only); set `,_jsx(_components.code,{children:`FIRSTFLOW_API_BASE_URL`}),` when `,_jsx(_components.a,{href:`/server/configuration`,children:`self-hosting`}),`. Observed conversations are what the dashboard turns into `,_jsx(_components.a,{href:`/features/analytics`,children:`analytics and sessions`}),`.`]}),`\n`,_jsxs(Heading,{level:`2`,id:`next-js-wrapper-@firstflow/nextjs`,children:[`Next.js wrapper `,_jsx(_components.code,{children:`@firstflow/nextjs`})]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.a,{href:`/react/nextjs`,children:_jsx(_components.code,{children:`@firstflow/nextjs`})}),` is a thin wrapper for App Router apps. Its default entry re-exports the browser SDK for client components, and its `,_jsx(_components.code,{children:`/server`}),` subpaths re-export the server helpers and pre-wrapped clients (`,_jsx(_components.code,{children:`/server/openai`}),`, `,_jsx(_components.code,{children:`/server/anthropic`}),`, `,_jsx(_components.code,{children:`/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.`]}),`\n`,_jsx(Heading,{level:`2`,id:`realtime-widget`,children:`Realtime widget`}),`\n`,_jsxs(_components.p,{children:[`The widget is the visible surface, but it has no logic of its own it renders the `,_jsx(_components.code,{children:`WidgetTree`}),` the backend pushes. The connection is a Socket.IO link at path `,_jsx(_components.code,{children:`/v1/socket`}),`, authenticated with a short-lived client JWT the server SDK mints (carrying `,_jsx(_components.code,{children:`workspace`}),`, `,_jsx(_components.code,{children:`sub`}),`, and `,_jsx(_components.code,{children:`traits`}),`), and the browser joins a per-user room so only that user’s widgets arrive. User interaction emits `,_jsx(_components.code,{children:`widget.action`}),` back; flow navigation (`,_jsx(_components.code,{children:`flow.next`}),` / `,_jsx(_components.code,{children:`flow.back`}),`) is resolved `,_jsx(_components.strong,{children:`server-side`}),`, which then composes and pushes the next tree. See `,_jsx(_components.a,{href:`/react/widget`,children:_jsx(_components.code,{children:`FirstflowWidget`})}),` for props and event handlers, and `,_jsx(_components.a,{href:`/react/realtime`,children:`realtime`}),` for connecting without React.`]}),`\n`,_jsxs(Heading,{level:`2`,id:`widget-kit-@firstflow/widget-kit`,children:[`widget-kit `,_jsx(_components.code,{children:`@firstflow/widget-kit`})]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.a,{href:`/widget-kit/overview`,children:_jsx(_components.code,{children:`@firstflow/widget-kit`})}),` is the runtime-agnostic rendering core that both SDKs build on. It defines the `,_jsxs(_components.a,{href:`/concepts/widgets-and-blocks`,children:[_jsx(_components.code,{children:`WidgetTree`}),` and `,_jsx(_components.code,{children:`Block`}),` schema`]}),` and a `,_jsx(_components.code,{children:`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 `,_jsx(_components.code,{children:`@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.`]}),`\n`,_jsx(Heading,{level:`2`,id:`ai-connect-the-mcp-server`,children:`AI Connect the MCP server`}),`\n`,_jsxs(_components.p,{children:[`The `,_jsx(_components.a,{href:`/mcp/overview`,children:`MCP server`}),` is how a coding agent operates FirstFlow instead of integrating it. Mounted at `,_jsx(_components.code,{children:`\u003cBACKEND_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 `,_jsx(_components.a,{href:`/concepts/agents`,children:`agents`}),`, inspect experiences and `,_jsx(_components.a,{href:`/concepts/audience`,children:`audience`}),` and then create tours, surveys, announcements, and guides, edit flow steps, and publish. What each call can do depends on its `,_jsx(_components.a,{href:`/mcp/scopes`,children:`scopes`}),`. Note that static `,_jsx(_components.a,{href:`/mcp/api-keys`,children:`API keys`}),` are rejected on `,_jsx(_components.code,{children:`/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 `,_jsx(_components.a,{href:`/use-docs-with-ai`,children:`Use these docs with AI`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`the-linking-id`,children:`The linking id`}),`\n`,_jsxs(_components.p,{children:[`The browser and server halves are joined by one value you own and persist: the browser’s `,_jsx(_components.code,{children:`conversationId`}),` must equal the server SDK’s `,_jsx(_components.code,{children:`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 `,_jsx(_components.a,{href:`/concepts/conversations-and-sessions`,children:`Conversations \u0026 sessions`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`next`,children:`Next`}),`\n`,_jsx(_components.p,{children:`Pick the side you’re integrating first.`}),`\n`,_jsxs(CardGroup,{cols:2,children:[_jsx(Card,{title:`Browser SDK`,icon:`react`,href:`/react/overview`}),_jsx(Card,{title:`Server SDK`,icon:`server`,href:`/server/overview`})]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Developer platform","description":"The two SDKs, realtime widget, MCP server, and widget-kit that integrate FirstFlow into an AI agent product.","href":"/features/platform"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"How the pieces fit","slug":"how-the-pieces-fit","depth":2,"children":[]},{"title":"Browser SDK @firstflow/react","slug":"browser-sdk-@firstflow/react","depth":2,"children":[]},{"title":"Server SDK @firstflow/sdk","slug":"server-sdk-@firstflow/sdk","depth":2,"children":[]},{"title":"Next.js wrapper @firstflow/nextjs","slug":"next-js-wrapper-@firstflow/nextjs","depth":2,"children":[]},{"title":"Realtime widget","slug":"realtime-widget","depth":2,"children":[]},{"title":"widget-kit @firstflow/widget-kit","slug":"widget-kit-@firstflow/widget-kit","depth":2,"children":[]},{"title":"AI Connect the MCP server","slug":"ai-connect-the-mcp-server","depth":2,"children":[]},{"title":"The linking id","slug":"the-linking-id","depth":2,"children":[]},{"title":"Next","slug":"next","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"firstflow","siteName":"FirstFlow","title":"Developer platform - FirstFlow","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-06-08T00:00:48.998Z","originalFileLocation":"features/platform.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"dark\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"The two SDKs, realtime widget, MCP server, and widget-kit that integrate FirstFlow into an AI agent product.\">\u003cmeta data-mint-head name=\"application-name\" content=\"FirstFlow\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/firstflow/SqR_4nKSiodpsf0M/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/firstflow/SqR_4nKSiodpsf0M/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/firstflow/SqR_4nKSiodpsf0M/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/firstflow/SqR_4nKSiodpsf0M/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/firstflow/SqR_4nKSiodpsf0M/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/firstflow/SqR_4nKSiodpsf0M/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/firstflow/SqR_4nKSiodpsf0M/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/firstflow/SqR_4nKSiodpsf0M/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/firstflow/SqR_4nKSiodpsf0M/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Developer platform - FirstFlow\">\u003cmeta data-mint-head property=\"og:description\" content=\"The two SDKs, realtime widget, MCP server, and widget-kit that integrate FirstFlow into an AI agent product.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"FirstFlow\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.firstflow.app/features/platform\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://firstflow.mintlify.app/_mintlify/api/og/v2?division=Features\u0026amp;title=Developer+platform\u0026amp;description=The+two+SDKs%2C+realtime+widget%2C+MCP+server%2C+and+widget-kit+that+integrate+FirstFlow+into+an+AI+agent+product.\u0026amp;theme=76cdaa703f51c3023c088d8e\u0026amp;signature=e281f42b02b7646300b648556b3caa58fd8aaf7826e62aa512b29716dc38aa89\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Developer platform - FirstFlow\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"The two SDKs, realtime widget, MCP server, and widget-kit that integrate FirstFlow into an AI agent product.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://firstflow.mintlify.app/_mintlify/api/og/v2?division=Features\u0026amp;title=Developer+platform\u0026amp;description=The+two+SDKs%2C+realtime+widget%2C+MCP+server%2C+and+widget-kit+that+integrate+FirstFlow+into+an+AI+agent+product.\u0026amp;theme=76cdaa703f51c3023c088d8e\u0026amp;signature=e281f42b02b7646300b648556b3caa58fd8aaf7826e62aa512b29716dc38aa89\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/firstflow/SqR_4nKSiodpsf0M/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"FirstFlow\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#171717\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.firstflow.app/features/platform\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/features/platform.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.firstflow.app/#organization\",\"name\":\"FirstFlow\",\"url\":\"https://docs.firstflow.app\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/firstflow/9x5VMZzjDzvDo9rV/logo/wordmark-light.svg?fit=max\u0026auto=format\u0026n=9x5VMZzjDzvDo9rV\u0026q=85\u0026s=b0da7cdb29a0300450bdcb0b1eb37970\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.firstflow.app#website\",\"name\":\"FirstFlow\",\"url\":\"https://docs.firstflow.app\",\"publisher\":{\"@id\":\"https://docs.firstflow.app/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.firstflow.app/features/platform#webpage\",\"url\":\"https://docs.firstflow.app/features/platform\",\"name\":\"Developer platform\",\"description\":\"The two SDKs, realtime widget, MCP server, and widget-kit that integrate FirstFlow into an AI agent product.\",\"dateModified\":\"2026-06-08T00:00:48.998Z\",\"isPartOf\":{\"@id\":\"https://docs.firstflow.app#website\"},\"breadcrumb\":{\"@id\":\"https://docs.firstflow.app/features/platform#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.firstflow.app/features/platform#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Features\",\"item\":\"https://docs.firstflow.app/features\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Developer platform\",\"item\":\"https://docs.firstflow.app/features/platform\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.firstflow.app/features/platform#article\",\"headline\":\"Developer platform\",\"name\":\"Developer platform\",\"description\":\"The two SDKs, realtime widget, MCP server, and widget-kit that integrate FirstFlow into an AI agent product.\",\"url\":\"https://docs.firstflow.app/features/platform\",\"mainEntityOfPage\":{\"@id\":\"https://docs.firstflow.app/features/platform#webpage\"},\"dateModified\":\"2026-06-08T00:00:48.998Z\",\"publisher\":{\"@id\":\"https://docs.firstflow.app/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.firstflow.app#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"FirstFlow\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"FirstFlow","nav":{"global":null,"tabs":[{"tab":"Docs","groups":[{"group":"Get Started","pages":[{"title":"Introduction","href":"/introduction"},{"title":"How it works","href":"/how-it-works"},{"title":"Quickstart","href":"/quickstart"},{"title":"Core concepts","href":"/concepts"}]},{"group":"Features","pages":[{"title":"Features","href":"/features"},{"title":"In-chat experiences","href":"/features/experiences"},{"title":"Authoring","href":"/features/authoring"},{"title":"Analytics \u0026 sessions","href":"/features/analytics"},{"title":"Targeting","description":"How FirstFlow decides who sees an experience and when triggers, audience, schedule, frequency, and the LLM classifier, all evaluated server-side.","href":"/features/targeting"},{"title":"Developer platform","href":"/features/platform"},{"title":"Integrations","description":"How FirstFlow connects to the outside world Slack, your webhooks and APIs from inside a flow, and the LLM providers that power its AI features.","href":"/features/integrations"}]},{"group":"Core Concepts","pages":[{"title":"Agents","href":"/concepts/agents"},{"title":"Experiences","href":"/concepts/experiences"},{"title":"Flows \u0026 nodes","href":"/concepts/flows-and-nodes"},{"title":"Triggers \u0026 audience","href":"/concepts/triggers-and-audience"},{"title":"Audience \u0026 segments","href":"/concepts/audience"},{"title":"Conversations \u0026 sessions","href":"/concepts/conversations-and-sessions"},{"title":"Widgets \u0026 blocks","href":"/concepts/widgets-and-blocks"}]},{"group":"AI Connect (MCP)","pages":[{"title":"AI Connect (MCP) overview","href":"/mcp/overview"},{"title":"Connect an agent","href":"/mcp/quickstart"},{"title":"Tool catalog","href":"/mcp/tools"},{"title":"Scopes \u0026 roles","href":"/mcp/scopes"},{"title":"API keys","href":"/mcp/api-keys"}]},{"group":"Integrations","pages":[{"title":"Slack","href":"/integrations/slack"},{"title":"Webhooks","href":"/integrations/webhooks"},{"title":"API calls","href":"/integrations/api-calls"},{"title":"AI Providers","href":"/integrations/ai-providers"}]},{"group":"Help","pages":[{"title":"Troubleshooting","href":"/troubleshooting"},{"title":"FAQ","href":"/faq"},{"title":"Use these docs with AI","href":"/use-docs-with-ai"}]}]},{"tab":"SDKs","pages":[{"title":"SDKs","href":"/react/overview"}]},{"tab":"Self-Hosting","pages":[{"title":"Self-Hosting","href":"/self-hosting/overview"}]}]},"title":"Developer platform - FirstFlow","buildId":"dpl_EnxDqhjaNc9a8MVa833TGULPd9uJ","deploymentId":"dpl_EnxDqhjaNc9a8MVa833TGULPd9uJ","dynamics":[],"chunks":[]}