How I Actually Ship with Claude
A senior end-to-end workflow: prototype in chat, build with Claude Code, wrap features behind your own API with evals, and monitor cost and quality in production.
Here is how the pieces fit into one dependable workflow — the way an experienced developer actually ships an AI feature, from idea to production.
1. Prototype the prompt in chat
Before writing any code, iterate on the prompt in a chat window against real examples. Cheap, fast, and it tells you whether the idea is even viable and which model tier you need.
2. Build the feature with Claude Code
Hand the working prompt to Claude Code on a clean branch: build the endpoint, wire the SDK, add the eval harness. Review the diff like a pull request before it lands.
3. Wrap it behind your own API
The browser talks to your endpoint; your server holds the key, rate-limits, validates input, and streams the reply. This is where security and structured output live.
4. Gate on evals
The feature ships behind an eval set that runs in CI. Pin the model ID so upgrades are deliberate. A prompt change that regresses the eval set does not merge.
5. Watch it in production
Log usage, stop_reason, and latency. Track cost per feature and cache-hit rates. Sample real outputs to catch quality drift the eval set missed, and feed the interesting failures back into the eval set.
The loop that keeps it healthy
Production surfaces a new failure → it becomes an eval case → you fix the prompt → the eval set guards against the regression forever. That feedback loop, more than any single trick, is what makes an AI feature stay good after launch.
Example
{
"how_i_ship_a_claude_feature": {
"1_prototype": "Iterate the prompt in chat on real examples; pick the model tier",
"2_build": "Claude Code on a clean branch builds the endpoint + eval harness; review the diff",
"3_wrap": "Browser -> my server -> Claude. Key server-side, rate-limited, streamed",
"4_gate": "Eval set runs in CI with a pinned model ID; regressions don't merge",
"5_monitor": "Log usage, stop_reason, latency, cache hit rate; sample outputs",
"6_loop": "Production failure -> new eval case -> prompt fix -> guarded forever"
}
}When to use it
- Prototype a new AI feature in claude.ai chat in an afternoon, then implement it with Claude Code the same day.
- Gate your AI feature behind a feature flag so you can enable it for beta testers before a full rollout.
- Monitor token usage and error rates in production dashboards from day one to catch cost spikes before they escalate.
More examples
Prototype in chat, implement with Claude Code
Shows the two-phase workflow: explore the design in chat, then hand the spec to Claude Code for implementation.
# Step 1: Prototype in claude.ai chat
# Ask Claude to show you what the API call, system prompt,
# and response parsing should look like
# Step 2: Implement with Claude Code
claude "Build the /api/summarise endpoint we designed in chat:
- POST body: { text: string }
- System prompt: 'Summarise in 3 bullets'
- Model: claude-haiku-4-5, max_tokens: 200
- Return { bullets: string[] }"Feature-flag the AI endpoint
Guards the AI route behind a feature flag so it can be enabled per user or cohort without a code deploy.
// Feature-flag gate in the API route
export async function POST(req) {
const user = await getUser(req);
if (!user.flags.includes("ai_summarise")) {
return Response.json({ error: "Feature not available" }, { status: 403 });
}
// ...call Claude...
}Monitor cost and error rate in production
Wraps every Claude call with latency, token, and error metrics so production dashboards show usage and cost trends.
async function callClaude(prompt, userId) {
const start = Date.now();
try {
const msg = await client.messages.create({
model: "claude-haiku-4-5",
max_tokens: 256,
messages: [{ role: "user", content: prompt }],
});
metrics.record("claude_latency_ms", Date.now() - start, { userId });
metrics.record("claude_output_tokens", msg.usage.output_tokens, { userId });
return msg.content[0].text;
} catch (err) {
metrics.increment("claude_error", { status: err.status ?? 0, userId });
throw err;
}
}
Discussion