From an empty project to a persisted, reported conversation in the dashboard.
This walks through the shortest path: an anonymous session, the AI SDK runtime, and the assistant-ui components. The other guides swap the runtime; the cloud side stays the same.
Create a project
Sign in at cloud.assistant-ui.com, create a project, and open Settings › Telemetry. Copy the Frontend API URL, https://proj-<id>.assistant-api.com. Create an API key under Settings › API keys only when your server needs one: for user tokens, trace exports, scores or the project read API. Nothing in this quickstart does.
Configure the environment
NEXT_PUBLIC_ASSISTANT_BASE_URL=https://proj-<id>.assistant-api.com
OPENAI_API_KEY=sk-…EXPO_PUBLIC_ASSISTANT_BASE_URL=https://proj-<id>.assistant-api.com
EXPO_PUBLIC_API_URL=https://your-app.example.comASSISTANT_BASE_URL=https://proj-<id>.assistant-api.com
API_URL=https://your-app.example.comWith NEXT_PUBLIC_ASSISTANT_BASE_URL set, the React runtimes create an anonymous cloud client on their own when you pass no cloud. The other platforms construct the client explicitly, as the next step shows, and name the server that hosts the chat route, because it is not on the app's own origin.
Allow anonymous sessions and your origin
Anonymous sessions are off until you turn them on in Settings › Access. While you are there, add your development origin, for example http://localhost:3000, to the allowed origins; an empty list allows every origin, which is fine for a first run.
Install and wire the runtime
npm install @assistant-ui/react @assistant-ui/ai-sdk ai @ai-sdk/openai"use client";
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/ai-sdk";
import { ThreadList } from "@/components/assistant-ui/elements/thread-list.aui";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
export default function ChatPage() {
const runtime = useChatRuntime();
return (
<AssistantRuntimeProvider runtime={runtime}>
<div className="grid h-dvh grid-cols-[240px_1fr]">
<ThreadList />
<Thread />
</div>
</AssistantRuntimeProvider>
);
}useChatRuntime() reads the base URL from the environment and sends chat requests to /api/chat, the AI SDK route you already have.
npx expo install @assistant-ui/react-native @assistant-ui/ai-sdk assistant-cloudimport { useMemo } from "react";
import { View } from "react-native";
import { AssistantRuntimeProvider } from "@assistant-ui/react-native";
import { AssistantCloud } from "assistant-cloud";
import { AssistantChatTransport, useChatRuntime } from "@assistant-ui/ai-sdk";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
export default function ChatScreen() {
const cloud = useMemo(
() =>
new AssistantCloud({
baseUrl: process.env.EXPO_PUBLIC_ASSISTANT_BASE_URL!,
anonymous: true,
}),
[],
);
const runtime = useChatRuntime({
cloud,
transport: new AssistantChatTransport({
api: `${process.env.EXPO_PUBLIC_API_URL}/api/chat`,
}),
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<View style={{ flex: 1 }}>
<Thread />
</View>
</AssistantRuntimeProvider>
);
}npm install @assistant-ui/react-ink @assistant-ui/ai-sdk assistant-cloud ink reactimport { useMemo } from "react";
import { Box } from "ink";
import { AssistantRuntimeProvider } from "@assistant-ui/react-ink";
import { AssistantCloud } from "assistant-cloud";
import { AssistantChatTransport, useChatRuntime } from "@assistant-ui/ai-sdk";
import { Thread } from "./components/thread.js";
export function Chat() {
const cloud = useMemo(
() =>
new AssistantCloud({
baseUrl: process.env.ASSISTANT_BASE_URL!,
anonymous: true,
}),
[],
);
const runtime = useChatRuntime({
cloud,
transport: new AssistantChatTransport({
api: `${process.env.API_URL}/api/chat`,
}),
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<Box flexDirection="column">
<Thread />
</Box>
</AssistantRuntimeProvider>
);
}Report model and usage
The browser cannot see which model answered or how many tokens it used. Add a messageMetadata callback to your route so the run report carries them:
import { convertToModelMessages, streamText } from "ai";
import { openai } from "@ai-sdk/openai";
export async function POST(req: Request) {
const { messages } = await req.json();
const model = openai("gpt-5.6-luna");
const result = streamText({
model,
messages: await convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse({
messageMetadata: ({ part }) => {
if (part.type === "finish") {
return { usage: part.totalUsage, finishReason: part.finishReason };
}
if (part.type === "finish-step") {
return { modelId: part.response.modelId, provider: model.provider };
}
return undefined;
},
});
}Send a message and look
Send a message. Within a moment the thread appears under Threads with an automatic title, the response under Runs with its duration, tokens, model and cost, and the send under Engagement. The Overview updates with the day's figures.
Next
- Replace the anonymous session with your users' identities: Authentication.
- Join your server's spans to the browser's report: Traces.
- Read what every field means: Run reports and the concepts.
- The working example: with-cloud on GitHub.