Software Engineering Foundations for Claude Integration
Learning Objectives
Explain how Claude integration rides on stateless REST over HTTP, and identify the endpoint, JSON payload shape, and authentication/versioning headers.
Apply async/await and concurrency to keep a Claude app responsive, and distinguish streaming via Server-Sent Events (SSE) from websockets.
Describe how the Python and TypeScript client SDKs wrap the raw REST call, auto-retry with exponential backoff, and raise typed exceptions — including the 429-vs-529 distinction.
Integrate a Claude feature into the SDLC: version prompts as code, protect secrets, review for LLM-specific concerns, and refactor behind a thin typed layer.
Pre-Reading Check — REST APIs and JSON
1. Which HTTP method and endpoint does an application use to send a message to Claude?
GET https://api.anthropic.com/v1/messagesPOST https://api.anthropic.com/v1/messagesPUT https://api.anthropic.com/v1/completionsPOST https://api.anthropic.com/v1/chat
2. Because the Messages API is stateless, what must your application do on every conversation turn?
Resend the entire conversation history in the requestReuse a session ID the server stored on the previous turnOpen a persistent connection that the server keeps aliveSend only the newest user message and let Claude recall the rest
3. Where should the ANTHROPIC_API_KEY live, and which header carries it?
Hardcoded in source; sent in the anthropic-version headerIn the request body as a model fieldIn an environment variable; sent in the x-api-key headerIn a cookie; sent in the content-type header
1. REST APIs and JSON
Key Points
A REST API exposes resources by URL and acts on them with a small, fixed set of HTTP verbs (GET, POST, PUT/PATCH, DELETE).
The core Claude endpoint is POST https://api.anthropic.com/v1/messages; you POST because you are submitting new work (a conversation turn).
REST is stateless: each request carries everything the server needs, so you resend the full messages history every turn.
Request and response bodies are both JSON; your job is serialization (out) and deserialization (in).
Authentication and versioning ride in headers: x-api-key (secret key), anthropic-version, and content-type.
Every request to Claude travels over a REST API using standard HTTP methods and URLs, exchanging data as JSON. REST treats everything the server exposes as a resource addressed by a URL, acted on with a handful of universal verbs.
HTTP method
Intent
Example in a Claude app
GET
Retrieve a resource
Fetch the status of a message batch
POST
Create a resource / submit work
Send a message to Claude
PUT / PATCH
Replace / update a resource
Update a stored configuration
DELETE
Remove a resource
Cancel a batch job
A minimal JSON request is a top-level object with typed fields: model selects the Claude model, max_tokens caps the response length (and cost), and messages is an ordered array of turns, each with a role (user or assistant) and content. The response is likewise JSON — a content array plus metadata such as token usage and a stop reason.
Because REST inherits HTTP's stateless property, each request must carry everything the server needs — which is why you resend the entire conversation history on every turn rather than relying on the server to remember it.
Three headers matter for a basic call:
Header
Purpose
x-api-key
Your secret API key, which authenticates and authorizes the request
anthropic-version
The API version (e.g., 2023-06-01), so Anthropic can evolve the API without breaking you
content-type
Declares the body format, application/json
The API key is a bearer credential: anyone who holds it can spend from your account. It belongs in an environment variable (ANTHROPIC_API_KEY), never hardcoded and never committed to version control.
Figure 6.1: Stateless request/response flow for a Messages API call
sequenceDiagram
participant App as Your App
participant API as api.anthropic.com/v1/messages
App->>API: POST /v1/messages
Note right of App: Headers: x-api-key, anthropic-version, content-type Body: full messages array (JSON)
API-->>App: 200 OK
Note left of API: JSON: content array, token usage, stop_reason
Note over App,API: Stateless: every turn resends the entire conversation history
Animation slot: an animated request/response arrow between "Your App" and "/v1/messages," showing headers and the JSON payload flowing each direction, then the full history growing on each new turn.
Post-Reading Check — REST APIs and JSON
1. Which HTTP method and endpoint does an application use to send a message to Claude?
GET https://api.anthropic.com/v1/messagesPOST https://api.anthropic.com/v1/messagesPUT https://api.anthropic.com/v1/completionsPOST https://api.anthropic.com/v1/chat
2. Because the Messages API is stateless, what must your application do on every conversation turn?
Resend the entire conversation history in the requestReuse a session ID the server stored on the previous turnOpen a persistent connection that the server keeps aliveSend only the newest user message and let Claude recall the rest
3. Where should the ANTHROPIC_API_KEY live, and which header carries it?
Hardcoded in source; sent in the anthropic-version headerIn the request body as a model fieldIn an environment variable; sent in the x-api-key headerIn a cookie; sent in the content-type header
Pre-Reading Check — Asynchronous Programming
1. What is the primary benefit of using async/await for Claude API calls?
It reduces the number of tokens each request consumesIt lets the program do other work while a request is in flight, instead of blockingIt guarantees requests never hit rate limitsIt encrypts the request body automatically
2. Claude streams responses using which mechanism?
Websockets, a bidirectional persistent connectionLong polling with repeated GET requestsServer-Sent Events (SSE), a unidirectional server-to-client pushgRPC bidirectional streaming
3. When firing many concurrent Claude requests, what guidance avoids tripping rate limits?
Launch every request simultaneously in one large burstRamp traffic up gradually and keep usage patterns consistentSend all requests over a single websocket to bypass limitsDisable retries so failed requests do not repeat
4. With SSE streaming, why must code watch for error events rather than only the initial HTTP status?
SSE never returns an HTTP status code at allThe status is 200 the moment the stream opens, so errors can occur mid-stream after that successSSE reports all errors as websocket close framesHTTP status codes are stripped by SSE for security reasons
2. Asynchronous Programming
Key Points
Asynchronous programming starts an operation and continues doing other work while it completes, keeping the app responsive during network waits.
async/await writes non-blocking code that reads sequentially; Python offers AsyncAnthropic, and TypeScript calls are promise-based.
Concurrency (e.g., Python's asyncio.gather) overlaps many requests in flight, but you must ramp up gradually to respect rate limits, or risk 429 errors.
Claude streaming uses Server-Sent Events (SSE), not websockets — a one-way server-to-client push over one long-lived HTTP response.
Because SSE returns 200 when the stream opens, mid-stream errors arrive as SSE error events, not as HTTP status codes.
A call to Claude is a network round-trip that can take seconds. If your program simply blocks and waits, it wastes time it could spend serving other users or issuing more requests. async/await lets you write non-blocking code that reads like ordinary sequential code: you mark a function async and await where you need the result, freeing the runtime to run other tasks meanwhile. Anthropic's Python SDK provides AsyncAnthropic (built on httpx); TypeScript calls are promise-based, so await is all you need.
The real payoff is concurrency: firing off many requests that overlap in flight rather than running strictly one after another. Since most of each request's duration is spent waiting on the network, that wait overlaps for free — summarizing 20 documents concurrently (e.g., with Python's asyncio.gather) can be dramatically faster than looping sequentially. The catch: concurrency raises the risk of hitting rate limits, the per-minute caps Anthropic enforces. The guidance is to ramp traffic up gradually and keep usage patterns consistent; sudden bursts produce 429 errors and can trip acceleration limits. Effective concurrency means bounding how many requests are in flight, not launching everything at once.
Websockets vs. SSE — a nuance the exam tests
A websocket is a persistent, bidirectional connection where client and server can send messages at any time — well suited to live multiplayer or collaborative editors. It is tempting to assume Claude streaming uses websockets. It does not. Anthropic's SDKs stream via Server-Sent Events (SSE), a simpler, unidirectional mechanism where the server pushes a sequence of events over a single long-lived HTTP response. The client makes one request; the server streams incremental chunks back down that one connection — a one-way flow, not the two-way conversation a websocket is built for.
Feature
Websocket
Server-Sent Events (SSE) — used by Claude
Direction
Bidirectional (both send anytime)
Unidirectional (server → client)
Protocol
Its own ws:// / wss:// protocol
Plain HTTP response
Claude streaming
Not used
Yes — this is how Claude streams
One consequence of SSE: because the HTTP status is 200 the moment the stream opens, an error can occur after that success code. Mid-stream errors arrive as SSE error events and do not follow the standard status-code error mechanism, so streaming code must watch for error events within the stream, not just the initial response status.
Animation slot: side-by-side comparison — a websocket showing arrows flowing both directions, versus SSE showing a single one-way stream of tokens from server to client, with a mid-stream error event popping up after the initial 200.
Post-Reading Check — Asynchronous Programming
1. What is the primary benefit of using async/await for Claude API calls?
It reduces the number of tokens each request consumesIt lets the program do other work while a request is in flight, instead of blockingIt guarantees requests never hit rate limitsIt encrypts the request body automatically
2. Claude streams responses using which mechanism?
Websockets, a bidirectional persistent connectionLong polling with repeated GET requestsServer-Sent Events (SSE), a unidirectional server-to-client pushgRPC bidirectional streaming
3. When firing many concurrent Claude requests, what guidance avoids tripping rate limits?
Launch every request simultaneously in one large burstRamp traffic up gradually and keep usage patterns consistentSend all requests over a single websocket to bypass limitsDisable retries so failed requests do not repeat
4. With SSE streaming, why must code watch for error events rather than only the initial HTTP status?
SSE never returns an HTTP status code at allThe status is 200 the moment the stream opens, so errors can occur mid-stream after that successSSE reports all errors as websocket close framesHTTP status codes are stripped by SSE for security reasons
Pre-Reading Check — SDKs That Wrap REST
1. What does a single client.messages.create(...) call do under the hood?
Opens a websocket and waits for a bidirectional handshakeSerializes params to JSON, adds required headers, POSTs to /v1/messages, and deserializes a typed MessageStores the conversation on Anthropic's servers so history need not be resentSends the API key in the request body instead of a header
2. By default, how do the SDKs handle transient failures like connection errors, 429s, and 5xx?
They fail immediately and require you to write all retry logicThey retry forever until the request succeedsThey retry twice by default with exponential backoff, honoring the retry-after headerThey retry once immediately with no delay
3. What is the key difference between a 429 and a 529 error?
429 means your account hit its own limit; 529 means Anthropic's servers are overloaded for everyone429 is a server bug; 529 is a client bug429 is fatal and non-retryable; 529 is always retried automatically foreverThey are identical; the number is chosen at random
4. How should you catch errors raised by the SDK?
String-match on the error message textCatch typed exception classes, most-specific first (e.g., RateLimitError before APIStatusError)Parse the raw JSON error object manually every timeCatch only a single generic Exception and ignore the type
3. SDKs That Wrap REST
Key Points
A client SDK packages the REST calls behind idiomatic, typed functions; Anthropic ships official SDKs in seven languages plus the ant CLI.
The Python (anthropic) SDK ships both Anthropic and AsyncAnthropic (on httpx, typed via Pydantic); TypeScript (@anthropic-ai/sdk) reads the key from the environment and ships full type definitions.
One typed client.messages.create(...) call serializes params to JSON, adds headers, POSTs to /v1/messages, and deserializes a typed Message.
SDKs auto-retry transient failures (connection, 429, 5xx) with exponential backoff, twice by default, honoring the retry-after header; a max-retries option adjusts this.
429 = your per-minute limit; 529 = Anthropic's servers overloaded for everyone. SDKs raise typed exceptions you catch most-specific-first.
You could hand-build every HTTP request, header, and JSON parse yourself. In practice you use a client SDK — a language-specific library that wraps REST behind idiomatic, typed functions. The Python SDK (anthropic) ships both a synchronous Anthropic client and an async AsyncAnthropic, both on httpx, typed via Pydantic. The TypeScript SDK (@anthropic-ai/sdk) auto-reads the key from ANTHROPIC_API_KEY and runs on Node.js, Deno, Bun, and browsers.
The SDK's central trick is to turn the raw POST /v1/messages into a single typed method call. When you invoke client.messages.create(...), the SDK serializes your typed parameters to JSON, adds the required headers, sends the POST, and deserializes the response into a typed Message — everything from Section 1 handled for you. Streaming is likewise abstracted over SSE. Every response also carries a unique request-id (exposed as _request_id), invaluable for debugging.
Figure 6.2: How the SDK wraps the raw REST call
flowchart LR
A["Your code: client.messages.create(...)"] --> B["Serialize typed params to JSON body"]
B --> C["Add headers: x-api-key, anthropic-version, content-type"]
C --> D["POST https://api.anthropic.com/v1/messages"]
D --> E["Deserialize JSON into typed Message object"]
E --> F["Typed Message returned to your code"]
Error handling and retries
Claude returns errors as JSON with a top-level error object (a type and message) plus a request_id. The status-code taxonomy is worth memorizing:
Status
Error type
Meaning
400
invalid_request_error
Malformed request format or content
401
authentication_error
API key missing, malformed, revoked, or expired
403
permission_error
Key lacks permission for the resource
404
not_found_error
Resource not found
429
rate_limit_error
Your account hit a per-minute limit (RPM / ITPM / OTPM)
500
api_error
Unexpected internal Anthropic error
529
overloaded_error
API temporarily overloaded across all users
A distinction the exam loves: 429 vs. 529. A 429 means your account exceeded its own per-minute limits — the fix is to slow your own traffic. A 529 means Anthropic's servers are temporarily at capacity for everyone, independent of your usage — the fix is to back off and retry, possibly with failover. Same "too busy" feeling, different cause and remedy.
The SDKs make much of this automatic: they retry transient failures — connection errors, 429s, and 5xx — using exponential backoff, twice by default, honoring the retry-after header when present. Exponential backoff means each retry waits longer than the last (roughly doubling). If you hand-roll retries, respect retry-after first, then reset-header math, then jittered exponential backoff — the jitter (small random delay) breaks the "thundering herd." Finally, the SDKs raise typed exceptions (a 404 becomes anthropic.NotFoundError); catch these most-specific-first rather than string-matching.
Figure 6.3: SDK retry and exponential-backoff state flow
Animation slot: a request flowing through the retry state machine — a 429 bounces into a "wait" state whose delay visibly doubles each attempt, until it either succeeds or raises a typed exception after retries are exhausted.
Post-Reading Check — SDKs That Wrap REST
1. What does a single client.messages.create(...) call do under the hood?
Opens a websocket and waits for a bidirectional handshakeSerializes params to JSON, adds required headers, POSTs to /v1/messages, and deserializes a typed MessageStores the conversation on Anthropic's servers so history need not be resentSends the API key in the request body instead of a header
2. By default, how do the SDKs handle transient failures like connection errors, 429s, and 5xx?
They fail immediately and require you to write all retry logicThey retry forever until the request succeedsThey retry twice by default with exponential backoff, honoring the retry-after headerThey retry once immediately with no delay
3. What is the key difference between a 429 and a 529 error?
429 means your account hit its own limit; 529 means Anthropic's servers are overloaded for everyone429 is a server bug; 529 is a client bug429 is fatal and non-retryable; 529 is always retried automatically foreverThey are identical; the number is chosen at random
4. How should you catch errors raised by the SDK?
String-match on the error message textCatch typed exception classes, most-specific first (e.g., RateLimitError before APIStatusError)Parse the raw JSON error object manually every timeCatch only a single generic Exception and ignore the type
Pre-Reading Check — Engineering Practices and the SDLC
1. What does "treat prompts as code" mean for a Claude integration?
Prompts should be generated at runtime and never storedPrompts belong in Git (e.g., a prompts/ directory) so they are diffable, reviewable, and revertiblePrompts should be compiled into a binary for securityPrompts should be embedded as hardcoded strings scattered through the code
2. What is the non-negotiable version-control rule for the ANTHROPIC_API_KEY?
Commit it so teammates can share one key easilyStore it in a public config file for transparencyNever commit it — keep it in the environment and .gitignore the .envEncode it in base64 in the README
3. What is the guiding principle of refactoring a Claude integration?
Spread model IDs and retry logic across as many files as possibleIsolate the integration behind a thin, well-typed layer that centralizes model IDs, prompts, retry policy, and timeoutsInline every prompt string directly at each call siteAvoid abstractions so each call is fully self-contained
4. How can Claude itself participate in code review within a CI pipeline?
By running in non-interactive mode, e.g., feeding a diff to a structured review promptBy automatically merging any PR that passes a single testBy storing the API key in the CI logs for auditingIt cannot; Claude only works interactively in a chat window
4. Engineering Practices and the SDLC
Key Points
An AI feature lives inside the SDLC — plan, build, review, test, release, maintain — like any other software.
Treat prompts as code: version them in Git (e.g., a prompts/ directory) so they are diffable, reviewable, and revertible; use meaningful commits and branches.
Never commit secrets: keep ANTHROPIC_API_KEY in the environment and .gitignore the .env; SDKs read the key from the environment by default.
Code review covers LLM-specific concerns — the status-code taxonomy, retries/backoff, timeouts, no hardcoded secrets, prompt injection, and cost controls; Claude can review in CI via non-interactive mode.
Refactor to isolate the integration behind a thin, typed layer that centralizes model IDs, prompts, retry policy, and timeouts — swap a model in one place, not fifty.
Version control (Git in practice) records the full change history of a codebase. The distinctive move for Claude integrations is to treat prompts as code: prompt templates are development artifacts, so they belong in the repo — typically in a prompts/ directory, organized by feature, one prompt per file. This makes a prompt change reviewable, diffable, revertible, and testable exactly like source code. Concrete habits: write meaningful commit messages (what and why), use branches to explore prompt/model variations, and use Git as a state log with checkpoints so you can cleanly revert an AI-generated change that does not pan out.
A non-negotiable: never commit secrets. Keep ANTHROPIC_API_KEY in environment variables and add .env to .gitignore. The SDKs read the key from the environment by default, which makes this painless.
Code review should cover the LLM-specific concerns alongside the usual ones: correct handling of the full status-code taxonomy (401/403/429/500/529), retry and backoff configuration, timeout handling, no hardcoded secrets, prompt-injection and untrusted-input handling, and cost/token controls such as max_tokens and batching. Claude can also participate: running the SDK or CLI in non-interactive mode lets you wire it into CI pipelines and pre-commit hooks — for example, feeding a diff to a structured security-review prompt to flag vulnerabilities before merge.
Refactoring behind a thin typed layer
Refactoring improves internal structure without changing external behavior. The guiding principle is to isolate the integration behind a thin, well-typed abstraction layer, so model IDs, prompts, retry policy, and timeouts are centralized and swappable. The analogy is a building's electrical panel: route everything through one panel with labeled breakers, so changing a fuse — swapping claude-sonnet-5 for a newer model, tightening a timeout, bumping the retry count — touches one place, not fifty. Small-scale refactoring is local (rename a prompt file, extract a helper); large-scale refactoring restructures whole modules (introduce the central client wrapper, move inline prompts to versioned files, standardize typed-exception handling).
Figure 6.4: SDLC integration pipeline for a Claude feature
flowchart TD
A["Plan: choose model, budget max_tokens, set rate-limit expectations"] --> B["Build: official SDK, prompts in versioned files, API key in environment"]
B --> C["Review: human + Claude-in-CI check error handling, retries, secrets, injection, cost"]
C --> D["Test: exercise typed layer, Git checkpoints to revert experiments"]
D --> E["Maintain: refactor behind thin client layer, swap models/prompts via central config"]
E -.-> A
Use the official SDK; keep prompts in versioned files; keep the API key in the environment
Review
Human + Claude-in-CI review of error handling, retries, secrets, prompt injection, cost
Test
Exercise the typed abstraction layer; use Git checkpoints to revert failed experiments
Maintain
Refactor behind the thin client layer; swap models/prompts via the central config
Animation slot: the SDLC loop animating stage by stage, with a "swap model" action rippling from the central client layer to every call site at once, illustrating single-point-of-change maintenance.
Post-Reading Check — Engineering Practices and the SDLC
1. What does "treat prompts as code" mean for a Claude integration?
Prompts should be generated at runtime and never storedPrompts belong in Git (e.g., a prompts/ directory) so they are diffable, reviewable, and revertiblePrompts should be compiled into a binary for securityPrompts should be embedded as hardcoded strings scattered through the code
2. What is the non-negotiable version-control rule for the ANTHROPIC_API_KEY?
Commit it so teammates can share one key easilyStore it in a public config file for transparencyNever commit it — keep it in the environment and .gitignore the .envEncode it in base64 in the README
3. What is the guiding principle of refactoring a Claude integration?
Spread model IDs and retry logic across as many files as possibleIsolate the integration behind a thin, well-typed layer that centralizes model IDs, prompts, retry policy, and timeoutsInline every prompt string directly at each call siteAvoid abstractions so each call is fully self-contained
4. How can Claude itself participate in code review within a CI pipeline?
By running in non-interactive mode, e.g., feeding a diff to a structured review promptBy automatically merging any PR that passes a single testBy storing the API key in the CI logs for auditingIt cannot; Claude only works interactively in a chat window