Your First Production API Call (Cost-Aware)
- Name the four disciplines that separate a production call from a toy one-liner: secrets, streaming, cost, and error handling
- Write a resilient streamed call that retries transient failures (429/5xx) with backoff — and never retries a 400
- Keep the model ID in config so switching models is a one-line change, not a search-and-replace
- Watch token cost on every call and cap it deliberately
A toy API call is one line. A production call handles errors, streams output, watches cost, and keeps secrets safe. Let's build that, step by step.
Step 1 — Secrets & model from config
export ANTHROPIC_API_KEY="sk-ant-..." # never in source control
Keep the model ID in config, not scattered literals, so migration is trivial (why). Pick it deliberately — Choosing a Model.
Step 2 — A resilient, streamed call
- Python
- TypeScript
import os, time, random, anthropic
client = anthropic.Anthropic()
MODEL = os.environ.get("CLAUDE_MODEL", "claude-sonnet-5")
def ask_stream(prompt, system=None, max_tokens=1024):
for attempt in range(5):
try:
with client.messages.stream(
model=MODEL, max_tokens=max_tokens,
system=system or anthropic.NOT_GIVEN,
messages=[{"role": "user", "content": prompt}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
final = stream.get_final_message()
print()
usage = final.usage
print(f"\n[tokens in/out: {usage.input_tokens}/{usage.output_tokens}]")
return final
except (anthropic.RateLimitError, anthropic.APIStatusError):
if attempt == 4: raise
time.sleep(min(2 ** attempt + random.random(), 30))
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const MODEL = process.env.CLAUDE_MODEL ?? "claude-sonnet-5";
export async function askStream(prompt: string, system?: string, maxTokens = 1024) {
for (let attempt = 0; attempt < 5; attempt++) {
try {
const stream = client.messages.stream({ model: MODEL, max_tokens: maxTokens, system,
messages: [{ role: "user", content: prompt }] });
for await (const e of stream)
if (e.type === "content_block_delta") process.stdout.write(e.delta.text ?? "");
const final = await stream.finalMessage();
console.error(`\n[tokens in/out: ${final.usage.input_tokens}/${final.usage.output_tokens}]`);
return final;
} catch (e: any) {
if (attempt === 4 || ![429, 500, 529].includes(e?.status)) throw e;
await new Promise(r => setTimeout(r, Math.min(2 ** attempt * 1000, 30000)));
}
}
}
Smoke-test it before wiring it into anything — one small call proves the stream, the token line, and your key all work:
First smoke test (Python REPL)
ask_stream("Say hello in one sentence.", max_tokens=64)Step 3 — Mind the cost
- Log token usage (above) so you can see what each call costs.
- Right-size
max_tokensand the model; cap input with focused prompts. - For repeated stable prefixes, add prompt caching.
- See Tokens & Pricing and Cost & Latency.
Step 4 — Handle the unhappy paths
- Retry transient errors (429/5xx) with backoff (above); don't retry 400s.
- Handle refusals gracefully.
- Set a timeout and a cost/iteration budget for anything agentic.
Verify
Force each path and watch what happens — a production call earns the name by failing well, not just succeeding:
Guided walkthrough1 of 4
- Text prints incrementally as it's generated, not in one blocking chunk at the end. That's the latency win users feel.
- After the response you see the [tokens in/out: …] line. That's your per-call cost meter, logged on every call.
- Set ANTHROPIC_API_KEY to a wrong value and re-run — you should get a clean error, not a stack-trace crash.
- 429/5xx errors retry with backoff up to 5 attempts; a malformed 400 request should surface immediately instead of retrying pointlessly.
Check yourself
0/3- A production call is four disciplines layered on the one-liner: secrets kept out of source, streamed output, cost watched, errors handled
- Retry transient failures (429/5xx) with exponential backoff plus jitter — and never retry a 400, which is a request bug you must fix
- Keep the model ID in config so switching models is one line, not a codebase-wide search-and-replace
- Log token usage on every call: it's your per-request cost meter and costs nothing to add
- Streaming improves perceived latency; timeouts and a cost/iteration budget keep agentic loops from running away