← Blog

Claude API Curl Example: Requests, Headers & Errors

2026-08-31 · 4 min read · SubToAPI Team

If you want to test the Claude API without writing a script, curl is the fastest way to do it. This article gives you a working curl example you can copy, paste, and run right now, plus the header and body details that trip people up the first time.

The short version: you send a POST request to the messages endpoint with an API key header, a version header, and a JSON body containing the model name, a max token limit, and a messages array. Below is the full breakdown, including streaming and common errors.

Minimal curl request

Here's a bare-bones example that sends a single user message and prints the response:

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-4-20250514",
    "max_tokens": 1024,
    "messages": [
      {"role": "user", "content": "Explain what a race condition is in one paragraph."}
    ]
  }'

Three headers are required every time:

The body needs at minimum model, max_tokens, and messages. Leave out max_tokens and you'll get a validation error — it's not optional like it is in some other APIs.

Adding a system prompt

System instructions go in a top-level system field, not inside the messages array:

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-4-20250514",
    "max_tokens": 1024,
    "system": "You are a terse code reviewer. Answer in bullet points only.",
    "messages": [
      {"role": "user", "content": "Review this function for bugs: def add(a, b): return a - b"}
    ]
  }'

Multi-turn conversations

Claude's API is stateless — there's no server-side conversation memory. Each request needs the full message history, alternating user and assistant roles:

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-4-20250514",
    "max_tokens": 1024,
    "messages": [
      {"role": "user", "content": "What is the capital of Portugal?"},
      {"role": "assistant", "content": "Lisbon."},
      {"role": "user", "content": "What is its population?"}
    ]
  }'

If you skip this and only send the latest message, Claude has no idea what "its" refers to.

Streaming with curl

Add "stream": true to the body and use curl -N to disable output buffering so you see tokens as they arrive instead of all at once at the end:

curl -N 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-4-20250514",
    "max_tokens": 1024,
    "stream": true,
    "messages": [
      {"role": "user", "content": "Write a haiku about deadlines."}
    ]
  }'

The response comes back as server-sent events — a sequence of event: and data: lines you parse incrementally rather than a single JSON blob.

Common curl errors and what they mean

Using curl against SubToAPI instead

If you already pay for Claude through a subscription and want an HTTPS API without separately provisioning Anthropic API billing, SubToAPI exposes the same request shape under its own endpoint and key format. The curl call looks almost identical — swap the host and the auth header:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-4-20250514",
    "max_tokens": 1024,
    "messages": [
      {"role": "user", "content": "Summarize the tradeoffs of REST vs gRPC."}
    ]
  }'

Instead of x-api-key, SubToAPI uses a standard Authorization: Bearer sub_live_... header, since that's the pattern most HTTP clients and tooling expect out of the box. Streaming, tool use, and usage metadata work the same way as the native API — see /docs/messages and /docs/streaming for the full parameter list. If you're setting this up for the first time, /docs/quickstart walks through generating a key and making your first request in under five minutes. Plans start with a free trial at /signup, and pricing details are at /pricing.

Turning curl into a script

Once the curl command works, wrapping it in a shell script or Makefile target is often enough for internal tooling — no SDK required:

#!/usr/bin/env bash
set -euo pipefail

curl -s 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-4-20250514\",
    \"max_tokens\": 1024,
    \"messages\": [{\"role\": \"user\", \"content\": \"$1\"}]
  }" | jq -r '.content[0].text'

Save that as ask.sh, run chmod +x ask.sh, and call ./ask.sh "your question" to get a plain-text answer piped through jq.

Questions

Why do I need anthropic-version if I'm not calling Anthropic's own API? It's specific to Anthropic's endpoint versioning scheme. Gateways like SubToAPI that proxy the same request format don't require it since they manage versioning internally.

Can I use curl for tool use / function calling? Yes — add a tools array to the request body with JSON schema definitions. The response and header structure stay the same as a basic message request; see /docs/tools for the full format.

Why does my curl command hang or return nothing? Usually a missing -N flag with streaming enabled, a firewall blocking outbound HTTPS, or a key that's valid but has zero remaining quota — check the response status code first with -i before assuming it's a network issue.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →