Skip to content

Build a Web Chat with GPT-6 Astra ​

Updated: 2026-09-08

This guide shows how to build your own browser-based AI chat and connect it to gpt-6-astra through Cubicspaces.

text
Browser -> Your application backend -> Cubicspaces Responses API -> gpt-6-astra

Do not call Cubicspaces directly from the browser. Never place the API key in frontend source, browser environment variables, Local Storage, or a public repository. The browser should call your backend, which stores the API key, calls the model, and forwards the stream.

API Configuration ​

ItemValue
Base URLhttps://cubicspaces.cloud/v1
EndpointPOST /responses
Modelgpt-6-astra
AuthenticationAuthorization: Bearer YOUR_API_KEY

New GPT-6 Astra web chats should use the Responses API directly. Chat Completions remains useful for existing client compatibility, but it should not be the first choice for a new application.

Streaming Backend ​

The following Node.js example uses Express and the OpenAI SDK. Install the dependencies first:

bash
npm install express openai

Store the Cubicspaces API key in a server-side environment variable:

bash
CUBICSPACES_API_KEY=YOUR_API_KEY
js
import express from "express";
import OpenAI from "openai";

const app = express();
app.use(express.json({ limit: "1mb" }));

const client = new OpenAI({
  apiKey: process.env.CUBICSPACES_API_KEY,
  baseURL: "https://cubicspaces.cloud/v1",
});

// Demo only. Persist conversation state in a database or Redis in production.
const conversations = new Map();

app.post("/api/chat", async (req, res) => {
  const message = String(req.body?.message || "").trim();
  const conversationId = String(req.body?.conversation_id || "").trim();

  if (!message || !/^[a-zA-Z0-9_-]{1,128}$/.test(conversationId)) {
    return res.status(400).json({ error: "invalid request" });
  }

  res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
  res.setHeader("Cache-Control", "no-cache, no-transform");
  res.setHeader("Connection", "keep-alive");
  res.flushHeaders();

  try {
    const stream = await client.responses.create({
      model: "gpt-6-astra",
      input: message,
      previous_response_id: conversations.get(conversationId),
      reasoning: { effort: "low" },
      max_output_tokens: 4096,
      stream: true,
    });

    for await (const event of stream) {
      if (event.type === "response.output_text.delta") {
        res.write(`event: delta\ndata: ${JSON.stringify({ text: event.delta })}\n\n`);
      }

      if (event.type === "response.completed") {
        conversations.set(conversationId, event.response.id);
        res.write(
          `event: done\ndata: ${JSON.stringify({ response_id: event.response.id })}\n\n`,
        );
      }

      if (event.type === "response.failed") {
        res.write(
          `event: error\ndata: ${JSON.stringify({
            message: event.response.error?.message || "request failed",
          })}\n\n`,
        );
      }
    }
  } catch (error) {
    const status = Number(error?.status) || 500;
    res.write(
      `event: error\ndata: ${JSON.stringify({ status, message: error?.message || "request failed" })}\n\n`,
    );
  } finally {
    res.end();
  }
});

app.listen(3000);

A production service should also authenticate users, limit message size, apply per-user rate limits, serialize requests for the same conversation, and cancel the model request when the browser disconnects.

Read the Stream in the Browser ​

Because chat uses a POST request, read the stream with fetch:

js
let conversationId = crypto.randomUUID();

async function sendMessage(message, onDelta) {
  const response = await fetch("/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      message,
      conversation_id: conversationId,
    }),
  });

  if (!response.ok || !response.body) {
    throw new Error(`Chat request failed: ${response.status}`);
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const frames = buffer.split(/\r?\n\r?\n/);
    buffer = frames.pop() || "";

    for (const frame of frames) {
      const event = frame.match(/^event: (.+)$/m)?.[1];
      const data = frame.match(/^data: (.+)$/m)?.[1];
      if (!event || !data) continue;

      const payload = JSON.parse(data);
      if (event === "delta") onDelta(payload.text);
      if (event === "error") throw new Error(payload.message || "Chat failed");
    }
  }
}

Do not render model output directly with innerHTML. If the UI supports Markdown, use a renderer that sanitizes embedded HTML.

Multi-Turn Conversations ​

The browser sends only your application's conversation_id. The backend omits previous_response_id on the first turn, saves the returned response.id, and sends it as previous_response_id on the next turn.

The example keeps conversation state in memory and loses it when the process restarts, so it is suitable only for local testing. In production, persist response IDs on the backend, associate them with the authenticated user and conversation, and verify ownership. Create a new conversation ID for a new chat and load the correct record when switching chats.

If the application does not use response IDs, store message history in your own database and reconstruct input for each request.

GPT-6 Astra Parameters ​

json
{
  "model": "gpt-6-astra",
  "input": "Review this code and suggest improvements.",
  "reasoning": {
    "effort": "low"
  },
  "max_output_tokens": 4096,
  "stream": true
}
  • Use max_output_tokens for the output limit.
  • Set reasoning effort with reasoning.effort: low, medium, high, xhigh, or max.
  • none and minimal are unsupported. Start with low for ordinary chat.
  • Do not send temperature, top_p, top_k, frequency_penalty, presence_penalty, logprobs, stop, or chat_template_kwargs.

Tool Calling ​

Define Responses tools in the backend when the chat needs web search, database access, or business APIs. When the model returns a function call, execute it on the backend and submit a function_call_output with the matching original call_id.

Do not execute privileged tools in the browser or trust model-generated arguments without validation. Validate arguments against JSON Schema and check user permissions before every tool call.

See OpenAI Responses for request and event formats.

Production Checklist ​

  • Keep the API key on the server only.
  • Authenticate the user on every request.
  • Bind conversation IDs to their owning user.
  • Limit message length, concurrency, and request frequency.
  • Let the browser stop generation.
  • Handle 400, 401, 429, and 5xx separately instead of showing every failure as a network error.
  • Retain the platform request ID for troubleshooting.