Build a Chatbot With the Claude API: A Tutorial
Building a chatbot with the Claude API comes down to four things: sending structured messages, keeping track of conversation history, streaming the response back to your UI, and deciding how you'll manage API keys and usage once the chatbot is live. This tutorial walks through each piece with working code, so you end up with a chatbot that actually holds a conversation instead of answering one question at a time.
If you've only called the API once with a single prompt, the gap you'll hit immediately is state — Claude doesn't remember previous turns unless you send them back yourself. That's the core design problem this tutorial solves, along with streaming and tool use, which are the two features that make a chatbot feel responsive and useful rather than like a glorified autocomplete box.
What you need before you start
- An Anthropic API key (or a SubToAPI key if you want a single key across providers — more on that below)
- A backend that can make server-side HTTP requests (Node.js, Python, or any language with an HTTP client)
- A basic frontend to render messages — a chat widget, a CLI, or a Slack bot, it doesn't matter for this tutorial
Never call the API directly from client-side JavaScript in a browser — your key would be exposed. Always proxy requests through your own backend.
Step 1: Design the conversation loop
A chatbot is just a loop: user sends a message, you append it to history, you send the whole history to Claude, Claude replies, you append that reply to history, repeat. The entire state of the conversation lives in an array of {role, content} objects that you maintain — the API itself is stateless between calls.
[
{ "role": "user", "content": "I need help picking a laptop for video editing." },
{ "role": "assistant", "content": "What's your budget, and do you edit 4K footage?" },
{ "role": "user", "content": "Budget is around $1500, mostly 1080p." }
]
Every request you send includes the full history up to that point. There's no session ID or conversation token on the API side — you own the state.
Step 2: Call the Messages API
Here's a minimal request using curl, assuming you're calling the Anthropic API directly:
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"system": "You are a friendly, concise shopping assistant for a laptop store.",
"messages": [
{ "role": "user", "content": "I need help picking a laptop for video editing." }
]
}'
The system field is where your chatbot's personality and constraints live — keep it short and specific. Vague system prompts ("be helpful and polite") produce vague chatbots; specific ones ("only recommend laptops from our catalog, ask about budget before suggesting a model") produce useful ones.
Step 3: Maintain conversation history
In practice you'll wrap this in a function that appends each new user message, sends the array, and appends the assistant's reply. Here's a Node.js example:
let history = [];
async function sendMessage(userText) {
history.push({ role: "user", content: userText });
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": process.env.ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 1024,
system: "You are a friendly, concise shopping assistant.",
messages: history
})
});
const data = await res.json();
const reply = data.content[0].text;
history.push({ role: "assistant", content: reply });
return reply;
}
For a real app, store history per user in a database or session store, not in memory — otherwise every restart wipes every conversation. Also watch the token count: long histories eventually exceed the context window, so trim or summarize older turns once you're well into a conversation.
Step 4: Stream responses for a better UX
Waiting several seconds for a full response feels slow in a chat interface. Streaming sends tokens as they're generated, so you can render text progressively, the same way ChatGPT or Claude.ai do it. You enable this by setting "stream": true and reading the response as server-sent events instead of a single JSON blob. If you're building this on SubToAPI, the streaming format matches the Anthropic API exactly, and the docs at /docs/streaming cover the event types you'll parse on the client.
Step 5: Add tool use (optional but powerful)
If your chatbot needs to check order status, look up inventory, or query a database mid-conversation, you give Claude a tool definition and it returns a tool_use block instead of plain text when it decides a lookup is needed. Your backend runs the actual function, sends the result back, and Claude incorporates it into its next reply. This is what turns a chatbot from "answers questions about general knowledge" into "answers questions about your specific product catalog." It's a bigger topic than fits here — see /docs/tools if you want to wire this up.
Step 6: Deploy and manage keys sensibly
Once the chatbot works locally, the operational questions start: how do you rotate keys without downtime, how do you see which feature is burning through tokens, how do you give a teammate access without sharing your root API key? This is exactly the layer SubToAPI sits on. You keep your existing Claude access, and SubToAPI gives you scoped sub_live_... application keys, per-key usage breakdowns, and team seats — so your chatbot's production key is separate from your testing key, and you're not pasting a single shared secret into every service that needs it. The request and response format is unchanged from what you've built in this tutorial; see /docs/quickstart to go from a raw Anthropic key to an API you can safely hand to different parts of your team, and /docs/messages for the exact request shape. Plans start at €9/month on the Solo tier, with team pricing on /pricing.
Common mistakes to avoid
- Sending the full history every time but forgetting the system prompt — it needs to be on every request, not just the first one.
- Not handling the
stop_reason— if a response was cut off bymax_tokens, your chatbot should either continue the generation or let the user know it was truncated. - Storing history client-side only — a page refresh shouldn't erase the conversation if you want returning users to pick up where they left off.
- Skipping error handling for rate limits — a chatbot under real traffic will eventually hit a 429, and it should retry with backoff rather than show a raw error to the user.
FAQ
Do I need a different API for streaming vs non-streaming chatbot responses? No, it's the same Messages endpoint — you just set "stream": true and parse server-sent events instead of a single JSON response.
How much conversation history should I send with each request? Send as much as fits comfortably under your model's context window and budget; for long conversations, summarize or drop the oldest turns rather than sending everything indefinitely.
Can I build a chatbot with Claude without managing raw Anthropic API keys myself? Yes — tools like SubToAPI sit on top of your existing Claude access and give you application-specific keys, usage tracking, and team access via /signup, without changing how you call the Messages API.