What You Need to Know
This sub-skill covers the engineering around a Claude integration. A fan-out splits one job into many independent API calls. Bounded concurrency caps how many run at once. Messages API rate limits are measured in requests, input tokens and output tokens per minute (RPM, ITPM and OTPM).
Take a bathroom fitter that uses Claude to turn site-survey notes into quotes. It sends a few hundred notes each night, and staff request single quotes from a web form.
HTTP and JSON
Everything in 2.3 runs on plain HTTP and JSON. Know which side sets which header. The 4xx versus 5xx division tells you whose bug it is. An idempotent retry is safe for a read, because reading twice changes nothing. Retrying a create needs thought, because the fitter could file one quote twice.
A JSON payload that crosses a service boundary is an interface. Renaming a field or changing a type breaks every consumer. So contract changes are versioned and reviewed like any API change, never shipped as internal details.
The official SDKs wrap the REST API in seven languages: Python, TypeScript, C#, Go, Java, PHP and Ruby. Typed error classes map onto the documented status codes. The SDK tables name no class for 402 or 413; read those off status_code. In every language, catching the specific class beats string-matching messages.
import anthropic
client = anthropic.Anthropic()
try:
response = client.messages.create(
model="claude-sonnet-5-5",
max_tokens=512,
messages=[{"role": "user", "content": record}],
)
except anthropic.RateLimitError:
... # 429: honour retry-after, back off
except anthropic.APIStatusError as err:
print(err.status_code, err.request_id)Bounded Concurrency
The fitter's nightly run can fail two ways. A serial loop sends one call at a time, wasting the rate limit and taking hours it does not need. An unbounded fan-out launches every request at once. It trips the token-bucket limiter and turns the run into a 429 storm. Sharp usage spikes can also trigger acceleration limits.
The correct shape is a worker pool or semaphore. It keeps enough requests in flight to use the documented RPM, ITPM and OTPM headroom, and no more. Transient failures, including rate limits and 5xx server errors, are retried with exponential backoff on top. That backoff is jittered and honours retry-after.
import asyncio
import random
import anthropic
# Hand-rolling backoff here, so disable the SDK default of two retries;
# otherwise every application attempt multiplies into three requests.
client = anthropic.AsyncAnthropic(max_retries=0)
semaphore = asyncio.Semaphore(5) # sized against your documented limits
def spend_capped(err: anthropic.RateLimitError) -> bool:
body = err.body if isinstance(err.body, dict) else {}
details = (body.get("error") or {}).get("details") or {}
return details.get("error_code") == "enforced_spend_limit_reached"
async def normalise(record: str) -> str:
async with semaphore:
for attempt in range(4):
try:
response = await client.messages.create(
model="claude-sonnet-5-5",
max_tokens=512,
messages=[{"role": "user", "content": record}],
)
return response.content[0].text
except anthropic.RateLimitError as err:
if spend_capped(err):
raise # the tier's monthly spend cap: no retry clears it
wait = float(err.response.headers.get("retry-after", 2 ** attempt))
await asyncio.sleep(wait + random.uniform(0, 1))
raise RuntimeError("still rate limited after backoff")
async def main(records: list[str]) -> list[str]:
return await asyncio.gather(*(normalise(r) for r in records))The SDKs already retry twice by default, so a hand-rolled loop that ignores this multiplies attempts: three application tries become nine requests. That amplifies the bursts backoff exists to damp, hence max_retries=0. OTPM counts the tokens generated. So lowering max_tokens is no rate-limit lever. It only risks truncating the fitter's quotes.
The loop also re-raises one 429 at once. As of September 2026, the tier's monthly spend cap returns a 429 with no retry-after and error.details.error_code set to enforced_spend_limit_reached. No retry clears it until access resumes.
The synchronous cousin is the blocked handler. If the fitter's web form calls the Messages API synchronously inside the request handler, a worker is pinned for the whole generation. That takes seconds to minutes, so throughput collapses under modest load. The fix is async I/O, streaming to the client, or a job queue, chosen by the interaction pattern.
Concurrency or Batch: the Deadline Decides
When the fan-out is large and the deadline loose, the Batches API from 2.3 returns. Results needed within hours go realtime with bounded concurrency. Results acceptable within a day, with cost the main concern, go to batch. Quotes due before the fitter's office opens stay realtime. 2.3 owns choosing the API; this sub-skill owns writing the concurrent code when realtime wins.
Prompts Are Source Code
A prompt change is a behaviour change with the blast radius of a code change. The same holds for output schemas, eval sets, CLAUDE.md files and settings. So they live in the repository, change through branches and review, carry history and roll back like code. A quote prompt tweaked live in the fitter's production console has no history, no review, no rollback, and no guarantee staging matches what users see.
Prompt and schema changes ride the code pipeline, with the eval suite as their regression gate. An edit that lowers eval scores fails the build like a failing test. That gate makes fast prompt iteration safe. If the pipeline is the only route to production, console editing is structurally impossible, not merely discouraged.
Review and Refactoring Discipline
Claude speeds up mechanical work, but some human disciplines do not relax. An AI-assisted diff gets the same review bar as a human one, because review catches a subtly wrong generated refactor. Green tests do not prove behaviour unchanged when no test pinned that behaviour. If no test covers how the fitter prices tiling labour, a refactor that changes it still passes.
Refactoring splits by scale. Small refactors go in behaviour-preserving steps, with the suite green after each. Large work on a poorly covered module first builds a safety net: characterisation tests pinning what the code does now. Then comes an incremental migration, each step small enough to review and revert. For the fitter, that means tests on today's quote prices first. A one-big-rewrite plan, with a new contract and review skipped because tests pass, stacks three violations.
In the exam
This second-heaviest sub-skill in the domain tests whether your engineering discipline is real. It expects protocol fluency over framework familiarity, and knowing which human disciplines do not relax. Wrong options are reasonable-looking shortcuts: the synchronous handler, the unbounded fan-out, the console-edited prompt, the review exemption. The exam states the production console edit as an anti-pattern outright. The one-big-rewrite recurs as a distractor because it stacks three violations.
Exam Traps
Fixing a 429 storm by retrying harder: immediate retries in a tight loop, or a blanket raised timeout.
The limiter is a token bucket, so immediate retries spend each refill the moment it arrives and keep the client pinned at the limit. The keyed answer honours retry-after with jittered exponential backoff, and bounds the concurrency that caused the storm.
Lowering max_tokens to relieve rate-limit pressure or cut costs on a struggling fan-out.
OTPM counts tokens actually generated, so the cap value does not factor into rate-limit arithmetic. The only guaranteed effect of a lower cap is truncated output, and the guide punishes max_tokens-as-cost-lever in its own sample.
Exempting AI-generated or mechanical-looking changes from human review because the test suite passes.
Tests certify only the behaviours they pin, and a subtly wrong refactor of under-covered code sails through green. The keyed posture is the same review bar for every behavioural change, whoever or whatever authored it.
Rewriting an under-tested module in one atomic pass, with a contract change riding along.
The keyed sequence for large-scale refactoring is safety net first (characterisation tests pinning current behaviour), then incremental migration. The atomic rewrite plus simultaneous interface change is the anti-pattern stated straight.
Practice Scenario
A postal service must normalise 5,000 address records against the Messages API before the 06:00 sorting run, roughly eight hours away. An engineer's first attempt fired all 5,000 requests concurrently; the run collapsed into cascading 429 rate_limit_error responses and got slower with each retry pass. What is the correct rework?
Build Exercise
Build the Fan-Out Three Ways
What you'll learn
- How serial, unbounded and bounded fan-outs behave against a real rate limit
- How SDK default retries interact with hand-rolled backoff
- How prompts and schemas ride the same review pipeline as code
- How to argue the realtime-versus-batch call from an actual deadline
- Write a serial baseline: loop over 50 independent documents, one Messages call each, and record the wall-clock time
Why: The serial loop is the floor every concurrency argument measures against, and its slowness is the reason the fan-out exists.
You should see: A completion time that is roughly 50 times a single call.
- Rework it with async I/O and a semaphore bounding concurrency at 5; record the time and confirm no 429s in the logs
Why: The bounded pool is the keyed pattern, and seeing it finish fast without tripping the limiter is the point of the whole sub-skill.
You should see: A large speed-up over serial with zero rate_limit_error responses.
- Remove the bound deliberately, fan out all 50 at once, and observe what the SDK default retries do; then add jittered backoff honouring retry-after and compare
Why: Watching the storm and its cure in miniature teaches both the failure mode and why hand-rolled retry loops must account for the SDK retrying twice on its own.
You should see: 429s under the unbounded fan-out, then a clean recovery once backoff and the bound return.
- Put the prompt and the JSON output schema in the repository next to the code, and make one prompt change via a branch and pull request with a reviewer
Why: Prompts-as-source-code only becomes real when a prompt change actually rides the pipeline once.
You should see: A merged PR whose diff is a prompt edit, with history and a rollback path.
- Write a paragraph in the PR description stating why this workload stayed realtime rather than batch, referencing your actual deadline
Why: The reasoning is the exam skill: the deadline decides, and writing the argument down proves you can make it under item conditions.
You should see: Two or three sentences naming the deadline, the completion-window risk, and the bounded-concurrency choice.
Sources
- Claude API errors — Claude Docs, typed SDK exceptions, default retry behaviour and the status table
- Rate limits — Claude Docs, token-bucket enforcement, acceleration limits, OTPM counting actual generated tokens and the spend-cap 429
- CLI, SDKs, and libraries — Claude Docs, the seven official SDKs and their shared behaviour
- Batch processing — Claude Docs, the batch alternative when the deadline allows it
- Claude Certified Developer Foundations Exam Guide — Domain 2, Software Engineering Foundations — Anthropic