From API key to first request
Using the Claude API productively takes more than an API key. This article walks through setting up an account and credentials, how the Messages API works with a system prompt, a messages array, and a model parameter, when streaming makes sense, and what a first working request looks like in PHP or JavaScript.
Table of Contents
- 1. What the Claude API is and when you need it
- 2. Creating an API key and securing credentials
- 3. The Messages API: system, messages, and model
- 4. Your first request: a minimal cURL example
- 5. PHP example: calling the Claude API from a script
- 6. JavaScript example: a request from the server
- 7. Streaming vs. non-streaming responses
- 8. Error handling, rate limits, and retries
- 9. Beginner mistakes compared side by side
- 10. Summary
- 11. FAQ
1. What the Claude API is and when you need it
The Claude API is the programmatic interface to Anthropic's Claude models. While the web interface at claude.ai is built for interactive conversations, the API is aimed at developers who want to embed Claude into their own application, a backend script, or an automation: a PHP backend that generates product descriptions, a Node service that pre-classifies support tickets, or a CLI tool that summarizes log files all talk to the same REST endpoint. There is no graphical layer between the application and the model, only HTTP requests with JSON payloads.
The central endpoint is POST https://api.anthropic.com/v1/messages. Everything that happens with Claude programmatically goes through this one endpoint: simple chat requests, tool calls, image analysis, and structured outputs. There are no separate endpoints for different tasks. Getting started only requires understanding three things: how authentication works, how a request is structured, and how a response is processed. This article covers exactly those three points in practice, including runnable examples in PHP and JavaScript.
2. Creating an API key and securing credentials
An API key is created in the Anthropic Console (console.anthropic.com), inside a workspace that belongs to your organization. Every key is tied to an organization and optionally to a workspace, which allows separate budgets and permissions for different projects or teams. The key typically starts with sk-ant- and is shown in plain text only once. If you lose it, you have to create a new one and revoke the old one; there is no way to view it again afterward for security reasons.
The key never belongs directly in source code. The common practice is an environment variable, ANTHROPIC_API_KEY, which the official SDKs pick up automatically. In local projects the value lives in a .env file excluded from version control via .gitignore. In production, secret managers or the hosting provider's environment variables take over that job. A key committed to a repository is not a theoretical risk; automated scanners actively search public repositories for exactly this pattern.
Billing is usage-based per token, split into input and output tokens, and depends on the chosen model. Claude Haiku 4.5 is the cheapest and fastest model for simple tasks, Claude Sonnet 5 offers the best balance of speed and capability for most use cases, and Claude Opus 4.8 is the most capable model for complex tasks. A small amount of credit is enough for initial experiments, and the console shows current usage in real time.
3. The Messages API: system, messages, and model
Every request to /v1/messages needs at least three fields: model as the model identifier, max_tokens as the upper bound on response length, and messages as an array of alternating user and assistant turns. The first element in messages must have the role user, after which user and assistant alternate. Each message consists of role and content, where content can be either a plain string or an array of typed content blocks, for example for text, images, or files.
The system prompt is deliberately not part of the messages array, but its own top-level field. It defines the role, tone, and constraints for the entire conversation, for example "You are an assistant that only responds with valid JSON." This separation is not an implementation detail; it is structurally important. Simulating a system prompt as the first message in the messages array technically works, but it throws away the clean separation between behavioral instructions and conversation history and makes later prompt caching harder. Every request also needs the headers x-api-key, anthropic-version, and content-type: application/json.
{
"model": "claude-opus-4-8",
"max_tokens": 1024,
"system": "You are a concise assistant for a German e-commerce team. Answer in German unless asked otherwise.",
"messages": [
{ "role": "user", "content": "Write one sentence describing a red running shoe." }
]
}
// Response shape (abbreviated)
{
"id": "msg_01abc...",
"type": "message",
"role": "assistant",
"content": [
{ "type": "text", "text": "A lightweight red running shoe built for fast tempo runs." }
],
"model": "claude-opus-4-8",
"stop_reason": "end_turn",
"usage": { "input_tokens": 28, "output_tokens": 14 }
}
4. Your first request: a minimal cURL example
The fastest way to test the Claude API without any dependencies is a single cURL call in the terminal. It lets you check whether the API key is set correctly and whether the request works at all before writing any code. This is also the first sensible step when debugging later integrations: if a PHP or Node script throws an error, the same cURL command with the same parameters clarifies within seconds whether the problem sits in your own code or in the request itself.
The response is a JSON object with a content array. Each block in it has a type, in the simplest case text with the actual response text. The stop_reason field shows why generation ended, usually end_turn for a naturally completed answer or max_tokens if the limit was hit before Claude finished. The usage object reports the actual input and output tokens consumed, important for cost control and for later tuning of max_tokens.
# Set the key once per shell session, never hardcode it in scripts
export ANTHROPIC_API_KEY="sk-ant-your-key-here"
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-4-8",
"max_tokens": 256,
"messages": [
{"role": "user", "content": "What is the capital of Germany?"}
]
}'
# Extract just the answer text with jq
response=$(curl -s https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"claude-opus-4-8","max_tokens":256,"messages":[{"role":"user","content":"Hello"}]}')
echo "$response" | jq -r '.content[0].text'
5. PHP example: calling the Claude API from a script
For PHP, Anthropic provides an official SDK, installable via composer require anthropic-ai/sdk. The SDK handles authentication, header construction, and error classification, so you do not need to set up your own HTTP client with Guzzle or cURL. The client reads the API key from the ANTHROPIC_API_KEY environment variable by default; an explicit parameter in the constructor is only needed when several keys are used in parallel, for example for different tenants in a multi-tenant application.
The SDK's response is a typed object whose content property is an array of polymorphic blocks. Before accessing ->text, the block type has to be checked, because a block can be of a different type, for example a thinking block when thinking is enabled. This type-checking pattern is not a PHP-specific detail; it runs through every official SDK and prevents a script from crashing with a fatal error when the response structure is unexpected.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use Anthropic\Client;
// The client reads ANTHROPIC_API_KEY from the environment automatically
$client = new Client();
$message = $client->messages->create(
model: 'claude-opus-4-8',
maxTokens: 512,
system: 'You write short, factual product summaries in German.',
messages: [
['role' => 'user', 'content' => 'Summarize a waterproof hiking backpack in two sentences.'],
],
);
// content is polymorphic - always check the block type before reading ->text
foreach ($message->content as $block) {
if ($block->type === 'text') {
echo $block->text . PHP_EOL;
}
}
echo 'Tokens used: ' . $message->usage->outputTokens . PHP_EOL;
6. JavaScript example: a request from the server
In a Node.js environment, @anthropic-ai/sdk, installable via npm install @anthropic-ai/sdk, plays the same role as the PHP SDK. Important: the Claude API is called exclusively from the server side, never directly from browser JavaScript, because otherwise the API key would be visible in the client bundle and readable by every visitor. A typical setup is a small Express or Fastify endpoint that accepts the request, forwards it to Claude, and returns only the result to the frontend.
The JavaScript response structure matches PHP and cURL exactly, because every SDK mirrors the same JSON shape of the REST API, only with language-typical naming. A script run locally with node script.js is well suited for quickly testing prompts before integrating them into a larger application, for example an existing Node middleware or a Lambda handler.
import Anthropic from "@anthropic-ai/sdk";
// Reads ANTHROPIC_API_KEY from the environment automatically
const client = new Anthropic();
async function main() {
const message = await client.messages.create({
model: "claude-opus-4-8",
max_tokens: 512,
system: "You write short, factual product summaries in German.",
messages: [
{ role: "user", content: "Summarize a waterproof hiking backpack in two sentences." },
],
});
// content is a discriminated union - narrow by .type before reading .text
for (const block of message.content) {
if (block.type === "text") {
console.log(block.text);
}
}
console.log(`Tokens used: ${message.usage.output_tokens}`);
}
main().catch((error) => {
console.error("Request failed:", error.message);
process.exit(1);
});
7. Streaming vs. non-streaming responses
By default a request waits until the complete response has been generated and only then returns a single JSON object. That is simple to process, but noticeably slow for users when responses are long, since they see nothing until the very last generated character. With "stream": true in the request, the API instead delivers a stream of server-sent events, so text appears as it is generated, just like in the claude.ai chat interface.
The event stream consists of several event types: message_start at the beginning, content_block_start and content_block_delta for each new piece of text, content_block_stop when a block finishes, message_delta with metadata such as stop_reason, and message_stop at the end. For a chat interface with a live display, streaming is almost always the right choice. A second, often overlooked rule: with a high max_tokens limit, roughly above 16,000 tokens, you should generally stream, because a non-streamed request risks an HTTP timeout during a long generation, regardless of whether the response is displayed live or not.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function streamAnswer() {
const stream = client.messages.stream({
model: "claude-opus-4-8",
max_tokens: 1024,
messages: [
{ role: "user", content: "Explain streaming responses in three short paragraphs." },
],
});
// Print text deltas as they arrive
stream.on("text", (delta) => {
process.stdout.write(delta);
});
// finalMessage() resolves once the stream completes, giving the full response
const finalMessage = await stream.finalMessage();
console.log(`\n\nTotal output tokens: ${finalMessage.usage.output_tokens}`);
}
streamAnswer();
8. Error handling, rate limits, and retries
The Claude API reports errors through HTTP status codes and a structured JSON error object. 400 means a malformed request, for example a missing required field, 401 means an invalid API key, 429 means an exceeded rate limit, and codes at or above 500 mean an error on Anthropic's side. Important for coding style: never catch errors by string-matching the error message, but through the typed exception classes every official SDK provides, for example RateLimitError or AuthenticationError in PHP and JavaScript.
Rate limits are capped per organization by requests per minute and tokens per minute and depend on your usage tier. On a 429 error, the retry-after header gives the recommended wait time in seconds. The official SDKs already retry 429 and 5xx errors automatically with exponential backoff, two retries by default, configurable on the client. For custom retry logic outside the SDKs, for example with raw HTTP calls, the same pattern applies: exponentially increasing wait time between attempts, an upper bound on the number of retries, and an immediate stop on 4xx errors that are not rate limits, since retrying there changes nothing.
Mironsoft
Claude API integration, AI-powered backends, and Magento automation
Ready to integrate Claude into your application?
We integrate the Claude API cleanly into existing PHP and Node applications, from the first request to production-ready workflows with streaming, error handling, and cost control.
API setup
Key management, model selection, and secure configuration for production
Backend integration
PHP and Node services with streaming, retries, and structured error handling
Magento & Hyva
Claude-powered modules for content, support, and product data
9. Beginner mistakes compared side by side
Most mistakes when starting out with the Claude API are structurally the same, whether you build with PHP, JavaScript, or plain cURL. The table below lines up the most common beginner mistakes against the recommended pattern.
| Task | Beginner mistake | Recommended pattern | Benefit |
|---|---|---|---|
| Storing the API key | Key hardcoded in source code | ANTHROPIC_API_KEY environment variable | No leak via commits or screenshots |
| Setting behavior instructions | Instruction as the first user message | Top-level system field | Clean separation, better caching |
| Reading the response | content[0].text with no type check | Check block.type before access | No fatal error on a different block type |
| Long responses | High max_tokens without streaming | stream: true above roughly 16,000 tokens | No HTTP timeout, immediate display |
| Rate limit (429) | Resend immediately with no wait | Respect retry-after, use backoff | Fewer failed attempts, steadier throughput |
None of these mistakes is hard to fix on its own, but combined they add up to an integration that works in testing and fails in production under load or during network hiccups. Applying the patterns from the table from the start saves larger refactors later.
10. Summary
Getting started with the Claude API always runs through the same basic building blocks: an API key from the Anthropic Console, stored securely as an environment variable, a request to /v1/messages with the fields model, max_tokens, and messages, and an optional but recommended system field for behavioral instructions. The official PHP and JavaScript SDKs handle authentication, error classification, and retry logic, so your own code can focus on the actual task instead of HTTP details.
Streaming is the right choice for interactive interfaces and for requests with a high max_tokens limit, while simple batch processing often does fine without it. Building in typed error handling, respecting rate limits, and cleanly separating the system prompt from the conversation history from the start avoids the most common pitfalls and gives you a foundation that scales to more complex use cases like tool use or structured outputs without a major rebuild.
Claude API for Developers: The Essentials at a Glance
API key & security
Key from the Anthropic Console, always stored as the ANTHROPIC_API_KEY environment variable, never in source code.
Messages API
Required fields model, max_tokens, messages. system as its own top-level field for behavior.
Streaming
stream: true for live interfaces and high max_tokens limits, otherwise non-streaming is fine.
PHP & JavaScript SDKs
Official SDKs handle auth, error classes, and automatic retries on 429/5xx.