Claude API Rust Client Library Example
Is there an official Claude API Rust SDK?
No. Anthropic ships official SDKs for Python and TypeScript/JavaScript, but there is no first-party Rust client library as of now. If you're building a Rust application that talks to Claude, you have two practical options: write a thin HTTP client yourself using reqwest and serde, or use one of the community-maintained crates (search crates.io for "anthropic" — quality and maintenance vary, so check the last commit date before depending on one).
This article shows you how to build a minimal, production-usable Claude client in Rust from scratch, since that's the most reliable path and takes under 50 lines of code. It also covers streaming, tool use, and error handling, which is where most hand-rolled clients break down.
Setting up the project
Add the dependencies you need to 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"
reqwest handles HTTP, serde handles JSON (de)serialization, and tokio gives you the async runtime. futures-util is only needed if you implement streaming.
A minimal Claude client struct
use serde::{Deserialize, Serialize};
use reqwest::Client;
#[derive(Serialize)]
struct Message {
role: String,
content: String,
}
#[derive(Serialize)]
struct MessagesRequest {
model: String,
max_tokens: u32,
messages: Vec<Message>,
}
#[derive(Deserialize, Debug)]
struct ContentBlock {
text: String,
}
#[derive(Deserialize, Debug)]
struct MessagesResponse {
content: Vec<ContentBlock>,
}
pub struct ClaudeClient {
api_key: String,
http: Client,
}
impl ClaudeClient {
pub fn new(api_key: &str) -> Self {
Self {
api_key: api_key.to_string(),
http: Client::new(),
}
}
pub async fn send(&self, prompt: &str) -> Result<String, reqwest::Error> {
let body = MessagesRequest {
model: "claude-3-5-sonnet-20241022".to_string(),
max_tokens: 1024,
messages: vec![Message {
role: "user".to_string(),
content: prompt.to_string(),
}],
};
let resp = self
.http
.post("https://api.anthropic.com/v1/messages")
.header("x-api-key", &self.api_key)
.header("anthropic-version", "2023-06-01")
.header("content-type", "application/json")
.json(&body)
.send()
.await?
.json::<MessagesResponse>()
.await?;
Ok(resp.content.first().map(|c| c.text.clone()).unwrap_or_default())
}
}
That's a working client: construct it with your key, call .send(), and you get the text response back as a String. In real code you'd want to handle non-200 responses explicitly rather than letting .json() fail silently — Anthropic returns structured error JSON on 4xx/5xx, and you should parse that into a proper error type instead of propagating a generic deserialization failure.
Handling errors properly
A common mistake in hand-written Rust clients is assuming the response body always matches your success struct. Check the status code first:
let resp = self.http.post(url).json(&body).send().await?;
if !resp.status().is_success() {
let err_body = resp.text().await?;
return Err(format!("Claude API error: {}", err_body).into());
}
let parsed: MessagesResponse = resp.json().await?;
This matters because rate limits (429), auth failures (401), and overloaded errors (529) all return different payloads, and your retry logic should branch on them.
Streaming responses in Rust
Streaming is where custom Rust clients get noticeably more code, since you have to parse server-sent events manually. With reqwest's bytes_stream() feature:
use futures_util::StreamExt;
let mut stream = self
.http
.post("https://api.anthropic.com/v1/messages")
.header("x-api-key", &self.api_key)
.header("anthropic-version", "2023-06-01")
.json(&stream_body)
.send()
.await?
.bytes_stream();
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
let text = String::from_utf8_lossy(&chunk);
for line in text.lines() {
if let Some(data) = line.strip_prefix("data: ") {
println!("{data}");
}
}
}
You'll need to accumulate partial JSON lines and match on event types (content_block_delta, message_stop, etc.) to build a usable abstraction. This is roughly 150–200 lines of real code once you handle tool-use deltas and partial UTF-8 boundaries correctly — it's the single biggest reason people reach for an SDK instead of writing their own.
A simpler path: a plain HTTPS endpoint
If you don't want to maintain streaming parsing, retry logic, and error-type mapping yourself, you can route your Rust service through SubToAPI instead of calling Anthropic directly. SubToAPI turns your existing Claude access into a standard HTTPS API with an application key (sub_live_...), so the Rust code above barely changes — you just swap the base URL and header:
.post("https://api.subtoapi.app/v1/messages")
.header("Authorization", format!("Bearer {}", std::env::var("SUBTOAPI_KEY")?))
.header("content-type", "application/json")
That gets you usage metadata per request, team seats if multiple services or developers share the same underlying access, and a dashboard to see spend without building your own logging. Full request/response shapes are in the docs and Messages reference; streaming behavior is documented at /docs/streaming and tool-use payloads at /docs/tools. There's a free trial at /signup and plans start at €9/month on the pricing page.
Tool use in Rust
Tool calls follow the same JSON-over-HTTP pattern — define a tools array in your request struct, matching the JSON schema Claude expects, and check response.content for blocks with type: "tool_use" instead of "text". Model this with an enum and a custom Deserialize implementation (or #[serde(tag = "type")]) rather than a single struct, since content blocks can be text, tool_use, or tool_result depending on the turn.
Choosing a community crate vs. rolling your own
If you'd rather not maintain HTTP plumbing, check crates.io for actively maintained Anthropic clients before writing your own — but verify the crate supports the request/response shape you need (especially streaming and tool use), since many community crates lag behind API changes. For most production services, a 50-line internal wrapper like the one above, pointed at either Anthropic directly or a gateway like SubToAPI, is easier to keep correct than depending on a third-party crate's release cadence.
questions
Is there an official Rust SDK for the Claude API? No. Anthropic currently maintains official SDKs for Python and TypeScript only. Rust developers use reqwest/serde directly or a community crate.
Can I use async Rust with the Claude API? Yes. reqwest with the tokio runtime handles both standard requests and streaming responses via bytes_stream(), which is the pattern shown above.
Do I need a custom Rust client if I use SubToAPI? No — SubToAPI exposes a standard JSON-over-HTTPS API, so the same reqwest client works; you only change the base URL and the bearer token header.