n4nAI

NuGet setup for an OpenAI-compatible C# client

Install and configure a NuGet OpenAI-compatible C# client for .NET apps: package setup, base URL, auth, and a test chat call to verify.

n4n Team4 min read823 words

Audio narration

Coming soon — every post will get a voice note here.

Wiring up a nuget openai compatible c# client against an OpenAI-style endpoint is straightforward, but the devil is in the package choice and the HttpClient lifecycle. This guide walks through a minimal .NET setup that talks to any OpenAI-compatible API without custom glue code, then scales it to a production-grade ASP.NET Core service.

Step 1: Scaffold the .NET project

Create a fresh console app to isolate the experiment. Use the SDK-style project system; it’s the only sane choice in 2024.

dotnet new console -n LlmClientDemo
cd LlmClientDemo

Target .NET 8 or later. The official OpenAI client library uses modern C# features (nullable reference types, async streams) and ships as a trimmed-friendly package. If you’re on an older LTS, bump the <TargetFramework> to net8.0 in the csproj. Pin your SDK version with a global.json if the team uses multiple runtimes:

{
  "sdk": { "version": "8.0.100", "rollForward": "latestFeature" }
}

Step 2: Install the NuGet package

The nuget openai compatible c# client we’ll use is the official OpenAI package maintained by OpenAI. It implements the REST contract exposed by OpenAI and any endpoint that mirrors /v1/chat/completions, /v1/embeddings, and the rest of the surface.

dotnet add package OpenAI

Avoid community forks that wrap the API with magic defaults or hard-code api.openai.com. The official package lets you override the endpoint, which is the entire trick for compatibility. After restore, confirm the package appears in your csproj:

<ItemGroup>
  <PackageReference Include="OpenAI" Version="2.*" />
</ItemGroup>

Run dotnet list package to verify the resolved version. If you see a 1.x Betalgo package instead, you installed the wrong feed—remove it.

Step 3: Choose your OpenAI-compatible endpoint

An OpenAI-compatible API exposes the same route shapes, JSON schemas, and auth header (Authorization: Bearer <key>). You can point the client at OpenAI directly, or at a gateway that aggregates multiple providers behind one URL.

If you route through a gateway such as n4n.ai, set the endpoint to its single OpenAI-compatible URL and use your gateway key; the gateway handles provider fallback when a backend is rate-limited and forwards cache-control hints so prompt caching behaves as expected. Direct provider keys work the same way, just with a different base URL.

Gateway vs direct provider

A gateway buys you redundancy and unified per-token metering. Direct access cuts a network hop and removes a vendor in the middle. For a nuget openai compatible c# client, the code is identical—only the Endpoint URI and key change. Decide based on whether you need multi-provider routing or not.

Step 4: Initialize the client with correct base address

Instantiate OpenAIClient with an ApiKeyCredential and OpenAIClientOptions. The Endpoint property is the override that makes the library talk to any compatible server.

using OpenAI;

var apiKey = Environment.GetEnvironmentVariable("LLM_API_KEY")
    ?? throw new InvalidOperationException("Set LLM_API_KEY");
var endpoint = new Uri("https://api.your-gateway.com/v1");

var client = new OpenAIClient(
    new ApiKeyCredential(apiKey),
    new OpenAIClientOptions { Endpoint = endpoint });

The client is thread-safe and manages its own HttpClient pipeline. Do not wrap it in a using block or recreate it per request. Treat the instance as a singleton. You can tune NetworkTimeout and UserAgent in OpenAIClientOptions, but leave RetryPolicy alone unless you want to override the built-in transient handling.

Step 5: Send your first chat completion

Get a ChatClient for a specific model and call CompleteChatAsync. Model names are opaque strings; pass whatever your endpoint expects.

using OpenAI.Chat;

var chatClient = client.GetChatClient("gpt-4o-mini");
var response = await chatClient.CompleteChatAsync(
    new ChatMessage[]
    {
        new SystemChatMessage("You are a terse C# expert."),
        new UserChatMessage("What is the difference between a struct and a class in C#?")
    });

Console.WriteLine(response.Value.Content[0].Text);

The response object also carries Usage (prompt and completion tokens). Log it. If you’re on a metered gateway, those numbers should match your billing dashboard. You can pass ChatCompletionOptions to set Temperature, MaxTokens, or TopP:

var opts = new ChatCompletionOptions { Temperature = 0.2f, MaxTokens = 512 };
var response = await chatClient.CompleteChatAsync(messages, opts);

Step 6: Stream tokens for interactive UX

Blocking on a full response feels sluggish for chat UIs. Use CompleteChatStreamingAsync to get a stream of delta messages.

await foreach (var update in chatClient.CompleteChatStreamingAsync(
    new ChatMessage[] { new UserChatMessage("Write a haiku about TCP retransmits.") },
    cancellationToken: default))
{
    foreach (var part in update.ContentUpdate)
    {
        Console.Write(part.Text);
    }
}

Streaming does not change the endpoint or auth. The nuget openai compatible c# client simply switches to the stream: true wire format and parses server-sent events. Always pass a CancellationToken from your controller so a disconnected client stops the backend generation.

Step 7: Wire configuration and DI in a real app

Hard-coding keys in source is for blog snippets only. Move configuration to appsettings.json and register the client as a singleton.

appsettings.json

{
  "Llm": {
    "ApiKeyEnv": "LLM_API_KEY",
    "Endpoint": "https://api.your-gateway.com/v1",
    "Model": "gpt-4o-mini"
  }
}

Program.cs (console with DI)

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using OpenAI;

var config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .AddEnvironmentVariables()
    .Build();

var llm = config.GetSection("Llm");
var key = Environment.GetEnvironmentVariable(llm["ApiKeyEnv"]!)
    ?? throw new InvalidOperationException("Missing key env var");

var services = new ServiceCollection();
services.AddSingleton(_ => new OpenAIClient(
    new ApiKeyCredential(key),
    new OpenAIClientOptions { Endpoint = new Uri(llm["Endpoint"]!) }));
services.AddSingleton(c => c.GetRequiredService<OpenAIClient>().GetChatClient(llm["Model"]!));

var provider = services.BuildServiceProvider();
var chat = provider.GetRequiredService<ChatClient>();

In ASP.NET Core, the same registration goes in Program.cs before builder.Build(). Inject ChatClient into a minimal API:

app.MapPost("/ask", async (ChatClient chat, string q) =>
{
    var r = await chat.CompleteChatAsync(new[] { new UserChatMessage(q) });
    return r.Value.Content[0].Text;
});

This pattern keeps the nuget openai compatible c# client reusable across controllers and background workers.

Step 8: Handle rate limits and failures

OpenAI-compatible endpoints return 429 when throttled and 5xx when a backend dies. The official client throws RequestFailedException with a Status property. Wrap calls in a retry loop with exponential backoff.

async Task<string> SafeCompleteAsync(ChatClient c, string prompt)
{
    for (var i = 0; i < 3; i++)
    {
        try
        {
            var r = await c.CompleteChatAsync(new[] { new UserChatMessage(prompt) });
            return r.Value.Content[0].Text;
        }
        catch (RequestFailedException ex) when (ex.Status == 429 || ex.Status >= 500)
        {
            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)));
        }
    }
    throw new InvalidOperationException("LLM request failed after retries");
}

If your gateway provides automatic fallback when a provider is degraded, you can skip multi-provider switching in your code. Otherwise, implement a secondary endpoint in the catch block. For production, consider Polly policies instead of hand-rolled loops.

Step 9: Verify the integration end to end

Run the console app. You should see either a printed answer or streamed tokens. Confirm three things:

  1. The HTTP call leaves your process to the configured Endpoint (use dotnet trace or a proxy like Fiddler).
  2. The Authorization: Bearer header is present and well-formed.
  3. response.Value.Usage is non-null and token counts are sane.

A minimal verification script:

export LLM_API_KEY="sk-your-key"
dotnet run --project LlmClientDemo

If you get a 401, your key or endpoint is wrong. A 404 means the model name or route prefix (/v1) is missing. A 200 with content proves the nuget openai compatible c# client is wired correctly. From here, drop the client into your service layer and treat LLM calls like any other external dependency: timeouts, circuit breakers, and structured logging.

Tagscsharpnugetopenai-apisetup

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All c# / .net llm api integration posts →