Claude API Rust Client Example: A Working Guide
If you're searching for a Claude API Rust client example, you're probably trying to do one of two things: call Claude from a Rust backend without pulling in a heavyweight SDK, or figure out how to structure requests, streaming, and error handling idiomatically in Rust. Anthropic doesn't publish an official Rust SDK, so most Rust developers end up writing a thin client on top of reqwest and serde. This article walks through exactly how to do that, with working code you can drop into a project today.
The short version: you need reqwest for HTTP, serde/serde_json for JSON (de)serialization, and tokio for async. From there, calling Claude is a straightforward POST request with a JSON body and two headers. Streaming adds a bit of complexity because you need to parse server-sent events manually, since there's no Rust SSE helper baked into the ecosystem the way there is for JavaScript.
Setting Up Your Cargo Project
Start with a minimal Cargo.toml:
[dependencies]
reqwest = { version = "0.12", features = ["json", "stream"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
futures-util = "0.3"
This gives you an async HTTP client, JSON support, a futures-based stream reader for SSE, and the Tokio runtime to drive it all.
A Basic Request-Response Client
Here's a minimal client struct that sends a message and returns the response text:
use serde::{Deserialize, Serialize};
use reqwest::Client;
#[derive(Serialize)]
struct Message {
role: String,
content: String,
}
#[derive(Serialize)]
struct MessageRequest {
model: String,
max_tokens: u32,
messages: Vec<Message>,
}
#[derive(Deserialize)]
struct ContentBlock {
text: String,
}
#[derive(Deserialize)]
struct MessageResponse {
content: Vec<ContentBlock>,
}
pub async fn ask_claude(api_key: &str, prompt: &str) -> Result<String, reqwest::Error> {
let client = Client::new();
let body = MessageRequest {
model: "claude-sonnet-4-5".to_string(),
max_tokens: 1024,
messages: vec![Message {
role: "user".to_string(),
content: prompt.to_string(),
}],
};
let res: MessageResponse = client
.post("https://api.anthropic.com/v1/messages")
.header("x-api-key", api_key)
.header("anthropic-version", "2023-06-01")
.json(&body)
.send()
.await?
.json()
.await?;
Ok(res.content.into_iter().map(|c| c.text).collect())
}
This is the core pattern you'll reuse everywhere: build the struct, serialize it with serde, send it with reqwest, deserialize the response. The important detail Rust developers often miss is that Anthropic's auth header is x-api-key, not a Bearer token, and you need the anthropic-version header on every request or you'll get a 400.
Handling Streaming Responses
Streaming is where Rust clients get noticeably more involved than their JavaScript equivalents. You set "stream": true in the request body, then read the response body as a byte stream and split it into SSE events manually:
use futures_util::StreamExt;
pub async fn stream_claude(api_key: &str, prompt: &str) -> Result<(), reqwest::Error> {
let client = Client::new();
let body = serde_json::json!({
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"stream": true,
"messages": [{"role": "user", "content": prompt}]
});
let mut stream = client
.post("https://api.anthropic.com/v1/messages")
.header("x-api-key", api_key)
.header("anthropic-version", "2023-06-01")
.json(&body)
.send()
.await?
.bytes_stream();
while let Some(chunk) = stream.next().await {
let bytes = chunk?;
let text = String::from_utf8_lossy(&bytes);
for line in text.lines() {
if let Some(data) = line.strip_prefix("data: ") {
println!("{data}");
}
}
}
Ok(())
}
This naive line-splitting works for most cases but can break if a chunk boundary falls in the middle of an SSE event. For production code, buffer partial lines across chunks rather than assuming each chunk contains complete lines.
Error Handling and Retries
Anthropic's API returns structured JSON error bodies with a type and message field on non-2xx responses. A Rust client should check the status code before trying to deserialize the success shape:
let response = client.post(url).json(&body).send().await?;
if !response.status().is_success() {
let status = response.status();
let err_body = response.text().await.unwrap_or_default();
return Err(format!("Claude API error {status}: {err_body}").into());
}
For 429 and 529 responses, implement exponential backoff with jitter. Rust's tokio::time::sleep combined with a simple retry loop is enough — you don't need an external crate unless you want jittered backoff out of the box, in which case backoff on crates.io works well with async closures.
When Not to Hand-Roll the Client
Writing your own Rust client is reasonable when you control both ends and want minimal dependencies. It gets less reasonable once you need multiple application-level API keys, per-key usage tracking, team seat management, or a dashboard for non-engineers on your team to see spend. That's infrastructure you'd otherwise build yourself on top of a single shared Claude key.
SubToAPI sits in front of your existing Claude access and exposes a clean HTTPS API with sub_live_... keys scoped per application or team member, so your Rust client code barely changes — you just point it at https://api.subtoapi.app/v1/messages and swap the header for Authorization: Bearer $SUBTOAPI_KEY:
let res = client
.post("https://api.subtoapi.app/v1/messages")
.header("Authorization", format!("Bearer {}", subtoapi_key))
.json(&body)
.send()
.await?;
Streaming, tool use, and usage metadata all work the same way through the proxy, so the Rust code above is directly reusable. Check the quickstart and messages docs for the exact request/response shapes, and streaming docs if you're building the SSE client described above. Plans start at €9/month on the Solo tier, with Team and Scale tiers for per-seat billing — see pricing for details, or just sign up and try it with a free trial.
questions
Is there an official Anthropic Rust SDK? No. Anthropic maintains official SDKs for Python and TypeScript/JavaScript. Rust developers build their own thin client using reqwest and serde, as shown above, since the REST API itself is straightforward to wrap.
Which Rust crates do I need to call the Claude API? At minimum reqwest (with json and stream features), serde/serde_json, and tokio for the async runtime. futures-util is useful for consuming streaming responses chunk by chunk.
How do I handle Claude's streaming format in Rust? Read the HTTP response as a byte stream, buffer incoming bytes, and split on newlines to find lines prefixed with data: . Each of those lines contains a JSON event you deserialize separately; buffer partial lines across chunk boundaries to avoid dropped data.