How to Build an AI Coding Assistant with Claude API
Building an AI coding assistant with the Claude API means wiring three things together: a prompt structure that gives Claude enough context about the codebase, a tool-use loop that lets Claude read files and run commands instead of guessing, and a way to stream responses back to whatever editor or CLI your users are working in. This guide walks through each piece with working code, then covers what changes when you move from a weekend prototype to something your team actually relies on.
The short answer for developers who just want a working setup: use the Messages API with tool calling for file operations, stream the output for responsiveness, and keep the system prompt focused on the specific language/framework context rather than trying to make the model universally smart. Everything below expands on how.
Core architecture of a coding assistant
A Claude-based coding assistant is not a single API call — it's a loop. The model asks for information (read this file, list this directory, run these tests), your code executes that action locally, and you feed the result back. This continues until Claude has enough context to produce an answer or a diff.
The minimal loop looks like this:
- Send the user's request plus a system prompt describing the project.
- Claude responds with either a final answer or a
tool_useblock. - Your code executes the requested tool (read file, grep, run linter) and returns the result.
- Repeat until Claude stops requesting tools.
This pattern is the same one used by CLI coding agents — the model doesn't need blind trust, it needs verifiable access to your actual files.
Defining tools for file access
Claude's tool use lets you describe functions in JSON schema and have the model call them with structured arguments. For a coding assistant, you typically need at least three tools: read a file, list a directory, and write/patch a file.
const tools = [
{
name: "read_file",
description: "Read the contents of a file at the given path",
input_schema: {
type: "object",
properties: {
path: { type: "string", description: "Relative path from project root" }
},
required: ["path"]
}
},
{
name: "list_directory",
description: "List files and folders in a directory",
input_schema: {
type: "object",
properties: { path: { type: "string" } },
required: ["path"]
}
},
{
name: "write_file",
description: "Write or overwrite a file with new content",
input_schema: {
type: "object",
properties: {
path: { type: "string" },
content: { type: "string" }
},
required: ["path", "content"]
}
}
];
When Claude returns a tool_use block, you execute the corresponding local function and send the result back as a tool_result message. Keep tool responses concise — truncate large files or return only relevant line ranges, since dumping an entire 3,000-line file back into context burns tokens fast for no benefit.
For a full walkthrough of the request/response shapes, see /docs/tools.
Handling the conversation loop
Here's a simplified version of the loop in JavaScript, assuming you're calling Claude directly:
async function runAssistant(userMessage, systemPrompt) {
let messages = [{ role: "user", content: userMessage }];
while (true) {
const response = await callClaude(messages, systemPrompt, tools);
if (response.stop_reason !== "tool_use") {
return response.content; // final answer
}
const toolUse = response.content.find(b => b.type === "tool_use");
const result = await executeTool(toolUse.name, toolUse.input);
messages.push({ role: "assistant", content: response.content });
messages.push({
role: "user",
content: [{ type: "tool_result", tool_use_id: toolUse.id, content: result }]
});
}
}
The executeTool function is where your actual security boundary lives — validate paths, restrict to the project root, and never let the model write outside a sandboxed directory without explicit confirmation.
Streaming for editor integration
If your assistant runs inside an editor extension or a terminal UI, streaming matters more than raw speed — users need to see tokens arrive so the tool feels responsive during longer generations (explaining a bug, writing a function, refactoring a module).
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-3-5-sonnet",
"stream": true,
"max_tokens": 1024,
"system": "You are a coding assistant for a Python/Django project.",
"messages": [{"role": "user", "content": "Explain why this migration fails."}]
}'
Parse the SSE stream and render tokens as they arrive rather than waiting for the full response — this alone makes the assistant feel dramatically faster even when total latency is unchanged. Details on chunk format are in /docs/streaming.
Turning a prototype into an internal tool
A prototype that calls Claude directly with a hardcoded key works fine for one developer. It breaks down the moment you want to:
- Give teammates their own scoped API keys instead of sharing one secret
- See per-user or per-project token usage
- Rotate a compromised key without redeploying every client
- Set spending limits before a runaway tool loop burns through your budget
This is the point where routing your coding assistant through SubToAPI makes sense. It sits on top of your existing Claude access and gives you sub_live_... application keys, so each internal tool, CI job, or teammate gets an isolated key with its own usage metadata — without touching your underlying Claude credentials. Streaming, tool use, and the standard Messages format all work the same way, so the loop above needs zero rewriting.
Setup is a signup and a key swap: create an account at /signup, generate a key from the dashboard, and follow /docs/quickstart to point your existing callClaude function at the new endpoint. Plans start at Solo (€9) for individual use, with Team (€19/seat) and Scale (€49/seat) adding multi-key management for when the assistant becomes something your whole team depends on — see /pricing for details.
Practical tips for coding assistants specifically
- Scope the system prompt to the actual stack. A system prompt naming the language, framework, and testing tools produces noticeably better suggestions than a generic "you are a helpful coding assistant."
- Cap tool-call depth. Set a hard limit (e.g., 8–10 tool calls per request) to prevent an assistant from looping through the filesystem indefinitely.
- Return diffs, not full files, for edits. Asking Claude to produce a unified diff instead of rewriting whole files reduces token usage and makes changes easier to review before applying.
- Log every tool call. When something goes wrong, the tool-call log is your debugging trail — what file was read, what was written, in what order.
questions
Do I need the full Anthropic SDK to build a coding assistant? No — the Messages API is plain HTTPS with JSON, so curl, fetch, or any HTTP client works. An SDK just wraps the same requests shown in /docs/messages.
How do I stop the assistant from making unlimited tool calls? Track a call counter in your loop and break after a fixed limit, returning a partial answer or asking the user for more direction instead of looping silently.
Can multiple developers share one Claude subscription for this? Yes — routing requests through SubToAPI gives each developer or service a separate sub_live_... key with its own usage tracking, so you avoid sharing one raw credential across a team.