← Blog

Build a Claude API Code Review Assistant: Full Tutorial

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

What you'll build

This tutorial walks through building a working code review assistant powered by Claude: a script that takes a diff, sends it to the Claude API with a structured prompt, and returns actionable review comments you can post to a pull request. By the end you'll have a CLI tool you can run locally or wire into CI, plus guidance on running it reliably across a team.

If you searched for "claude api code review assistant tutorial," you're likely trying to answer one of two questions: how do I structure a prompt so Claude gives useful review feedback instead of generic praise, and how do I get a diff from Git into the API and back into a usable comment format. Both are covered below, with working code.

How Claude-based code review actually works

A code review assistant is not magic — it's three steps repeated for every pull request:

  1. Extract the diff. Pull the changed lines from Git (or your CI provider's webhook payload).
  2. Send it to Claude with context. A clear system prompt, the diff, and optionally the full file for context.
  3. Parse the response into actionable comments. Ask for structured output (line numbers, severity, explanation) so you can post it back to GitHub/GitLab automatically.

The trick that separates a toy script from something your team will actually trust is step 2: prompt structure. Claude is good at finding real issues — unhandled errors, off-by-one logic, SQL injection risks, missing null checks — but only if you tell it what to look for and in what format to answer.

Step 1: Get the diff

git diff main...feature-branch -- '*.ts' '*.js' > review.diff

For CI, most providers expose the diff directly in the webhook or via their API (e.g., GitHub's /repos/{owner}/{repo}/pulls/{pull_number}/files).

Step 2: Write a prompt that produces useful feedback

Vague prompts ("review this code") produce vague output. Be explicit about format and severity:

You are a senior software engineer performing a code review.
Review the following diff. For each issue found, output a JSON object with:
- "file": file path
- "line": approximate line number
- "severity": "blocker" | "warning" | "nit"
- "comment": a concise, actionable explanation

Only report real issues: bugs, security risks, performance problems,
or violations of the stated style guide. Do not comment on formatting
if a linter would catch it. If there are no issues, return an empty array.

Diff:
<<<DIFF>>>

Step 3: Call the API

Here's the implementation using SubToAPI, which exposes Claude through a standard HTTPS API with an application key (sub_live_...) instead of managing Anthropic credentials and billing directly:

import fs from "fs";

const diff = fs.readFileSync("review.diff", "utf8");

const prompt = `You are a senior software engineer performing a code review.
Review the following diff. For each issue found, output a JSON array of objects with:
- "file", "line", "severity" ("blocker"|"warning"|"nit"), "comment".
Only report real issues. Return [] if none.

Diff:
${diff}`;

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-opus-4",
    max_tokens: 2000,
    messages: [{ role: "user", content: prompt }],
  }),
});

const data = await res.json();
const reviewText = data.content[0].text;

let issues = [];
try {
  issues = JSON.parse(reviewText);
} catch {
  console.error("Could not parse model output as JSON:", reviewText);
}

issues.forEach((issue) => {
  console.log(`[${issue.severity.toUpperCase()}] ${issue.file}:${issue.line} — ${issue.comment}`);
});

Setup for the above takes two steps: sign up at /signup and get a key, then follow /docs/quickstart to confirm your first request works before wiring it into CI.

Step 4: Post comments back to the PR

Once you have structured issues, map each one to a comment on the PR using your Git provider's API. For GitHub, that's POST /repos/{owner}/{repo}/pulls/{pull_number}/comments with path, line, and body fields pulled directly from the parsed JSON.

for (const issue of issues) {
  await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${pr}/comments`, {
    method: "POST",
    headers: { Authorization: `token ${GITHUB_TOKEN}` },
    body: JSON.stringify({
      body: `**${issue.severity.toUpperCase()}**: ${issue.comment}`,
      path: issue.file,
      line: issue.line,
      commit_id: headSha,
    }),
  });
}

Making it production-ready

A few things matter once this moves from a personal script to something your team relies on:

Common pitfalls

questions

Can Claude catch security vulnerabilities, not just style issues? Yes, if you ask explicitly. Claude performs well on injection risks, unsafe deserialization, and missing input validation when the prompt names these categories — generic "review this" prompts tend to focus on style instead.

Should I send the full file or just the diff? Send the diff for speed and cost, but include a few lines of surrounding context per hunk so Claude understands the change without needing the entire file.

How do I avoid rate limit errors when reviewing large PRs? Batch all changed files into a single request per PR instead of one request per file, and use streaming for large payloads so the connection doesn't time out before the response completes.

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 →