← Blog

Claude API Rust Client Library Setup Guide

2026-10-10 · 4 min read · SubToAPI Team

There is no official Anthropic Rust SDK as of this writing, so "setting up a Claude API client in Rust" means one of two things: wiring up reqwest and serde yourself against the raw HTTPS endpoint, or pulling in a community crate that wraps it. Both work. This guide shows the manual setup first, because it's the most reliable path when community crates lag behind API changes, then covers the shortcuts.

The short answer: add reqwest, serde, serde_json, and tokio to your Cargo.toml, build a small struct that holds your API key and base URL, and implement one method that POSTs to the messages endpoint. Fifteen minutes of setup gets you a working client you fully control.

Step 1: Project and dependencies

Start a new binary or library crate:

cargo new claude-client
cd claude-client

Add the dependencies you need:

[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/serde_json handle request and response bodies, tokio gives you the async runtime, and futures-util is needed if you plan to consume streaming responses.

Step 2: Define request and response types

Model the shapes you actually send and receive. Keep it minimal at first — you can expand fields as you need them.

use serde::{Deserialize, Serialize};

#[derive(Serialize)]
struct Message {
    role: String,
    content: String,
}

#[derive(Serialize)]
struct ChatRequest {
    model: String,
    max_tokens: u32,
    messages: Vec<Message>,
}

#[derive(Deserialize, Debug)]
struct ContentBlock {
    #[serde(rename = "type")]
    block_type: String,
    text: Option<String>,
}

#[derive(Deserialize, Debug)]
struct ChatResponse {
    id: String,
    content: Vec<ContentBlock>,
    #[serde(rename = "stop_reason")]
    stop_reason: Option<String>,
}

Step 3: Build the client struct

struct ClaudeClient {
    api_key: String,
    base_url: String,
    http: reqwest::Client,
}

impl ClaudeClient {
    fn new(api_key: &str) -> Self {
        Self {
            api_key: api_key.to_string(),
            base_url: "https://api.anthropic.com/v1".to_string(),
            http: reqwest::Client::new(),
        }
    }

    async fn send_message(&self, prompt: &str) -> Result<ChatResponse, reqwest::Error> {
        let body = ChatRequest {
            model: "claude-sonnet-4-5".to_string(),
            max_tokens: 1024,
            messages: vec![Message {
                role: "user".to_string(),
                content: prompt.to_string(),
            }],
        };

        let res = self
            .http
            .post(format!("{}/messages", self.base_url))
            .header("x-api-key", &self.api_key)
            .header("anthropic-version", "2023-06-01")
            .header("content-type", "application/json")
            .json(&body)
            .send()
            .await?;

        res.json::<ChatResponse>().await
    }
}

Note the two non-standard headers: x-api-key instead of Authorization: Bearer, and anthropic-version, which is mandatory on every request. Forgetting either one is the most common cause of a 401 when developers port a client from another language into Rust.

Step 4: Call it from async main

#[tokio::main]
async fn main() {
    let api_key = std::env::var("ANTHROPIC_API_KEY").expect("set ANTHROPIC_API_KEY");
    let client = ClaudeClient::new(&api_key);

    match client.send_message("Explain ownership in Rust in two sentences.").await {
        Ok(response) => {
            for block in response.content {
                if let Some(text) = block.text {
                    println!("{}", text);
                }
            }
        }
        Err(e) => eprintln!("request failed: {}", e),
    }
}

This is enough to go from zero to a working Rust program calling Claude in about 60 lines. From here you'll want to add retry logic for 429s and 5xxs, a timeout on the reqwest::Client, and proper error types instead of propagating reqwest::Error directly.

Handling streaming in Rust

Streaming responses use server-sent events, and consuming SSE in Rust manually means handling raw bytes and parsing data: lines yourself:

use futures_util::StreamExt;

async fn stream_message(client: &reqwest::Client, api_key: &str, prompt: &str) {
    let body = serde_json::json!({
        "model": "claude-sonnet-4-5",
        "max_tokens": 1024,
        "stream": true,
        "messages": [{"role": "user", "content": prompt}]
    });

    let res = client
        .post("https://api.anthropic.com/v1/messages")
        .header("x-api-key", api_key)
        .header("anthropic-version", "2023-06-01")
        .json(&body)
        .send()
        .await
        .unwrap();

    let mut stream = res.bytes_stream();
    while let Some(chunk) = stream.next().await {
        let chunk = chunk.unwrap();
        let text = String::from_utf8_lossy(&chunk);
        for line in text.lines() {
            if line.starts_with("data:") {
                println!("{}", &line[5..]);
            }
        }
    }
}

This gets raw event text; parsing it into typed deltas requires matching on event: types (content_block_delta, message_stop, etc.) and is the part of a Rust Claude client that takes the most boilerplate to get fully correct.

When to skip writing your own client

Hand-rolling an HTTP client is fine for a side project, but it means you own API versioning, retry backoff, streaming parsing, and key rotation yourself — in every service that calls Claude. If you're running this from multiple services or a team, SubToAPI gives you a single HTTPS endpoint with sub_live_... application keys, so your Rust struct above barely changes — you just point base_url at https://api.subtoapi.app/v1 and swap the x-api-key header for Authorization: Bearer $SUBTOAPI_KEY. You get streaming, usage metadata, and per-key limits without maintaining that logic in Rust yourself. Check the quickstart and messages endpoint docs for the exact request shape, and streaming docs if you're building the SSE consumer above.

Questions

Is there an official Claude Rust SDK? No, Anthropic does not publish an official Rust crate. Developers either build a thin reqwest-based client, as shown above, or use a community crate, which may lag behind API updates.

What crates do I actually need for a minimal Claude client in Rust? reqwest with the json and stream features, serde and serde_json for (de)serialization, and tokio for async. Add futures-util only if you're consuming streaming responses.

Why does my Rust client get a 401 even with a valid key? Almost always a missing anthropic-version header, or using Authorization: Bearer instead of the x-api-key header that Claude's raw API expects. If you're calling SubToAPI instead, it's the reverse — use Authorization: Bearer $SUBTOAPI_KEY as shown in the docs.

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 →