Anthropic API Best Practices for Production Apps
Building a reliable product on top of the Anthropic API means going beyond "call the endpoint and print the response." Production systems need to handle rate limits gracefully, retry failures without duplicating work, manage context windows efficiently, and keep costs predictable as usage scales. This article covers the practices that actually matter once you move from a prototype to something real users depend on.
The short version: treat the API like any other external dependency you don't fully control. Add retries with backoff, cache what you can, stream long responses, validate tool inputs before executing them, and monitor token usage per user or feature so costs never surprise you.
Authentication and Key Management
Never hardcode API keys in source code or commit them to version control. Load them from environment variables or a secrets manager, and rotate keys periodically.
If multiple applications or team members need access, avoid sharing a single raw key across everyone. It makes usage impossible to attribute and revocation risky — killing one leaked key means breaking every integration at once. Tools like SubToAPI solve this by letting you issue separate application keys (sub_live_...) per app or environment while billing runs through one account. See /docs/quickstart for a setup example.
export SUBTOAPI_KEY="sub_live_xxx"
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Summarize this ticket."}]
}'
Handle Rate Limits and Errors Properly
Rate limits and transient errors (5xx, network timeouts) are normal at scale, not exceptions. Build retry logic with exponential backoff and jitter rather than retrying immediately in a tight loop, which just amplifies the problem.
async function callWithRetry(fn, maxRetries = 5) {
let attempt = 0;
while (attempt < maxRetries) {
try {
return await fn();
} catch (err) {
const retryable = err.status === 429 || err.status >= 500;
if (!retryable || attempt === maxRetries - 1) throw err;
const delay = Math.min(1000 * 2 ** attempt, 15000) + Math.random() * 300;
await new Promise((r) => setTimeout(r, delay));
attempt++;
}
}
}
Distinguish between error types: a 400 (bad request) means your payload is wrong and retrying won't help — fix the request. A 429 (rate limited) or 5xx means back off and retry. Log the response body on failures; it usually tells you exactly what's wrong.
Stream Long Responses
For anything user-facing that generates more than a sentence or two, use streaming instead of waiting for the full completion. It reduces perceived latency dramatically and lets you show partial output as it's generated. See /docs/streaming for the event format.
const response = 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: 2048,
stream: true,
messages: [{ role: "user", content: "Write a deployment checklist." }],
}),
});
const reader = response.body.getReader();
// process server-sent events as they arrive
Streaming also makes timeouts less likely to hurt the user experience — even if the full generation takes 20 seconds, they see tokens appearing immediately.
Manage Context and Prompt Structure
Large context windows are tempting to fill, but every token you send costs money and adds latency. A few habits help:
- Keep system prompts stable and reusable. Don't regenerate instructions dynamically if they don't need to change per request.
- Trim conversation history. For chat applications, summarize or drop older turns instead of sending the entire history on every call.
- Separate instructions from data. Put user-supplied content clearly marked (e.g., inside XML-like tags) so the model doesn't confuse instructions with input it should just process.
- Use prompt caching where supported. Repeated large system prompts or reference documents don't need to be reprocessed from scratch on every request if the platform supports caching.
Review the request/response reference in /docs/messages to understand exactly what fields affect token usage and pricing.
Use Tools Deliberately
Tool use (function calling) is powerful but also the easiest place to introduce bugs or security issues. A few practices:
- Validate tool inputs server-side. Never trust that the model will always produce well-formed arguments — validate types and ranges before executing anything.
- Keep tool descriptions precise. Vague descriptions lead to the model calling the wrong tool or misusing parameters. Be explicit about what each tool does and doesn't do.
- Limit tool scope. Give the model only the tools it needs for the current task rather than exposing every internal function; this reduces both errors and blast radius if something goes wrong.
- Log every tool call and its result. This is essential for debugging unexpected behavior later.
Full details on the schema and multi-turn tool flows are in /docs/tools.
Monitor Usage and Cost
Token usage compounds quickly once you have real traffic. Track usage per endpoint, per customer, or per feature — not just in aggregate — so you can spot a runaway prompt or an abusive user before it shows up as a shocking invoice.
If you're running an internal tool or SaaS product on top of Claude, having usage metadata and per-key breakdowns in one dashboard saves a lot of manual log-digging. SubToAPI exposes this alongside the API itself, so you can see cost by application key without building your own tracking layer — see /pricing for how usage maps to plans.
Test Prompts Like Code
Treat prompts as versioned artifacts, not throwaway strings. Keep them in your repository, write test cases with expected output patterns, and re-run those tests when you change a prompt or upgrade a model version. A prompt that worked well last month can behave differently after a model update — catching that in a test suite is much cheaper than catching it in production.
Getting Started
If you're setting up API access for the first time, start with /docs/quickstart to get a key and make your first request, then move to /docs/messages and /docs/streaming once you're building real features. A free trial is available at /signup if you want to test the setup before committing to a plan.
Questions
Do I need to implement my own retry logic if I use a wrapper service? If the wrapper handles retries and backoff for you, you don't need to duplicate that logic client-side — check the service's docs to confirm what's handled automatically versus what you still need to manage.
How much context should I send on every request? Only what the model needs to complete the current task well. Trim or summarize older conversation turns and avoid resending large static documents unless prompt caching is in use.
What's the biggest mistake teams make with tool use? Trusting model-generated tool arguments without validation. Always validate types, ranges, and permissions server-side before executing any tool call.