CCDVF-D2.2 · Domain 2 · Applications & Integration · 33.1% of CCD-F

Software Engineering Foundations for Claude Applications.

8 min read·8 sections·Tier A

The Claude Messages API is a plain REST API that accepts and returns JSON - POST /v1/messages with a stateless request/response contract - so the ordinary software-engineering discipline you already use for any HTTP service (JSON contracts, async I/O, version control, code review, disciplined refactoring) is exactly what a production Claude integration needs, not a special category of its own. Claude docs: messages/create The exam trap is treating the API as a black box; the statelessness trap is assuming the server remembers anything - conversation memory is your app's responsibility, every call resends the full turn history.

Official Claude API docsCCD-F Domain 2 · Applications & IntegrationCCD-F only
Software Engineering Foundations for Claude Applications, hero illustration featuring Loop mascot in a warm gallery scene.
Domain CCDVF-D2Applications & Integration · 33.1%
On this page
01 · Summary

TLDR

The Claude Messages API is a plain REST API that accepts and returns JSON - POST /v1/messages with a stateless request/response contract - so the ordinary software-engineering discipline you already use for any HTTP service (JSON contracts, async I/O, version control, code review, disciplined refactoring) is exactly what a production Claude integration needs, not a special category of its own. Claude docs: messages/create The exam trap is treating the API as a black box; the statelessness trap is assuming the server remembers anything - conversation memory is your app's responsibility, every call resends the full turn history.

POST /v1/messages
Messages API verb
CCDVF-D2
Exam domain
33.1%
Domain weight
Stateless per call
API statefulness
model, max_tokens, messages
Required request fields
02 · Definition

What it is

This objective is general software-engineering discipline, applied specifically to shipping production Claude apps. The load-bearing insight for developers: the Claude Messages API *is* a REST API that accepts and returns JSON, so the same practices used for any HTTP service - resource-oriented verbs and paths, JSON contracts, asynchronous I/O, version control, code review, and disciplined refactoring - are exactly what a well-built Claude integration needs. Concretely, "Create a Message" is POST /v1/messages, a stateless call: you send prior conversational turns and the model returns the next one. REST's defining constraint - stateless client-server interaction over a uniform interface, per Fielding's original dissertation - is exactly how the Messages API's multi-turn design behaves.

None of this is Claude-specific machinery to relearn from scratch. An engineer who already knows how to design a resource, validate a JSON contract, reason about blocking vs. non-blocking I/O, keep a codebase under version control with reviewed pull requests, and refactor without changing observable behavior already has the core toolkit this objective tests - the job is recognizing that a Claude integration is an instance of that toolkit, not an exception to it.

03 · Mechanics

How it works

REST + JSON contract. The request body is JSON with required fields model, max_tokens, and messages (an array of {role, content} turns alternating user/assistant); the response is a JSON Message object with id, type, role, content (an array of content blocks), model, stop_reason, and usage (input_tokens/output_tokens). Because the API is stateless, the server does not remember prior turns - you resend the full conversation history on every call; state lives entirely in your application, not on Anthropic's servers. This is the single most common source of "my app forgot what the user said" bugs: it isn't a bug in the API, it's a missing array element in the request your app sent.

Asynchronous programming. The official Python SDK ships both a synchronous Anthropic client and an AsyncAnthropic client; async calls use await client.messages.create(...), letting an application fan out concurrent requests - parallel tool calls, multiple users, batch workloads - without blocking on each one sequentially. Async matters most when the workload is inherently concurrent or when a call streams; a single simple integration with light traffic gets no meaningful benefit from async machinery it doesn't need.

Streaming. Setting "stream": true returns the response incrementally as Server-Sent Events instead of one blocking payload; SDKs expose client.messages.stream(...) with helpers like get_final_message() to assemble the complete response once streaming finishes. The concrete engineering reason to reach for this: it prevents client/proxy timeouts on long generations or high max_tokens requests that would otherwise sit silent until the full response is ready. Streaming is a latency/UX and timeout-avoidance tool, not a cost-reduction one - token billing is identical whether or not you stream. For the full mechanics of parsing stream events, see the dedicated streaming concept page - see Study Next.

Version control and code review. Use git for history and branching, and treat the pull-request review flow - propose changes on a branch, get them reviewed before merge - as standard practice for anything that ships to production. The Claude-specific implication: system prompts, tool schemas, and orchestration code are source, not configuration to be hand-edited live. A system-prompt change that skips code review is exactly as risky as an unreviewed change to any other production code path, and arguably riskier, since prompt behavior is harder to unit-test deterministically than most application logic.

Refactoring. Restructuring code without changing its observable behavior is the discipline that keeps an evolving prompt/tool/agent codebase maintainable as it grows - renaming and reorganizing tool definitions, splitting an orchestration loop into smaller functions, or consolidating duplicated system-prompt fragments, all without altering what the agent actually does. The distinction that matters for a Claude app specifically: a change is a refactor only if the observable behavior (the agent's outputs, tool-call sequences, eval scores) is provably unchanged - anything that shifts behavior, even a prompt wording tweak intended to be "just cleanup," is a behavior change requiring re-evaluation against the eval suite (see the systems-life-cycle-management page's test-phase discipline), not a refactor exempt from testing.

Software Engineering Foundations for Claude Applications mechanics, painterly diagram featuring Loop mascot.
04 · In production

Where you'll see it

Production Claude chatbot

Each turn POSTs JSON (model, max_tokens, messages) to /v1/messages and parses the JSON Message back; because the endpoint is stateless, the app owns the conversation array and resends it every turn. Under concurrent load it switches to AsyncAnthropic and streams via SSE so long generations don't block or time out.

Prompt and tool-schema change management

System prompts and tool definitions live in git and go through the same pull-request code-review flow as any other production source; a claimed 'refactor' to the orchestration loop is verified against the eval suite before merge, not assumed behavior-safe by inspection.

05 · Compare

Side-by-side

General SWE practiceOrdinary REST serviceClaude Messages API equivalent
Resource-oriented endpointOne verb per resource action, e.g. POST /ordersPOST /v1/messages - one endpoint, one resource: message creation
Request/response contractJSON body in, JSON body outJSON in (model, max_tokens, messages) → JSON Message out (id, content[], stop_reason, usage)
StatelessnessServer holds no client session state by defaultServer holds no conversation state at all - the full turn history is resent every call
Async I/ONon-blocking client for concurrency under loadAsyncAnthropic + await client.messages.create(...) for concurrent/parallel calls
Long-running response handlingChunked transfer or SSE to avoid client timeoutsstream: true → Server-Sent Events; client.messages.stream(...) + get_final_message()
06 · When to use

Decision tree

01

Is a client-visible bug where the model 'forgot' something the user said earlier in the conversation?

YesCheck whether the app is resending the full messages array on every call - the API is stateless, so a missing turn in the request is a common cause to check first, not necessarily a model failure.
NoStatelessness isn't the issue here; look at prompt content or tool behavior instead.
02

Does the workload need to issue multiple concurrent or parallel Claude calls (fan-out, batch, multi-user)?

YesUse AsyncAnthropic with await client.messages.create(...) so calls don't block each other sequentially.
NoA synchronous client is simpler and sufficient - async adds complexity a low-concurrency workload doesn't need.
03

Is a single call's max_tokens or expected generation long enough to risk a client/proxy timeout?

YesSet stream: true and consume Server-Sent Events (or the SDK's .stream() helper) so the client receives output incrementally instead of waiting on one blocking payload.
NoA standard blocking call is fine; streaming buys latency/UX, not lower cost.
04

Is a proposed prompt, tool-schema, or orchestration-loop change intended as pure cleanup with no behavior change?

YesVerify it against the eval suite before treating it as a no-risk refactor - only a provably behavior-identical change qualifies as a refactor; anything that shifts outputs or tool-call sequences is a behavior change requiring re-evaluation.
NoThis is a feature/behavior change - route it through the normal test-before-deploy discipline, not the lighter refactor bar.
05

Is a system prompt or tool schema being edited directly against production without a reviewed pull request?

YesStop - treat it as source under version control and code review like any other production code path, not as configuration safe to hand-edit live.
NoStandard PR-review discipline is already being followed.
07 · On the exam

Question patterns

Software Engineering Foundations for Claude Applications exam trap, painterly cautionary scene featuring Loop mascot.
A production chatbot's users report that Claude periodically "forgets" facts they stated two messages earlier, even though the app never restarts. What's the most likely root cause, given the Messages API's design?
The app is not resending the full messages array on every call - the API is stateless, so any turn missing from the request is invisible to the model, indistinguishable from never having been said. The named distractor "the model's context window is too small for this conversation" is a real failure mode in general, but the stated symptom (forgetting recent, nearby turns in an ordinary chat) points to a missing-turn bug in the request payload long before context-window exhaustion becomes the explanation.
A backend needs to process 200 independent customer support tickets through Claude as fast as possible, where each ticket's request is fully independent of the others. Sequential synchronous calls take too long. What's the fix?
Use AsyncAnthropic and issue the 200 calls concurrently with await client.messages.create(...), since the requests are independent and don't need to block on one another. The named distractor "switch to streaming for each request to speed things up" misapplies streaming - streaming reduces time-to-first-token for a single long response, it does not parallelize independent requests; concurrency needs async, not streaming.
A report-generation endpoint sets `max_tokens` to 8,000 and users' browsers are timing out waiting for the full response before anything renders. What's the correct architectural fix, and what does it NOT solve?
Set stream: true (or use the SDK's client.messages.stream(...) helper) so output arrives incrementally as Server-Sent Events, preventing the client/proxy from timing out on a long blocking wait. It does NOT reduce token cost - billing is identical streamed or not. The named distractor "reduce max_tokens to avoid the timeout" treats the symptom by truncating legitimate output instead of fixing the actual timeout mechanism.
An engineer renames several tool-definition variables, splits a large orchestration function into three smaller ones, and ships it labeled as "just a refactor, no review needed since behavior can't have changed." What's missing from that claim?
A refactor is only exempt from behavior scrutiny if the observable behavior (agent outputs, tool-call sequences, eval scores) is *provably* unchanged - that has to be verified against the eval suite, not assumed from the nature of the edit. The named distractor "renaming variables and splitting functions can never change behavior, so no verification is needed" overstates the guarantee - a reachable bug in the split (e.g. a dropped parameter) turns a claimed refactor into an unverified behavior change.
A team edits their production system prompt directly in the deployment config to fix an urgent issue, without a pull request or code review, planning to "backfill the PR later." What software-engineering principle does this violate, and why does it matter more for a Claude app than it might elsewhere?
It violates standard version-control and code-review discipline - a system prompt is source, not configuration safe to hand-edit live, and should go through the same reviewed pull-request flow as any other production code. It matters more here because prompt behavior is harder to unit-test deterministically than most application logic, so an unreviewed prompt edit is more likely to ship a silent regression than an unreviewed edit to typed application code would be. The named distractor "prompts are just text content, not code, so review requirements don't apply" is the exact misconception this objective tests against.
08 · FAQ

Frequently asked

Is the Claude Messages API a special AI-only protocol, different from a normal REST API?
No. It's a plain REST/JSON endpoint: POST /v1/messages, a JSON request body, a JSON Message response. The same HTTP-service engineering practices you already use elsewhere apply directly.
Do I need async and streaming for every Claude API call?
No. Synchronous requests are fine for most traffic. Async (AsyncAnthropic) earns its keep for concurrent/parallel calls; streaming earns its keep for long generations at risk of client/proxy timeouts. Neither is a default requirement.
09 · Practice with AI

Work this with your AI

Work this concept hands-on with Claude Code, Codex, or claude.ai. Copy a prompt, paste it into your assistant, and practise in tandem. Each one keeps you active (explain it back, get drilled, or build) rather than just reading.

  • Drill it like the exam (scenario MCQs)
    Practice in the exam's scenario-MCQ format with trap awareness.
  • Explain it back (Feynman)
    Build durable, transferable understanding of a concept you can half-state.
  • Test me, adapting the difficulty
    Active recall practice on a concept you think you know.
  • Check my prerequisites first
    Before studying a concept that keeps not sticking.
  • Find the high-leverage 20%
    When a domain feels too big and you are short on time.
Self-check

Test yourself

Three diagnostic questions on this primitive. Reveal each answer when you have a guess. Want a full 60-question mock? Open the mock hub →

Q1A production chatbot's users report that Claude periodically "forgets" facts they stated two messages earlier, even though the app never restarts. What's the most likely root cause, given the Messages API's design?
The app is not resending the full messages array on every call - the API is stateless, so any turn missing from the request is invisible to the model, indistinguishable from never having been said. The named distractor "the model's context window is too small for this conversation" is a real failure mode in general, but the stated symptom (forgetting recent, nearby turns in an ordinary chat) points to a missing-turn bug in the request payload long before context-window exhaustion becomes the explanation.
Q2A backend needs to process 200 independent customer support tickets through Claude as fast as possible, where each ticket's request is fully independent of the others. Sequential synchronous calls take too long. What's the fix?
Use AsyncAnthropic and issue the 200 calls concurrently with await client.messages.create(...), since the requests are independent and don't need to block on one another. The named distractor "switch to streaming for each request to speed things up" misapplies streaming - streaming reduces time-to-first-token for a single long response, it does not parallelize independent requests; concurrency needs async, not streaming.
Q3A report-generation endpoint sets `max_tokens` to 8,000 and users' browsers are timing out waiting for the full response before anything renders. What's the correct architectural fix, and what does it NOT solve?
Set stream: true (or use the SDK's client.messages.stream(...) helper) so output arrives incrementally as Server-Sent Events, preventing the client/proxy from timing out on a long blocking wait. It does NOT reduce token cost - billing is identical streamed or not. The named distractor "reduce max_tokens to avoid the timeout" treats the symptom by truncating legitimate output instead of fixing the actual timeout mechanism.
Last reviewed: 2026-05-04·Refresh cadence: monthly
CCDVF-D2.2 · CCDVF-D2 · Applications & Integration

Software Engineering Foundations for Claude Applications, complete.

You've covered the full ten-section breakdown for this primitive, definition, mechanics, code, false positives, comparison, decision tree, exam patterns, and FAQ. One technical primitive down on the path to CCA-F.

More platforms →