← Blog

Build a Claude API Customer Support Bot: Full Tutorial

2026-10-13 · 5 min read · SubToAPI Team

Building a customer support bot with the Claude API means combining a well-scoped system prompt, conversation memory, and tool use so the model can answer common questions, look up order or account data, and know when to hand off to a human. This tutorial walks through that full stack, not just a demo that answers "hello" in a loop.

By the end you'll have a working bot that can classify incoming questions, call a backend function to fetch order status, maintain context across a multi-turn conversation, and stream replies to the frontend so users aren't staring at a blank screen while Claude thinks.

What a support bot actually needs

A production support bot is three things stacked on top of each other:

  1. A system prompt that defines scope, tone, and escalation rules.
  2. Tool definitions that let Claude fetch real data (orders, tickets, account status) instead of guessing.
  3. Conversation state so the bot remembers what the customer already said.

Skip any of these and you get either a bot that hallucinates order numbers or one that forgets the user's name two messages in.

Step 1: Write a system prompt that constrains behavior

Don't just say "you are a helpful assistant." Be explicit about boundaries:

You are the support assistant for Acme Store. You help customers with
order status, returns, and account questions.

Rules:
- If you don't have enough information to answer, ask a clarifying question.
- Never invent order numbers, tracking numbers, or refund amounts.
- Use the lookup_order tool whenever a customer mentions an order number
  or asks about shipping status.
- If a customer is angry, asks for a refund over €200, or asks for a
  manager, respond with the escalation phrase: "Let me connect you with
  a team member" and stop answering further questions in that thread.
- Keep answers under 4 sentences unless the customer asks for detail.

This single prompt removes most of the failure modes people run into: invented data, endless rambling, and bots that never escalate.

Step 2: Define a tool for order lookup

Claude's tool use (function calling) is what turns a chatbot into something that can actually resolve tickets. Define a tool schema and let Claude decide when to call it:

{
  "name": "lookup_order",
  "description": "Fetch order status, tracking number, and items for a given order ID",
  "input_schema": {
    "type": "object",
    "properties": {
      "order_id": { "type": "string", "description": "Customer order ID, e.g. ORD-10234" }
    },
    "required": ["order_id"]
  }
}

When Claude emits a tool_use block requesting lookup_order, your backend runs the real database query, returns the result as a tool_result, and Claude incorporates it into its next reply. This is the difference between "your order probably shipped" and "your order ORD-10234 shipped yesterday via DHL, tracking 1Z999AA10123456784." See /docs/tools for the exact message format if you're implementing this through SubToAPI's endpoint.

Step 3: Keep conversation history per customer

Claude's API is stateless — each request includes the full message array. Your job is to persist that array per conversation (usually keyed by session ID or customer ID) and append to it on every turn:

const history = await getConversation(sessionId); // from your DB
history.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",
    system: SUPPORT_SYSTEM_PROMPT,
    max_tokens: 1024,
    tools: [lookupOrderTool],
    messages: history
  })
});

const data = await response.json();
history.push({ role: "assistant", content: data.content });
await saveConversation(sessionId, history);

Trim old messages once the thread gets long — summarizing the first few turns into a short note is usually enough to stay within context without losing the thread of the conversation.

Step 4: Stream the response for a responsive UI

Support widgets feel broken if the reply appears all at once after a 3-4 second wait. Streaming fixes that by sending tokens as they're generated:

const stream = 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: 1024,
    stream: true,
    messages: history
  })
});

for await (const event of parseSSE(stream.body)) {
  if (event.type === "content_block_delta") {
    ws.send(event.delta.text); // push to the customer's chat widget
  }
}

Details on event types are in /docs/streaming.

Step 5: Handle escalation cleanly

Your system prompt tells Claude when to say "let me connect you with a team member," but your application code needs to act on that phrase — detect it in the assistant's reply and trigger a handoff (open a ticket, notify a human agent, stop auto-replying). Don't rely on the model to actually page someone; it can only produce text, so the handoff logic lives in your backend.

Step 6: Watch usage before you scale support volume

A support bot that handles hundreds of conversations a day burns through tokens fast, especially with long conversation histories and tool round-trips. Before rolling it out to your whole support queue, check per-conversation token cost and set a max_tokens ceiling that fits your budget. If you're running this through SubToAPI, usage metadata is attached to every response so you can track spend per team or per bot without building your own metering — see /docs/messages for the response fields, or /pricing for plan details if you're scaling beyond a single API key.

Getting from zero to a working prototype is usually a 20-minute job once the system prompt and tool schema are in place — the /docs/quickstart covers the raw API setup if you want to wire this up yourself, or start a trial at /signup if you'd rather skip key management and get straight to the bot.

Questions

Does the Claude API support function calling for things like order lookups? Yes — it's called tool use. You define a JSON schema for each function, Claude decides when to call it based on the conversation, and your backend executes the real logic and returns the result as a tool_result message.

How do I stop the bot from making up answers about orders or refunds? Constrain it explicitly in the system prompt ("never invent order numbers") and back every factual claim with a tool call to your real database instead of letting the model guess from training data.

Can a Claude-based support bot hand off to a human agent? The model can't trigger a handoff itself — have it output a consistent phrase or flag when escalation conditions are met, then detect that phrase in your application code and route to a human queue.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →