How to Build a Claude-Powered Recommendation Engine
A Claude-powered recommendation engine is a system where Claude doesn't compute similarity scores from scratch — it reasons over a shortlist of candidates (products, articles, courses, movies) that your own retrieval layer already narrowed down, and then ranks, explains, and personalizes the output in natural language. This is different from a classic collaborative-filtering or embedding-only recommender: Claude adds judgment, context awareness, and human-readable rationale on top of your existing data.
The short answer to "how do I build one" is: pair a retrieval step (vector search, SQL filters, or a simple candidate list) with a Claude call that re-ranks and explains those candidates based on user intent, history, and constraints. Below is a practical architecture you can implement in an afternoon.
Why use Claude instead of (or alongside) embeddings
Pure embedding-based recommenders are good at "find things similar to X" but bad at:
- Reasoning about constraints ("show me running shoes, but nothing over €120 and nothing I've already bought")
- Explaining why something was recommended
- Blending multiple weak signals (recent browsing, stated preference, season, inventory)
- Handling cold-start users where there's no history to embed
Claude is good at exactly these things because it reads structured context and produces structured, justified output. The trade-off is cost and latency per call, so the right design uses retrieval to cut the candidate pool down to 10–50 items before Claude ever sees them. Claude should never be your similarity search — it should be your ranking and explanation layer.
The architecture
A minimal Claude-powered recommendation engine has three stages:
- Candidate generation — pull 20–50 plausible items using whatever you already have: SQL filters, a vector DB (pgvector, Pinecone, Weaviate), or even a simple "same category" query.
- Context assembly — build a compact JSON payload describing the user (recent views, purchase history, stated preferences, budget) and the candidate list (id, title, price, tags, short description).
- Claude ranking + explanation — send that payload to Claude with a system prompt that defines the ranking logic and the required output format, then parse the response.
This keeps token usage predictable since the candidate pool is bounded, and it keeps Claude's job narrow: rank and explain, not search.
Step 1: Build the candidate list
You don't need a sophisticated retrieval system to start. A simple query is enough:
SELECT id, title, price, category, tags, short_description
FROM products
WHERE category = $1 AND in_stock = true
ORDER BY popularity DESC
LIMIT 40;
If you have embeddings already, swap this for a vector similarity query against the user's recent activity. Either way, the output is the same: a bounded JSON array you'll pass to Claude.
Step 2: Design the prompt
The system prompt should fix the output format so you can parse it reliably downstream. Ask for JSON, not prose:
You are a recommendation ranking engine. You will receive a user profile
and a list of candidate items. Rank the top 5 items for this user and
return ONLY valid JSON in this shape:
{
"recommendations": [
{ "id": "string", "reason": "one sentence, specific to this user" }
]
}
Do not recommend items already in the user's purchase history.
Respect any stated budget constraints exactly.
Keep the "reason" field short and specific — this is what makes Claude-based recommendations feel better than plain embedding matches: each one comes with a plausible, user-specific justification.
Step 3: Call the model
Here's a working example against SubToAPI's Messages endpoint, which exposes Claude through a standard HTTPS API with your own sub_live_ key:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 500,
"system": "You are a recommendation ranking engine. Return only valid JSON...",
"messages": [
{
"role": "user",
"content": "User profile: {\"budget\":120,\"recent_views\":[\"trail-runner-02\"],\"purchased\":[\"hydration-pack-01\"]}\nCandidates: [{\"id\":\"trail-runner-07\",\"price\":99,...}]"
}
]
}'
And in JavaScript, assembling the payload dynamically:
async function getRecommendations(user, candidates) {
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",
max_tokens: 500,
system: "You are a recommendation ranking engine. Return only valid JSON matching {recommendations:[{id,reason}]}.",
messages: [
{
role: "user",
content: `User: ${JSON.stringify(user)}\nCandidates: ${JSON.stringify(candidates)}`
}
]
})
});
const data = await res.json();
return JSON.parse(data.content[0].text);
}
This gives every application user a dedicated, scoped key, plus per-call usage metadata so you can see exactly what a single recommendation request costs. The full request/response shape is documented at /docs/messages, and /docs/quickstart walks through getting a key for your first integration.
Handling scale and latency
A few practical notes once this goes into production:
- Stream the explanations if you're rendering a UI live. If recommendations populate a page incrementally, use streaming responses so the first result appears before the full set finishes — see /docs/streaming.
- Cache candidate sets, not Claude outputs. User context changes often enough that caching the final ranking is risky, but caching the candidate pool for a few minutes is safe and cuts retrieval load.
- Use tool calls for live inventory checks. If stock or price changes frequently, give Claude a tool to verify availability before finalizing the list rather than trusting a stale candidate snapshot — covered in /docs/tools.
- Batch users where you can. If you're generating recommendations for a newsletter or nightly job, send larger batches per call instead of one call per user to reduce overhead.
Evaluating quality
Don't ship this blind. Track three things from day one: click-through rate on Claude-recommended items vs. your previous system, whether the "reason" text matches the actual item attributes (hallucination check), and p95 latency per request. A/B testing against your existing recommender for 2–4 weeks is enough to tell whether the explanation layer is actually moving conversion, not just adding cost.
If you're prototyping this for a side project or an internal tool, start with the free trial at /signup — Solo plans at /pricing are enough to run this architecture end-to-end before you commit to a team seat.
Questions
Do I need a vector database to build a Claude-powered recommender? No. A vector DB helps with candidate generation at scale, but a simple SQL filter or category match works fine for small catalogs. Claude's value is in ranking and explaining a bounded candidate list, not in similarity search itself.
How many candidates should I send to Claude per request? 20–50 is the practical sweet spot. Fewer than 10 limits diversity; more than 50 increases token cost and latency without meaningfully improving ranking quality.
Can Claude recommend items it hasn't seen before? No — Claude can only rank and explain items you include in the candidate payload. It has no access to your catalog unless you pass it explicitly or give it a tool call to query inventory in real time.