← Blog

Claude API C# .NET Integration: A Practical Guide

2026-10-18 · 5 min read · SubToAPI Team

There is no official Anthropic SDK for .NET, which is the first thing most C# developers discover when they start searching for "claude api c sharp dotnet integration." That's not a blocker — the Claude API is a plain HTTPS/JSON API, and .NET's HttpClient handles it without any extra dependencies. The real work is structuring the request correctly, parsing streamed responses, and managing authentication across your app.

This guide walks through a working integration: setting up the HTTP client, sending a message request, handling streaming responses with IAsyncEnumerable, and dealing with tool calls. It also covers where a managed API layer like SubToAPI removes some of the repetitive plumbing if you'd rather not maintain low-level HTTP code for every new feature.

Setting up the HTTP client

Create a single HttpClient instance and reuse it — .NET's HttpClientFactory is the recommended pattern for anything beyond a quick script, since creating a new client per request can exhaust sockets under load.

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

var client = new HttpClient
{
    BaseAddress = new Uri("https://api.anthropic.com/v1/")
};

client.DefaultRequestHeaders.Add("x-api-key", Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"));
client.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

Store the API key in an environment variable or in appsettings.json via IConfiguration — never hardcode it in source. If you're building a multi-tenant app where different customers each need their own Claude access, this is where things get more complex: you either proxy every request through your own backend with a shared key, or you give each tenant an isolated key. SubToAPI exists specifically for that second case — it issues scoped sub_live_... keys per app or team so you don't build that isolation layer yourself.

Sending a basic message request

A request to the Messages endpoint is a POST with a JSON body containing the model, max tokens, and a messages array:

public async Task<string> SendMessageAsync(string userMessage)
{
    var payload = new
    {
        model = "claude-3-5-sonnet-20241022",
        max_tokens = 1024,
        messages = new[]
        {
            new { role = "user", content = userMessage }
        }
    };

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

    var response = await client.SendAsync(request);
    response.EnsureSuccessStatusCode();

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

EnsureSuccessStatusCode is enough for a prototype, but production code should check for 429s and 529s explicitly and implement exponential backoff — Claude's API rate-limits aggressively under load, and .NET's Polly library pairs well with HttpClient for retry policies.

Handling streaming responses

Streaming is where the C# integration gets genuinely more involved than in languages with first-class SSE libraries. The Claude API streams server-sent events, and .NET doesn't have a built-in SSE parser — you read the response stream line by line and parse data: prefixed JSON chunks yourself.

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

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

    using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    response.EnsureSuccessStatusCode();

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

    while (!reader.EndOfStream)
    {
        var line = await reader.ReadLineAsync();
        if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data: ")) continue;

        var data = line["data: ".Length..];
        using var doc = JsonDocument.Parse(data);
        var root = doc.RootElement;

        if (root.TryGetProperty("delta", out var delta) &&
            delta.TryGetProperty("text", out var text))
        {
            yield return text.GetString() ?? "";
        }
    }
}

Consume it with await foreach:

await foreach (var chunk in StreamMessageAsync("Explain dependency injection in .NET"))
{
    Console.Write(chunk);
}

This works, but it's hand-rolled — you're responsible for handling reconnects, malformed chunks, and the various event types (message_start, content_block_delta, message_stop) correctly. If your application needs streaming in multiple places (a chat UI, a background worker, a CLI tool), this parsing logic gets duplicated unless you extract it into a shared library. SubToAPI's streaming endpoint (/docs/streaming) returns the same SSE format, so this exact parser works against it unchanged — you're swapping the base URL and auth header, not rewriting the client.

Tool use from C#

Claude's tool use (function calling) works the same way over HTTP regardless of language — you define tool schemas in the request, and Claude returns a tool_use content block instead of plain text when it decides to call one:

var payload = new
{
    model = "claude-3-5-sonnet-20241022",
    max_tokens = 1024,
    tools = new object[]
    {
        new
        {
            name = "get_weather",
            description = "Get current weather for a city",
            input_schema = new
            {
                type = "object",
                properties = new
                {
                    city = new { type = "string" }
                },
                required = new[] { "city" }
            }
        }
    },
    messages = new[] { new { role = "user", content = "What's the weather in Lisbon?" } }
};

Parse the response for a tool_use block, run your actual C# method (an HTTP call, a database query, whatever the tool represents), then send the result back as a tool_result message in a follow-up request. This round-trip is identical whether you're hitting Anthropic directly or through SubToAPI — see /docs/tools for the request/response shapes.

Where SubToAPI fits

If you're integrating Claude into an internal tool or a small SaaS, the parts above — retries, streaming parsers, key management, usage tracking — add up to real maintenance work that has nothing to do with your actual product. SubToAPI wraps the same Messages API behind https://api.subtoapi.app/v1/messages, issues per-application keys you can rotate independently, and gives you usage and spend visibility per key without building a billing dashboard yourself. For a C# app, nothing in the integration code changes — same HttpClient, same JSON shapes, different base URL and bearer token:

client.BaseAddress = new Uri("https://api.subtoapi.app/v1/");
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("SUBTOAPI_KEY"));

Check /docs/quickstart and /docs/messages for the full request reference, and /pricing if you're comparing Solo, Team, and Scale plans for a project with multiple developers or services hitting the API.

questions

Is there an official Anthropic SDK for .NET? No. Anthropic maintains official SDKs for Python and TypeScript/JavaScript. For C# and .NET, you call the REST API directly with HttpClient, as shown above, or use one of several community NuGet packages that wrap the same endpoints.

How do I handle Claude API streaming in ASP.NET Core? Read the HTTP response as a stream with HttpCompletionOption.ResponseHeadersRead, parse each data: line as JSON, and expose the result to clients via Server-Sent Events or a SignalR hub, since raw SSE isn't natively supported by browsers connecting to arbitrary backends without CORS handling.

Can I use SubToAPI instead of calling Anthropic directly from .NET? Yes. The request and response formats match, so existing HttpClient code works unchanged — you just point it at api.subtoapi.app and use a sub_live_... key from your dashboard after signing up at /signup.

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 →