Public API
Build your own chat UI
Use the public chat API to put your AI in a chat you design yourself, with the Vercel AI SDK or a few lines of plain JavaScript.
The website widget runs on a public API, and so can your own chat. Use it when you want the conversation in your design system, inside an existing app, or in a UI that's entirely yours.
It takes two calls from your web page:
- Start a session when the visitor sends their first message.
POST /sessionsreturns a session token for that visitor's conversation. - Send messages.
POST /chat, with the token in anX-Uare-Sessionheader, streams your AI's reply back as it's written.
https://app.uare.ai/api/v1/public/ai/your-handleThere's no API key. Your AI answers web pages on the websites its owner allows, in public mode, the way it answers visitors on its public profile. The owner adds them in their Settings on uare.ai, http://localhost:3000 or whichever local address you use included, to try it while you build.
The reply streams in the AI SDK UI message format, so useChat reads it directly. Anything that reads server-sent events can read it too.
React with the AI SDK
Install the AI SDK:
npm install ai @ai-sdk/reactThen give useChat a transport whose headers start the session the first time the visitor sends a message, and reuse it after that:
"use client";
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { useRef, useState } from "react";
const API = "https://app.uare.ai/api/v1/public/ai/your-handle";
type Session = { session_id: string; token: string };
async function startSession(): Promise<Session> {
const res = await fetch(`${API}/sessions`, { method: "POST" });
const body = await res.json();
if (!res.ok) throw new Error(body.error?.message ?? `The chat couldn't start (${res.status}).`);
return body;
}
export function Chat() {
const [input, setInput] = useState("");
const session = useRef<Promise<Session> | null>(null);
const { messages, sendMessage, status, error } = useChat({
transport: new DefaultChatTransport({
api: `${API}/chat`,
// No session until the visitor's first message; every message after it reuses the session.
headers: async () => {
session.current ??= startSession().catch((err) => {
session.current = null; // the next message tries again
throw err;
});
return { "X-Uare-Session": (await session.current).token };
},
}),
});
const busy = status === "submitted" || status === "streaming";
return (
<div>
{messages.map((message) => (
<p key={message.id}>
<strong>{message.role === "user" ? "You" : "AI"}: </strong>
{message.parts.map((part, i) => (part.type === "text" ? <span key={i}>{part.text}</span> : null))}
</p>
))}
{error ? <p>{error.message}</p> : null}
<form
onSubmit={(e) => {
e.preventDefault();
sendMessage({ text: input });
setInput("");
}}
>
<input value={input} onChange={(e) => setInput(e.target.value)} disabled={busy} />
<button type="submit" disabled={busy || !input.trim()}>Send</button>
</form>
</div>
);
}Your AI's replies often use markdown. Render text parts with a markdown component, such as Streamdown, for headings, lists and links.
The stream also carries your AI's suggested follow-ups as a data-suggestions part, and transient data-quota and data-status parts you can read with useChat's onData callback. Stream parts lists them all.
Plain JavaScript
No library needed: a form, and a script that starts a session with the visitor's first message, reads the stream and handles each data: line.
<form id="chat">
<input id="message" placeholder="Ask me anything" autocomplete="off" />
<button>Send</button>
</form>
<p id="reply"></p>
<script type="module" src="chat.js"></script>This page shows the reply as plain text: your AI's replies often use markdown, and its marks (**bold**, - lists, links) appear as typed. To format them, render the text with a markdown library and sanitize the HTML it produces before you put it on the page, because the reply isn't text your page wrote.
Try it from a terminal
The API reads the Origin header a browser sends. Set it yourself to an allowed website to try the API with curl:
API=https://app.uare.ai/api/v1/public/ai/your-handle
TOKEN=$(curl -s -X POST "$API/sessions" -H "Origin: http://localhost:3000" | sed -E 's/.*"token":"([^"]+)".*/\1/')
curl -N -X POST "$API/chat" \
-H "Origin: http://localhost:3000" \
-H "X-Uare-Session: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"text": "Hi! Who are you?"}'The allowed-websites list decides which sites can show your AI. It isn't a password: anyone can set an Origin header outside a browser, so it can't make a conversation private.
Bring back a conversation
A session lasts up to two hours, and ends after an hour without messages. To show a visitor's conversation again after a page reload, keep the session and token, for example in sessionStorage, and load the messages:
const { messages } = await fetch(`${API}/sessions/${sessionId}/messages`, {
headers: { "x-uare-session": token },
}).then((res) => res.json());They come back as AI SDK UIMessage objects, ready for useChat({ id: sessionId, messages, transport }).
When a session expires, POST /chat returns 404 session_expired: start a new session. Your AI remembers the conversation within a session, not across sessions.
Limits
- Each conversation includes 10 visitor messages. After that,
POST /chatreturns429 message_limit_reachedwith acontinue_urlto your AI's profile on uare.ai, where the visitor can keep going. - Regenerating a reply (
"trigger": "regenerate-message",useChat'sregenerate()) uses up a free message, like sending one. - Messages are limited to 4,000 characters.
- A conversation answers one message at a time. Sending another while a reply is still streaming returns
409 turn_in_progress. - Rate limits apply per visitor IP address. They're listed in the reference, and every response says how many requests are left in its
RateLimit-*headers.
Next steps
- Public API reference: every endpoint, field, stream part and error.
- Customize the website widget, if the ready-made chat fits after all.