The Messages API accepts a top-level system instruction, a sequence of user and assistant messages, a selected model, and an output-token limit. A response contains content blocks, a stop reason, and usage information. Treat the result as structured data, not merely a text string.
Keep API keys in your server environment. A browser client must call your own backend; exposing an API key in client-side JavaScript lets anyone reuse it. Use an environment variable locally, a managed secret store in deployment, and a server-side allow list for the model identifiers your application permits.
| Layer | Owns | Does not own |
|---|---|---|
| Browser | A question, loading state, and a safe error message. | API credentials or raw provider responses. |
| Your server | Validation, configuration, retries, response shaping, and logs. | A reason to expose secrets to the client. |
| Claude API | A model response to the request you constructed. | Your users, permissions, or business actions. |
Environment variables prevent accidental commits but are not a complete production secret strategy. Restrict who can read production secrets, rotate them, and never log authorization headers. Local `.env` files belong in ignore rules.
A browser needs a backend endpoint that authenticates the user, applies rate limits, and decides which model and capabilities are available.
A Messages request uses a top-level `system` parameter for instructions; there is no system role inside the input messages array. The messages array represents conversational user and assistant turns. Set `max_tokens` deliberately because it is an upper bound, not a guarantee that Claude will use that many tokens.
Use a configuration value such as `CLAUDE_MODEL` rather than spreading a model name through every route and job.
Claude responses contain typed content blocks. A simple text response usually contains a text block, but applications that support tools or other modalities must inspect block types instead of assuming `content[0]` is always text.
Persist request IDs, selected model, latency, usage, and a redacted correlation ID. These fields make support and cost investigation possible.
A request can stop because it completed, reached the output limit, requested tools, hit a stop sequence, or was refused. Your application must branch on stop reason. Retrying the same request blindly after a maximum-token stop can duplicate work or cost.
Timeouts, rate limits, and transient server errors should return a clear retryable state to the user. Separate retry policy from prompt content.
Configuration errors should be loud on the server and harmless to the browser. A missing key or model setting is a deployment problem, not a reason to return a stack trace to a customer.
Build one small adapter around the SDK. That is the place to set timeouts, choose the configured model, collect request metadata, and translate provider errors into your application error shape.
The official Python SDK reads the key from the environment. Replace the model setting with an enabled model for your account rather than hard-coding an old tutorial model.
import os
from anthropic import Anthropic
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
response = client.messages.create(
model=os.environ["CLAUDE_MODEL"],
max_tokens=300,
system="Answer in two concise bullet points.",
messages=[{"role": "user", "content": "What is idempotency?"}],
)
for block in response.content:
if block.type == "text":
print(block.text)
print(response.stop_reason)
Convert a provider response into a small stable contract for your frontend. Do not expose raw provider objects or secrets.
def to_api_response(text: str, stop_reason: str, input_tokens: int, output_tokens: int) -> dict:
return {
"answer": text,
"complete": stop_reason == "end_turn",
"usage": {"input_tokens": input_tokens, "output_tokens": output_tokens},
}
print(to_api_response("Idempotency prevents duplicate effects.", "end_turn", 42, 9))
This helper fails during startup rather than halfway through a customer request.
def require_setting(settings: dict, key: str) -> str:
value = settings.get(key, "").strip()
if not value:
raise RuntimeError(f"Missing required setting: {key}")
return value
print(require_setting({"CLAUDE_MODEL": "configured-model"}, "CLAUDE_MODEL"))
configured-model
Model responses can contain more than one block type. A small helper avoids scattering that detail through routes.
def first_text_block(blocks: list[dict]) -> str:
for block in blocks:
if block.get("type") == "text":
return block["text"]
return ""
print(first_text_block([{"type": "text", "text": "Ready"}]))
Ready
A short lab
0 of 2 completed
No. Use the top-level system parameter for system instructions in the Messages API.
No. Keep an account-approved model identifier in configuration because availability and model names change.
Explore 500+ free tutorials across 20+ languages and frameworks.