OpenAI with .NET

OpenAI can be integrated into .NET applications through the official OpenAI .NET SDK.

The official SDK is distributed as the OpenAI NuGet package and provides client types for major OpenAI capabilities, including chat, responses, embeddings, images, audio, files, vector stores, moderation, realtime, and other API areas. OpenAI's official repository states that the library is generated from the OpenAI OpenAPI specification in collaboration with Microsoft.

As of the current 2026-10-06 development cycle, the latest published OpenAI NuGet package verified here is 2.13.0, released on August 10, 2026.

The important architecture is:

                     .NET Application
                           |
             +-------------+-------------+
             |                           |
             v                           v
      OpenAI .NET SDK          Microsoft.Extensions.AI
             |                           |
             |                           v
             |                       IChatClient
             |                           |
             +-------------+-------------+
                           |
                           v
                       OpenAI API
                           |
                           v
                      AI Services

You can either use the OpenAI SDK directly or adapt OpenAI clients to the provider-neutral IChatClient abstraction. Microsoft currently exposes AsIChatClient adapters for OpenAI's ChatClient and also for ResponsesClient.

What Is OpenAI with .NET?

OpenAI with .NET means integrating OpenAI's models and API capabilities into C# and .NET applications.

The official SDK provides clients organized around API capabilities:

OpenAI
|
+-- Chat
|    +-- ChatClient
|
+-- Responses
|    +-- ResponsesClient
|
+-- Embeddings
|    +-- EmbeddingClient
|
+-- Images
|    +-- ImageClient
|
+-- Audio
|    +-- AudioClient
|
+-- Files
|    +-- OpenAIFileClient
|
+-- Vector Stores
|    +-- VectorStoreClient
|
+-- Moderation
|    +-- ModerationClient
|
+-- Realtime
|    +-- RealtimeClient
|
+-- Assistants
|    +-- AssistantClient
|
+-- Models
|    +-- OpenAIModelClient

This organization is documented in the official OpenAI .NET repository.

Why Use the Official OpenAI .NET SDK?

The official SDK provides several advantages.

Official API integration

The package is maintained by OpenAI and is generated from the OpenAI API specification.

Strongly typed C# APIs

Instead of constructing raw HTTP requests manually, you can use types such as:

ChatClient
ResponsesClient
EmbeddingClient
ImageClient
AudioClient

Async support

The SDK provides asynchronous counterparts for API operations. For example, CompleteChatAsync is the async equivalent of CompleteChat.

Streaming support

The SDK provides streaming APIs for chat and responses.

Tool calling

The SDK supports tool and function calling through its chat APIs.

Structured outputs

The official SDK supports schema-based structured outputs. The official examples include CreateJsonSchemaFormat for chat completions.

Microsoft.Extensions.AI integration

OpenAI clients can be adapted to IChatClient, allowing the rest of a .NET application to remain provider-neutral.

Installing OpenAI for .NET

Create a project:

dotnet new console -n OpenAIDotNetDemo
cd OpenAIDotNetDemo

Install the official SDK:

dotnet add package OpenAI --version 2.13.0

The verified NuGet package version is 2.13.0.

You can also add the Microsoft abstraction package:

dotnet add package Microsoft.Extensions.AI
dotnet add package Microsoft.Extensions.AI.OpenAI

The Microsoft.Extensions.AI.OpenAI package provides an IChatClient implementation for the OpenAI package and OpenAI-compatible endpoints.

OpenAI API Key

OpenAI requires an API key to call the API.

The official OpenAI SDK documentation recommends keeping the key in a secure location such as an environment variable instead of hard-coding it in source code.

On Windows PowerShell:

$env:OPENAI_API_KEY="your-api-key"

For a persistent environment variable:

setx OPENAI_API_KEY "your-api-key"

After using setx, start a new terminal before running the application.

For local .NET development, User Secrets are another good option:

dotnet user-secrets init
dotnet user-secrets set "OpenAI:ApiKey" "your-api-key"

Do not put real API keys in:

Git repositories
appsettings.json committed to Git
public JavaScript
HTML
client-side Blazor WebAssembly
mobile app source
screenshots

Reading the API Key

var apiKey =
    Environment.GetEnvironmentVariable(
        "OPENAI_API_KEY")
    ?? throw new InvalidOperationException(
        "OPENAI_API_KEY is not configured.");

Keep authentication outside your business logic.

The ChatClient

For chat-completions-style interactions, the OpenAI SDK provides:

OpenAI.Chat.ChatClient

Basic example:

using OpenAI.Chat;

string apiKey =
    Environment.GetEnvironmentVariable(
        "OPENAI_API_KEY")
    ?? throw new InvalidOperationException(
        "OPENAI_API_KEY is not configured.");

string model =
    Environment.GetEnvironmentVariable(
        "AI_MODEL")
    ?? throw new InvalidOperationException(
        "AI_MODEL is not configured.");

ChatClient client =
    new(model, apiKey);

The official repository currently demonstrates constructing ChatClient with a model identifier and API key.

Sending Your First Request

A simple request is:

ChatCompletion completion =
    await client.CompleteChatAsync(
        "Explain dependency injection in ASP.NET Core.");

Read the response:

Console.WriteLine(
    completion.Content[0].Text);

The official SDK documents CompleteChat and CompleteChatAsync for chat completions.

Complete Console Application

using OpenAI.Chat;

var apiKey =
    Environment.GetEnvironmentVariable(
        "OPENAI_API_KEY")
    ?? throw new InvalidOperationException(
        "OPENAI_API_KEY is not configured.");

var model =
    Environment.GetEnvironmentVariable(
        "AI_MODEL")
    ?? throw new InvalidOperationException(
        "AI_MODEL is not configured.");

ChatClient client =
    new(model, apiKey);

ChatCompletion completion =
    await client.CompleteChatAsync(
        "Explain dependency injection in ASP.NET Core.");

Console.WriteLine(
    completion.Content[0].Text);

The model identifier is intentionally configured externally rather than hard-coded into the source.

Understanding ChatCompletion

The returned object represents the result of a chat completion.

Conceptually:

ChatCompletion
|
+-- Content
+-- FinishReason
+-- Usage
+-- ToolCalls
+-- Other metadata

Depending on the request, content may contain ordinary text, structured data, or other supported content.

Messages Instead of One String

For simple requests, a single string is convenient.

For applications with multiple roles and conversation history, use chat messages.

using OpenAI.Chat;

List<ChatMessage> messages =
[
    new SystemChatMessage(
        "You are a helpful .NET assistant."),

    new UserChatMessage(
        "What is dependency injection?")
];

ChatCompletion completion =
    await client.CompleteChatAsync(
        messages);

This lets the application represent:

System
User
Assistant
Tool

as separate conversation elements.

System Instructions

For a support assistant:

List<ChatMessage> messages =
[
    new SystemChatMessage(
        """
        You are an enterprise customer support assistant.

        Use only the supplied application information.
        Do not invent product features.
        Keep answers concise.
        """),

    new UserChatMessage(
        "How do I reset my password?")
];

ChatCompletion completion =
    await client.CompleteChatAsync(
        messages);

This follows the system/user separation covered in the previous articles.

Conversation History

Store the conversation as messages:

List<ChatMessage> messages =
[
    new SystemChatMessage(
        "You are a helpful assistant.")
];

messages.Add(
    new UserChatMessage(
        "What is ASP.NET Core?"));

ChatCompletion first =
    await client.CompleteChatAsync(
        messages);

messages.Add(
    new AssistantChatMessage(first));

messages.Add(
    new UserChatMessage(
        "How does dependency injection work in it?"));

ChatCompletion second =
    await client.CompleteChatAsync(
        messages);

The assistant response can be inserted back into the history so future requests have access to the conversation.

Streaming Responses

For long responses, you may want text to appear progressively.

The official SDK provides CompleteChatStreaming and CompleteChatStreamingAsync.

Example:

AsyncCollectionResult<StreamingChatCompletionUpdate> updates =
    client.CompleteChatStreamingAsync(
        "Explain ASP.NET Core middleware.");

await foreach (
    StreamingChatCompletionUpdate update
    in updates)
{
    foreach (
        ChatMessageContentPart part
        in update.ContentUpdate)
    {
        Console.Write(
            part.Text);
    }
}

The exact collection/update types are part of the current OpenAI .NET SDK's streaming surface.

Why Streaming Matters

Without streaming:

Request
  |
  v
Wait
  |
  v
Complete Response

With streaming:

Request
  |
  v
Chunk 1
  |
  v
Chunk 2
  |
  v
Chunk 3
  |
  v
Chunk 4

This is useful for:

chat applications
AI assistants
interactive documentation
real-time UI
long-form generation

The ResponsesClient

The OpenAI .NET SDK also includes:

OpenAI.Responses.ResponsesClient

The current official SDK exposes CreateResponse, CreateResponseAsync, and streaming variants through ResponsesClient.

Example:

#pragma warning disable OPENAI001

using OpenAI.Responses;

var apiKey =
    Environment.GetEnvironmentVariable(
        "OPENAI_API_KEY")
    ?? throw new InvalidOperationException(
        "OPENAI_API_KEY is not configured.");

ResponsesClient client =
    new(apiKey);

ResponseResult response =
    await client.CreateResponseAsync(
        "configured-model",
        "Explain dependency injection in C#.");

Console.WriteLine(
    response.GetOutputText());

#pragma warning restore OPENAI001

The current official SDK examples mark the Responses APIs as experimental with OPENAI001, so applications using that surface should account for API evolution.

Chat API vs Responses API

The SDK currently exposes both chat-completions and responses surfaces.

Conceptually:

OpenAI .NET SDK
|
+-- ChatClient
|    |
|    +-- Chat completions
|
+-- ResponsesClient
     |
     +-- Responses API

For a straightforward conversational application:

ChatClient

is easy to understand.

For applications using the newer Responses API capabilities, the corresponding:

ResponsesClient

is the direct SDK surface.

OpenAI's current .NET repository includes examples for Responses streaming, reasoning, web search, file search, and other capabilities.

Using ResponsesClient with Options

The Responses API can be configured with:

CreateResponseOptions options =
    new()
    {
        Model = "configured-model"
    };

Then:

options.InputItems.Add(
    ResponseItem.CreateUserMessageItem(
        "Explain ASP.NET Core dependency injection."));

ResponseResult response =
    await client.CreateResponseAsync(
        options);

Console.WriteLine(
    response.GetOutputText());

The current ResponsesClient API uses CreateResponseOptions.InputItems for input items and the model is set through the options or convenience overloads.

Previous Response IDs

The Responses API supports continuing from a previous response through a previous response identifier.

Conceptually:

Response 1
   |
   v
Response ID
   |
   v
Response 2

The current SDK exposes PreviousResponseId in CreateResponseOptions and convenience overloads.

Example:

CreateResponseOptions options =
    new()
    {
        Model = "configured-model",
        PreviousResponseId = previousResponseId
    };

options.InputItems.Add(
    ResponseItem.CreateUserMessageItem(
        "Continue from the previous request."));

ResponseResult response =
    await client.CreateResponseAsync(
        options);

This is different from maintaining your own List<ChatMessage> history, so choose the conversation-state strategy that fits your application architecture.

OpenAIClient

The SDK also provides a parent:

OpenAIClient

which can create feature-specific clients.

For example:

using OpenAI;

OpenAIClient client =
    new(apiKey);

Then:

ChatClient chatClient =
    client.GetChatClient(model);

or:

AudioClient audioClient =
    client.GetAudioClient(
        "configured-audio-model");

The official repository documents OpenAIClient as a convenient way to create feature-specific clients while sharing underlying implementation details.

Multiple OpenAI Clients

A single application might need:

OpenAIClient
|
+-- ChatClient
+-- EmbeddingClient
+-- ImageClient
+-- AudioClient
+-- FileClient

This provides a central client-management pattern.

Dependency Injection in ASP.NET Core

The official OpenAI .NET SDK documents its clients as thread-safe and suitable for singleton registration in ASP.NET Core dependency injection.

A simple registration is:

builder.Services.AddSingleton(
    _ =>
    {
        var apiKey =
            builder.Configuration[
                "OpenAI:ApiKey"]
            ?? throw new InvalidOperationException(
                "OpenAI API key is not configured.");

        var model =
            builder.Configuration[
                "OpenAI:Model"]
            ?? throw new InvalidOperationException(
                "OpenAI model is not configured.");

        return new ChatClient(
            model,
            apiKey);
    });

Then inject:

public sealed class AIService
{
    private readonly ChatClient _client;

    public AIService(
        ChatClient client)
    {
        _client = client;
    }
}

Configuration with appsettings.json

Use configuration for the model:

{
  "OpenAI": {
    "Model": "your-model-name"
  }
}

Keep the API key outside source-controlled configuration.

For example, User Secrets:

dotnet user-secrets set "OpenAI:ApiKey" "your-api-key"

Or environment variables.

Strongly Typed Configuration

Create:

public sealed class OpenAIOptions
{
    public string? ApiKey { get; set; }

    public string? Model { get; set; }
}

Register:

builder.Services.Configure<OpenAIOptions>(
    builder.Configuration.GetSection(
        "OpenAI"));

Then:

public sealed class AIService
{
    private readonly ChatClient _client;

    public AIService(
        IOptions<OpenAIOptions> options)
    {
        var settings =
            options.Value;

        _client =
            new ChatClient(
                settings.Model,
                settings.ApiKey);
    }
}

For production, prefer registering the client once rather than constructing it repeatedly inside requests.

Microsoft.Extensions.AI Integration

One of the strongest .NET patterns is to adapt the OpenAI SDK to:

IChatClient

Example:

using Microsoft.Extensions.AI;
using OpenAI;

var client =
    new OpenAIClient(apiKey)
        .GetChatClient(model)
        .AsIChatClient();

Microsoft currently documents AsIChatClient(ChatClient) as the adapter from the OpenAI ChatClient to IChatClient.

This creates:

OpenAI ChatClient
       |
       v
AsIChatClient()
       |
       v
IChatClient

Why Use IChatClient?

The direct SDK approach:

MyService
   |
   v
OpenAI ChatClient

couples your service to OpenAI.

The abstraction approach:

MyService
   |
   v
IChatClient
   |
   v
OpenAI

allows you to change the implementation later.

For example:

IChatClient
|
+-- OpenAI
+-- Azure OpenAI
+-- Ollama
+-- Other provider

Microsoft's Microsoft.Extensions.AI.OpenAI package explicitly provides this integration.

Direct OpenAI SDK vs IChatClient

Use the direct OpenAI SDK when:

you need OpenAI-specific features
you need the exact OpenAI SDK API
you need OpenAI-specific options
you want direct control

Use IChatClient when:

you want provider abstraction
your application may switch providers
you want Microsoft.Extensions.AI middleware
you want common chat abstractions

A hybrid architecture is often ideal:

Application Services
       |
       v
IChatClient
       |
       v
OpenAI ChatClient
       |
       v
OpenAI

while specialized OpenAI-only components can still use direct SDK clients when necessary.

OpenAI Chat Completion Options

The direct SDK supports chat options.

For example:

ChatCompletionOptions options =
    new()
    {
        Temperature = 0.2f
    };

Then:

ChatCompletion completion =
    await client.CompleteChatAsync(
        messages,
        options);

The exact available option set depends on the version and endpoint represented by the installed SDK.

Structured Outputs with the OpenAI SDK

The official OpenAI .NET SDK supports schema-based structured outputs.

The official example creates:

ChatResponseFormat.CreateJsonSchemaFormat(
    ...
)

and configures:

jsonSchemaIsStrict: true

The SDK example then parses the response with JsonDocument.

Example:

ChatCompletionOptions options =
    new()
    {
        ResponseFormat =
            ChatResponseFormat.CreateJsonSchemaFormat(
                jsonSchemaFormatName:
                    "ticket_analysis",
                jsonSchema:
                    BinaryData.FromString(
                        """
                        {
                          "type": "object",
                          "properties": {
                            "category": {
                              "type": "string"
                            },
                            "priority": {
                              "type": "string"
                            },
                            "summary": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "category",
                            "priority",
                            "summary"
                          ],
                          "additionalProperties": false
                        }
                        """),
                jsonSchemaIsStrict: true)
    };

Then:

ChatCompletion completion =
    await client.CompleteChatAsync(
        messages,
        options);

Read the structured response:

using JsonDocument json =
    JsonDocument.Parse(
        completion.Content[0].Text);

The current official example demonstrates this same CreateJsonSchemaFormat pattern.

Structured Output with Microsoft.Extensions.AI

When using IChatClient, the equivalent architecture is:

var response =
    await chatClient.GetResponseAsync<
        TicketAnalysis>(
        messages);

This was covered in the previous article.

The two approaches are:

Direct OpenAI SDK
        |
        v
OpenAI-specific API

and:

IChatClient
        |
        v
Provider-neutral abstraction
        |
        v
OpenAI implementation

Both are valid.

Tool Calling with the OpenAI SDK

The OpenAI SDK supports tool and function calling.

The basic workflow is:

User Request
    |
    v
AI Model
    |
    v
Tool Call
    |
    v
.NET Application
    |
    v
Execute Tool
    |
    v
Tool Result
    |
    v
AI Model
    |
    v
Final Answer

The official SDK documents using ChatCompletionOptions.Tools and handling a ChatFinishReason.ToolCalls result.

A conceptual tool definition:

ChatTool tool =
    ChatTool.CreateFunctionTool(
        functionName:
            "get_order_status",
        functionDescription:
            "Gets the current status of an order.");

Then:

ChatCompletionOptions options =
    new()
    {
        Tools =
        {
            tool
        }
    };

When the model requests a tool, inspect the returned tool calls and execute only approved operations.

Handling Tool Calls

The SDK's documented flow is:

CompleteChat
     |
     v
ToolCalls?
     |
 +---+---+
 |       |
No      Yes
 |       |
 v       v
Answer   Execute Tool
         |
         v
      Add Tool Result
         |
         v
     CompleteChat

The official repository demonstrates checking ChatFinishReason.ToolCalls, adding the assistant message with the tool calls to the conversation, executing each tool, and adding ChatRequestToolMessage results before making the next completion request.

Tool Security

Never assume a tool call should be executed simply because the model requested it.

The application should enforce:

authentication
authorization
input validation
rate limits
resource access rules
tool allowlists

The model chooses or requests a tool.

The application controls whether that operation is actually permitted.

OpenAI Embeddings with .NET

The SDK includes:

EmbeddingClient

for embeddings. The official SDK lists OpenAI.Embeddings and EmbeddingClient among its client areas.

Conceptually:

EmbeddingClient client =
    new(
        "configured-embedding-model",
        apiKey);

Then generate embeddings from text according to the SDK's current embedding API.

Embeddings become important later in the series for:

RAG
semantic search
vector databases
similarity
document retrieval

OpenAI Images with .NET

The SDK includes:

ImageClient

under OpenAI.Images.

This allows a .NET application to integrate image-generation workflows without manually constructing HTTP requests.

A typical architecture is:

ASP.NET Core
    |
    v
Image Service
    |
    v
ImageClient
    |
    v
OpenAI Image API
    |
    v
Image Result

OpenAI Audio with .NET

The SDK includes:

AudioClient

under OpenAI.Audio.

The official SDK documentation includes audio input/output examples and audio transcription workflows.

This is useful for:

speech-to-text
text-to-speech
voice applications
audio processing

OpenAI Files with .NET

The SDK also includes file operations.

OpenAIFileClient

can support application workflows involving uploaded files. The official SDK repository lists files as a dedicated client area.

Files become increasingly useful for:

document processing
retrieval
file-based workflows
AI assistants

OpenAI Vector Stores

The SDK currently includes:

VectorStoreClient

which is part of the official client organization.

This becomes relevant later when the series reaches:

RAG
vector search
semantic retrieval

OpenAI Moderation

The SDK also includes:

ModerationClient

under OpenAI.Moderations.

This can be placed in an application pipeline:

User Input
   |
   v
Validation / Moderation
   |
   v
OpenAI Model
   |
   v
Response
   |
   v
Output Controls

Use application-specific safety policies appropriate to the service you are building.

OpenAI Realtime

The SDK also exposes:

RealtimeClient

for realtime functionality.

Realtime applications introduce a different architecture:

Client
  |
  v
Realtime Connection
  |
  v
OpenAI Realtime
  |
  +-- audio
  +-- events
  +-- tool calls
  +-- responses

This becomes relevant later when building voice and realtime AI applications.

OpenAI Models

The SDK includes:

OpenAIModelClient

for model-related operations.

Keep model selection configuration-driven rather than scattering model identifiers throughout application code.

For example:

{
  "OpenAI": {
    "Model": "your-model-name"
  }
}

Model Configuration

A clean service might read:

string model =
    configuration["OpenAI:Model"]
    ?? throw new InvalidOperationException(
        "OpenAI model is not configured.");

Then:

ChatClient client =
    new(model, apiKey);

This allows model changes without recompiling business code.

ASP.NET Core Architecture

A production application should avoid calling OpenAI directly from controllers.

Prefer:

ASP.NET Core Controller
        |
        v
AI Application Service
        |
        v
OpenAI Client / IChatClient
        |
        v
OpenAI API

For example:

public interface IOpenAIService
{
    Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default);
}

Implementation:

public sealed class OpenAIService
    : IOpenAIService
{
    private readonly ChatClient _client;

    public OpenAIService(
        ChatClient client)
    {
        _client = client;
    }

    public async Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default)
    {
        ChatCompletion completion =
            await _client.CompleteChatAsync(
                prompt,
                cancellationToken);

        return completion
            .Content[0]
            .Text;
    }
}

Register:

builder.Services.AddScoped<IOpenAIService, OpenAIService>();

ASP.NET Core Endpoint

app.MapPost(
    "/api/ai",
    async (
        AIRequest request,
        IOpenAIService service,
        CancellationToken cancellationToken) =>
    {
        var response =
            await service.GenerateAsync(
                request.Prompt,
                cancellationToken);

        return Results.Ok(
            new
            {
                response
            });
    });

Request model:

public sealed class AIRequest
{
    public required string Prompt { get; init; }
}

Architecture:

POST /api/ai
      |
      v
AIRequest
      |
      v
IOpenAIService
      |
      v
ChatClient
      |
      v
OpenAI

ASP.NET Core Streaming

For a chat UI, you can stream results from the OpenAI client to the HTTP response.

Conceptually:

Browser
   |
   v
ASP.NET Core
   |
   v
Streaming AI Service
   |
   v
ChatClient
   |
   v
OpenAI
   |
   +-- chunk
   +-- chunk
   +-- chunk
   |
   v
Browser

The exact transport can be:

Server-Sent Events
SignalR
HTTP streaming

depending on the application.

OpenAI with .NET

Using IChatClient in ASP.NET Core

An alternative is to register:

IChatClient

instead of ChatClient.

Example:

builder.Services.AddSingleton<IChatClient>(
    _ =>
    {
        var apiKey =
            builder.Configuration[
                "OpenAI:ApiKey"]
            ?? throw new InvalidOperationException();

        var model =
            builder.Configuration[
                "OpenAI:Model"]
            ?? throw new InvalidOperationException();

        return new OpenAIClient(apiKey)
            .GetChatClient(model)
            .AsIChatClient();
    });

Then:

public sealed class AIService
{
    private readonly IChatClient _chatClient;

    public AIService(
        IChatClient chatClient)
    {
        _chatClient = chatClient;
    }
}

This is particularly useful if you plan to support both OpenAI and another provider later.

Configuration-Driven OpenAI Clients

The official OpenAI SDK also includes configuration-driven ASP.NET Core integration for clients. Its current ASP.NET Core example uses AddResponsesClient and ResponsesClientSettings to bind a client from configuration.

Conceptually:

appsettings
    |
    v
ResponsesClientSettings
    |
    v
ResponsesClient
    |
    v
ASP.NET Core DI

The official sample shows:

builder.AddResponsesClient(
    "Clients:ResponsesClient");

and then injects ResponsesClient into the endpoint.

Because the current configuration APIs are marked experimental in that sample, inspect the versioned SDK documentation before adopting them in a long-lived production project.

Error Handling

OpenAI integration should handle failures explicitly.

Common categories include:

authentication failure
authorization failure
invalid request
rate limiting
network failure
timeout
model/configuration problem
content/response processing failure
tool execution failure

A basic service can use:

try
{
    ChatCompletion completion =
        await client.CompleteChatAsync(
            prompt,
            cancellationToken);
}
catch (Exception ex)
{
    logger.LogError(
        ex,
        "OpenAI request failed.");

    throw;
}

For production, use more specific exception handling and preserve the original cancellation behavior.

Cancellation Support

Always pass the cancellation token:

await client.CompleteChatAsync(
    messages,
    cancellationToken:
        cancellationToken);

This is especially important in ASP.NET Core because the client may disconnect before the model completes the request.

Timeout Handling

Long AI requests should have appropriate timeout behavior.

At the application level:

HTTP Request
   |
   v
CancellationToken
   |
   v
OpenAI Request

Do not let abandoned browser requests continue consuming resources unnecessarily.

Retry Policies

The official SDK documentation includes an example for automatically retrying errors.

However, not every error should be retried.

A sensible policy distinguishes:

Transient network error
       |
       v
Retry

Rate limit
       |
       v
Controlled backoff

Invalid request
       |
       v
Do not blindly retry

Authentication error
       |
       v
Fix configuration

Cancellation
       |
       v
Stop

This is a general application pattern rather than something to apply identically to every OpenAI operation.

Rate Limiting

Applications calling OpenAI should protect themselves from uncontrolled request volume.

In ASP.NET Core:

Client
  |
  v
Rate Limiter
  |
  v
AI Service
  |
  v
OpenAI

Possible dimensions:

requests per user
requests per IP
requests per subscription
requests per tenant
tokens per operation

AI Cost Controls

OpenAI usage can become expensive if prompts are unnecessarily large.

Control:

prompt length
conversation history
retrieved context
output size
request rate
model selection

Architecture:

Request
  |
  v
Input Limits
  |
  v
Prompt / Context
  |
  v
AI Model
  |
  v
Usage Tracking

Usage Tracking

Record relevant response metadata where available:

model
input usage
output usage
total usage
duration
request ID
operation

This is essential for production observability.

Logging OpenAI Requests

Do not log API keys.

Avoid blindly logging complete user prompts if they may contain sensitive data.

Prefer metadata:

Operation = CustomerSupport
Model = configured-model
DurationMs = ...
Success = true

Add detailed payload logging only under controlled diagnostics and appropriate data-protection rules.

OpenAI and HttpClient

The SDK handles HTTP communication internally.

For most applications, use the official SDK rather than writing:

HttpClient.PostAsync(...)

against the REST API manually.

Direct HTTP is still useful for:

very specialized protocol access
unsupported SDK features
diagnostic work
low-level integration

The official SDK also exposes protocol-level methods when more direct REST access is needed.

OpenAI Protocol Methods

The SDK documents protocol methods that accept binary request content and return binary response content.

This is useful when you need:

raw request body
raw API response
direct protocol access

The official repository describes these as lower-level protocol methods alongside the strongly typed APIs.

Most application code should prefer the strongly typed API.

Testing OpenAI Applications

Do not make every unit test call the real OpenAI API.

Separate:

Business Logic
      |
      +-- unit tests
      |
      v
AI Adapter
      |
      +-- integration tests
      |
      v
OpenAI

For example:

public interface IAITextGenerator
{
    Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default);
}

Your business service depends on:

IAITextGenerator

rather than directly on ChatClient.

Then unit tests can use a fake implementation.

Mocking the OpenAI SDK

The official OpenAI .NET repository includes guidance for mocking clients as part of its advanced scenarios.

The principle is:

Application Service
       |
       v
IAIService
       |
 +-----+-----+
 |           |
 v           v
Fake        OpenAI
Test        Production

This keeps unit tests deterministic.

Integration Tests

Use real OpenAI requests for a smaller integration test suite.

For example:

Integration Test
   |
   v
OpenAI SDK
   |
   v
OpenAI API

Check:

request succeeds
response is usable
structured output parses
tool call works
streaming works

Don't make your entire CI pipeline dependent on remote model behavior unless that trade-off is intentional.

OpenAI and Prompt Management

The architecture from earlier topics fits directly:

Prompt Repository
      |
      v
Prompt Template
      |
      v
Rendered Messages
      |
      v
OpenAI Client
      |
      v
OpenAI Model

A good service interface is:

public interface IOpenAIService
{
    Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default);
}

The prompt itself can live outside the C# service.

OpenAI and System Prompts

Use:

List<ChatMessage> messages =
[
    new SystemChatMessage(
        systemPrompt),

    new UserChatMessage(
        userPrompt)
];

This keeps:

System Prompt

separate from:

User Request

and matches the prompt-management architecture already established in the series.

OpenAI and Prompt Templates

Example:

Summarize this document for {{audience}}.

Document:
{{document}}

Render:

var prompt =
    renderer.Render(
        template,
        new Dictionary<string, string>
        {
            ["audience"] =
                "senior .NET developers",

            ["document"] =
                documentText
        });

Then send to OpenAI:

ChatCompletion completion =
    await client.CompleteChatAsync(
        prompt);

This connects topics 25 through 31 into one architecture.

OpenAI and Structured Responses

A typed output can use:

var response =
    await chatClient.GetResponseAsync<
        TicketAnalysis>(
        messages);

or the direct SDK can use CreateJsonSchemaFormat with ChatCompletionOptions.

Therefore:

Prompt Template
      |
      v
System/User Messages
      |
      v
OpenAI
      |
      v
Structured Response
      |
      v
C# Object

OpenAI and JSON

The direct SDK supports structured output and JSON Schema through chat completion options. The official example uses ChatResponseFormat.CreateJsonSchemaFormat and then parses the returned JSON using JsonDocument.

This is useful when you need OpenAI-specific control rather than the provider-neutral IChatClient abstraction.

OpenAI and RAG

At this stage of the series, a basic RAG architecture is:

Documents
   |
   v
Chunking
   |
   v
Embeddings
   |
   v
Vector Search
   |
   v
Retrieved Context
   |
   v
Prompt
   |
   v
OpenAI
   |
   v
Answer

The OpenAI SDK already provides an EmbeddingClient, and it also includes vector-store related client APIs.

Detailed RAG implementation will be covered later in the series.

OpenAI and ASP.NET Core

A production ASP.NET Core architecture might look like:

                       ASP.NET Core
                             |
             +---------------+----------------+
             |                                |
             v                                v
        Prompt Service                 Context Service
             |                                |
             +---------------+----------------+
                             |
                             v
                      AI Application
                          Service
                             |
               +-------------+-------------+
               |                           |
               v                           v
          ChatClient                  Other Clients
               |                           |
               +-------------+-------------+
                             |
                             v
                         OpenAI API

The key principle is not to scatter OpenAI calls throughout controllers.

OpenAI and Clean Architecture

A Clean Architecture implementation can be:

Presentation
     |
     v
Application
     |
     +-- AI Interfaces
     |
     v
Infrastructure
     |
     +-- OpenAI

For example:

public interface IAIChatService
{
    Task<string> ChatAsync(
        string prompt,
        CancellationToken cancellationToken = default);
}

Infrastructure:

public sealed class OpenAIChatService
    : IAIChatService
{
    private readonly ChatClient _client;

    public OpenAIChatService(
        ChatClient client)
    {
        _client = client;
    }

    public async Task<string> ChatAsync(
        string prompt,
        CancellationToken cancellationToken = default)
    {
        var completion =
            await _client.CompleteChatAsync(
                prompt,
                cancellationToken);

        return completion.Content[0].Text;
    }
}

Now the application layer does not depend directly on OpenAI.

OpenAI with Dependency Injection

Register the SDK client once:

builder.Services.AddSingleton(
    _ =>
    {
        var apiKey =
            builder.Configuration[
                "OpenAI:ApiKey"]
            ?? throw new InvalidOperationException(
                "OpenAI API key is missing.");

        var model =
            builder.Configuration[
                "OpenAI:Model"]
            ?? throw new InvalidOperationException(
                "OpenAI model is missing.");

        return new ChatClient(
            model,
            apiKey);
    });

Then:

builder.Services.AddScoped<IAIChatService, OpenAIChatService>();

This aligns with the official SDK's guidance that its clients are thread-safe and can be registered as singletons.

OpenAI with a Provider-Neutral Architecture

For a larger system, use:

IChatClient

and configure OpenAI as the implementation.

Application
     |
     v
IChatClient
     |
     v
OpenAI

Later you can change:

OpenAI

to:

Azure OpenAI

or another supported provider without rewriting your application-level AI interfaces.

OpenAI Compatible Endpoints

The official SDK also supports configuring a custom endpoint and API key, which can be useful for OpenAI-compatible APIs or proxy endpoints. The SDK documentation demonstrates this using OpenAIClientOptions.Endpoint.

Conceptually:

var client =
    new ChatClient(
        model: "configured-model",
        credential:
            new ApiKeyCredential(apiKey),
        options:
            new OpenAIClientOptions
            {
                Endpoint =
                    new Uri(
                        "https://your-endpoint/")
            });

This should only be used when the target endpoint implements the API surface expected by the SDK.

Experimental APIs

The official OpenAI .NET SDK marks some APIs as experimental while their .NET surface is still evolving. The repository documents the need to explicitly suppress the associated diagnostic when using such APIs.

For example, the current Responses examples use:

#pragma warning disable OPENAI001

This is not something to add blindly throughout the application.

Prefer stable APIs when they meet the requirement.

For experimental features:

check version
check changelog
test carefully
isolate usage

Checking SDK Versions

The OpenAI package version should be visible in your project:

<PackageReference
    Include="OpenAI"
    Version="2.13.0" />

NuGet currently lists 2.13.0 as the verified latest package version for this article's date.

After updating:

dotnet restore
dotnet build
dotnet test

Review the OpenAI SDK changelog before upgrading production applications. The official changelog records API-surface changes such as the evolution of ResponsesClient.

OpenAI SDK Project Structure

A practical ASP.NET Core application could use:

MyAiApp/
|
+-- Controllers/
|
+-- Application/
|   +-- AI/
|       +-- IAIService.cs
|       +-- AIService.cs
|
+-- Infrastructure/
|   +-- OpenAI/
|       +-- OpenAIService.cs
|       +-- OpenAIOptions.cs
|
+-- Prompts/
|   +-- Support/
|   +-- Summary/
|   +-- Classification/
|
+-- Models/
|   +-- AI/
|
+-- Program.cs
+-- appsettings.json

This keeps:

OpenAI SDK
Prompt Management
Application Logic

separate.

Complete OpenAI Service Example

using OpenAI.Chat;

public interface IOpenAIService
{
    Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default);
}

public sealed class OpenAIService
    : IOpenAIService
{
    private readonly ChatClient _client;

    public OpenAIService(
        ChatClient client)
    {
        _client = client;
    }

    public async Task<string> GenerateAsync(
        string prompt,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(prompt))
        {
            throw new ArgumentException(
                "Prompt is required.",
                nameof(prompt));
        }

        ChatCompletion completion =
            await _client.CompleteChatAsync(
                prompt,
                cancellationToken);

        if (completion.Content.Count == 0)
        {
            throw new InvalidOperationException(
                "OpenAI returned no content.");
        }

        return completion.Content[0].Text;
    }
}

Complete ASP.NET Core Setup

using OpenAI.Chat;

var builder =
    WebApplication.CreateBuilder(args);

builder.Services.AddSingleton(
    _ =>
    {
        var apiKey =
            builder.Configuration[
                "OpenAI:ApiKey"]
            ?? throw new InvalidOperationException(
                "OpenAI API key is missing.");

        var model =
            builder.Configuration[
                "OpenAI:Model"]
            ?? throw new InvalidOperationException(
                "OpenAI model is missing.");

        return new ChatClient(
            model,
            apiKey);
    });

builder.Services.AddScoped<
    IOpenAIService,
    OpenAIService>();

var app =
    builder.Build();

app.MapPost(
    "/api/ai",
    async (
        AIRequest request,
        IOpenAIService service,
        CancellationToken cancellationToken) =>
    {
        var answer =
            await service.GenerateAsync(
                request.Prompt,
                cancellationToken);

        return Results.Ok(
            new
            {
                answer
            });
    });

app.Run();

public sealed class AIRequest
{
    public required string Prompt { get; init; }
}

Example Request

{
  "prompt": "Explain dependency injection in ASP.NET Core."
}

Response:

{
  "answer": "Dependency injection is a design pattern..."
}

OpenAI with System Prompts, Templates, and JSON

The topics covered so far combine into a single architecture:

Prompt Template
       |
       v
System Prompt
       |
       +
Runtime Context
       |
       +
User Request
       |
       v
OpenAI ChatClient
       |
       v
JSON / Structured Output
       |
       v
C# DTO
       |
       v
Validation
       |
       v
Business Logic

This is the foundation for most production AI services.

Practical Project: Build an OpenAI Chat API

Create an ASP.NET Core API with:

POST /api/chat

Request:

{
  "message": "Explain dependency injection."
}

The system prompt:

You are a professional .NET programming assistant.

Rules:
- Prefer current .NET concepts.
- Use practical C# examples.
- Explain important assumptions.
- Do not invent APIs.

Architecture:

POST /api/chat
     |
     v
ChatRequest
     |
     v
Chat Service
     |
     v
ChatClient
     |
     v
OpenAI
     |
     v
Response

Practical Project: Build a JSON Ticket Analyzer

Input:

{
  "message": "My credit card was charged twice."
}

Output:

{
  "category": "Billing",
  "priority": "High",
  "summary": "Customer reports a duplicate charge."
}

Implement:

OpenAI ChatClient
+
ChatCompletionOptions
+
JSON Schema
+
System.Text.Json
+
ASP.NET Core

This combines the current and previous two topics.

Practical Project: Build an OpenAI Document Assistant

Architecture:

Document
   |
   v
Text Extraction
   |
   v
Prompt Template
   |
   v
OpenAI
   |
   v
Structured Answer
   |
   +-- answer
   +-- sources
   +-- confidence
   |
   v
ASP.NET Core API

The later RAG topics will expand this into retrieval and vector search.

Practical Project: Build a Streaming AI Chat Application

Architecture:

Browser
   |
   v
ASP.NET Core
   |
   v
AI Service
   |
   v
ChatClient
   |
   v
OpenAI
   |
   +-- partial response
   +-- partial response
   +-- partial response
   |
   v
Browser

Use:

CompleteChatStreamingAsync

from the official SDK.

Practical Project: Build an OpenAI Tool-Calling Assistant

Build:

User
 |
 v
OpenAI
 |
 v
Tool Call
 |
 +-- getOrderStatus
 +-- searchProducts
 +-- getCustomer
 |
 v
.NET Application
 |
 v
Tool Result
 |
 v
OpenAI
 |
 v
Final Response

The application should validate every tool request before execution.

Common Mistakes

Hard-coding API keys

Avoid:

new ChatClient(
    "model",
    "real-secret-key");

Use environment variables or secure configuration.

Creating a client on every request

Use DI and singleton lifetime where appropriate. The official SDK documents its clients as thread-safe.

Mixing OpenAI calls into controllers

Use an application service.

Using model identifiers everywhere

Centralize model configuration.

Ignoring cancellation

Pass CancellationToken.

Treating all errors as retryable

Different failures need different handling.

Assuming experimental APIs are stable

Current Responses-related surfaces include experimental APIs in the official SDK.

Parsing AI text manually when structured output exists

Use structured output or JSON Schema when the application needs a fixed contract.

Executing tool calls without authorization

The application must control tool access.

Logging sensitive prompts

Use privacy-aware logging.

Coupling the entire application to ChatClient

Use IChatClient when provider portability is important.

Frequently Asked Questions

What is the official OpenAI SDK for .NET?

The official package is:

OpenAI

The official repository is openai/openai-dotnet.

What is the current verified OpenAI .NET package version?

The current package verified for this article is 2.13.0, released August 10, 2026.

What is ChatClient?

ChatClient is the official OpenAI .NET client for chat-completion operations.

What is ResponsesClient?

ResponsesClient is the official SDK client for the Responses API. The current .NET SDK exposes synchronous, asynchronous, and streaming response methods.

Should I use ChatClient or ResponsesClient?

Use the client surface that matches the OpenAI API capability your application needs.

For straightforward chat-completion scenarios:

ChatClient

For newer Responses API workflows:

ResponsesClient

Check the SDK version and current API documentation because the Responses .NET surface is still marked experimental in the verified current SDK.

Can I use OpenAI with IChatClient?

Yes. Microsoft provides AsIChatClient(ChatClient) and other OpenAI adapters.

Why use IChatClient instead of ChatClient?

IChatClient reduces provider coupling and lets application code share a common abstraction across different AI providers.

Is the OpenAI .NET SDK thread-safe?

The official repository states that its clients are thread-safe and can be registered as singletons in ASP.NET Core.

Can I stream OpenAI responses with C#?

Yes. ChatClient provides CompleteChatStreaming and CompleteChatStreamingAsync, and ResponsesClient provides corresponding streaming methods.

Can OpenAI with .NET perform tool calling?

Yes. The official SDK supports tools and function calling through the chat completion APIs.

Can OpenAI with .NET produce JSON?

Yes. The official SDK supports structured outputs and JSON Schema-based response formats.

Can I use embeddings?

Yes. The SDK provides EmbeddingClient.

Can I process images?

The SDK includes an ImageClient.

Can I work with audio?

The SDK includes AudioClient and the official repository documents audio examples.

Can I work with files?

Yes. The SDK exposes a file client and other file-related APIs.

Can I use vector stores?

The SDK includes VectorStoreClient.

Can I use a custom OpenAI-compatible endpoint?

The official SDK documents configuring a custom base URL using OpenAIClientOptions.Endpoint.

Should I use OpenAI directly from a browser?

Do not expose a long-lived secret API key in client-side code. Put OpenAI access behind a controlled server-side application.

Can I use OpenAI in ASP.NET Core?

Yes. The official SDK provides dependency-injection patterns, and Microsoft also provides IChatClient integration.

Can I use OpenAI with Blazor?

Yes. For server-side Blazor, the server can securely call OpenAI. For browser-executed code, keep the API key on the server and expose an application API.

Can I use OpenAI with .NET MAUI?

Yes, but the API key should not be embedded directly in the shipped client application. A mobile application should generally call your secured backend.

Are all OpenAI .NET APIs stable?

No. Some current APIs are marked experimental. The official SDK documentation explains that experimental APIs can change as their .NET design evolves.

Interview Questions

What NuGet package provides the official OpenAI .NET SDK?

OpenAI

The package is maintained by OpenAI.

What is ChatClient?

The OpenAI .NET client for chat-completion requests.

What is ResponsesClient?

The OpenAI .NET client for Responses API operations.

What is OpenAIClient?

A parent client that can create feature-specific OpenAI clients and share common underlying implementation details.

How do you authenticate?

Use an OpenAI API key, preferably supplied through secure configuration or environment variables rather than source code.

How do you call OpenAI asynchronously?

Use methods such as:

CompleteChatAsync(...)
CreateResponseAsync(...)

The SDK provides asynchronous counterparts to its synchronous API methods.

How do you stream a chat response?

Use:

CompleteChatStreamingAsync(...)

and enumerate the returned updates asynchronously.

How do you use OpenAI with IChatClient?

Use:

new OpenAIClient(apiKey)
    .GetChatClient(model)
    .AsIChatClient();

Microsoft documents this adapter directly.

How do you create structured output with the direct SDK?

Use ChatResponseFormat.CreateJsonSchemaFormat with ChatCompletionOptions. The official OpenAI SDK includes a structured-output example using this approach.

How should OpenAI clients be registered in ASP.NET Core?

The official SDK documents its clients as thread-safe and suitable for singleton registration.

Why should controllers not directly call OpenAI?

Separating the AI infrastructure into a service improves testing, observability, error handling, and architecture.

Why use an abstraction such as IChatClient?

It prevents business/application code from being tightly coupled to one provider.

How do you secure an OpenAI API key?

Keep it server-side and store it in secure configuration, an environment variable, or a secret-management service.

What is the difference between model configuration and prompt configuration?

Model configuration identifies the model and request settings.

Prompt configuration controls the instructions and runtime content sent to the model.

How do you test code that uses OpenAI?

Use interfaces and fakes/mocks for unit tests, then use a smaller set of real integration tests against OpenAI.

The official repository also documents mocking and testing-related advanced scenarios.

Best Practices

Keep API keys out of source code

Use:

environment variables
User Secrets
secret-management systems

Register clients once

Use DI and singleton lifetime where appropriate. The SDK documents its clients as thread-safe.

Use cancellation tokens

Especially in ASP.NET Core.

Centralize model configuration

Use:

OpenAI:Model

instead of scattering model identifiers through the codebase.

Separate prompts from OpenAI infrastructure

Use the prompt-template architecture already established in the series.

Keep OpenAI behind an application service

This makes your application easier to test and evolve.

Use IChatClient when provider abstraction matters

Direct SDK use is still appropriate for OpenAI-specific features.

Prefer asynchronous methods

Use:

CompleteChatAsync
CreateResponseAsync

in web applications and background services.

Use streaming when the UI benefits from incremental output

Chat applications are a common example.

Validate tool calls

Never treat a tool request as automatic authorization.

Validate structured responses

JSON syntax is not business correctness.

Track usage

Record model, duration, request metadata, and usage data as appropriate.

Review experimental APIs before production adoption

Current Responses-related SDK APIs are marked experimental in the verified SDK.

Keep model selection configurable

This makes model upgrades and environment-specific configuration easier.

Prefer strongly typed SDK APIs over raw HTTP

Use raw protocol methods only when you actually need lower-level access. The official SDK provides both layers.

OpenAI .NET Architecture

A practical production architecture is:

                              ASP.NET Core
                                   |
                                   v
                          AI Application Service
                                   |
                   +---------------+---------------+
                   |                               |
                   v                               v
             Prompt Management              Context Management
                   |                               |
                   +---------------+---------------+
                                   |
                                   v
                              IChatClient
                                   |
                                   v
                          OpenAI ChatClient
                                   |
                                   v
                              OpenAI API
                                   |
             +---------------------+----------------------+
             |                     |                      |
             v                     v                      v
          Chat                   Tools                Structured
                                                       Output

For OpenAI-specific services:

                         OpenAIClient
                              |
         +--------------------+--------------------+
         |                    |                    |
         v                    v                    v
    ChatClient        EmbeddingClient        ImageClient
         |                    |                    |
         +--------------------+--------------------+
                              |
                              v
                         OpenAI API

OpenAI .NET Learning Path

A practical sequence is:

1. OpenAI with .NET
        |
        v
2. OpenAI API with C#
        |
        v
3. OpenAI SDK with .NET
        |
        v
4. OpenAI Chat Applications
        |
        v
5. OpenAI Text Generation
        |
        v
6. OpenAI Streaming
        |
        v
7. OpenAI Function Calling
        |
        v
8. OpenAI Tool Calling
        |
        v
9. OpenAI Structured Outputs
        |
        v
10. OpenAI Embeddings
        |
        v
11. OpenAI Vision
        |
        v
12. OpenAI Multimodal AI

This matches the progression of the .NET + AI roadmap.

Key Takeaways

The official OpenAI .NET SDK gives C# applications a strongly typed way to work with OpenAI services.

The core client types are:

OpenAIClient
ChatClient
ResponsesClient
EmbeddingClient
ImageClient
AudioClient
OpenAIFileClient
VectorStoreClient
ModerationClient
RealtimeClient

The official SDK documents these client areas directly.

For ordinary chat:

ChatClient client =
    new(model, apiKey);

ChatCompletion completion =
    await client.CompleteChatAsync(
        prompt);

For streaming:

await foreach (
    StreamingChatCompletionUpdate update
    in client.CompleteChatStreamingAsync(
        prompt))
{
    Console.Write(
        update.ContentUpdate);
}

For the Responses API:

ResponsesClient client =
    new(apiKey);

ResponseResult response =
    await client.CreateResponseAsync(
        "configured-model",
        prompt);

The current official Responses examples mark this API surface as experimental, so check the installed SDK version and changelog before using it in a long-lived production codebase.

For provider-neutral .NET architecture:

IChatClient chatClient =
    new OpenAIClient(apiKey)
        .GetChatClient(model)
        .AsIChatClient();

Microsoft documents this adapter as part of the OpenAI integration for Microsoft.Extensions.AI.

The key architectural separation is:

Application Logic
       |
       v
AI Abstraction
       |
       v
OpenAI SDK
       |
       v
OpenAI API

rather than:

Controller
   |
   +-- API key
   +-- prompt
   +-- model configuration
   +-- OpenAI request
   +-- error handling
   +-- business logic

The next topic in the roadmap is OpenAI API with C#, where the OpenAI API itself will be examined more deeply: requests, authentication, endpoints, payloads, response objects, chat versus Responses API concepts, error handling, and direct API-level control.

Post a Comment

Previous Post Next Post