Build a Customer Chatbot from Scratch with Claude API
Building a customer chatbot with the Claude API comes down to four decisions: how you structure the system prompt, how you manage conversation history, how you stream responses to the frontend, and how you handle authentication and rate limits in production. This guide walks through all four with working code, so you can go from an empty repo to a deployed support bot in an afternoon.
If you just want the shortest path to a working chatbot, skip to the "Minimal working example" section below and adapt it. The rest of the article explains the decisions behind it.
What you need before you start
- An Anthropic API key (or a SubToAPI key if you want a simpler billing and key-management setup — more on that below)
- A clear idea of the chatbot's scope: order status, product questions, refund policy, escalation to a human, etc.
- A place to store conversation history per user session (even an in-memory map works for a prototype)
Claude doesn't have built-in memory. Every request you send has to include the full conversation so far, plus a system prompt that defines the bot's role and boundaries.
Step 1: Write a system prompt that actually constrains the bot
The system prompt is the single highest-leverage piece of a customer chatbot. A vague prompt produces a chatbot that drifts into generic advice, invents policies, or argues with angry customers. A good one is specific about tone, scope, and what to do when it doesn't know something.
You are the support assistant for Acme Outdoor Gear.
Rules:
- Only answer questions about orders, shipping, returns, and product sizing.
- If you don't know the answer, say so and offer to connect the customer to a human agent.
- Never make up order numbers, tracking info, or discount codes.
- Keep responses under 4 sentences unless the customer asks for detail.
- If the customer is frustrated or asking for a refund over €200, say you're escalating to a human and stop.
Keep this in a separate config file or database field, not hardcoded in your request logic, so support or product teams can iterate on it without a deploy.
Step 2: Manage conversation state
Claude's Messages API is stateless — you send the whole message array every time. For a chatbot, that means:
let conversation = [];
function addUserMessage(text) {
conversation.push({ role: "user", content: text });
}
function addAssistantMessage(text) {
conversation.push({ role: "assistant", content: text });
}
For production, store conversation keyed by session ID in Redis or a database with a TTL (24–48 hours is typical for support chats). Trim old messages once the thread gets long — after 15–20 turns, summarize earlier context into a short system-level note instead of sending every message verbatim. This keeps token usage and latency predictable.
Minimal working example
Here's a complete request/response cycle. This example uses SubToAPI's endpoint, which wraps the same Messages format Anthropic uses, so swapping providers later is a one-line change:
async function chat(userMessage, conversation) {
conversation.push({ role: "user", content: userMessage });
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 500,
system: SYSTEM_PROMPT,
messages: conversation
})
});
const data = await response.json();
const reply = data.content[0].text;
conversation.push({ role: "assistant", content: reply });
return reply;
}
That's a fully functional chatbot backend. The frontend just needs a text input, a message list, and a call to this function per turn.
Step 3: Stream responses for a better UX
Customers expect chat to feel instant. Waiting several seconds for a full response before anything appears on screen feels broken, even if the total latency is fine. Streaming solves this by sending tokens as they're generated:
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 500,
system: SYSTEM_PROMPT,
messages: conversation,
stream: true
})
});
On the frontend, read the response body as a stream and append text chunks to the chat bubble as they arrive. See /docs/streaming for the full event format and a client-side example.
Step 4: Add tools for order lookups and escalation
A generic chatbot that only talks is of limited use for support. Real value comes from letting Claude call functions — look up an order by ID, check shipping status, or trigger a handoff to a human agent. You define a tool schema and Claude decides when to call it:
{
"name": "get_order_status",
"description": "Look up the current status of a customer order",
"input_schema": {
"type": "object",
"properties": {
"order_id": { "type": "string" }
},
"required": ["order_id"]
}
}
When Claude needs this info, it returns a tool_use block instead of plain text. Your backend runs the actual lookup against your order database, sends the result back in the next request, and Claude incorporates it into its reply. Full details and request/response examples are in /docs/tools.
Step 5: Handle keys, rate limits, and billing before launch
This is the part that gets skipped in "hello world" tutorials and then causes an incident on launch day. A production chatbot needs:
- Per-application API keys so you can revoke or rotate access without taking down other integrations
- Usage visibility so you know which bot, team, or customer is consuming tokens
- A plan that scales with traffic instead of a single personal API key shared across environments
This is exactly what SubToAPI adds on top of raw Claude access: scoped sub_live_ keys per app, streaming and tool use with the same request format shown above, usage metadata per key, and team seats so support engineers can see logs without sharing credentials. Plans start at €9/month for solo projects, €19/seat for teams, and €49/seat for scale, with a free trial at signup — see /pricing. If you're starting from zero, /docs/quickstart gets you a working key and first request in a few minutes.
Putting it together
A production-ready customer chatbot is: a tight system prompt, a session-based message history, streamed responses, a couple of well-scoped tools for real data lookups, and proper key/usage management underneath. None of these pieces is complicated individually — the work is in getting the prompt and escalation rules right for your specific support flows, which only comes from testing against real customer questions.
questions
Do I need to fine-tune Claude to build a customer chatbot? No. Fine-tuning is rarely necessary for support bots — a well-written system prompt, tool calls for real data, and good conversation management cover almost all use cases without training a custom model.
How do I stop the chatbot from making up answers about orders or policies? Give it explicit instructions to say "I don't know" rather than guess, and use tool calls to fetch real order and policy data instead of relying on the model's memory for facts.
What's the fastest way to add authentication and usage tracking to a Claude-based chatbot? Use scoped API keys per application instead of one shared key. SubToAPI handles this out of the box with per-key usage metadata and team seats — see /docs for setup.