Skip to main content
Lesson 17 of 25 · Building OpenAI Agents

ChatKit: Overview and Installation

Lesson 17 of 25 Updated November 3, 2026 Developer
On this page
  1. Why ChatKit
  2. Two ways to power it
  3. The three-piece architecture
  4. Install
  5. The minimal server
  6. The React interface
  7. Without React
  8. Security from day one
  9. If you already have an Agent Builder workflow
  10. Key takeaways

Lesson rewritten November 3, 2026. The previous version described an API that no longer exists: a new ChatKit({ apiKey, agentId }) object, ChatProvider, ChatWindow, and MessageInput components, an @openai/chatkit/react module, and a cdn.openai.com/chatkit/… script. This lesson now follows the official OpenAI ChatKit guides, Python SDK openai-chatkit 1.6.5, and @openai/chatkit-react 1.6.1. We ran the example server locally to verify every step. Note: Agent Builder shuts down on November 30, 2026; ChatKit remains available. Sources: ChatKit · Advanced integrations with ChatKit · Python SDK quickstart · Official starter app.

Why ChatKit

You’ve built an agent with the Agents SDK. It runs in your terminal, and no one else can use it. To put it in your users’ hands, you need a conversation interface—and building a good chat UI from scratch is expensive. You have to handle scrolling, streaming messages, conversation resumption, attachments, tool call display, and network error handling.

ChatKit is OpenAI’s answer: a conversation component you embed in your product that handles the entire user experience. The documentation highlights its interactive widgets, starter suggestions, tool call display, attachments, and agent reasoning visualization. You keep control of what matters: the agent, your data, and your rules.

Two ways to power it

ChatKit is just an interface. It needs a server that receives messages, runs the agent, and streams the response back. OpenAI describes two approaches:

PathHow it worksWho it’s for
Your own ChatKit server (advanced integration)Your Python server, using the openai-chatkit SDK, receives requests from the UI and runs the agent of your choice, such as an Agents SDK agentAny new project: this is the path OpenAI recommends
Hosted Agent Builder workflowOpenAI runs a workflow designed in Agent Builder; your server only creates sessionsExisting integrations during the transition period: Agent Builder shuts down November 30, 2026

This course follows the first path—the only one with a future. We summarize the second at the end of the lesson for those who need to maintain an existing integration until shutdown.

The three-piece architecture

A ChatKit integration comes down to three elements. Distinguishing them avoids most beginner mistakes:

  1. ChatKit.js, the component itself: a web component <openai-chatkit> that displays the conversation in an iframe. It loads from OpenAI’s CDN (https://cdn.platform.openai.com/deployments/chatkit/chatkit.js), not from npm: the npm package @openai/chatkit contains only its type definitions.
  2. React bindings, @openai/chatkit-react, which expose the ChatKit component and the useChatKit hook. These are optional: without React, you control the web component directly.
  3. Your ChatKit server, a single HTTP endpoint, conventionally /chatkit, that receives all requests from the interface. A ChatKitServer instance routes them, relies on a store to persist conversation threads, and runs your agent.

The OpenAI API key never leaves the server: the interface talks only to your /chatkit endpoint.

Install

On the interface side, install the React bindings and load the ChatKit script in your page:

npm install @openai/chatkit-react
<!-- In the <head> of your page -->
<script src="https://cdn.platform.openai.com/deployments/chatkit/chatkit.js"></script>

On the server side, install the ChatKit Python SDK (Python 3.10 or later), which brings in the Agents SDK, plus a web server:

pip install openai-chatkit fastapi uvicorn

The minimal server

The server below follows the official quickstart. ChatKitServer requires only one method, respond, called for each user message. It reloads the latest thread items, converts them to agent input with simple_to_agent_input, runs the agent in streaming mode, and translates that stream into ChatKit events with stream_agent_response:

# main.py
from typing import Any, AsyncIterator

from agents import Agent, Runner
from chatkit.agents import AgentContext, simple_to_agent_input, stream_agent_response
from chatkit.server import ChatKitServer, StreamingResult
from chatkit.types import ThreadMetadata, ThreadStreamEvent, UserMessageItem
from fastapi import FastAPI, Request
from fastapi.responses import Response, StreamingResponse

from memory_store import MemoryStore  # in-memory store from the starter app

assistant = Agent[AgentContext[dict[str, Any]]](
    name="Product Assistant",
    instructions="You help product users. Respond in English, briefly.",
    model="gpt-6.1-sol",
)


class MyChatKitServer(ChatKitServer[dict[str, Any]]):
    async def respond(
        self,
        thread: ThreadMetadata,
        input_user_message: UserMessageItem | None,
        context: dict[str, Any],
    ) -> AsyncIterator[ThreadStreamEvent]:
        # The last 30 thread items, restored in chronological order
        page = await self.store.load_thread_items(
            thread.id, after=None, limit=30, order="desc", context=context
        )
        agent_input = await simple_to_agent_input(list(reversed(page.data)))
        agent_context = AgentContext(thread=thread, store=self.store, request_context=context)
        result = Runner.run_streamed(assistant, agent_input, context=agent_context)
        async for event in stream_agent_response(agent_context, result):
            yield event


app = FastAPI()
server = MyChatKitServer(MemoryStore())


@app.post("/chatkit")
async def chatkit_endpoint(request: Request) -> Response:
    result = await server.process(await request.body(), {})
    if isinstance(result, StreamingResult):
        return StreamingResponse(result, media_type="text/event-stream")
    return Response(content=result.json, media_type="application/json")

Run it with OPENAI_API_KEY in your environment: uvicorn main:app --port 8000.

The store deserves two words. ChatKit stores nothing on its own: threads, messages, widgets, and tool calls are persisted by a class that inherits from chatkit.store.Store and implements about a dozen methods (load_thread, save_thread, load_thread_items, add_thread_item…). To get started, reuse the memory_store.py file from the official starter app: it keeps everything in memory while the process runs. In production, replace it with a database. The documentation recommends storing objects as JSON so SDK updates don’t force schema migrations on you.

The React interface

On the interface side, useChatKit receives the configuration and returns a control object, which you pass to the ChatKit component:

import { ChatKit, useChatKit } from '@openai/chatkit-react';

export function Assistant() {
  const { control } = useChatKit({
    api: {
      url: '/chatkit',
      // In development, a placeholder value is enough; in production,
      // use your domain key (see below)
      domainKey: 'local-dev',
    },
  });

  return <ChatKit control={control} className="h-[600px] w-[360px]" />;
}

Serve the interface and /chatkit from the same origin: in development, your build tool’s proxy handles this (the starter app configures Vite’s proxy to redirect /chatkit to port 8000). If the server lives on a different origin, allow it explicitly, for example with FastAPI’s CORSMiddleware.

Without React

A WordPress site, a Rails application, or a static page can integrate the same assistant: the web component works directly once the CDN script loads.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.platform.openai.com/deployments/chatkit/chatkit.js"></script>
  </head>
  <body>
    <div id="chat-root" style="height: 600px"></div>
    <script>
      const chatkit = document.createElement('openai-chatkit');
      chatkit.setOptions({
        api: { url: '/chatkit', domainKey: 'local-dev' },
      });
      document.getElementById('chat-root').append(chatkit);
    </script>
  </body>
</html>

Every option you’ll see in the following lessons has the same form in both cases: an object passed to useChatKit in React, or to setOptions on the web component.

Security from day one

Three rules, drawn from the ChatKit production guide, apply from the prototype stage:

  • The API key stays on the server. It must not appear in interface code or in your repository: read it from an environment variable or a secrets manager.
  • Authenticate every request to /chatkit. The SDK doesn’t handle this: your application must verify the user’s session or token, then pass their identity to server.process in the context (lesson 21). Without this, anyone can run your agent at your expense.
  • Register your domains. Before going live, declare each hostname that will display ChatKit on the OpenAI platform’s Domain allowlist page, and copy the generated key into domainKey. On load, the ChatKit iframe verifies this key with OpenAI and refuses to display if it’s missing or doesn’t match.

If you already have an Agent Builder workflow

Existing integrations hooked to a hosted workflow work until Agent Builder shuts down. Their principle differs: your server creates a ChatKit session by calling POST https://api.openai.com/v1/chatkit/sessions, with the header OpenAI-Beta: chatkit_beta=v1, the workflow ID, and a user identifier unique to each authenticated user. It then returns the client_secret obtained to the interface. On the interface side, the api option then takes a getClientSecret function instead of a URL.

Don’t start a new project on this model. For an existing integration, export the workflow to Agents SDK code and switch to your own ChatKit server before November 30, 2026, following the Migrate from Agent Builder guide.

Key takeaways

  • ChatKit is a ready-to-integrate conversation interface; your server runs the agent
  • For any new project: your own ChatKit server (pip install openai-chatkit), with a respond method and a /chatkit endpoint; the hosted Agent Builder shuts down November 30, 2026
  • The interface loads from OpenAI’s CDN; in React, use useChatKit and <ChatKit control={control} /> from @openai/chatkit-react
  • The store persists conversation threads: in memory for testing, in a database for production
  • API key server-side only, authenticated /chatkit, registered domains to obtain the domainKey