Whenever someone tells me "the prototype works, can we ship next week?", I tend to pause and ask three questions: What's your expected user count? What SLA do you owe customers? Where does the data live? It's surprising how rarely all three get an immediate answer.
The Claude API code itself fits in a few dozen lines. But the design decisions around it — the ones you should make before writing more code — will save you weeks of restructuring later. Here's the three-axis framework I keep returning to as an indie developer, across my own projects and client work for deriving infrastructure requirements.
Step 1: Estimate traffic in three tiers
The first numbers to nail down are peak QPS (queries per second) and monthly token volume. Without these, your organizational rate limits, model choice, and caching strategy all drift.
I like a coarse three-tier breakdown:
- Tier S (~1,000 MAU, ~10 RPM): Personal MVPs and internal tools. Usually fits within the default Tier 1 limits — you can start without special accommodations
- Tier M (1,000–50,000 MAU, ~100 RPM): Public-facing services in early growth. You'll need a Tier 2 upgrade request, prompt caching, and async background jobs
- Tier L (50,000+ MAU, 1,000+ RPM): A single-region, single-model setup no longer holds. Plan for multi-cloud (Bedrock or Vertex AI) and model fallback chains from the start
A rough peak-QPS formula: (expected DAU × requests/user/day) ÷ active seconds per day (I use 36,000 = 10 hours) × peak factor 3. For 10,000 DAU at 3 requests each, that's about 2.5 peak QPS. Convert to monthly volume and you have the input for token cost estimates.
For the deeper sizing math, Claude API Monthly Cost Calculation and Prompt Caching Design Guide covers the model-by-model breakdown.
Step 2: Reverse-engineer availability from your SLA
The gap between 99.5% and 99.95% translates to about 4 hours vs. 22 minutes of monthly downtime. That gap is also the design boundary between "single provider works" and "fallback is mandatory."
My field heuristic:
- 99.5% (≤4 hrs/month): Direct Anthropic API calls + retries + reasonable timeouts is enough. Internal tools and free services typically design to this line
- 99.9% (≤43 min/month): Add prompt caching, response caching, and one fallback path through the same model on a different region (e.g., Bedrock)
- 99.95%+ (≤22 min/month): You need a hierarchical fallback — primary model (Sonnet 5.5) → secondary (Haiku 5.5) → optionally a local inference fallback (Gemma family). Plan to automate SLO reports against this number
One subtle point: distinguish between "Anthropic's API SLA" and "your service's SLA." Unless you've signed Anthropic's Production SLA explicitly, your numbers will always be lower than Claude's. Whatever you promise users should be tracked internally as an SLO, not blindly inherited.
The implementation patterns are detailed in Claude API Multi-Model Fallback for High Availability.
Step 3: Make the data-residency and compliance call
This is where B2B SaaS deployments stumble most often. When a client once said "the data must stay in our country," I answered on the spot: the Anthropic API is US-centric, so direct calls are off the table. Rechecking the official docs for this update, I found that answer had gone stale.
The API now has an inference_geo parameter that can restrict inference to the US (for Claude 4.6 and later models, at 1.1× on every token category, cache included). But the choices are "US only" or "global" (the default) — not a specific country such as Japan. So the line I draw hasn't moved: if the contract says "in our country," this parameter alone doesn't satisfy it.
Three decision points to lock in:
- Will personal data hit Claude? If yes, design PII masking into the request path. Phone numbers, addresses, and card details should be tokenized before they ever reach the API
- Is this a regulated industry (finance, healthcare, government)? If so, evaluate Bedrock (to keep traffic inside AWS) or Vertex AI (to inherit Google Cloud's residency settings) from day one. Note that regional and multi-region endpoints on both carry roughly a 10% premium over global ones
- Customer data and training: The Claude API doesn't use customer data for training by default, but enterprise clients often ask you to put that in writing. The Enterprise plan includes a DPA (Data Processing Agreement) you can sign
Before sending, even regex-level tokenization goes a long way. The first layer I add is a thin reversible mask that swaps phone numbers, email addresses, and 16-digit sequences for placeholder tokens, then restores them after the response returns.
const PATTERNS: [RegExp, string][] = [
[/\b\d{3}-\d{4}-\d{4}\b/g, "PHONE"], // phone numbers
[/\b[\w.+-]+@[\w-]+\.[\w.-]+\b/g, "EMAIL"], // email addresses
[/\b\d{16}\b/g, "CARD"], // card numbers
];
function mask(text: string) {
const restore = new Map<string, string>();
let i = 0;
for (const [re, label] of PATTERNS) {
text = text.replace(re, (m) => {
const token = `[[${label}_${i++}]]`;
restore.set(token, m);
return token;
});
}
return { masked: text, restore };
}
function unmask(text: string, restore: Map<string, string>) {
for (const [token, original] of restore) text = text.split(token).join(original);
return text;
}Regex alone won't catch free-form fields like names or addresses; a "human reviews it before it reaches Claude" step is the realistic complement. For the reversible-masking preprocessing design, see Masking Personal Data Before Sending Reviews and Crash Logs to the Claude API.
A minimum viable cost formula
When someone asks "How much will this cost?", I start with this formula. This section also contains one of my own mistakes:
// Estimated monthly cost in USD
function estimateMonthlyCost({
dau, // expected daily active users
requestsPerUser, // requests per user per day
inputTokensAvg, // average input tokens per request
outputTokensAvg, // average output tokens per request
inputPricePerMTok, // input $/M tokens(Sonnet 5.5 = 2.0)
outputPricePerMTok, // output $/M tokens(Sonnet 5.5 = 10.0)
cacheHitRate = 0.5, // prompt cache hit rate after rollout
cacheReadMult = 0.05, // cache read multiplier (0.05 for Sonnet 5.5 / Opus 5.5, 0.1 for others)
cacheWriteMult = 1.25, // 5-minute cache write multiplier
geoMult = 1.0, // 1.1 when you pin inference to the US
safetyFactor = 1.3 // buffer for retries and bursts
}) {
const monthlyRequests = dau * requestsPerUser * 30;
const inMTok = (monthlyRequests * inputTokensAvg) / 1_000_000;
const outMTok = (monthlyRequests * outputTokensAvg) / 1_000_000;
// hits pay the read multiplier, misses pay the write multiplier
const inputMult = cacheHitRate * cacheReadMult + (1 - cacheHitRate) * cacheWriteMult;
const base = inMTok * inputPricePerMTok * inputMult + outMTok * outputPricePerMTok;
return Math.ceil(base * geoMult * safetyFactor);
}
const common = {
dau: 5000, requestsPerUser: 3,
inputTokensAvg: 2000, outputTokensAvg: 500,
inputPricePerMTok: 2.0, outputPricePerMTok: 10.0,
};
console.log(estimateMonthlyCost(common)); // 4446
console.log(estimateMonthlyCost({ ...common, geoMult: 1.1 })); // 4891
console.log(estimateMonthlyCost({ ...common, cacheHitRate: 0, cacheWriteMult: 1.0 })); // 5265An earlier version of this article said the example returns "roughly $3,800/month." When I finally ran it for this update, even with the prices of that time (Sonnet 4.6 at $3/$15) it printed about $6,318 — the number simply didn't match. I had written the code and the explanation separately and never executed them together. I've now updated the prices and cache multipliers to current values and added the cost of cache misses (writes) to the formula.
Here is the actual output from Node.js 22 (5,000 DAU, 3 requests each, 2,000 input / 500 output tokens; Sonnet 5.5 prices checked on the official pricing page on October 15, 2026):
| Scenario | Monthly estimate (incl. 1.3 buffer) |
|---|---|
| No caching | about $5,265 |
| 50% cache hit rate | about $4,446 |
| 50% hit rate + US-only inference (1.1×) | about $4,891 |
Prices get revised, so I check the date on the pricing page before trusting any number in code.
Run every estimate before it goes into writing. A hand-calculated answer is exactly the kind I now stop and run once.
The 1.3 safety factor is still non-negotiable. In practice, retries, internal testing, employee dogfooding, and unexpected bursts inflate the bill 1.2–1.5× over the naive estimate. Budgeting at +30% from day one keeps end-of-month invoices boring.
Fold rate limits (429) into the design decision
Even after nailing the three axes, rate limits pull you back to reality. Tier classification describes steady state; the moment batch jobs and scheduled runs overlap, multiple workers start eating into the same organization-wide RPM. I've run nightly jobs for several projects under one organization account and watched a burst from one project drag another into 429s more than once.
The skeleton of the fix is simple: always add jitter to your exponential backoff, and honor the server's Retry-After above everything else. Without jitter, several workers rejected at the same instant retry on the same interval, get rejected together on the next wave, and repeat.
async function callWithBackoff(fn, { maxRetries = 5, baseMs = 500 } = {}) {
for (let attempt = 0; ; attempt++) {
try {
return await fn();
} catch (err) {
const status = err?.status ?? err?.response?.status;
if (status !== 429 && status !== 503) throw err; // retry only transient overload
if (attempt >= maxRetries) throw err;
// Server instruction (Retry-After: seconds) takes priority
const retryAfter = Number(err?.headers?.["retry-after"]);
const serverWait = Number.isFinite(retryAfter) ? retryAfter * 1000 : 0;
// Exponential backoff + full jitter
const backoff = Math.min(baseMs * 2 ** attempt, 20_000);
const jittered = Math.random() * backoff;
await new Promise((r) => setTimeout(r, Math.max(serverWait, jittered)));
}
}
}Retrying only 429 and 503 is deliberate. Retrying 4xx validation errors just re-throws broken requests forever and burns quota for nothing. On top of that, watching response headers like anthropic-ratelimit-requests-remaining and throttling yourself before the budget runs out — preemptive pacing — visibly cuts how often you hit 429 at all.
If you want to go a layer deeper on observation and retry design, Rewriting Production Retries After Observing Claude API 429s picks up the thread.
Pre-launch checklist
After working through the three axes, sort each item below into "decided" or "open." Even one open item is reason enough to delay launch by a week.
- [ ] Peak QPS and monthly token estimates exist on paper
- [ ] Target SLA is defined and a fallback path matches it
- [ ] PII handling policy (masking, what's OK to send) is documented
- [ ] Monthly cost estimate exists with an alert threshold for overruns
- [ ] User-facing fallback UI is designed for rate-limit / outage conditions
- [ ] On-call notification path reaches a human during incidents
- [ ] Data residency requirements (in the contract) match the implementation
I strongly recommend taking these seven items into a one-hour review meeting with everyone involved. That alignment session prevents more production incidents than any amount of clever code.
What to do today
Before closing this tab, run the peak-QPS estimate for your project. It takes five minutes with a calculator. Once you have that number, the tier classification, model choice, and SLA conversation all snap into focus.
If you want to follow up with implementation details, Claude API Production Launch Infrastructure Checklist walks through eight concrete patterns for turning "code that works" into "code that holds in production." Read it together with this one and you'll have both the design decisions and the implementation patterns covered.