An AI chatbot for your website: architecture, streaming code and cost

Building a website chatbot: the key stays on the server, a FastAPI backend with streaming, a cheap model, a cost estimate and limits against abuse.

6 min · · development · API keys

An AI chatbot for your website can be built in an evening, but one architectural mistake — an API key in the page code — can wipe out your balance overnight. Below: the right architecture, a working FastAPI backend with streaming and a simple widget, choosing an inexpensive model, the cost of 10,000 chats, and settings that cap spending if someone starts abusing the bot.

Architecture: frontend → backend → API

bash
Browser (widget)  →  your backend /api/chat  →  https://api.zukko.pro/v1
     no key            fetch_... key, limits,           model
                       system prompt

The fetch_... key stays on the server only. Anything in the page's JavaScript can be read by any visitor through developer tools. With your key they could send requests to any model at your expense. So the browser talks only to your backend, and the backend talks to the API.

What the backend does:

  • keeps the key in an environment variable;
  • adds the system prompt — the user can't see or replace it;
  • trims the chat history and message length;
  • limits request frequency per IP;
  • hides API error details from the visitor.

Choosing a model

A website bot usually does fine with an inexpensive model: questions are short and answers come from the knowledge base in the system prompt. Prices as of October 7, 2026, USD per 1M tokens:

ModelidInput / outputIntelligence indexContext
DeepSeek V4.1 Flashdeepseek-v4.1-flash0.013 / 0.038391M
Qwen 3.8 Flashqwen3.8-flash0.038 / 0.075401M
GLM 5.3 Flashglm-5.3-flash0.04 / 0.0442128K
GPT-6 Lunagpt-6-luna0.04 / 0.21381M
Gemini 3.8 Flashgemini-3.8-flash0.063 / 0.288411M
GPT-6.1 Solgpt-6.1-sol0.27 / 1.32521M

The intelligence index is the Artificial Analysis Intelligence Index in the Zukko catalog. For the current list and prices, call GET https://api.zukko.pro/v1/models or open the Models page in the dashboard.

How to choose: collect 30–50 real visitor questions, run them through 2–3 cheap models with your system prompt and rate the answers. If none of them copes, try GPT-6.1 Sol. The model is one line in the code and easy to change. More on choosing in the article how to choose a model for a chatbot.

An AI chatbot for your website: the backend code

Python 3.10+, FastAPI and the official OpenAI SDK. Install: pip install fastapi uvicorn openai. File main.py:

bash
import os
import time
from collections import defaultdict, deque
from typing import Literal

from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import FileResponse, StreamingResponse
from openai import APIStatusError, AsyncOpenAI
from pydantic import BaseModel, Field

client = AsyncOpenAI(
    base_url="https://api.zukko.pro/v1",
    api_key=os.environ["ZUKKO_API_KEY"],  # fetch_... key, server side only
)

Key points:

  • The client can't send a system role — the Message model allows only user and assistant.
  • The if chunk.choices check is required: the stream's last chunk arrives with usage and an empty choices.
  • max_tokens=400 caps the length and cost of a single answer.
  • The request counter lives in memory — enough for a single process. With several processes or servers, move it to Redis. If a proxy sits in front of the app, take the client IP from the header your proxy sets, not from request.client.host.

Run: ZUKKO_API_KEY=fetch_your_key uvicorn main:app. More on streaming in the article about stream=true.

The widget on the page

A minimal index.html: history is kept in the browser and sent in full; the backend takes the last 10 messages.

bash
<pre id="log"></pre>
<form id="form">
  <input id="text" maxlength="1000" autocomplete="off" placeholder="Your question">
  <button>Send</button>
</form>
<script>
const history = [];
const log = document.getElementById("log");
const form = document.getElementById("form");
const input = document.getElementById("text");

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  const text = input.value.trim();

If a reverse proxy sits between the backend and the browser, turn off response buffering for the /api/chat route — otherwise the text arrives all at once at the end instead of in pieces.

What 10,000 chats cost

Assumptions: a system prompt with a knowledge base of 1,500 tokens; each chat has 4 questions of 50 tokens and 4 answers of 150. The model receives the whole history each time, so input for request n = 1,500 + 200 × (n − 1) + 50.

  • Input per chat: 1,550 + 1,750 + 1,950 + 2,150 = 7,400 tokens.
  • Output per chat: 4 × 150 = 600 tokens.
  • 10,000 chats: 74M input and 6M output.

Formula: 74 × input price + 6 × output price (prices per 1M tokens).

ModelCalculation10,000 chats
deepseek-v4.1-flash74 × 0.013 + 6 × 0.038$1.19
glm-5.3-flash74 × 0.04 + 6 × 0.04$3.20
qwen3.8-flash74 × 0.038 + 6 × 0.075$3.26
gpt-6-luna74 × 0.04 + 6 × 0.21$4.22
gemini-3.8-flash74 × 0.063 + 6 × 0.288$6.39
gpt-6.1-sol74 × 0.27 + 6 × 1.32$27.90

Prices as of October 7, 2026, calculated without caching. The system prompt repeats in every request, so for models whose provider reports cache reads, part of the input may cost less — for example, Gemini 3.8 Flash charges 0.013 for a cache read versus 0.063 for input. Cache writes aren't billed for these models. The totals are an example calculation: plug in your own prompt and chat sizes.

Protection against abuse

An open chat on a website is free model access for everyone, bots included. Set limits at two levels.

At the Zukko key level (in the dashboard):

  • a separate key just for the bot;
  • daily and monthly dollar limits — when exhausted the API returns 429 key_daily_limit_reached or key_monthly_limit_reached, while the account's other keys keep working;
  • an allowed-models list with only the bot's model — a request to any other gets 403 model_not_allowed_for_key.

The limits are soft: the last request let through may slightly exceed the cap. Details in the key limits docs.

At the backend level:

  • a message limit per IP or per session;
  • caps on message length and the number of history messages;
  • max_tokens for the answer;
  • bot protection on the form if a stream of automated requests starts;
  • a spending log: for regular (non-streaming) responses the gateway sends an X-Zukko-Cost-Micros header; for streaming ones, check the cost via GET /v1/usage or in the dashboard.

If the key leaks anyway, revoke it in the dashboard right away and only then issue a new one. More on storing keys in the article on API key security.

To build the bot this way, create a separate key with a small daily limit in the dashboard and put it into ZUKKO_API_KEY on the server.

FAQ

Can I call the API directly from JavaScript on the page?

No. Any visitor can read a key from the page code. API requests must go only from your server.

How much does a website chatbot cost per month?

It depends on the number of chats and the model. By our calculation, 10,000 chats of 4 questions each cost from $1.19 on DeepSeek V4.1 Flash to $27.90 on GPT-6.1 Sol at prices as of October 7, 2026.

What happens when the key limit runs out?

The API returns 429, and the backend from the example shows the visitor "Chat is temporarily unavailable". The daily limit resets at 00:00 UTC, the monthly one at the start of the month UTC.

Can I do the same in Node.js?

Yes. Same architecture: a backend on Express or another framework, the official OpenAI SDK for Node with baseURL: "https://api.zukko.pro/v1", the key in an environment variable and the answer streamed to the client.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

AI chatbot for a website: backend, code and cost · Zukko