Prompting Claude through an API requires a different mindset than prompting through chat.
In chat, you can clarify, iterate, and course-correct. In an API, you get one shot. Your prompt needs to produce consistent, parseable output every time—because your code is processing the response automatically.
Here are the patterns that work in production.
Pattern 1: Structured Output with JSON
Problem: Free-form text responses are hard to parse programmatically.
Solution: Explicitly request JSON and provide the schema.
const prompt = `Analyze this customer review and extract sentiment.
Review: "${reviewText}"
Respond ONLY with JSON in this exact format:
{
"sentiment": "positive" | "negative" | "neutral",
"confidence": 0.0 to 1.0,
"keywords": ["array", "of", "strings"],
"summary": "one sentence"
}`
const response = await callClaude(prompt)
const analysis = JSON.parse(response)
Why it works:
- Claude understands JSON structure
- The schema acts as a contract
- Easier to validate and handle errors
- No ambiguous output
Pro tip: Add Respond ONLY with valid JSON. No markdown, no explanation. to eliminate code fences.
Pattern 2: Few-Shot Examples
Problem: Claude interprets your intent differently than you expect.
Solution: Show examples of exactly what you want.
const prompt = `Convert user queries to SQL WHERE clauses.
Examples:
Input: "users who signed up last week"
Output: created_at >= NOW() - INTERVAL 7 DAY
Input: "premium users in California"
Output: plan = 'premium' AND state = 'CA'
Input: "users who haven't logged in for 30 days"
Output: last_login_at < NOW() - INTERVAL 30 DAY
Now convert this query:
Input: "${userQuery}"
Output:`
Why it works:
- Examples disambiguate intent
- Shows format, style, and edge cases
- Claude mimics the pattern
When to use:
- Classification tasks
- Format conversion
- Domain-specific transformations
Pattern 3: Role + Constraints
Problem: Claude is too verbose or goes off-task.
Solution: Set clear role and constraints upfront.
const systemPrompt = `You are a technical documentation generator.
Rules:
1. Output only valid markdown
2. Use code blocks with language tags
3. Maximum 200 words per section
4. No pleasantries or meta-commentary`
const message = await anthropic.messages.create({
model: 'claude-opus-4-20250514',
max_tokens: 1024,
system: systemPrompt,
messages: [{ role: 'user', content: userPrompt }],
})
Why it works:
- System prompts persist across conversations
- Constraints prevent scope creep
- Role sets context for tone and style
Pattern 4: Chain of Thought for Complex Tasks
Problem: Claude makes logical errors on multi-step problems.
Solution: Ask for step-by-step reasoning.
const prompt = `Determine if this expense is reimbursable.
Company policy:
- Travel: reimbursable if business-related
- Meals: reimbursable up to $50/day while traveling
- Equipment: requires pre-approval
Expense: "${expenseDescription}"
Amount: $${amount}
Think through this step by step:
1. What category is this expense?
2. Does it meet the policy requirements?
3. Are there any red flags?
Then respond with JSON:
{
"reimbursable": true/false,
"reason": "explanation",
"requiresReview": true/false
}`
Why it works:
- Explicit steps reduce errors
- Reasoning is visible for debugging
- Higher accuracy on edge cases
Pattern 5: Validation and Self-Correction
Problem: Claude's first response isn't always correct.
Solution: Build validation into the prompt.
const prompt = `Generate a SQL query for: "${userRequest}"
After writing the query:
1. Check for SQL injection vulnerabilities
2. Verify all referenced columns exist in schema
3. Confirm the query answers the original question
If you find issues, revise and provide the corrected query.
Schema:
${schemaDefinition}
Respond with JSON:
{
"query": "SELECT ...",
"explanation": "what this query does",
"warnings": ["any concerns or limitations"]
}`
Why it works:
- Self-correction improves accuracy
- Warnings surface edge cases
- Reduces back-and-forth debugging
Pattern 6: Fallback Handling
Problem: Sometimes Claude can't complete the task.
Solution: Explicitly handle the "I don't know" case.
const prompt = `Extract the company name from this job posting.
Job posting: "${jobText}"
If you can't find a company name, respond with:
{ "companyName": null, "reason": "explanation" }
Otherwise respond with:
{ "companyName": "Company Name", "confidence": 0.0 to 1.0 }`
Why it works:
- Prevents hallucinations
- Structured error handling
- Code can gracefully handle missing data
Pattern 7: Context Windows and Chunking
Problem: Input is too large for the context window.
Solution: Process in chunks with aggregation.
async function summarizeLongDocument(text: string): Promise<string> {
const chunkSize = 4000 // ~1000 tokens
const chunks = splitIntoChunks(text, chunkSize)
// Summarize each chunk
const chunkSummaries = await Promise.all(
chunks.map(chunk =>
callClaude(`Summarize this section in 2-3 sentences:\n\n${chunk}`)
)
)
// Combine summaries
const combined = chunkSummaries.join('\n\n')
// Final summary
return callClaude(
`Synthesize these summaries into one cohesive summary:\n\n${combined}`
)
}
Why it works:
- Breaks large tasks into manageable pieces
- Each step fits within token limits
- Final synthesis maintains coherence
Pattern 8: Dynamic Prompt Construction
Problem: Different users need different prompts.
Solution: Build prompts programmatically based on context.
function buildPrompt(user: User, task: string): string {
const parts = [`Task: ${task}`]
// Add user context if relevant
if (user.expertise === 'beginner') {
parts.push('Explain in simple terms without jargon.')
}
// Add company context
if (user.company) {
parts.push(`Company context: ${user.company.industry}`)
}
// Add constraints
parts.push('Respond in under 100 words.')
return parts.join('\n\n')
}
Why it works:
- Personalized without manual prompt writing
- Consistent structure
- Easy to A/B test prompt variations
Pattern 9: Streaming for Progressive Disclosure
Problem: Long responses feel slow.
Solution: Stream output and display incrementally.
async function streamResponse(prompt: string) {
const stream = await anthropic.messages.stream({
model: 'claude-opus-4-20250514',
max_tokens: 2048,
messages: [{ role: 'user', content: prompt }],
})
let fullResponse = ''
for await (const chunk of stream) {
if (chunk.type === 'content_block_delta') {
const text = chunk.delta.text
fullResponse += text
// Send to frontend immediately
sendToClient(text)
}
}
return fullResponse
}
Why it works:
- Users see results immediately
- Feels faster even if total time is the same
- Can stop generation if needed
Anti-Patterns to Avoid
❌ Vague Instructions
// Bad
const prompt = "Analyze this data"
// Good
const prompt = "Count unique users by signup month. Return JSON with month and count."
❌ No Output Format Specified
// Bad
const prompt = "What are the key points?"
// Good
const prompt = "Extract 3-5 key points as a JSON array of strings."
❌ Assuming Context
// Bad (assumes Claude remembers previous context)
const prompt = "Now do the same for category B"
// Good (provides all context)
const prompt = `Analyze category B using the same criteria:
- Metric 1
- Metric 2
- Metric 3`
Testing Your Prompts
Before deploying:
- Test edge cases: Empty input, very long input, malformed input
- Test consistency: Same input should produce similar output
- Test parsing: Ensure your code can parse every response
- Test failure modes: What happens when Claude can't complete the task?
// Example test suite
describe('Claude API Prompts', () => {
it('handles empty input gracefully', async () => {
const result = await extractKeywords('')
expect(result).toEqual({ keywords: [], error: 'No input provided' })
})
it('returns valid JSON', async () => {
const result = await extractKeywords('sample text')
expect(() => JSON.parse(result)).not.toThrow()
})
it('respects max token limits', async () => {
const result = await generateSummary(longText)
expect(result.length).toBeLessThan(500)
})
})
The Reality Check
No prompt is perfect. Even with these patterns, you'll encounter:
- Edge cases you didn't anticipate
- Responses that don't parse
- Occasional hallucinations
- Rate limits and timeouts
Build your application to handle imperfection. Log failures. Monitor patterns. Iterate on prompts based on real usage.
The best prompt is the one that works 95% of the time and fails gracefully the other 5%.
Related: Claude API Series
- Building Production Apps with Claude API: A Practical Guide - Error handling, cost optimization, and deployment strategies
- How I Built This Website with Claude Code - Applying these patterns to real development
Official Resources
- Anthropic Prompt Engineering Guide - Complete guide to effective prompting
- Claude API Documentation - Full API reference and examples
- Prompt Library - Example prompts for common use cases
- API Best Practices - Official recommendations from Anthropic
What prompt patterns have you found effective? I'm always looking to learn from others building with the Claude API.