Claude API Integration with .NET Core: A Guide
Integrating the Claude API into a .NET Core application means adding a typed HTTP client, handling JSON serialization for messages, and managing authentication headers correctly — there's no official Anthropic SDK for .NET, so you're working directly with HttpClient and System.Text.Json. This guide walks through a working setup: project structure, request/response models, streaming, tool use, and error handling.
If you just need a working endpoint without maintaining the plumbing yourself, you can also route requests through SubToAPI, which exposes Claude over a standard HTTPS API with an application key — useful when you want to skip token management and rate-limit handling in your .NET codebase.
Setting Up the Project
Start with a standard ASP.NET Core project (Web API or a console app, the client code is the same either way):
dotnet new webapi -n ClaudeDotNetDemo
cd ClaudeDotNetDemo
No third-party NuGet package is required — System.Net.Http.Json (built into .NET Core) handles serialization cleanly.
Register a named HttpClient in Program.cs:
builder.Services.AddHttpClient("Claude", client =>
{
client.BaseAddress = new Uri("https://api.anthropic.com/");
client.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
client.DefaultRequestHeaders.Add("x-api-key", builder.Configuration["Claude:ApiKey"]);
});
Store the key in appsettings.json for local dev and in environment variables or a secrets manager in production. Never hardcode it.
Calling Claude from C#
Define minimal request/response models — you don't need to map every field, just what you use:
public record ClaudeMessage(string Role, string Content);
public record ClaudeRequest(
string Model,
int MaxTokens,
List<ClaudeMessage> Messages
);
A basic call using IHttpClientFactory:
public class ClaudeService
{
private readonly IHttpClientFactory _factory;
public ClaudeService(IHttpClientFactory factory) => _factory = factory;
public async Task<string> AskClaudeAsync(string prompt)
{
var client = _factory.CreateClient("Claude");
var request = new ClaudeRequest(
Model: "claude-3-5-sonnet-latest",
MaxTokens: 1024,
Messages: new() { new ClaudeMessage("user", prompt) }
);
var response = await client.PostAsJsonAsync("v1/messages", request);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync();
using var doc = await JsonDocument.ParseAsync(stream);
return doc.RootElement
.GetProperty("content")[0]
.GetProperty("text")
.GetString() ?? string.Empty;
}
}
Register the service with builder.Services.AddScoped<ClaudeService>() and inject it into your controllers.
Handling Streaming Responses
For chat UIs or long-form generation, streaming avoids making users wait for the full response. Claude streams server-sent events (SSE), which .NET can read line by line from the response stream:
public async IAsyncEnumerable<string> StreamClaudeAsync(string prompt)
{
var client = _factory.CreateClient("Claude");
var request = new
{
model = "claude-3-5-sonnet-latest",
max_tokens = 1024,
stream = true,
messages = new[] { new { role = "user", content = prompt } }
};
var httpRequest = new HttpRequestMessage(HttpMethod.Post, "v1/messages")
{
Content = JsonContent.Create(request)
};
using var response = await client.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead);
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
if (line.StartsWith("data: "))
{
var json = line["data: ".Length..];
if (json == "[DONE]") yield break;
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("delta", out var delta) &&
delta.TryGetProperty("text", out var text))
{
yield return text.GetString() ?? string.Empty;
}
}
}
}
Expose this as an IAsyncEnumerable<string> from a controller action returning text/event-stream, and consume it on the frontend with EventSource or fetch with a reader.
Adding Tool Use
Claude's tool use feature lets the model call functions you define — useful for looking up data, running calculations, or triggering actions in your .NET backend. Define tools as JSON schemas in the request body:
var tools = new[]
{
new
{
name = "get_order_status",
description = "Look up the status of an order by ID",
input_schema = new
{
type = "object",
properties = new { order_id = new { type = "string" } },
required = new[] { "order_id" }
}
}
};
When Claude's response includes a tool_use block, parse the input object, run your actual C# logic (e.g., a database lookup), and send the result back as a tool_result message in a follow-up request. This request/response loop is the same regardless of language — the .NET-specific part is just JSON parsing with JsonDocument or deserializing into your own DTOs.
Error Handling and Retries
Production code needs to handle rate limits (HTTP 429), transient network failures, and malformed responses. A simple retry policy with Polly works well:
dotnet add package Microsoft.Extensions.Http.Polly
builder.Services.AddHttpClient("Claude")
.AddTransientHttpErrorPolicy(policy =>
policy.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));
Always check response.IsSuccessStatusCode before parsing, and log the raw response body on failure — it usually contains a clear error message from the API.
A Simpler Path with SubToAPI
If you'd rather not manage API keys, retry logic, and usage tracking yourself across multiple .NET services or teams, SubToAPI sits in front of Claude and gives you a single application key (sub_live_...), request logging, and per-team usage metadata — all through the same messages endpoint shape, so the C# code above needs almost no changes beyond the base URL and header:
client.BaseAddress = new Uri("https://api.subtoapi.app/");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", builder.Configuration["SubToAPI:Key"]);
The quickstart docs cover authentication and request formatting, and pricing starts at €9/month for solo projects with team seats available as you scale.
Wrapping Up
A working Claude integration in .NET Core comes down to three things: a properly configured HttpClient, correct JSON handling for both standard and streamed responses, and solid error handling around rate limits. Once that foundation is in place, adding tool use or swapping models is a matter of changing the request payload, not rewriting your client code.
Questions
Do I need a NuGet package to call Claude from .NET? No. System.Net.Http.Json and System.Text.Json, both built into .NET Core, are enough to send requests and parse responses without any third-party SDK.
How do I handle Claude's streaming responses in ASP.NET Core? Read the HTTP response stream line by line, parse each data: line as JSON, and expose the results to clients as IAsyncEnumerable<string> or forward them over text/event-stream.
Can I use dependency injection with the Claude HTTP client? Yes — register a named HttpClient via AddHttpClient in Program.cs and inject IHttpClientFactory into your services, which is the standard ASP.NET Core pattern for external API calls.