ChatKit: Overview and Installation
On this page
Lesson rewritten November 3, 2026. The previous version described an API that no longer exists: a
new ChatKit({ apiKey, agentId })object,ChatProvider,ChatWindow, andMessageInputcomponents, an@openai/chatkit/reactmodule, and acdn.openai.com/chatkit/…script. This lesson now follows the official OpenAI ChatKit guides, Python SDKopenai-chatkit1.6.5, and@openai/chatkit-react1.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:
| Path | How it works | Who 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 agent | Any new project: this is the path OpenAI recommends |
| Hosted Agent Builder workflow | OpenAI runs a workflow designed in Agent Builder; your server only creates sessions | Existing 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:
- 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/chatkitcontains only its type definitions. - React bindings,
@openai/chatkit-react, which expose theChatKitcomponent and theuseChatKithook. These are optional: without React, you control the web component directly. - Your ChatKit server, a single HTTP endpoint, conventionally
/chatkit, that receives all requests from the interface. AChatKitServerinstance 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 toserver.processin 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 arespondmethod and a/chatkitendpoint; the hosted Agent Builder shuts down November 30, 2026 - The interface loads from OpenAI’s CDN; in React, use
useChatKitand<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 thedomainKey