Integration Guides

Claude API curl Example: Copy-Paste Requests for Messages, Streaming, Images and Tools (2026)

Kenji Watanabe

A working Claude API curl example needs one POST to https://api.anthropic.com/v1/messages with three headers (x-api-key, anthropic-version: 2023-06-01, content-type: application/json) and a JSON body containing model, max_tokens and messages. The copy-paste version is below, followed by variants for streaming, images, tools, token counting and debugging.

curl is the fastest way to separate "is my key and network fine" from "is my SDK configured correctly". Every command here follows the request shapes in Anthropic's official documentation at platform.claude.com, and each one is written to run as-is once ANTHROPIC_API_KEY is exported. If you want the conceptual walk-through instead of a cookbook, start with how to use the Claude API.

The minimal Claude API curl example

Export your key once so it never lands in shell history or a script:

export ANTHROPIC_API_KEY="your-api-key-here"

Then send a message. This is the request from Anthropic's get-started guide:

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-5",
    "max_tokens": 1000,
    "messages": [
      {"role": "user", "content": "Explain what an HTTP 429 means in one sentence."}
    ]
  }'

What each piece does:

Part

Purpose

x-api-key

Your API key. A missing or wrong key returns 401 authentication_error.

anthropic-version

The API version. 2023-06-01 is the value used throughout Anthropic's examples.

content-type

The body is JSON.

model

A model ID such as claude-opus-5, claude-sonnet-5 or claude-haiku-4-5.

max_tokens

The ceiling on generated tokens. Required.

messages

The conversation so far, as alternating user and assistant turns.

Reading the response

A successful call returns a single JSON object. Per Anthropic's get-started guide, the shape looks like this (text shortened):

{
  "model": "claude-opus-5",
  "id": "msg_013mHbppMPd2PrVJzGMZPt2D",
  "type": "message",
  "role": "assistant",
  "content": [{"type": "text", "text": "..."}],
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": {"input_tokens": 21, "output_tokens": 305}
}

content is an array of blocks, not a string, so pull the text out with jq rather than reading the raw body:

curl -sS 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-sonnet-5", "max_tokens": 300,
       "messages": [{"role": "user", "content": "Name three HTTP status codes."}]}' \
  | jq -r '.content[] | select(.type == "text") | .text'

Check stop_reason whenever an answer looks cut off. A value of max_tokens means the reply hit your ceiling, not that the model finished. The usage field is what you are billed on.

Claude API curl examples for common request types

System prompt and multi-turn conversation

The system prompt is a top-level system field, not a message. Earlier turns go into messages in order, ending with a user turn:

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-sonnet-5",
    "max_tokens": 500,
    "system": "You are a concise assistant for backend engineers.",
    "messages": [
      {"role": "user", "content": "What does idempotent mean for an API?"},
      {"role": "assistant", "content": "Repeating the same request has the same effect as sending it once."},
      {"role": "user", "content": "Give one example with HTTP methods."}
    ]
  }'

The API keeps no state between calls, so every request must resend the history you want the model to see. Do not end the array with an assistant turn: Anthropic's error reference states that Claude 4.6 and later models reject a prefilled final assistant message with a 400.

Streaming with curl

Add "stream": true to the body. Anthropic's streaming guide uses this request; adding curl's -N (no buffering) flag makes events print as they arrive instead of in chunks:

curl -N https://api.anthropic.com/v1/messages \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "Hello"}],
    "max_tokens": 256,
    "stream": true
  }'

The output is server-sent events. Anthropic documents the order as message_start, then for each content block a content_block_start, one or more content_block_delta and a content_block_stop, then one or more message_delta, and finally message_stop, with occasional ping events in between. The text arrives in text_delta payloads, and the usage counts in message_delta are cumulative. Anthropic also notes that curl has no single-command way to assemble the final message from the stream, so treat this as an inspection tool. Our Claude API streaming guide covers consuming the stream in code.

Sending an image by URL

Images go in as content blocks inside a user message. This is the URL-source example from Anthropic's vision guide:

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-opus-5",
    "max_tokens": 1024,
    "messages": [{
      "role": "user",
      "content": [
        {"type": "image", "source": {"type": "url", "url": "https://platform.claude.com/docs/images/vision-example.jpg"}},
        {"type": "text", "text": "Describe this image."}
      ]
    }]
  }'

For a local file, use a base64 source ("type": "base64", "media_type": "image/jpeg", "data": "..."). Anthropic lists JPEG, PNG, GIF and WebP as supported formats. Base64 payloads are long, which is where the heredoc pattern in the quoting section below becomes useful.

Defining a tool

Tools are declared in a top-level tools array with a JSON Schema for the input. The definition below follows the get_weather example used across Anthropic's documentation:

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-5",
    "max_tokens": 1024,
    "tools": [{
      "name": "get_weather",
      "description": "Get the current weather in a given location",
      "input_schema": {
        "type": "object",
        "properties": {
          "location": {"type": "string", "description": "The city and state, e.g. San Francisco, CA"}
        },
        "required": ["location"]
      }
    }],
    "messages": [{"role": "user", "content": "What is the weather like in San Francisco?"}]
  }'

When the model decides to call the tool, the response has stop_reason set to tool_use and a tool_use content block with an id, the tool name and an input object. You run the tool yourself, then send a new request that appends the assistant turn plus a user turn containing a tool_result block whose tool_use_id matches that id.

Counting tokens before you send

The token counting endpoint accepts the same inputs as a message request, does not need max_tokens, and returns only a count. From Anthropic's token counting guide:

curl https://api.anthropic.com/v1/messages/count_tokens \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "content-type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-opus-5",
    "system": "You are a scientist",
    "messages": [{"role": "user", "content": "Hello, Claude"}]
  }'

The response is a single field such as {"input_tokens": 14}. Anthropic lists token counting as free to use, with its own requests-per-minute limit separate from message creation.

Listing available models

A GET request with the same two auth headers returns the models your key can use, newest first:

curl https://api.anthropic.com/v1/models \
  -H "anthropic-version: 2023-06-01" \
  -H "x-api-key: $ANTHROPIC_API_KEY"

Anthropic's reference documents limit (default 20, maximum 1000) plus after_id and before_id for pagination. Each entry carries the model id, a display_name and a capabilities object. Running this first is the quickest way to confirm a model ID before a 404 surprises you.

Debugging a failing curl request

Print the status line and response headers while discarding the body. This form is from Anthropic's error reference:

curl -sS -D - -o /dev/null 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"}]}'

Every response includes a request-id header; include it if you contact support. Errors come back as JSON with a top-level error object holding type and message, plus a request_id field. The status codes you will meet most often, per Anthropic's error reference:

Status

error.type

Usual cause

400

invalid_request_error

Malformed JSON, a missing field, or an unsupported parameter

401

authentication_error

Missing, malformed, revoked or expired key

403

permission_error

The key cannot access that resource

404

not_found_error

Wrong path or model ID

413

request_too_large

Body over 32 MB on the Messages API

429

rate_limit_error

Rate limit or spend cap reached

529

overloaded_error

Temporary high load across the API

For a 400, the message usually names the offending field; our Claude API error 400 guide walks through the common ones. For 401 and 403, see Anthropic API key not working.

Shell quoting pitfalls

Most broken curl examples fail in the shell, not at the API:

  • Variables do not expand inside single quotes. $ANTHROPIC_API_KEY works in the double-quoted header, but a variable placed inside the single-quoted -d '...' body is sent literally.
  • Apostrophes end a single-quoted body. Anthropic's own examples write What'\''s to put an apostrophe inside -d '...'. Rephrasing avoids it entirely.
  • Large or generated bodies belong in a heredoc or a file. Anthropic's base64 examples use -d @- <<EOF so shell variables expand inside the JSON. Alternatively, write the body to request.json and pass -d @request.json.
  • Validate JSON before blaming the API. Running the body through jq . catches a trailing comma faster than reading a 400 message.

Pointing the same curl at a gateway

If you reach Claude through a proxy or gateway that exposes an Anthropic-compatible endpoint, the request body stays identical. Only the host and the key change, and some gateways also accept Authorization: Bearer alongside x-api-key. Our Claude API proxy guide explains what to verify first, and the ROIBest AI setup note on calling the gateway from SDKs and curl shows the exact base URL and headers for that service.

FAQ

What headers does the Claude API require in a curl request?

Three: x-api-key with your API key, anthropic-version set to 2023-06-01, and content-type: application/json for requests with a JSON body. The GET request to list models needs only the first two.

Why does my Claude API curl request return 401?

The key is missing, malformed, revoked or expired, according to Anthropic's error reference. Check that the variable is exported in the same shell and is referenced inside double quotes, not single quotes.

How do I stream a Claude API response with curl?

Add "stream": true to the JSON body and use curl -N so output is not buffered. You receive server-sent events; the text is in the text_delta payloads of content_block_delta events.

Is max_tokens required in a curl request to the Messages API?

Yes. model, max_tokens and messages are all required on the Messages endpoint. The token counting endpoint is the exception: its examples omit max_tokens.