← Blog

Claude API Integration with .NET Core: A Guide

2026-09-27 · 5 min read · SubToAPI Team

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.

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 →