Claude API C# / .NET Example

Call the Claude API from C# using HttpClient. Complete working example with async/await, system prompt, streaming SSE, and exponential-backoff retry — no official .NET SDK required.

💥 50p impulse-buy: Power Prompts PDF (first 10 buyers) 30 battle-tested Claude Code prompts · 8-page PDF · paste into CLAUDE.md and never re-type a prompt again · 50p impulse-buy, no commitment

There is no official Anthropic .NET SDK. This guide shows the complete, production-ready pattern for calling the Claude API from C# using HttpClient and System.Text.Json — no third-party packages required.

Requirements

// .NET 8 (recommended) or .NET 6+
// No NuGet packages beyond the BCL

Minimal example

using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")
    ?? throw new InvalidOperationException("ANTHROPIC_API_KEY not set");

using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", apiKey);
http.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");

var payload = new
{
    model = "claude-sonnet-4-6",
    max_tokens = 1024,
    messages = new[] { new { role = "user", content = "Explain prompt caching in one paragraph." } }
};

var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await http.PostAsync("https://api.anthropic.com/v1/messages", content);
response.EnsureSuccessStatusCode();

var body = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(body);
var text = doc.RootElement
    .GetProperty("content")[0]
    .GetProperty("text")
    .GetString();

Console.WriteLine(text);

With system prompt

var payload = new
{
    model = "claude-sonnet-4-6",
    max_tokens = 512,
    system = "You are a concise technical writer. Answer in bullet points.",
    messages = new[]
    {
        new { role = "user", content = "What are the Claude API rate limits?" }
    }
};

Reusable ClaudeClient class

public class ClaudeClient : IDisposable
{
    private readonly HttpClient _http;
    private const string BaseUrl = "https://api.anthropic.com/v1/messages";

    public ClaudeClient(string apiKey)
    {
        _http = new HttpClient();
        _http.DefaultRequestHeaders.Add("x-api-key", apiKey);
        _http.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
    }

    public async Task<string> CompleteAsync(
        string userMessage,
        string? systemPrompt = null,
        string model = "claude-sonnet-4-6",
        int maxTokens = 1024)
    {
        var messages = new[] { new { role = "user", content = userMessage } };
        object payload = systemPrompt is null
            ? new { model, max_tokens = maxTokens, messages }
            : new { model, max_tokens = maxTokens, system = systemPrompt, messages };

        var content = new StringContent(
            JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");

        var resp = await _http.PostAsync(BaseUrl, content);
        resp.EnsureSuccessStatusCode();

        using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
        return doc.RootElement.GetProperty("content")[0].GetProperty("text").GetString()!;
    }

    public void Dispose() => _http.Dispose();
}

// Usage:
using var claude = new ClaudeClient(
    Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")!);
string answer = await claude.CompleteAsync("What is a context window?");
Console.WriteLine(answer);

Streaming SSE in C#

public async IAsyncEnumerable<string> StreamAsync(string userMessage)
{
    var payload = new
    {
        model = "claude-sonnet-4-6",
        max_tokens = 1024,
        stream = true,
        messages = new[] { new { role = "user", content = userMessage } }
    };

    var req = new HttpRequestMessage(HttpMethod.Post, BaseUrl)
    {
        Content = new StringContent(
            JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
    };

    using var resp = await _http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
    resp.EnsureSuccessStatusCode();

    using var stream = await resp.Content.ReadAsStreamAsync();
    using var reader = new StreamReader(stream);

    while (!reader.EndOfStream)
    {
        var line = await reader.ReadLineAsync();
        if (string.IsNullOrEmpty(line) || !line.StartsWith("data:")) continue;
        var data = line.Substring(5).Trim();
        if (data == "[DONE]") break;

        using var doc = JsonDocument.Parse(data);
        var root = doc.RootElement;
        if (root.TryGetProperty("delta", out var delta) &&
            delta.TryGetProperty("text", out var textEl))
        {
            yield return textEl.GetString() ?? "";
        }
    }
}

// Usage:
var sb = new StringBuilder();
await foreach (var chunk in claude.StreamAsync("Write a haiku about APIs."))
{
    Console.Write(chunk);
    sb.Append(chunk);
}
Console.WriteLine();
string fullResponse = sb.ToString();

Exponential-backoff retry for 429 errors

public async Task<string> CompleteWithRetryAsync(string message, int maxRetries = 4)
{
    for (int attempt = 0; attempt <= maxRetries; attempt++)
    {
        try { return await CompleteAsync(message); }
        catch (HttpRequestException ex) when (attempt < maxRetries)
        {
            // Retry on 429 (rate limit) and 529 (overloaded)
            if (ex.StatusCode is not (System.Net.HttpStatusCode.TooManyRequests
                or (System.Net.HttpStatusCode)529))
                throw;

            var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt))
                        + TimeSpan.FromMilliseconds(Random.Shared.Next(0, 200));
            await Task.Delay(delay);
        }
    }
    throw new InvalidOperationException("Max retries exceeded");
}

Model and pricing quick reference

Model IDInput $/MTokOutput $/MTokBest for
claude-haiku-4-5-20251001$0.80$4.00High-volume, latency-sensitive
claude-sonnet-4-6$3.00$15.00Most workloads (best price/quality)
claude-opus-4-7$15.00$75.00Long-context reasoning, highest quality

Estimate costs for your .NET app at the Claude API Cost Calculator. For more API patterns, see the error handling and retry guide and rate limits explained.

Frequently asked questions

Is there an official Anthropic C# / .NET SDK?
Not as of mid-2026. Anthropic provides official SDKs for Python, Node.js, TypeScript, Java, Go, and Ruby, but not C#. Use `System.Net.Http.HttpClient` with `System.Text.Json` to call the REST API directly — the example on this page shows the complete boilerplate.
Which NuGet packages do I need to call the Claude API from C#?
None beyond the .NET base class libraries. `System.Net.Http` (HttpClient), `System.Text.Json` (JsonSerializer), and `System.IO` are all in-box from .NET 6+. No third-party package required.
How do I stream Claude responses in C#?
Set `stream: true` in your JSON body. Use `HttpCompletionOption.ResponseHeadersRead` when sending the request, then read the response stream line-by-line. Each SSE line starting with `data:` contains a JSON object with a `delta.text` field — accumulate these into your StringBuilder.
What .NET version do I need for the Claude API?
.NET 6 or later is recommended. It ships a modern `HttpClient`, async streams (`IAsyncEnumerable`), and `System.Text.Json` with source-generator support. The examples on this page target .NET 8 (current LTS as of 2026).
How do I handle rate limiting (429) from the Anthropic API in C#?
Check for `HttpStatusCode.TooManyRequests` in the response. Read the `retry-after` header if present; otherwise use exponential backoff starting at 1 second, doubling up to 32 seconds with ±10% jitter. The Retry class in the example below implements this pattern.

Free tools

Cost Calculator → API Cookbook → Diff Summarizer → Skills Browser →

More examples

Claude API Python QuickstartClaude API Node.js / TypeScript QuickstartClaude API Streaming in PythonClaude API Streaming in Node.js / TypeScriptClaude API Tool Use in PythonClaude API Tool Use in Node.js / TypeScript