Rubric
Contents — domains, guide and mocks

SDKs, REST and streaming

CCDV-F 5.214 min read · checked 21 September 2026

Task statementTechnical Fundamentals (6.1%) — foundational engineering practices for AI application development, including integrating with SDKs that wrap REST APIs, and websockets

One messages.create call, end to end

Your code
SDK client
Claude API
Step 1: Your code to SDK client: messages.create(...)
Step 2: SDK client : Add auth + version headers
Step 3: SDK client to Claude API: POST /v1/messages, JSON body
Step 4: Claude API to SDK client: HTTP 429 with retry-after
Step 5: SDK client : Back off, retry (max 2)
Step 6: Claude API to SDK client: HTTP 200, Message JSON
Step 7: SDK client to Your code: Typed object, _request_id
Everything between your code and the API is convenience. The wire protocol is one HTTPS POST carrying JSON.

What the REST layer actually requires

The Claude API is a plain REST API. Messages are created with POST /v1/messages at https://api.anthropic.com, the body is JSON, and the response is JSON. Three headers matter on every raw call: the credential, anthropic-version — a dated API version string such as 2023-06-01 — and content-type: application/json. Keys can be presented as a bearer token in Authorization, and x-api-key remains accepted; a key that spans several workspaces also needs anthropic-workspace-id.

The same request with no SDK at allbash
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-5",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Hello, Claude!"}]
  }'

anthropic-version is the part people forget. It is a dated contract: pinning it means a later change to the API's shape does not silently alter what your code receives. The SDKs send it for you, which is one reason a hand-rolled client is a maintenance liability rather than a saving.

What the SDK adds on top

Official SDKs exist for Python, TypeScript, C#, Go, Java, PHP and Ruby. They are thin, but the things they are thin about are exactly the things that bite in production.

ConcernRaw RESTOfficial SDK (Python)
Auth and versioningYou set every headerSent automatically; key read from ANTHROPIC_API_KEY
Transient failuresYou write the backoffRetries 408, 409, 429, 5xx and connection errors — max_retries defaults to 2
TimeoutsWhatever your HTTP client defaults to10 minutes by default; set per client or per request
ErrorsStatus codes and JSON bodiesTyped classes: RateLimitError, BadRequestError, APITimeoutError
StreamingYou parse the SSE framesmessages.stream(...) with text_stream and get_final_message()
ConcurrencyYour own async plumbingAsyncAnthropic alongside the synchronous client
Support diagnosticsRead the response headermessage._request_id on the returned object
Beta featuresHand-written beta headersclient.beta.messages.create(..., betas=[…])
Configuring the client instead of wrapping itpython
from anthropic import Anthropic, RateLimitError, APITimeoutError

# One place for policy: retries and timeout live on the client.
client = Anthropic(max_retries=4, timeout=30.0)   # key read from ANTHROPIC_API_KEY

try:
    msg = client.with_options(timeout=120.0).messages.create(
        model="claude-sonnet-5", max_tokens=1024,
        messages=[{"role": "user", "content": prompt}],
    )
except RateLimitError:
    # The SDK already backed off max_retries times before raising.
    queue_for_later(prompt)
except APITimeoutError:
    fall_back_to_cached_answer()
else:
    log.info("ok", request_id=msg._request_id, tokens=msg.usage.output_tokens)

Streaming: server-sent events, not websockets

A long answer can take a long time. With stream set to true the API returns the response incrementally as server-sent events over ordinary HTTP — a single request whose response body arrives in labelled frames. The user sees text appear immediately, and your connection is never idle long enough to look dead to a proxy.

The event sequence of one streamed response

  1. Message startmessage_start — empty shell
  2. Block startcontent_block_start per block
  3. Block deltascontent_block_delta — the pieces
  4. Block stopcontent_block_stop — block complete
  5. Message endmessage_delta has stop_reason, usage

blocks repeat — ping frames may appear anywhere

message_delta carries the stop_reason and cumulative usage — so a streamed call is only finished when you have read it, not when text stops arriving.

Deltas come in kinds. Text arrives as text_delta; a tool call's arguments arrive as input_json_delta — partial JSON strings you must accumulate until content_block_stop before parsing, because no intermediate fragment is valid JSON; reasoning arrives as thinking_delta followed by a signature_delta. Errors can arrive mid-stream as an error event, for example an overloaded_error, which is why a streamed call needs the same failure handling as any other.

Streaming with the SDK helpertypescript
const stream = client.messages.stream({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: prompt }],
});

// Show tokens as they arrive.
stream.on("text", (chunk) => process.stdout.write(chunk));

// The helper accumulates the frames into one complete Message.
const message = await stream.finalMessage();
if (message.stop_reason === "max_tokens") {
  markTruncated(message);      // text stopped arriving — that is not "finished"
}

Which shape of call does this workload need?

How should this call be made?
  • A person is waiting
    Stream itSSE; first token in under a second
  • Machine-to-machine
    Plain requestsimpler to parse and test
  • Very long output
    Stream itavoids long idle connections
  • Bulk, not urgent
    Batch itcheaper; covered in Domain 1
Streaming is about perceived latency, not throughput. Nothing here is a websocket decision, because the API call is a request either way.

Ordinary engineering practice, applied here

The rest of this objective is the general practice the exam guide expects of one to five years of engineering, made specific by the fact that calls are slow and metered. Four habits carry most of it.

  • Do not block on I/O. A Claude call takes seconds, not milliseconds. Use the asynchronous client and bounded concurrency so a handful of slow calls cannot exhaust a thread pool or a connection limit.
  • Configure timeouts deliberately. The default is generous — ten minutes — because long generations are legitimate. An interactive path needs a much shorter one plus a fallback, set per request rather than globally.
  • Keep configuration out of the code. Model id, effort, limits and the key itself come from environment or config, so a model change is a deploy setting rather than a code change. Key handling specifically is covered in 7.4.
  • Log the request id and the usage. _request_id is the identifier support can look up; usage is the only honest source for cost. Both are on the response object already.

Traps the wrong answers are built from

Tempting but wrongDo this instead
Wrapping SDK calls in your own retry loopSet max_retries on the client; two layers multiply attempts and cost.
Hand-rolling an HTTP client to “avoid a dependency”Wrap the official SDK in a thin adapter and inherit its failure handling.
Omitting anthropic-version on raw REST callsSend the dated version header on every request so the contract is pinned.
Parsing input_json_delta fragments as they arriveAccumulate partial JSON until content_block_stop, then parse once.
Treating the end of the text stream as successRead stop_reason and usage from message_delta before using the result.
Serial for loops over hundreds of documentsUse the async client with bounded concurrency, or the Batch API.

You should now be able to

  • Describe the REST call an SDK makes, including endpoint, body and required headers.
  • Name what the official SDKs add — retries, timeouts, typed errors, streaming helpers, request ids — and configure each.
  • Implement streaming and handle the event sequence, including partial tool-input JSON and mid-stream errors.
  • Distinguish server-sent events from websockets and say which the Claude API uses.
  • Apply async, concurrency limits, timeouts and externalised configuration to a Claude integration.

Practice questions

Original questions written for this lesson, in the exam’s style. Answer first, then open the reasoning — every option is explained, including why the wrong ones are tempting.

  1. Question 1

    A service already uses the official Python SDK with default settings. To “make it more resilient”, a developer adds a decorator that retries any exception up to three times with a fixed one-second delay. During a brief rate-limit event the service saturates its own worker pool and the incident lasts far longer than the limit did.

    What is the best correction?

    1. AKeep the decorator but raise the delay between attempts to ten seconds.
    2. BRemove the decorator and set max_retries on the client instead.
    3. CDisable the SDK's own retries and keep the custom decorator.
    4. DCatch only RateLimitError in the decorator and retry that.
    Show answer and reasoning
    1. AIncorrect. It softens the storm without removing the multiplication of attempts, and still retries errors that will never succeed.
    2. BCorrect. The SDK already retries transient statuses with backoff; one policy in one place prevents attempts multiplying.
    3. CIncorrect. It removes the multiplication but throws away backoff, jitter and honouring retry-after.
    4. DIncorrect. Better targeted, but it still stacks a second retry layer on top of the SDK's.
  2. Question 2

    A chat UI streams responses. When Claude calls a tool, the front end sometimes throws a JSON parse error and drops the turn. The logs show it parsing the contents of each content_block_delta as it arrives.

    What is the correct handling?

    1. AAccumulate input_json_delta fragments until content_block_stop, then parse once.
    2. BRequest the tool call without streaming, then stream only the final text.
    3. CWrap each parse in a try/except and ignore the failures.
    4. DUse text_delta events to reconstruct the tool arguments.
    Show answer and reasoning
    1. ACorrect. Tool input is streamed as partial JSON strings; intermediate fragments are not valid JSON on their own.
    2. BIncorrect. It avoids the symptom by giving up streaming for the part users most want to see progress on.
    3. CIncorrect. It silences the error but discards fragments, so the assembled tool input is incomplete.
    4. DIncorrect. Tool input never arrives as text_delta; those carry the assistant's prose.
  3. Question 3

    An architecture review asks how a new voice assistant should connect. The browser holds a live microphone session; the backend calls Claude for each utterance.

    Which description is accurate?

    1. AThe backend should open a websocket to the Claude API for lower latency.
    2. BA websocket between browser and backend is normal; the backend calls Claude over HTTP and may stream with SSE.
    3. CServer-sent events are two-way, so one SSE connection can carry the audio up and the reply down.
    4. DStreaming should be avoided because partial output cannot be validated.
    Show answer and reasoning
    1. AIncorrect. The documented streaming transport for the Messages API is server-sent events; no websocket transport is documented.
    2. BCorrect. Websockets suit the two-way browser session; the Claude call remains a request that can stream its response back.
    3. CIncorrect. SSE is one-directional — server to client — over a normal HTTP response.
    4. DIncorrect. Streaming is precisely for perceived latency; the SDK helpers still give you the complete message to validate.

Sources

Drafted with AI assistance and checked against the sources above; expert review is in progress. Spotted an error? Tell us and it gets fixed, dated and listed on how this is written.