Claude Messages from Your Terminal with curl
A Claude Messages request without an SDK
When an integration breaks, I want the smallest reproducible request: a URL, headers, and a JSON file. Calling the Anthropic Messages API directly makes it easier to separate authentication problems from payload mistakes and application bugs. This walkthrough uses Bash, curl, and jq, with Fluxo as the relay.
Fluxo gives you one key and one base URL, https://api.fluxorelay.com, for Claude, GPT, Gemini, DeepSeek, Kimi, GLM, Qwen, Grok, and more. Here, we will use the Anthropic-shaped Messages endpoint rather than an OpenAI-shaped chat request.
Set your Fluxo key without saving it in shell history
If you want to follow along, create a Fluxo account and generate an API key. Run these commands in Bash; the first reads your key without displaying it. Keep the resulting environment local, and never commit it to your repository.
read -r -s -p 'Fluxo API key: ' FLUXO_API_KEY; printf '\n'; export FLUXO_API_KEY
export FLUXO_BASE_URL='https://api.fluxorelay.com'
Check your tools before building the request. These examples use curl's --fail-with-body option, available in curl 7.76.0 and later. — curl --version; jq --version
Choose the exact Claude model identifier
Start by inspecting the model catalog. Display names and request identifiers are not necessarily identical, so do not invent a lowercase slug from a product name.
curl --silent --show-error --fail-with-body "$FLUXO_BASE_URL/v1/models" -H "Authorization: Bearer $FLUXO_API_KEY" -o models.json
jq . models.json — Find Claude Haiku 4.5 in the returned catalog and copy its exact request identifier. Then paste that identifier at this prompt. Keeping it in a variable makes subsequent commands reusable.
read -r -p 'Model identifier from the catalog: ' MODEL; export MODEL
Build and send POST /v1/messages
The Messages API takes a model, an output-token limit, and a messages array. A system instruction belongs in the top-level system field, not in a message with a system role. jq handles JSON quoting so shell punctuation does not quietly corrupt your payload.
jq -n --arg model "$MODEL" '{model: $model, max_tokens: 256, system: "Answer concisely for a developer working in a terminal.", messages: [{role: "user", content: "Explain HTTP connection pooling in three sentences."}]}' > request.json
curl --silent --show-error --fail-with-body "$FLUXO_BASE_URL/v1/messages" -H "x-api-key: $FLUXO_API_KEY" -H 'anthropic-version: 2023-06-01' -H 'content-type: application/json' --data-binary @request.json -o response.json
This command uses the Anthropic-style authentication header and an explicit API version. Saving the response separately keeps your terminal readable and gives you an artifact to inspect when something fails.
jq -r '.content[]? | select(.type == "text") | .text' response.json
jq '{model, stop_reason, usage, error}' response.json
Content is an array of typed blocks, not a single text field. Filtering text blocks is fine for this example, but production clients should handle other block types deliberately. Check stop_reason: max_tokens means the output reached your requested limit.
Send conversation history explicitly
Messages requests are stateless. To continue a conversation, send the earlier user message, the assistant response, and the new user message together. Preserve the assistant's content blocks rather than flattening everything into text.
jq --slurpfile reply response.json '.messages += [{role: "assistant", content: $reply[0].content}, {role: "user", content: "Show one practical tuning mistake."}]' request.json > followup.json
Repeat the previous POST command with --data-binary @followup.json. Only build this follow-up after the first request succeeds.
Stream Messages events and inspect failures
Enable streaming in the payload, then disable curl's output buffering. The result is server-sent events, not one JSON document, so do not pipe the whole stream into jq as ordinary JSON.
jq '.stream = true' request.json > stream.json
curl --no-buffer --silent --show-error --fail-with-body "$FLUXO_BASE_URL/v1/messages" -H "x-api-key: $FLUXO_API_KEY" -H 'anthropic-version: 2023-06-01' -H 'content-type: application/json' --data-binary @stream.json
Inspect error bodies before retrying; authentication and malformed payloads need fixes, not backoff. Streaming clients must also detect error events after the connection opens.
Fluxo bills per real token, including cache reads and writes, with no monthly fee. Inspect usage rather than estimating cost from visible text. The same service also works with Claude Code, Codex, Gemini CLI, and any OpenAI SDK, though their request formats differ. Users earn a referral share when people they invite pay. If you need help reproducing a failure, leave a note on the contact page after signing in, with credentials removed.