---
title: "OpenAI"
description: "Use GistUI with OpenAI's Chat Completions API, with plain fetch or the official openai SDK."
---

> Documentation Index
> Fetch the complete documentation index at: https://gistui.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenAI

Send `prompt().text` as the system message, ask for a stream, and pass the text deltas to the browser.

```sh
npm i @gistui/catalog @gistui/server
yarn add @gistui/catalog @gistui/server
pnpm add @gistui/catalog @gistui/server
bun add @gistui/catalog @gistui/server
```

## With fetch

```ts title="api/answer.ts"
import { prompt } from "@gistui/catalog";
import { textDeltas, toReadable } from "@gistui/server";

export async function POST(req: Request) {
  const { messages } = await req.json();

  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "gpt-5",
      stream: true,
      messages: [{ role: "system", content: prompt().text }, ...messages],
    }),
  });

  // A failed request has no stream to read: answer with its status instead.
  if (!res.ok) return new Response(await res.text(), { status: res.status });
  return new Response(toReadable(textDeltas(res, "openai")));
}
```

## With the `openai` SDK

The SDK gives you chunks; yield their text and wrap the generator with `toReadable`.

```ts title="api/answer.ts"
import OpenAI from "openai";
import { prompt } from "@gistui/catalog";
import { toReadable } from "@gistui/server";

const client = new OpenAI();

export async function POST(req: Request) {
  const { messages } = await req.json();

  const stream = await client.chat.completions.create({
    model: "gpt-5",
    stream: true,
    messages: [{ role: "system", content: prompt().text }, ...messages],
  });

  async function* text() {
    for await (const chunk of stream) {
      const delta = chunk.choices[0]?.delta?.content;
      if (delta) yield delta;
    }
  }
  return new Response(toReadable(text()));
}
```

## Render it

```tsx
import "@gistui/styles/styles.css";
import { GistUI } from "@gistui/react";
import { ui } from "@gistui/react/ui";

const res = await fetch("/api/answer", { method: "POST", body: JSON.stringify({ messages }) });
<GistUI library={ui} stream={res.body} onAction={handle} />;
```

The same works in [Vue, Svelte, Solid and vanilla JavaScript](/docs/frameworks/).

## Validate and repair on the server

To log or store the repaired program, pass the text through `repairStream`. It forwards the text as it arrives and appends repairs at the end.

```ts
import { library } from "@gistui/catalog";
import { repairStream, textDeltas, toReadable } from "@gistui/server";

return new Response(toReadable(repairStream(textDeltas(res, "openai"), library)));
```

## In a chat, without your own route

For a prototype, `@gistui/chat` has an adapter that calls OpenAI directly. A key in the browser is readable by every visitor, so use this for local demos only; in production, point `url` at your own endpoint.

```ts
import { createChat, openai } from "@gistui/chat";

const store = createChat({
  adapter: openai({ model: "gpt-5", url: "/api/chat" }), // your endpoint, OpenAI's request and response shape
  system: chatPrompt, // prompt({ mode: "inline" }).text, sent by your server in production
});
```

Source: https://gistui.com/docs/models/openai/index.mdx
