← Blog

Claude API RAG Implementation Tutorial

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

What RAG with Claude actually means

Retrieval-augmented generation (RAG) is the pattern of fetching relevant text from your own data — docs, tickets, PDFs, database rows — and injecting it into the prompt before calling Claude. Claude itself has no built-in vector search, so RAG is something you build around the API: a retrieval layer (embeddings + vector store) feeding a generation layer (the Claude Messages API). This tutorial walks through both halves end to end, with working code you can adapt.

If you've been searching for a "claude api rag implementation tutorial," you're likely trying to answer one of two questions: how do I ground Claude's answers in my own data, and how do I keep the pipeline simple enough to maintain. Below is a minimal but production-shaped implementation covering chunking, embedding, retrieval, and prompt construction, plus notes on what breaks in practice.

Step 1: Chunk your source documents

RAG quality lives or dies on chunking. Claude has a large context window, but stuffing entire documents into every request wastes tokens and dilutes relevance. Aim for chunks of 300–800 tokens with some overlap so you don't cut sentences mid-thought.

function chunkText(text, maxChars = 1800, overlap = 200) {
  const chunks = [];
  let start = 0;
  while (start < text.length) {
    const end = Math.min(start + maxChars, text.length);
    chunks.push(text.slice(start, end));
    start = end - overlap;
  }
  return chunks;
}

Store each chunk alongside metadata: source filename, section title, page number. You'll need this later to produce citations.

Step 2: Generate embeddings and store vectors

Claude doesn't offer an embeddings endpoint, so you'll pair it with an embedding model (OpenAI's text-embedding-3-small, Voyage AI's voyage-2, or a local model like all-MiniLM-L6-v2) and a vector store (pgvector, Pinecone, Qdrant, or even an in-memory array for a prototype).

import { OpenAI } from "openai";
const openai = new OpenAI();

async function embed(texts) {
  const res = await openai.embeddings.create({
    model: "text-embedding-3-small",
    input: texts,
  });
  return res.data.map((d) => d.embedding);
}

Insert each chunk's embedding into your vector store with its metadata. For a quick prototype, cosine similarity against an in-memory array is fine:

function cosineSim(a, b) {
  let dot = 0, normA = 0, normB = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }
  return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}

function topK(query, chunks, k = 5) {
  return chunks
    .map((c) => ({ ...c, score: cosineSim(query, c.embedding) }))
    .sort((a, b) => b.score - a.score)
    .slice(0, k);
}

Step 3: Retrieve at query time

When a user asks a question, embed the query, retrieve the top-k chunks, and assemble them into context. Keep retrieved context separate from the user's question in the prompt structure — Claude handles clearly delimited context better than a single merged blob.

const queryEmbedding = (await embed([userQuestion]))[0];
const relevant = topK(queryEmbedding, allChunks, 5);

const context = relevant
  .map((c, i) => `[${i + 1}] Source: ${c.source}\n${c.text}`)
  .join("\n\n");

Step 4: Call Claude with grounded context

Build a system prompt that tells Claude to answer only from the provided context and to cite sources by index. This reduces hallucination and makes answers auditable.

const systemPrompt = `You answer questions using only the context below.
If the answer isn't in the context, say you don't know.
Cite sources using [1], [2], etc. matching the context blocks.

Context:
${context}`;

const response = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
  },
  body: JSON.stringify({
    model: "claude-sonnet-4",
    max_tokens: 1024,
    system: systemPrompt,
    messages: [{ role: "user", content: userQuestion }],
  }),
});

const data = await response.json();
console.log(data.content[0].text);

This example uses SubToAPI as the API endpoint, which wraps Claude access in a standard REST interface with an application key (sub_live_...) instead of managing provider credentials directly — useful if you're already building the rest of your stack around HTTPS APIs and want usage metadata per key out of the box. The request/response shape matches the Messages API, so swapping providers later is mostly a base-URL change. Full request options are in the docs.

Step 5: Stream the response for better UX

RAG answers can be long, especially with citations. Streaming keeps the UI responsive instead of waiting for the full generation.

body: JSON.stringify({
  model: "claude-sonnet-4",
  max_tokens: 1024,
  system: systemPrompt,
  messages: [{ role: "user", content: userQuestion }],
  stream: true,
})

Parse the server-sent events and append text deltas to your UI as they arrive. See the streaming guide for the event format if you're consuming it from the browser or a server-side proxy.

Common failure modes to watch for

Where to go from here

Once the core loop works, the next improvements are usually: hybrid search (combining keyword and vector search), query rewriting before retrieval, and caching embeddings for frequently accessed documents. None of that changes the shape of the Claude call itself — it's still a Messages request with a well-constructed system prompt and context block.

If you want to skip provisioning and auth plumbing while testing this pipeline, sign up for a free trial and generate an API key from the dashboard; the quickstart covers the first request in under five minutes, and pricing has details on per-seat plans if you're building this for a team.

FAQ

Does Claude have a native RAG feature? No. Claude provides the generation step through the Messages API; retrieval (embeddings, vector search, chunking) is something you implement separately and feed into the prompt as context.

Which embedding model should I use with Claude? Any embedding model works since Claude doesn't require a matching provider — OpenAI's text-embedding-3-small, Voyage AI, or open-source models like MiniLM are all common choices. Pick based on cost and the language/domain of your documents.

How much context should I retrieve per query? Start with 3–5 chunks of 300–800 tokens each. Measure answer quality on real queries, then adjust — more context isn't always better, since irrelevant chunks can distract the model from the right answer.

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 →