Claude API C# .NET Tutorial: HttpClient Guide
Calling Claude from C# and .NET
There's no official Anthropic SDK for .NET, which is the main reason developers search for a Claude API C# .NET tutorial instead of just installing a package. The good news: Claude's API is plain HTTPS with JSON, so HttpClient and System.Text.Json are all you need. This guide walks through a working .NET console app that sends a request, parses the response, and handles streaming — plus how to simplify the whole thing if you'd rather skip manual HTTP plumbing.
Everything below works with .NET 6, 7, or 8. If you're on an older framework, the same logic applies with minor syntax tweaks.
Setting Up Your .NET Project
Create a console project and add the only dependency you actually need:
dotnet new console -n ClaudeDemo
cd ClaudeDemo
System.Net.Http and System.Text.Json ship with .NET, so there's nothing to install for a basic integration. Store your API key as an environment variable rather than hardcoding it:
export CLAUDE_API_KEY="your-api-key-here"
On Windows PowerShell: $env:CLAUDE_API_KEY="your-api-key-here".
Making Your First Request with HttpClient
Here's a minimal request/response cycle against an Anthropic-compatible messages endpoint:
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var apiKey = Environment.GetEnvironmentVariable("CLAUDE_API_KEY");
var client = new HttpClient { BaseAddress = new Uri("https://api.anthropic.com/") };
client.DefaultRequestHeaders.Add("x-api-key", apiKey);
client.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
var payload = new
{
model = "claude-3-5-sonnet-20241022",
max_tokens = 1024,
messages = new[]
{
new { role = "user", content = "Explain dependency injection in one paragraph." }
}
};
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("v1/messages", content);
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
This runs, but parsing raw JSON strings every time isn't maintainable. The next step is modeling the response.
Building a Strongly-Typed Client
Define C# records that map to the response shape, then deserialize into them instead of printing raw strings:
public record ContentBlock(string Type, string? Text);
public record ClaudeResponse(
string Id,
string Model,
List<ContentBlock> Content,
string Role
);
var result = JsonSerializer.Deserialize<ClaudeResponse>(body, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
});
var answer = result?.Content.FirstOrDefault(c => c.Type == "text")?.Text;
Console.WriteLine(answer);
Wrap the whole thing in a service class with a single SendMessageAsync method, inject HttpClient via IHttpClientFactory in ASP.NET Core apps, and you have something production-usable without writing or maintaining a full SDK.
Handling Streaming Responses in C#
Streaming matters for chat UIs and long completions — nobody wants to wait 20 seconds staring at a blank screen. Claude streams responses as server-sent events, and .NET can read them with HttpResponseMessage.Content.ReadAsStreamAsync():
var request = new HttpRequestMessage(HttpMethod.Post, "v1/messages")
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
if (line is null || !line.StartsWith("data:")) continue;
var data = line["data:".Length..].Trim();
if (data == "[DONE]") break;
Console.Write(data);
}
This is the part that trips up most C# developers new to the Claude API — SSE parsing isn't built into HttpClient, so you're reading lines manually and filtering out event metadata.
Error Handling and Retries
Production code needs to handle rate limits (429), overloaded errors (529), and transient network failures. A simple exponential backoff wrapper covers most cases:
async Task<HttpResponseMessage> SendWithRetryAsync(Func<Task<HttpResponseMessage>> call, int maxRetries = 3)
{
for (int attempt = 0; attempt < maxRetries; attempt++)
{
var response = await call();
if (response.IsSuccessStatusCode) return response;
if ((int)response.StatusCode is not (429 or 529)) return response;
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}
throw new Exception("Max retries exceeded");
}
Log status codes and response bodies on failure — Claude's error responses include a type field that tells you exactly what went wrong (invalid request, authentication, overloaded, etc.).
Using SubToAPI to Simplify Claude Access from .NET
If you'd rather not maintain custom HTTP plumbing, retry logic, and header management across every .NET service that talks to Claude, SubToAPI gives you a standard HTTPS API with sub_live_... application keys, built-in streaming, and usage metadata — the same HttpClient code above works against it with a different base URL and Authorization header:
client.BaseAddress = new Uri("https://api.subtoapi.app/v1/");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("SUBTOAPI_KEY"));
var response = await client.PostAsync("messages", content);
This is useful for teams who want one dashboard to issue and revoke application keys for multiple .NET services, track usage across environments, and manage seats without juggling raw Anthropic credentials in every project. Check the quickstart or the messages and streaming docs for the full request/response reference, and see pricing for Solo, Team, and Scale plans. You can also start with a free trial at signup.
questions
Is there an official Anthropic SDK for C# or .NET? No. Anthropic maintains official SDKs for Python and TypeScript only. For C#, developers use HttpClient directly or rely on community NuGet packages, which vary in maintenance quality.
Can I stream Claude responses in a Blazor or ASP.NET Core app? Yes. Read the response stream with HttpCompletionOption.ResponseHeadersRead, parse server-sent event lines, and push tokens to the client via SignalR or a streaming HTTP response as they arrive.
Do I need a separate HTTP client for each Claude model? No. The model is just a string field in the request payload (e.g. claude-3-5-sonnet-20241022). One client class can call any available model by changing that parameter.