OpenAI and Anthropic APIs
Chat Completions / Messages APIs, tools, structured outputs, streaming end-to-end — client contract, gateway patterns, retries, routing, token accounting, and failure modes.
What these APIs are (plain English)
Most product traffic still goes through hosted chat APIs: messages in, tokens out, optional tools and structured JSON. OpenAI and Anthropic are the two contracts you will see constantly in industry code.
Learn one deeply; the other is a mapping exercise (roles, tool schema shape, streaming events).
Analogy: these APIs are SQL drivers for intelligence — your app should talk to a gateway, not sprinkle vendor SDKs in every feature flag path.
| Concern | Practice |
|---|---|
| Auth | Server-side env keys; never browser secrets |
| Models | Pin IDs; log on every trace |
| Streaming | SSE for TTFT; don’t buffer in proxies |
| Tools | Strict schemas; execute server-side |
| Structured out | JSON schema / tool args; validate again |
| Limits | Respect 429 + Retry-After; queue UX |
Interview cue: Talk about gateway patterns, token accounting, and failure modes — not just “call chat.completions.”
The problem they solve
| Need | Hosted chat API |
|---|---|
| Strong general models | No GPU fleet on day one |
| Tool calling + JSON | First-class product features |
| Streaming UX | Token deltas for TTFT |
| Rapid iteration | Swap model ids without retrain |
Self-host (vLLM, Ollama) still often keeps the same client shape — OpenAI-compatible — so gateway skills transfer.
How they fit LLM apps
flowchart TB
FE[Frontend] --> GW[Your gateway]
GW --> Router{Router}
Router --> OAI[OpenAI]
Router --> ANT[Anthropic]
Router --> Local[vLLM / Ollama]
GW --> Tools[Tool runtime]
GW --> Obs[OTel + cost logs]
Your gateway owns: auth, tenant quotas, PII redaction, model routing, retries, and observability. Provider SDKs are drivers — not the product architecture.
Architecture: the client contract
sequenceDiagram
participant App
participant GW as Gateway
participant P as Provider
App->>GW: POST /chat (stream)
GW->>P: messages + tools
P-->>GW: token deltas / tool_calls
GW->>GW: maybe execute tools
GW-->>App: SSE chunks
Messages and roles
| Role | Typical use |
|---|---|
system / developer |
Policy, style, non-user instructions |
user |
End-user content |
assistant |
Prior model turns |
tool / tool_result |
Observations after tool calls |
Anthropic’s Messages API names differ slightly; map carefully and keep a provider-agnostic internal message type.
Tools and structured outputs
// Shape — one interface, many providers
type ChatRequest = {
model: string;
messages: Msg[];
tools?: ToolDef[];
responseFormat?: "text" | "json_schema";
stream?: boolean;
};Validate model JSON again on your side (zod/pydantic). Providers reduce errors; they do not eliminate them. See Structured outputs.
Mapping OpenAI ↔ Anthropic (mental cheat sheet)
| Concept | OpenAI-ish | Anthropic-ish |
|---|---|---|
| Chat call | Chat Completions / Responses | Messages |
| System | system message / instructions |
system parameter |
| Tools | tools + tool_calls |
tools + tool_use blocks |
| Tool result | role: tool |
tool_result content blocks |
| Streaming | SSE token events | SSE content-block events |
Keep one internal Msg / ToolCall type; adapt at the edge.
How to use (resilient client sketch)
// Pseudocode — timeouts, idempotency, token logging
const res = await provider.chat({
model: "gpt-4.1-mini", // pin; don’t use floating aliases in prod without intent
messages,
stream: true,
headers: { "Idempotency-Key": idempKey },
signal: AbortSignal.timeout(45_000),
});
// log: model, input_tokens, output_tokens, ttft_ms, request_idStreaming
- Prefer SSE for chat UIs
- Separate TTFT timeout vs idle/total deadlines (see Networking for AI apps)
- Disable proxy buffering on LLM paths
- Heartbeat / comment frames if proxies idle-timeout
sequenceDiagram
participant UI
participant GW
participant P as Provider
UI->>GW: POST stream
GW->>P: stream=true
P-->>GW: first token
Note over GW: TTFT clock stops
GW-->>UI: SSE delta
P-->>GW: more tokens
GW-->>UI: SSE delta
P-->>GW: done + usage
GW-->>UI: final + metrics
Retries and idempotency
| Error | Retry? | Notes |
|---|---|---|
| 408 / 429 / 5xx | Often yes | Honor Retry-After; jitter |
| 400 schema | No | Fix request |
| Mid-stream disconnect | Careful | May have partial side effects if tools already ran |
| Tool side effects | Only with idempotency keys | Otherwise double-charge |
Ship rule: never retry a mutating tool blindly after a timeout — you may have succeeded server-side.
Gateway responsibilities (checklist)
- AuthN/AuthZ + tenant quotas
- Model allowlist + pin
- PII redaction before logs
- Timeout budgets (TTFT vs total)
- Token + cost accounting
- Tool policy + HITL hooks
- Provider failover / routing
Cost/latency routing deep dive: Cost, latency, routing. Observability: OpenTelemetry for LLMs.
Walkthrough: tool loop behind a gateway
- UI streams a user goal to your gateway.
- Gateway calls provider with tools enabled.
- Model returns
tool_calls→ gateway validates + executes. - Observations appended; second model call may finalize.
- Trace stores provider
request_id, tokens, tool latencies.
Same loop as Agents and ReAct — providers only supply the model step.
Alternatives
| Option | When |
|---|---|
| OpenAI-compatible self-host (vLLM, Ollama) | Cost/control; same client shape |
| Vertex / Bedrock / Azure OpenAI | Enterprise cloud alignment |
| Multi-provider router | Quality/latency/cost hedging |
| Batch / async APIs | Offline evals and backfills |
Production gotchas
- Floating model aliases change behavior under you — pin + changelog
- Double billing on retries without idempotency
- Tool loops without max steps / budgets
- Logging prompts that contain secrets or regulated PII
- SDK version skew across services → different defaults
- Assuming JSON mode = valid business objects — still validate
- Proxy buffering — users see multi-second blank TTFT
- Cross-provider prompt reuse — system prompts tuned to one model fail on another
- Ignoring usage fields — finance cannot attribute cost
flowchart TD
Incident[Prod LLM incident] --> Q{Likely cause}
Q -->|Blank spinner| Buf[Proxy buffering / TTFT]
Q -->|Wrong answers after deploy| Pin[Model alias moved]
Q -->|Duplicate refunds| Idem[Retry without idempotency]
Q -->|Cost spike| Loop[Unbounded tools / retries]
Failure modes checklist
- Browser holds the API key
latestmodel tag in prod- One global 60s timeout; TTFT never measured
- Tools executed in the frontend
- Provider error string shown raw to end users
Hands-on next steps
- Wrap OpenAI + Anthropic behind one interface.
- Add TTFT + total timeouts, retry policy, token logs.
- Stream to a UI; verify nginx won’t buffer.
- Guided Talk to models in the real world + Structured outputs.
Micro-project
Implement ChatClient with:
openaiandanthropicadapters- Pin model ids via env
- Stream SSE to a tiny HTML page
- Log
ttft_ms,input_tokens,output_tokens,request_id - Inject a deliberate 429 and prove backoff +
Retry-After
Interview whiteboard: the gateway
Five boxes in order:
- Client UI
- Your gateway (auth, quotas, redaction, routing)
- Provider adapters
- Tool runtime
- Observability (tokens, TTFT, request ids)
If someone draws the browser talking to OpenAI directly, correct them — that is a security answer, not a product answer.
Token accounting that finance accepts
Log per request:
provider,model,input_tokens,output_tokensttft_ms,total_ms,request_idtenant_id,feature,prompt_version
Roll up daily. Catch tool-loop cost spikes with per-run budgets. See Cost, latency, routing.
Structured outputs in the real world
Providers offer JSON mode / schema constraints. Still:
- Validate with zod/pydantic
- Retry once on schema failure with repair prompt — bounded
- Fall back to safe error for users
- Never execute tool args that failed schema
Deep dive: Structured outputs.
Multi-provider routing heuristics
| Signal | Action |
|---|---|
| Provider 5xx / regional outage | Fail over to backup model |
| Latency SLO miss | Route “easy” traffic to smaller/faster model |
| Cost spike | Cap max tokens; prefer mini models for classify |
| Quality regression | Pin previous model; shadow eval new |
Routers without evals are just randomizers.
Tradeoffs summary
| Hosted APIs when… | Self-host when… |
|---|---|
| Quality + speed to market | Cost at scale / data residency |
| Soft peak traffic | Steady high QPS GPUs justified |
| Need frontier models | Open weights meet the bar |
Checklist
- Keys only on server
- Models pinned
- TTFT and total timeouts separated
- Idempotency on mutating paths
- JSON validated client-side
- Proxy buffering disabled on stream routes
- Usage logged with tenant + feature
Glossary
| Term | Meaning |
|---|---|
| TTFT | Time to first token |
| SSE | Server-Sent Events streaming |
| Idempotency key | Client key so retries don’t double-apply |
| Tool call | Model-requested function invocation |
| Gateway | Your server edge in front of providers |
| Pin | Exact model/version id in config |
Debugging playbook (first hour)
| Symptom | First checks | Fix direction |
|---|---|---|
| Blank spinner 3–8s | Proxy buffering? TTFT never measured? | Disable buffer; separate TTFT budget |
| Duplicate charges / emails | Timeout + retry after tool ran? | Idempotency keys; don’t blind-retry tools |
| Schema parse errors | Provider JSON vs your zod? | Validate; one repair retry max |
| Cost spike overnight | Tool loop / max_tokens / alias change? | Budgets; pin model; cap steps |
| Quality cliff after “no deploy” | Floating latest alias? |
Pin ids; changelog watch |
| 429 storms | Shared key / no backoff? | Queues; Retry-After; per-tenant quotas |
Anti-patterns
- Browser holds the API key — always server-side.
- SDK calls from every feature flag path — use one gateway.
- One 60s timeout for everything — TTFT and total differ.
- Trust JSON mode alone — validate business objects.
- Retry POSTs that already executed tools — double side effects.
- Show raw provider errors to users — map to safe UX.
- Copy-paste system prompts across providers — retune per model.
Security and privacy threat note
- Keys in client bundles or mobile apps = compromise.
- Prompt logs often contain PII — redact before OTel/W&B.
- Tool args are untrusted input — schema + allowlist + HITL for money/PII.
- SSRF via tools that fetch URLs — restrict egress.
- Multi-tenant: never reuse conversation state across tenants.
Interview prompts you should be able to answer
- Draw the gateway between UI and providers — what does it own?
- How do OpenAI tool_calls map to Anthropic tool_use conceptually?
- When is a retry safe after a streaming timeout?
- What do you log so finance can attribute LLM spend?
- Why pin model ids instead of
gpt-4o-latest-style aliases?
End-to-end: one product request
sequenceDiagram
participant UI
participant GW as Gateway
participant P as Provider
participant T as Tool runtime
participant Obs as OTel / cost
UI->>GW: stream chat + tenant
GW->>GW: auth, quota, redact, pin model
GW->>P: messages + tools
P-->>GW: tool_calls
GW->>T: validate + execute (idempotent)
T-->>GW: observation
GW->>P: continue
P-->>GW: tokens + usage
GW->>Obs: ttft, tokens, request_id, run ids
GW-->>UI: SSE + final metrics
Production readiness checklist
- Keys only on server; rotate documented
- Model allowlist + pins
- TTFT vs total vs idle timeouts
- Idempotency on mutating tools
- JSON schema validated client-side
- Proxy buffering disabled on stream routes
- Usage logged: tenant, feature, model, tokens, request_id
- Max tool steps + $ budget per run
- Provider failover tested with chaos 5xx
How it works end-to-end (request lifecycle)
- Client builds messages (+ tools / response format).
- Gateway authN/authZ, injects server-side API keys.
- Router picks provider/model (cost, latency, capability).
- Outbound call with timeouts; stream or JSON.
- On tool_call: validate → execute → append observation → continue.
- Validate structured output; meter tokens; emit traces.
sequenceDiagram
participant App
participant GW as Gateway
participant P as Provider
participant Tools
App->>GW: chat request
GW->>P: messages + tools
P-->>GW: tool_call or text
alt tool_call
GW->>Tools: execute
Tools-->>GW: result
GW->>P: continue
end
GW-->>App: stream/final + usage
OpenAI vs Anthropic mapping (practical)
| Concept | OpenAI-ish | Anthropic-ish |
|---|---|---|
| Chat API | Chat Completions / Responses | Messages |
| System | system role / instructions |
system param |
| Tools | tools + tool_calls |
tools + tool_use blocks |
| Structured | JSON schema / structured outputs | tool args or constrained decode |
| Streaming | SSE token events | SSE content blocks |
| Multimodal | image parts in messages | image content blocks |
Learn the shape, not every SDK method name. Pin SDK versions.
Retries, idempotency, and partial streams
| Case | Practice |
|---|---|
| 429 / 5xx | Exponential backoff + jitter; honor Retry-After |
| Timeouts | Distinct connect vs total; cancel upstream |
| Streaming mid-fail | Client must handle truncated; don’t double-bill UX blindly |
| Tool side effects | Idempotency keys before retrying mutate tools |
| Exactly-once chat POST | Idempotency key at gateway |
Never retry non-idempotent tool executions without dedupe.
Prompt caching and cost controls
Providers offer prompt caching / prefix benefits for large stable system prompts. Practical rules:
- Keep stable prefixes identical byte-for-byte when possible
- Put volatile user content at the end
- Log cache hit rates when the API exposes them
- Still validate outputs — cache ≠ correctness
Pair with Redis for AI caching for response caches at your edge.
Safety and data handling
| Risk | Mitigation |
|---|---|
| PII to provider | Redact/tokenize; DPA review |
| Prompt injection | Tool allowlists; don’t treat model text as trusted |
| Key leak | Server-only keys; rotate; no mobile embeds |
| Training on your data | Check org settings / zero-retention options |
See Privacy and data for AI and Guardrails.
Observability fields (minimum)
provider, model, request_id, ttft_ms, total_ms, prompt_tokens, completion_tokens, cached_tokens (if any), finish_reason, tool_names, route_reason, tenant_id.
Wire to OpenTelemetry for LLMs.
Worked walkthrough: dual-provider support bot
ChatClientinterface with OpenAI + Anthropic drivers.- Router: default cheap model; escalate on low confidence / hard intents.
- Tools:
search_docs,create_ticketwith HITL on create. - Structured JSON for ticket fields; re-validate in app.
- Chaos: force 5xx on primary → failover secondary.
- Dashboard: cost/day, TTFT p95, tool error rate.
FAQ (hosted APIs)
Should every service call OpenAI directly?
No — one gateway.
OpenAI-compatible local models?
Yes for Ollama/vLLM — still keep the interface.
Is Anthropic “better”?
Task-dependent. Route with evals, not Twitter.
Deep dive: message roles and tool loops
Roles exist so models distinguish policy, user intent, and tool observations. Stuffing tool JSON into user messages without role discipline causes brittle agents. Keep tool results as tool role / blocks; truncate aggressively.
Putting what / why / how together
| Lens | Hosted APIs |
|---|---|
| What | Messages in → tokens out (+ tools/JSON/stream) |
| Why | Strong models without owning GPUs day one |
| How | Gateway + pin models + retries + meter + validate |
Architecture that survives production
flowchart TB
Clients[Web / workers / agents] --> GW[API gateway]
GW --> Limiter[Rate limit + quotas]
Limiter --> Router[Model router]
Router --> OAI[OpenAI]
Router --> ANT[Anthropic]
Router --> Local[vLLM OpenAI-compatible]
GW --> Cache[Optional Redis exact cache]
GW --> Obs[Traces + usage warehouse]
Gateway owns: auth, quotas, routing, redaction, retries, tool execution sandbox, and billing meters. Feature code calls ChatClient.complete(...) only.
Streaming UX contracts
- Flush first token quickly (TTFT)
- Heartbeats so proxies don’t kill idle SSE
- Final usage message after stream
- Client abort cancels upstream when possible
Buffering the entire completion in nginx “for convenience” destroys the product feel.
Tool-calling contract (provider-agnostic)
- Declare tools with tight JSON Schema.
- Model returns tool name + args.
- Gateway validates args again.
- AuthZ + timeout + byte cap.
- Return observation; continue until stop or step limit.
Anthropic tool_use vs OpenAI tool_calls differ in wire format — normalize inside the gateway so agents see one shape.
Quotas and unfair traffic
| Layer | Mechanism |
|---|---|
| Per API key | Provider limits |
| Per tenant | Your Redis counters |
| Per user | Abuse prevention |
| Global | Shed load with 503 + Retry-After |
When shedding, prefer degrading model class before hard failing interactive chat — if product allows.
Interview whiteboard
Boxes: Client → Gateway → Router → Providers; side boxes Tools, Cache, Metering. Red line: keys never leave gateway.
Failure story bank
- Browser key → scraped → $10k bill.
- Infinite tool loop without step cap.
- Retry without idempotency → double refund tool.
- Router sticky on outage → 100% errors instead of failover.
End-to-end lab checklist (do this once)
- Dual drivers behind one interface
- Stream TTFT logged
- 429 backoff honored
- Tool validate + deny path
- Usage rows exportable for a fake invoice
Production readiness checklist (gateway)
- Keys only on server; rotation tested
- Model ids pinned; logged on every trace
- Timeouts, retries, Retry-After honored
- Tool schema validation + authZ + step caps
- Structured outputs re-validated in app
- Token + $ meters per tenant
- Streaming path not buffered away
- Provider failover / degrade tested
- Redaction policy for logs and W&B exports
- Load shed behavior documented
What “good” looks like in a design doc
Gateway diagram, provider list, routing policy, tool allowlists, idempotency, quota model, privacy mode (retention), and SLOs (TTFT/error/$). SDK snippets alone are insufficient.
Common interview traps
- Putting API keys in mobile apps
- “We’ll just call the SDK in the React app”
- No answer for 429 storms
- Treating streamed partial JSON as final structured output
Multimodal and audio notes
Image/audio parts change token accounting and PII risk (faces, documents). Pin modalities per route; reject unexpected MIME types at the gateway. Eval multimodal separately — text golden sets won’t catch OCR failures.
Glossary addendum
| Term | Meaning |
|---|---|
| TTFT | Time to first token |
| Tool loop | Model ↔ tool observations until stop |
| Router | Chooses provider/model per request |
| Idempotency key | Dedupes retries on side-effecting calls |
| Prompt cache | Provider-side reuse of stable prefixes |
Micro-project stretch
Add a forced-failover chaos switch and a Redis exact-match cache in front of the gateway; prove hit/miss metrics and that cache keys include model + tool schema hash.
How to evaluate gateway quality (not vibes)
| Signal | Target instinct |
|---|---|
| TTFT p95 | Product UX budget |
| Error rate by provider | Failover health |
| Tool validation fail rate | Schema quality |
| $/successful task | Routing efficiency |
| Cache hit rate | Exact-cache payoff |
| Structured output parse fail | Schema + model mismatch |
Build a synthetic canary every minute: tiny chat + one tool dry-run. Canaries catch DNS/key/quota issues before users do.
Provider outage playbook
- Detect via canary + provider status
- Router shifts traffic; announce degrade if quality drops
- Disable non-critical batch jobs to save quota
- Incident doc: request_ids, route_reason, error bodies (redacted)
Deep dive: Anthropic extended thinking vs OpenAI reasoning models
Reasoning/thinking modes change latency and cost dramatically. Treat them as different model classes in the router: only escalate intents that need them; never make them the default for “hi.” Log thinking-token usage separately when exposed.
Comparing with open-weight serving
Keep the same gateway. Swap router target to vLLM OpenAI-compatible for sensitive tenants. Evals must compare task quality at equal latency budgets — not Elo from social media.
Putting what / why / how together
| Lens | Answer |
|---|---|
| What | Hosted chat + tools + stream + JSON |
| Why | Ship intelligence without GPUs first |
| How | Gateway, pin, meter, validate, failover |
Related
Core: Structured outputs, Serving and streaming, Networking for AI apps. Guided Talk to models in the real world. Advanced Key Tech: OpenTelemetry for LLMs. Key tech: Ollama. Inference: Cost, latency, routing.