Blog
4 min read

How to Add an AI Chatbot to Your App (Without Leaking Your API Key)

A beginner-friendly guide to adding an AI chat feature: the safe architecture, a working Next.js backend route that streams Claude's replies, the frontend code to display them, and the limits, costs and abuse protection you need before launch.

Adding a chat assistant to your app is one of the most requested AI features — a support bot, a writing helper, a "chat with your data" panel. The core is surprisingly small. The parts that matter are where the API key lives and what stops someone running up your bill.

The safe architecture

Browser  →  your backend (/api/chat)  →  AI provider
         ←  streamed reply             ←

The browser never talks to the AI provider directly. If it did, your API key would be in code every visitor downloads. Your backend holds the key, decides what's allowed, and forwards the conversation. (Background: what is an API key?)

Step 1: Get an API key and set a limit

Create an API key in the Claude Console and put it in your server's environment:

ANTHROPIC_API_KEY=sk-ant-...

Set a monthly spending limit on the account now, before writing any code. API usage is billed per token, separately from any Claude chat subscription.

Step 2: The backend route

Here's a Next.js route handler that sends the conversation to Claude and streams the reply back word by word, so users see text appear immediately instead of waiting for the full answer:

// app/api/chat/route.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic(); // reads ANTHROPIC_API_KEY

const SYSTEM_PROMPT =
  "You are the support assistant for Acme Notes, a note-taking app. " +
  "Answer questions about using Acme Notes. Be brief and friendly. " +
  "If you don't know, say so and suggest emailing support.";

export async function POST(req: Request) {
  const { messages } = (await req.json()) as {
    messages: Anthropic.MessageParam[];
  };

  // Basic guards: the client controls this input, so check it.
  if (!Array.isArray(messages) || messages.length === 0 || messages.length > 40) {
    return new Response("Bad request", { status: 400 });
  }

  const stream = client.messages.stream({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    system: SYSTEM_PROMPT,
    messages,
  });

  const encoder = new TextEncoder();
  const body = new ReadableStream({
    async start(controller) {
      for await (const event of stream) {
        if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
          controller.enqueue(encoder.encode(event.delta.text));
        }
      }
      controller.close();
    },
  });

  return new Response(body, {
    headers: { "Content-Type": "text/plain; charset=utf-8" },
  });
}

Install the SDK with npm install @anthropic-ai/sdk. (What's an SDK? See here.)

Choosing a model: the example uses Claude Opus 5.5. For a high-volume support bot, a smaller, cheaper model such as Sonnet or Haiku is often enough — test a few with your real questions and pick on quality and cost.

Step 3: The frontend

The browser sends the conversation and reads the stream as it arrives:

async function sendMessage(history: { role: "user" | "assistant"; content: string }[]) {
  const res = await fetch("/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ messages: history }),
  });

  const reader = res.body!.getReader();
  const decoder = new TextDecoder();
  let reply = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    reply += decoder.decode(value, { stream: true });
    showReply(reply); // update your chat bubble
  }
  return reply;
}

The API is stateless — it doesn't remember previous messages — so you send the whole conversation each time. Keep the history in your component's state, or in your database if conversations should survive a reload.

Step 4: Protect your bill before launch

A public chat box connected to a paid API is an invitation. Bots will find it. Before launching:

  • Require login, if you can. Anonymous chat is the most abused kind.
  • Rate limit per user and per IP — for example 20 messages per hour. See what is rate limiting?
  • Cap conversation length (the 40-message check above) and message size.
  • Set max_tokens so a single reply can't run long.
  • Add a CAPTCHA on anonymous forms. See how to add a CAPTCHA.
  • Watch spend daily for the first weeks.

How to stop bots from running up your AI app's bill covers this in depth.

Step 5: Make it actually useful

  • Give it your content. A support bot that knows nothing about your product isn't much help. For a few pages of docs, include them in the system prompt. For a large help centre, use RAG.
  • Let it look things up — order status, account details — with function calling, enforcing permissions in your code.
  • Tell it what not to do: no legal or medical advice, no promises about refunds, hand off to a human when asked.
  • Test with real questions before and after every prompt change.

The summary

  • Browser → your backend → AI provider. The key stays on the server.
  • Stream replies so users see text immediately.
  • Rate limit, cap lengths, set max_tokens, and set a spending limit before launch.
  • Ground it in your own content and test it with real questions.

EasySpawn runs your app's backend on its own server with secrets stored as server-side environment variables — so your chatbot's API key stays where it belongs — and Claude Code can build, run and test the feature end to end. See how it works or join the waitlist.

Related: Server-Sent Events vs WebSockets · How to Keep API Keys Out of an AI-Built App · What Is an LLM? · Frontend vs Backend · What Is a System Prompt?

Keep reading