How to Build a Claude-Powered Helpdesk Bot
A Claude-powered helpdesk bot is a chat interface backed by Claude that answers support questions, looks up order or account data through tools, and hands off to a human when it can't resolve something. The core pieces are: an API connection to Claude, a system prompt that constrains the bot to your product and policies, tool definitions for anything that requires live data, and a fallback path to a human agent.
This article walks through that build end to end — from getting API access to shipping a working bot — with code you can adapt directly.
What the bot actually needs to do
Before writing any code, define the bot's job narrowly. Most helpdesk bots need to:
- Answer FAQs (billing, account setup, feature usage)
- Look up order/ticket/subscription status via an internal API
- Collect missing details (order ID, email) before acting
- Escalate to a human when confidence is low or the user asks for one
- Keep a readable transcript for support agents who pick up the handoff
A bot that tries to do everything in one giant prompt without tools will hallucinate answers about things it can't actually check — order status, refund eligibility, account state. Tool use solves that by giving Claude a way to fetch real data instead of guessing.
Getting API access
You need a Claude API key to send requests. If you want streaming, tool calling, usage metadata and per-app keys without managing billing and quota logic yourself, SubToAPI turns your existing Claude access into an HTTPS API with sub_live_... keys you can issue per project or per team seat. Sign up at /signup and follow /docs/quickstart to get your first key working in a few minutes.
A basic request to create a message looks like this:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 500,
"system": "You are a support assistant for Acme Cloud.",
"messages": [
{"role": "user", "content": "I was charged twice this month, can you check?"}
]
}'
Full request/response shape is documented at /docs/messages.
Writing the system prompt
The system prompt is where most of the "is this bot actually helpful" work happens. For a helpdesk bot, be explicit about scope, tone, and what it should never do:
You are the support assistant for Acme Cloud.
- Only answer questions about Acme Cloud billing, account settings, and product features.
- If you need an order ID, account email, or ticket number to answer, ask for it before responding.
- Never invent order status, refund amounts, or account details — use the lookup_order tool.
- If the user is angry, asks for a human, or the issue involves a refund over €200, escalate immediately.
- Keep answers under 4 sentences unless the user asks for detail.
Keeping the prompt this concrete reduces both hallucination and scope creep — the two most common failure modes in support bots.
Giving the bot tools to look things up
Instead of letting Claude guess at order status or account state, define a tool it can call:
{
"name": "lookup_order",
"description": "Fetch order status and billing details by order ID",
"input_schema": {
"type": "object",
"properties": {
"order_id": { "type": "string" }
},
"required": ["order_id"]
}
}
When Claude decides it needs this data, it returns a tool call instead of a text answer. Your backend executes the real lookup against your database or billing provider, then sends the result back in a follow-up message so Claude can finish the reply. SubToAPI passes tool calls through unchanged, so any tool schema that works against Claude directly works the same way — see /docs/tools for the request/response flow.
const res = 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: supportSystemPrompt,
tools: [lookupOrderTool],
messages: conversation
})
});
If the response contains a tool_use block, run your lookup, append a tool_result message with the real data, and send the conversation back for Claude to produce the final answer.
Streaming for a live chat feel
Helpdesk bots feel much better when responses stream token by token rather than appearing all at once after a multi-second wait. Use streaming for any chat UI:
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: 500,
stream: true,
system: supportSystemPrompt,
messages: conversation
})
});
Each chunk arrives as a server-sent event you append to the chat window as it decodes. Implementation details and event types are at /docs/streaming.
Handling multi-turn context
Support conversations rarely resolve in one exchange — you need order IDs, confirmations, and clarifications across multiple turns. Keep the full message array (user and assistant turns) and pass it back on every request rather than resending just the latest message. Trim older turns once the conversation grows long enough to risk hitting context limits, but keep the first user message and any confirmed facts (order ID, account email) pinned so the bot doesn't re-ask for them.
Escalating to a human
Build escalation as an explicit signal the bot can emit, not something you try to detect by scanning free text. Add a rule in the system prompt instructing Claude to output a fixed marker like [ESCALATE] when it decides a human is needed, then have your backend watch for that marker, strip it from the user-facing text, and open a ticket in your support queue with the full transcript attached. This is far more reliable than trying to guess intent from arbitrary phrasing after the fact.
Monitoring usage
Once the bot is live, you want visibility into volume and cost per team or app, especially if multiple products share one Claude setup. SubToAPI's dashboard tracks usage per API key, which is useful if your support bot and other internal tools share the same account — see /pricing for how seats and keys are organized across Solo, Team, and Scale plans.
Questions
Does the bot need to be a separate app from my existing support tooling? No — most teams embed it as a widget or sidebar inside their existing helpdesk (Zendesk, Intercom, a custom dashboard) and call the Claude API from that same backend.
How do I stop the bot from making up answers about order status or refunds? Never let Claude answer those from its own knowledge. Require a tool call that hits your real backend data, and instruct the system prompt to refuse to answer without it.
What happens if the bot can't resolve an issue? Define an explicit escalation marker in the system prompt, detect it in your backend, and route the full transcript to a human agent's queue rather than leaving the user in a loop.