OpenAI API with C#

After learning how to use OpenAI with .NET, the next step is understanding the OpenAI API itself.

The SDK is convenient, but a strong C# developer should also understand what happens underneath:

C# Application
      |
      v
HTTP Request
      |
      +-- Authorization
      +-- Headers
      +-- JSON Body
      |
      v
OpenAI REST API
      |
      v
Model
      |
      v
JSON Response
      |
      v
C#

This knowledge becomes extremely useful when:

debugging SDK behavior
working with HttpClient
building custom API abstractions
using unsupported SDK features
integrating with proxies
building middleware
working with OpenAI-compatible endpoints
diagnosing HTTP errors
understanding request/response payloads

OpenAI's current API reference identifies the Responses API as the primary interface for direct model requests, supporting text and image input, stateful interactions, built-in tools, and function calling. Chat Completions remains available as a separate API surface. The REST API version is currently v1.

The current API architecture can be visualized as:

                        OpenAI API
                            |
        +-------------------+-------------------+
        |                   |                   |
        v                   v                   v
    Responses         Chat Completions       Realtime
        |                   |                   |
        v                   v                   v
   Model Requests       Chat Requests       Live Sessions

For new general-purpose model integrations, the Responses API is the main API surface to learn. OpenAI's current migration guidance also states that the Assistants API was sunset on August 26, 2026, with Responses recommended for new integrations.

What Is the OpenAI API?

The OpenAI API is an HTTP-based service that allows applications to send requests to OpenAI models and receive model-generated results.

At the lowest level, C# is simply making an HTTP request.

For example:

POST /v1/responses
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

with JSON such as:

{
  "model": "your-model-name",
  "input": "Explain dependency injection in ASP.NET Core."
}

The API returns a JSON response.

OpenAI's API reference documents the REST API as version v1 and describes authentication using Bearer credentials.

API Architecture

A production C# application normally looks like:

ASP.NET Core
     |
     v
Application Service
     |
     v
HttpClient / OpenAI SDK
     |
     v
HTTPS
     |
     v
https://api.openai.com
     |
     v
OpenAI API
     |
     v
Model

There are two major ways to access the API from C#:

1. Direct HTTP
   HttpClient

and:

2. Official .NET SDK
   OpenAI NuGet package

Understanding both is valuable.

Direct HTTP vs SDK

Direct HTTP

You manually control:

URL
HTTP method
headers
JSON body
serialization
error handling
response parsing

Example:

using HttpClient

Advantages:

full control
no SDK dependency
easy protocol inspection
easy custom middleware
useful for unsupported endpoints

Disadvantages:

more code
manual JSON handling
manual error handling
manual response parsing
more maintenance

Official SDK

The official OpenAI .NET package wraps the REST API in strongly typed C# clients. The current SDK includes clients such as ChatClient, ResponsesClient, EmbeddingClient, ImageClient, AudioClient, OpenAIFileClient, VectorStoreClient, ModerationClient, and others. It is generated from OpenAI's OpenAPI specification in collaboration with Microsoft.

Advantages:

strongly typed API
less boilerplate
built-in request handling
async methods
streaming support
tool calling
structured outputs

Disadvantages:

additional dependency
SDK version changes
some newer surfaces may be experimental
less direct control than raw HTTP

The two approaches ultimately communicate with the same REST API.

OpenAI API Authentication

OpenAI API authentication uses a Bearer credential.

The HTTP header is:

Authorization: Bearer OPENAI_API_KEY

OpenAI's current API reference explicitly states that API keys are secret and should not be exposed in browser or client-side code. Server applications should load credentials from environment variables or a key-management service.

In C#:

using System.Net.Http.Headers;

httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue(
        "Bearer",
        apiKey);

Never Hard-Code API Keys

Avoid:

var apiKey =
    "sk-xxxxxxxxxxxxxxxx";

Do not commit API keys into:

Git
GitHub
appsettings.json
HTML
JavaScript
Blazor WebAssembly
mobile application binaries

Use:

environment variables
.NET User Secrets
Azure Key Vault
AWS Secrets Manager
GCP Secret Manager
another secure secret store

OpenAI's current API reference specifically instructs developers not to expose API keys in client-side code.

Using an Environment Variable

Windows PowerShell:

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

Persistent Windows environment variable:

setx OPENAI_API_KEY "your-api-key"

Then read it:

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

Linux/macOS:

export OPENAI_API_KEY="your-api-key"

Using .NET User Secrets

For local ASP.NET Core development:

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

Read from configuration:

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

This is preferable to putting secrets in a committed configuration file.

The OpenAI Base URL

The public OpenAI API is accessed under:

https://api.openai.com/v1/

OpenAI's current API documentation refers to the REST API as version v1.

For example:

https://api.openai.com/v1/responses

or:

https://api.openai.com/v1/chat/completions

The Responses API

The Responses API is currently OpenAI's main general-purpose interface for model requests.

It supports:

text input
image input
text output
stateful interactions
function calling
web search
file search
computer use
other tools

OpenAI's current Responses overview describes it as the advanced interface for generating model responses and extending models with built-in tools and function calling.

A basic request is:

POST https://api.openai.com/v1/responses

Body:

{
  "model": "your-model-name",
  "input": "Explain dependency injection in ASP.NET Core."
}

First OpenAI API Call with HttpClient

Create a console application:

dotnet new console -n OpenAIApiCSharp
cd OpenAIApiCSharp

No OpenAI SDK is required for the following example.

C#:

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

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.");

using var httpClient =
    new HttpClient
    {
        BaseAddress =
            new Uri("https://api.openai.com/v1/")
    };

httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue(
        "Bearer",
        apiKey);

var request = new
{
    model,
    input =
        "Explain dependency injection in ASP.NET Core."
};

using var response =
    await httpClient.PostAsJsonAsync(
        "responses",
        request);

response.EnsureSuccessStatusCode();

var json =
    await response.Content.ReadAsStringAsync();

Console.WriteLine(json);

The important pieces are:

BaseAddress
Authorization
POST
JSON request
response

Request Anatomy

The HTTP request is:

POST
    |
    v
https://api.openai.com/v1/responses

Headers
    |
    +-- Authorization
    +-- Content-Type

Body
    |
    +-- model
    +-- input
    +-- optional configuration

For example:

POST /v1/responses HTTP/1.1
Host: api.openai.com
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Body:

{
  "model": "your-model-name",
  "input": "Hello"
}

Using JsonSerializer

Instead of:

PostAsJsonAsync(...)

you can serialize explicitly.

var json =
    JsonSerializer.Serialize(
        request);

using var content =
    new StringContent(
        json,
        System.Text.Encoding.UTF8,
        "application/json");

using var response =
    await httpClient.PostAsync(
        "responses",
        content);

This gives more control over:

serializer options
naming policies
custom converters
payload logging
content construction

Create Request DTOs

Instead of anonymous objects:

public sealed class OpenAIResponseRequest
{
    public required string Model { get; init; }

    public required string Input { get; init; }
}

Then:

var request =
    new OpenAIResponseRequest
    {
        Model = model,
        Input =
            "Explain dependency injection."
    };

This is preferable in a reusable application service.

Creating a Response DTO

A simplified response representation could be:

public sealed class OpenAIResponse
{
    public string? Id { get; set; }

    public string? Model { get; set; }

    public List<OpenAIOutputItem> Output { get; set; } = [];
}

And:

public sealed class OpenAIOutputItem
{
    public string? Type { get; set; }

    public string? Role { get; set; }

    public List<OpenAIContentItem> Content { get; set; } = [];
}

Then:

public sealed class OpenAIContentItem
{
    public string? Type { get; set; }

    public string? Text { get; set; }
}

For production systems, model the parts of the response your application actually needs rather than mirroring the entire API response blindly.

Extracting Text from a Responses API Result

Because response output is item-based, a reusable helper is useful.

using System.Text.Json;

public static class OpenAIResponseParser
{
    public static string ExtractText(
        string json)
    {
        using var document =
            JsonDocument.Parse(json);

        if (!document.RootElement.TryGetProperty(
                "output",
                out var output))
        {
            throw new InvalidOperationException(
                "The response did not contain output.");
        }

        foreach (
            var outputItem
            in output.EnumerateArray())
        {
            if (!outputItem.TryGetProperty(
                    "type",
                    out var type))
            {
                continue;
            }

            if (!string.Equals(
                    type.GetString(),
                    "message",
                    StringComparison.Ordinal))
            {
                continue;
            }

            if (!outputItem.TryGetProperty(
                    "content",
                    out var content))
            {
                continue;
            }

            foreach (
                var contentItem
                in content.EnumerateArray())
            {
                if (!contentItem.TryGetProperty(
                        "type",
                        out var contentType))
                {
                    continue;
                }

                if (!string.Equals(
                        contentType.GetString(),
                        "output_text",
                        StringComparison.Ordinal))
                {
                    continue;
                }

                if (contentItem.TryGetProperty(
                        "text",
                        out var text))
                {
                    return text.GetString() ?? string.Empty;
                }
            }
        }

        return string.Empty;
    }
}

Then:

var text =
    OpenAIResponseParser.ExtractText(
        json);

Console.WriteLine(text);

The official SDK hides this response traversal for you with APIs such as ResponseResult.GetOutputText().

Why Response Parsing Matters

A common beginner assumption is:

response JSON
    |
    v
response["text"]

The Responses API is richer than a single flat string.

A response can contain multiple output item types:

Response
|
+-- message
|    |
|    +-- output_text
|
+-- tool call
|
+-- other output item

This is one reason the official SDK is convenient.

Using the Official OpenAI SDK

Install:

dotnet add package OpenAI

The official .NET library is designed to provide convenient access to the OpenAI REST API using strongly typed C# clients.

Basic Responses API with the SDK

The current official C# sample uses ResponsesClient:

#pragma warning disable OPENAI001

using OpenAI.Responses;

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.");

ResponsesClient client =
    new(apiKey);

ResponseResult response =
    await client.CreateResponseAsync(
        model,
        "Explain dependency injection in ASP.NET Core.");

Console.WriteLine(
    response.GetOutputText());

#pragma warning restore OPENAI001

The current official repository provides ResponsesClient.CreateResponseAsync(...), but the Responses client is currently marked experimental in the SDK, so the warning suppression shown above is part of the current example.

Why OPENAI001 Appears

The official SDK marks some APIs experimental while their .NET design continues to evolve. The SDK documentation states that experimental APIs require explicit acknowledgement of the diagnostic.

That means:

Experimental API
      |
      v
Check current SDK version
      |
      v
Check changelog
      |
      v
Test before production upgrade

Do not assume the current experimental surface will remain unchanged.

Responses API with Options

Instead of a simple prompt overload, you can build:

#pragma warning disable OPENAI001

using OpenAI.Responses;

var options =
    new CreateResponseOptions
    {
        Model = model
    };

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

ResponseResult response =
    await client.CreateResponseAsync(
        options);

Console.WriteLine(
    response.GetOutputText());

#pragma warning restore OPENAI001

This is useful when you need control over:

model
input items
tools
reasoning settings
streaming
other response configuration

The current official SDK's response examples use CreateResponseOptions and InputItems in this style.

Responses API Input Items

The Responses API uses input items instead of requiring every interaction to be represented as one large string.

For example:

options.InputItems.Add(
    ResponseItem.CreateUserMessageItem(
        "What is ASP.NET Core?"));

You can conceptually build:

Input Items
|
+-- User message
+-- Previous context
+-- Tool result
+-- Other supported input items

The Responses API documentation describes input and output as item-oriented and supports stateful interactions.

System Instructions with Responses API

A typical request separates application instructions from user input.

Conceptually:

Instructions
    +
User Input
    |
    v
Responses API

For SDK usage, use the current CreateResponseOptions surface and supply the appropriate instructions/input configuration supported by the version you are using.

For raw HTTP, keep the request structure aligned with the current Responses API schema rather than inventing custom fields.

Chat Completions API

The Chat Completions API remains available.

Endpoint:

POST /v1/chat/completions

OpenAI's current API reference still documents it as a separate API surface.

Basic request:

{
  "model": "your-model-name",
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant."
    },
    {
      "role": "user",
      "content": "Explain dependency injection."
    }
  ]
}

Calling Chat Completions with HttpClient

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

var request = new
{
    model,
    messages = new[]
    {
        new
        {
            role = "system",
            content =
                "You are a helpful .NET assistant."
        },
        new
        {
            role = "user",
            content =
                "Explain dependency injection."
        }
    }
};

using var httpClient = new HttpClient();

httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue(
        "Bearer",
        apiKey);

using var response =
    await httpClient.PostAsJsonAsync(
        "https://api.openai.com/v1/chat/completions",
        request);

response.EnsureSuccessStatusCode();

var json =
    await response.Content.ReadAsStringAsync();

Console.WriteLine(json);

Chat Completions Response Structure

A simplified response is:

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "Dependency injection is..."
      }
    }
  ]
}

The C# code can parse the first message:

using JsonDocument =
    System.Text.Json.JsonDocument;

using var document =
    JsonDocument.Parse(json);

var text =
    document.RootElement
        .GetProperty("choices")[0]
        .GetProperty("message")
        .GetProperty("content")
        .GetString();

This differs from the Responses API's output-item structure.

Responses vs Chat Completions

The conceptual difference is:

Chat Completions
|
+-- messages
+-- choices
+-- assistant content

versus:

Responses
|
+-- input items
+-- output items
+-- tools
+-- stateful interactions

OpenAI's current API reference presents Responses as the advanced general-purpose interface, while Chat Completions remains documented separately.

For new general model integrations, learn Responses first.

Assistants API Is No Longer the New-Integration Choice

Older tutorials may show:

Assistants
Threads
Runs

The current OpenAI migration documentation states that the Assistants API was sunset on August 26, 2026 and that new integrations should use the Responses API.

So new C# applications should not be designed around old Assistants examples.

OpenAI API Request Pipeline

A modern C# request can be viewed as:

C# Request Model
       |
       v
JSON Serialization
       |
       v
HttpRequestMessage
       |
       +-- Authorization
       +-- Content-Type
       +-- Client Request ID
       |
       v
OpenAI /v1/responses
       |
       v
Model
       |
       v
HTTP Response
       |
       v
JSON Parsing
       |
       v
Application DTO

Using HttpRequestMessage

For maximum control:

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

var requestObject = new
{
    model,
    input =
        "Explain dependency injection in ASP.NET Core."
};

var json =
    JsonSerializer.Serialize(
        requestObject);

using var request =
    new HttpRequestMessage(
        HttpMethod.Post,
        "https://api.openai.com/v1/responses");

request.Headers.Authorization =
    new AuthenticationHeaderValue(
        "Bearer",
        apiKey);

request.Content =
    new StringContent(
        json,
        Encoding.UTF8,
        "application/json");

using var response =
    await httpClient.SendAsync(
        request,
        cancellationToken);

var responseBody =
    await response.Content.ReadAsStringAsync(
        cancellationToken);

response.EnsureSuccessStatusCode();

Console.WriteLine(responseBody);

This is useful when you need:

custom headers
request IDs
diagnostics
custom HTTP handlers
retry middleware
distributed tracing

Supplying a Client Request ID

OpenAI's current API reference documents X-Client-Request-Id as an optional client-generated identifier. It recommends using a unique value per request; OpenAI logs it for supported endpoints, and it can help support locate a request when a network issue prevents you from receiving the server-generated request ID.

In C#:

var clientRequestId =
    Guid.NewGuid().ToString();

request.Headers.TryAddWithoutValidation(
    "X-Client-Request-Id",
    clientRequestId);

This can be especially useful for:

ASP.NET Core trace ID
correlation ID
distributed tracing
production troubleshooting

Reading the OpenAI Request ID

OpenAI's response headers include:

x-request-id

as a unique identifier for the API request. The API reference recommends logging request IDs in production for troubleshooting.

In C#:

if (response.Headers.TryGetValues(
        "x-request-id",
        out var values))
{
    var requestId =
        values.FirstOrDefault();

    Console.WriteLine(
        $"OpenAI Request ID: {requestId}");
}

This is extremely useful when diagnosing production failures.

Rate Limit Headers

OpenAI's current API reference documents rate-limit headers such as:

x-ratelimit-limit-requests
x-ratelimit-remaining-requests
x-ratelimit-reset-requests

x-ratelimit-limit-tokens
x-ratelimit-remaining-tokens
x-ratelimit-reset-tokens

These can help your application understand request and token capacity.

In C#:

foreach (
    var header
    in response.Headers)
{
    Console.WriteLine(
        $"{header.Key}: " +
        string.Join(
            ", ",
            header.Value));
}

For production, capture only the headers you actually need.

OpenAI Processing Time Header

The API reference documents:

openai-processing-ms

as a response header containing processing time information.

Read it:

if (response.Headers.TryGetValues(
        "openai-processing-ms",
        out var processingValues))
{
    var processingMs =
        processingValues.FirstOrDefault();
}

This lets you distinguish model-side processing time from total network/application latency.

HTTP Status Codes

Your C# service should not assume every request returns 200.

Possible categories include:

2xx
success

4xx
request/client problem

5xx
server-side problem

Application-specific examples include:

authentication failure
invalid request
rate limiting
resource problems
server errors

Always inspect:

response.IsSuccessStatusCode

or:

response.EnsureSuccessStatusCode();

For a production service, custom error handling is usually preferable to blindly calling EnsureSuccessStatusCode().

Reading an Error Response

OpenAI error responses are JSON.

A useful pattern:

var body =
    await response.Content.ReadAsStringAsync(
        cancellationToken);

if (!response.IsSuccessStatusCode)
{
    throw new HttpRequestException(
        $"OpenAI request failed with " +
        $"{(int)response.StatusCode}: {body}");
}

For a production library, create a dedicated exception model rather than embedding raw server JSON into exception messages.

Creating a Reusable OpenAI HTTP Client

Instead of creating HttpClient manually in every service:

builder.Services.AddHttpClient(
    "OpenAI",
    client =>
    {
        client.BaseAddress =
            new Uri(
                "https://api.openai.com/v1/");
    });

Then:

public sealed class OpenAIHttpService
{
    private readonly HttpClient _httpClient;

    public OpenAIHttpService(
        IHttpClientFactory factory)
    {
        _httpClient =
            factory.CreateClient("OpenAI");
    }
}

Add authentication using a handler or configure it in the service.

Using a Delegating Handler

A clean architecture can centralize API authentication:

public sealed class OpenAIAuthenticationHandler
    : DelegatingHandler
{
    private readonly IConfiguration _configuration;

    public OpenAIAuthenticationHandler(
        IConfiguration configuration)
    {
        _configuration = configuration;
    }

    protected override Task<HttpResponseMessage>
        SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
    {
        var apiKey =
            _configuration["OpenAI:ApiKey"]
            ?? throw new InvalidOperationException(
                "OpenAI API key is missing.");

        request.Headers.Authorization =
            new AuthenticationHeaderValue(
                "Bearer",
                apiKey);

        return base.SendAsync(
            request,
            cancellationToken);
    }
}

Register:

builder.Services.AddTransient<
    OpenAIAuthenticationHandler>();

builder.Services.AddHttpClient(
    "OpenAI",
    client =>
    {
        client.BaseAddress =
            new Uri(
                "https://api.openai.com/v1/");
    })
    .AddHttpMessageHandler<
        OpenAIAuthenticationHandler>();

Now the service does not need to manually add the Bearer header for every request.

API Service Abstraction

Define:

public interface IOpenAIHttpService
{
    Task<string> CreateResponseAsync(
        string model,
        string input,
        CancellationToken cancellationToken = default);
}

Implementation:

using System.Net.Http.Json;

public sealed class OpenAIHttpService
    : IOpenAIHttpService
{
    private readonly HttpClient _httpClient;

    public OpenAIHttpService(
        IHttpClientFactory factory)
    {
        _httpClient =
            factory.CreateClient("OpenAI");
    }

    public async Task<string> CreateResponseAsync(
        string model,
        string input,
        CancellationToken cancellationToken = default)
    {
        var request =
            new
            {
                model,
                input
            };

        using var response =
            await _httpClient.PostAsJsonAsync(
                "responses",
                request,
                cancellationToken);

        var body =
            await response.Content.ReadAsStringAsync(
                cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException(
                $"OpenAI API request failed: " +
                $"{(int)response.StatusCode}");
        }

        return body;
    }
}

Now:

Application
    |
    v
IOpenAIHttpService
    |
    v
HttpClient
    |
    v
OpenAI REST API

ASP.NET Core Example

Request:

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

Endpoint:

app.MapPost(
    "/api/openai",
    async (
        AIRequest request,
        IOpenAIHttpService service,
        IConfiguration configuration,
        CancellationToken cancellationToken) =>
    {
        var model =
            configuration["OpenAI:Model"]
            ?? throw new InvalidOperationException(
                "OpenAI model is missing.");

        var json =
            await service.CreateResponseAsync(
                model,
                request.Prompt,
                cancellationToken);

        return Results.Content(
            json,
            "application/json");
    });

A better production endpoint would normally parse the OpenAI response and return only the application fields that your API contract exposes.

Do Not Expose the OpenAI Response Directly

Avoid building APIs where your clients receive the entire provider response:

Browser
   |
   v
OpenAI full response

Prefer:

Browser
   |
   v
Your API DTO

For example:

public sealed class AIResponse
{
    public required string Answer { get; init; }

    public required string RequestId { get; init; }
}

Then:

return Results.Ok(
    new AIResponse
    {
        Answer = answer,
        RequestId = requestId
    });

This protects your public API from becoming tightly coupled to OpenAI's response schema.

OpenAI API and System Prompts

The API request should separate application instructions and user data according to the API surface you choose.

For a chat-oriented request, you can represent:

System
User

separately.

For the Responses API, use its current input/instruction model rather than assuming Chat Completions message payloads are interchangeable.

The important architectural principle is:

Application Instructions
        +
User Input
        |
        v
OpenAI API

not:

One giant unstructured string

OpenAI API and Prompt Templates

Suppose your template is:

Summarize this document for {{audience}}.

Document:
{{document}}

C#:

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

            ["document"] =
                documentText
        });

Then:

await openAIService.CreateResponseAsync(
    model,
    prompt,
    cancellationToken);

This connects the API layer with the prompt-management topics already covered.

OpenAI API and JSON

You can send a request asking for JSON:

var request =
    new
    {
        model,
        input =
            """
            Analyze this support ticket.

            Return a JSON object containing:
            category
            priority
            summary
            """
    };

For stricter output, use the API's structured-output capabilities rather than depending only on textual instructions.

The Responses API reference includes structured outputs as a dedicated guide.

OpenAI API and Tools

The Responses API supports tools such as web search and file search, and also supports function calling. OpenAI's current API overview explicitly lists these capabilities.

Conceptually:

C# Application
      |
      v
OpenAI Responses API
      |
      v
Model
      |
      v
Tool Call
      |
      v
C# Application
      |
      v
Tool Execution
      |
      v
Tool Result
      |
      v
OpenAI
      |
      v
Final Response

The important architectural rule remains:

Model requests an operation
        |
        v
Application decides whether it is allowed

OpenAI API and Web Search

OpenAI's current API overview lists web search as a built-in tool available through Responses.

The official .NET SDK sample creates a web-search tool using:

ResponseTool.CreateWebSearchTool()

and adds it to CreateResponseOptions.

The API-level concept is:

Response Request
   |
   +-- model
   +-- input
   +-- tools

OpenAI API and Function Calling

Function calling allows the model to request your application to execute a defined function.

Example:

getOrderStatus

C# application:

public sealed class OrderStatusRequest
{
    public string? OrderId { get; set; }
}

The API interaction becomes:

User
 |
 v
OpenAI
 |
 v
Function Call
 |
 v
C#
 |
 v
Business Logic
 |
 v
Result
 |
 v
OpenAI

The Responses API currently lists function calling among its supported capabilities.

OpenAI API and Image Input

The current Responses API supports image inputs. OpenAI's developer quickstart explicitly demonstrates sending text and an image as inputs.

Conceptually:

{
  "model": "your-model-name",
  "input": [
    {
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "What is in this image?"
        },
        {
          "type": "input_image",
          "image_url": "..."
        }
      ]
    }
  ]
}

The C# application can construct the equivalent JSON using strongly typed request objects or anonymous objects and serialize them with System.Text.Json.

OpenAI API and File Input

The current API documentation also includes file-related capabilities and file search within the Responses ecosystem.

This enables architectures such as:

Upload File
    |
    v
OpenAI Files / File Search
    |
    v
Responses API
    |
    v
Answer

File workflows become especially useful for:

PDF analysis
document assistants
knowledge bases
RAG
enterprise search

OpenAI API and Conversations

The current Responses API also has a conversation-oriented API surface.

OpenAI's API reference lists:

Create conversation
Retrieve conversation
Update conversation
Delete conversation
Create item
Retrieve item
List items

as part of the Responses ecosystem.

This means a production application can choose between:

Application-managed conversation history

and OpenAI-managed conversation state, depending on the requirements.

Application-Managed State

Your .NET application can maintain:

Conversation
|
+-- System instructions
+-- User message
+-- Assistant response
+-- User message
+-- Assistant response

Advantages:

maximum control
custom database
tenant separation
custom retention
provider portability

API-Managed State

The Responses API provides stateful interaction features.

Advantages can include:

simpler conversation handling
less manual history construction
API-native state management

But the application still needs to understand:

retention
privacy
deletion
cost
state ownership

OpenAI's current data-controls documentation notes that Responses application state has a default retention period when store is enabled, while organizations with Zero Data Retention have different handling.

store and Application State

For APIs that support stored response state, the application should deliberately decide whether state is stored.

Do not assume:

store = true

or:

store = false

is always appropriate.

Choose based on:

conversation architecture
privacy
retention
debugging
compliance
user expectations

The current OpenAI data-controls documentation specifically describes /v1/responses application-state retention behavior and the interaction with Zero Data Retention.

OpenAI API and Model Selection

Model selection should be configuration-driven:

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

C#:

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

Do not scatter model identifiers throughout the codebase.

OpenAI's model catalog changes over time, so current model availability should be checked against the official model documentation rather than assuming a model name remains available forever.

Pinned Model Versions

OpenAI's current API reference notes that model prompting behavior can change between snapshots and recommends pinned model versions plus application evaluations when consistent behavior matters.

That leads to:

Development
    |
    v
Model Snapshot
    |
    v
Evaluation
    |
    v
Production

instead of:

Production
    |
    v
Automatically changing model behavior

where reproducibility matters.

Model Configuration by Environment

Development:

{
  "OpenAI": {
    "Model": "development-model"
  }
}

Production:

{
  "OpenAI": {
    "Model": "production-model"
  }
}

The application binary remains unchanged.

Only configuration changes.

Request Timeouts

A web application must respect cancellation.

using var response =
    await httpClient.SendAsync(
        request,
        HttpCompletionOption.ResponseHeadersRead,
        cancellationToken);

This is useful especially for:

streaming
large responses
long-running requests
browser cancellation

OpenAI Streaming with Raw HTTP

The OpenAI API supports streaming events.

The current API reference lists Responses streaming events as a dedicated API surface.

At the HTTP level, streaming is delivered incrementally rather than as one final JSON document.

Conceptually:

HTTP Connection
      |
      +-- event
      +-- event
      +-- event
      +-- event
      |
      v
final response

In ASP.NET Core, this can be forwarded to:

SSE
HTTP streaming
SignalR

depending on your UI architecture.

HttpCompletionOption

For streaming:

using var response =
    await httpClient.SendAsync(
        request,
        HttpCompletionOption.ResponseHeadersRead,
        cancellationToken);

Then access:

await response.Content
    .ReadAsStreamAsync(
        cancellationToken);

The actual event format should be processed according to the current Responses streaming specification.

Streaming Parser Architecture

Do not parse the stream as one giant JSON response immediately.

Use:

HTTP stream
     |
     v
Streaming event parser
     |
     v
application event
     |
     v
UI / state

This is different from ordinary non-streaming JSON.

OpenAI API and Usage

AI APIs consume model resources.

Your C# application should track:

request count
token usage
duration
model
operation
errors

The OpenAI API exposes usage information on response objects, while the API reference also documents rate-limit headers.

A useful application metric is:

Operation: ResumeAnalysis
Model: configured-model
Requests: 10,000
Success: 9,940
Validation failures: 60
Average latency: ...
Token usage: ...

Rate-Limit Handling

A production application should inspect rate-limit behavior.

The API reference documents request and token limit headers, including remaining request and token capacity and reset information.

Architecture:

Request
   |
   v
Rate Limit Check
   |
   +---- capacity available ---> OpenAI
   |
   +---- exhausted ------------> backoff / queue

Retry with Backoff

A retry strategy should distinguish:

Transient failure
      |
      v
Retry with backoff

from:

Invalid request
      |
      v
Fix request

and:

Authentication failure
      |
      v
Fix credentials

Do not retry everything.

Using Polly or Resilience Pipelines

In modern .NET applications, resilience middleware can be placed around the HTTP client.

Architecture:

Application
    |
    v
HttpClient
    |
    v
Resilience Pipeline
    |
    +-- retry
    +-- timeout
    +-- circuit breaker
    |
    v
OpenAI API

This is preferable to embedding retry loops in every AI service.

Error Handling Service

Create:

public sealed class OpenAIException
    : Exception
{
    public int? StatusCode { get; }

    public string? RequestId { get; }

    public OpenAIException(
        string message,
        int? statusCode = null,
        string? requestId = null,
        Exception? innerException = null)
        : base(message, innerException)
    {
        StatusCode = statusCode;
        RequestId = requestId;
    }
}

Then:

throw new OpenAIException(
    "OpenAI request failed.",
    (int)response.StatusCode,
    requestId);

Now application code can handle:

catch (OpenAIException ex)
{
    logger.LogError(
        ex,
        "OpenAI request {RequestId} failed.",
        ex.RequestId);
}

Logging Request IDs

A useful production log:

Operation = DocumentSummary
Model = configured-model
RequestId = req_xxxxx
DurationMs = ...
Status = Success

OpenAI's API reference explicitly recommends logging x-request-id values for production troubleshooting.

OpenAI API and Distributed Tracing

Your application may already have:

TraceId
SpanId
CorrelationId

You can also send a unique:

X-Client-Request-Id

to OpenAI.

This creates:

Browser Request
      |
      v
ASP.NET Trace ID
      |
      v
X-Client-Request-Id
      |
      v
OpenAI
      |
      v
x-request-id

The current API reference documents X-Client-Request-Id and x-request-id specifically for troubleshooting and correlation.

OpenAI API and IHttpClientFactory

IHttpClientFactory is usually preferable to manually constructing a new HttpClient in every operation.

Use:

builder.Services.AddHttpClient(
    "OpenAI",
    client =>
    {
        client.BaseAddress =
            new Uri(
                "https://api.openai.com/v1/");
    });

Then:

var client =
    factory.CreateClient("OpenAI");

This integrates naturally with:

dependency injection
delegating handlers
telemetry
resilience
connection management

Direct REST vs Official SDK Architecture

The two architectures are:

Direct REST

Application
   |
   v
HttpClient
   |
   v
OpenAI REST API

and:

Official SDK

Application
   |
   v
ResponsesClient / ChatClient
   |
   v
OpenAI REST API

The SDK itself ultimately communicates with the API.

The official .NET library documents both strongly typed clients and lower-level protocol methods for scenarios where more direct REST access is required.

When Direct HTTP Is Better

Use direct HTTP when:

you need exact protocol control
you need an endpoint not yet surfaced conveniently by the SDK
you are building an internal abstraction
you need unusual request handling
you need custom middleware
you are debugging raw payloads

When the SDK Is Better

Use the official SDK when:

you want strongly typed models
you want less boilerplate
you need streaming helpers
you need tool helpers
you need structured-output helpers
you want official C# abstractions

Hybrid Architecture

A practical enterprise design can use both:

Application
     |
     +----------------------+
     |                      |
     v                      v
IChatClient / SDK      Raw HttpClient
     |                      |
     +-----------+----------+
                 |
                 v
            OpenAI API

For example:

90%
normal model calls
      |
      v
official SDK

10%
specialized endpoint
      |
      v
raw HttpClient

This gives you flexibility without abandoning the SDK completely.

OpenAI API with C#

OpenAI API and JSON Serialization

Use System.Text.Json for application-owned JSON.

Request:

var request =
    new
    {
        model,
        input
    };

var json =
    JsonSerializer.Serialize(
        request);

Response:

using var document =
    JsonDocument.Parse(
        responseBody);

DTO:

var result =
    JsonSerializer.Deserialize<MyDto>(
        responseBody);

This keeps JSON logic explicit.

API Request DTO Example

public sealed class ResponseRequest
{
    public required string Model { get; init; }

    public required string Input { get; init; }
}

Serialize:

var request =
    new ResponseRequest
    {
        Model = model,
        Input = prompt
    };

var json =
    JsonSerializer.Serialize(
        request);

This is useful when building a reusable HTTP client library.

Generic OpenAI API Wrapper

Create:

public interface IOpenAIApiClient
{
    Task<JsonDocument> PostAsync(
        string endpoint,
        object request,
        CancellationToken cancellationToken = default);
}

Implementation:

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

public sealed class OpenAIApiClient
    : IOpenAIApiClient
{
    private readonly HttpClient _httpClient;

    public OpenAIApiClient(
        HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<JsonDocument> PostAsync(
        string endpoint,
        object request,
        CancellationToken cancellationToken = default)
    {
        using var response =
            await _httpClient.PostAsJsonAsync(
                endpoint,
                request,
                cancellationToken);

        var body =
            await response.Content
                .ReadAsStringAsync(
                    cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException(
                $"OpenAI API error: " +
                $"{(int)response.StatusCode}");
        }

        return JsonDocument.Parse(
            body);
    }
}

Now the application can use:

var response =
    await apiClient.PostAsync(
        "responses",
        new
        {
            model,
            input = prompt
        },
        cancellationToken);

Generic Typed Endpoint Wrapper

You can go further:

public interface IOpenAIApiClient
{
    Task<TResponse> PostAsync<TRequest, TResponse>(
        string endpoint,
        TRequest request,
        CancellationToken cancellationToken = default);
}

Implementation:

using System.Net.Http.Json;

public sealed class OpenAIApiClient
    : IOpenAIApiClient
{
    private readonly HttpClient _httpClient;

    public OpenAIApiClient(
        HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<TResponse>
        PostAsync<TRequest, TResponse>(
            string endpoint,
            TRequest request,
            CancellationToken cancellationToken = default)
    {
        using var response =
            await _httpClient.PostAsJsonAsync(
                endpoint,
                request,
                cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            var error =
                await response.Content
                    .ReadAsStringAsync(
                        cancellationToken);

            throw new HttpRequestException(
                $"OpenAI API request failed: " +
                $"{error}");
        }

        var result =
            await response.Content
                .ReadFromJsonAsync<TResponse>(
                    cancellationToken);

        return result
            ?? throw new InvalidOperationException(
                "OpenAI response was empty.");
    }
}

This creates a generic HTTP abstraction.

Why Not Expose HttpClient Everywhere?

Avoid:

Controller
   |
   +-- HttpClient
   +-- JSON
   +-- auth
   +-- retry
   +-- OpenAI URL
   +-- model

Prefer:

Controller
   |
   v
Application Service
   |
   v
IOpenAIClient
   |
   v
HttpClient
   |
   v
OpenAI

This creates clean separation of concerns.

OpenAI API Security Architecture

A secure design is:

Browser
   |
   v
Your ASP.NET Core API
   |
   v
Authentication
   |
   v
Authorization
   |
   v
OpenAI Service
   |
   v
Secret Store
   |
   v
API Key
   |
   v
OpenAI

Do not make:

Browser
   |
   v
OpenAI API

with your server secret embedded in the browser.

OpenAI's current API reference explicitly states that API keys should not be exposed in client-side code.

OpenAI API and Multi-Tenant Applications

A SaaS application might have:

Tenant A
Tenant B
Tenant C

Your backend should control:

tenant authorization
usage accounting
rate limits
prompt selection
data isolation
model configuration

OpenAI API credentials should normally remain server-side.

OpenAI API and Per-Tenant Usage

Track:

tenant
operation
model
request count
token usage
errors
latency

Then:

Tenant A
  |
  +-- 500 requests
  +-- ... tokens

Tenant B
  |
  +-- 900 requests
  +-- ... tokens

This can feed:

billing
quotas
rate limiting
analytics

OpenAI API and Request Headers

A production request often has:

Authorization: Bearer ...
Content-Type: application/json
X-Client-Request-Id: ...

OpenAI's current API reference also documents organization and project headers for applications that need to specify those scopes.

Use organization/project headers only when your account and architecture require them.

API Versioning

OpenAI's REST API is currently v1.

The API reference states that OpenAI aims to avoid breaking changes in major API versions and identifies the REST API version as v1.

That does not mean model behavior is static.

Model behavior can change between snapshots, which is why pinned versions and evals matter for reproducibility.

API Client Versioning

The OpenAI .NET SDK follows its own release lifecycle.

The official SDK says released client libraries adhere to semantic versioning, while experimental APIs can change before stabilization.

Therefore track both:

OpenAI REST API version
+
OpenAI .NET SDK version
+
model version

These are separate concepts.

OpenAI API and CI/CD

A good deployment pipeline is:

Code
  |
  v
Unit Tests
  |
  v
Integration Tests
  |
  v
AI Evaluation
  |
  v
Build
  |
  v
Deploy
  |
  v
Monitor

Track:

SDK version
model version
prompt version
schema version

This makes regressions easier to identify.

Testing Direct API Calls

For direct HTTP clients, unit-test:

request serialization
headers
authentication setup
response parsing
error mapping
retry behavior
cancellation

Avoid calling OpenAI for every test.

Fake HTTP Message Handler

You can test the HTTP layer with a custom HttpMessageHandler.

public sealed class FakeHttpMessageHandler
    : HttpMessageHandler
{
    protected override Task<HttpResponseMessage>
        SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
    {
        var response =
            new HttpResponseMessage(
                HttpStatusCode.OK)
            {
                Content =
                    JsonContent.Create(
                        new
                        {
                            id = "resp_test",
                            output = Array.Empty<object>()
                        })
            };

        return Task.FromResult(
            response);
    }
}

Then:

var httpClient =
    new HttpClient(
        new FakeHttpMessageHandler())
    {
        BaseAddress =
            new Uri(
                "https://api.openai.com/v1/")
    };

This lets tests run without a real network request.

Contract Testing

For stable API integrations, test that your request matches the expected OpenAI API contract.

For example:

POST /v1/responses

Required:
model
input

Then verify:

method
path
headers
JSON properties

This catches accidental request changes.

OpenAI API and Observability

A useful production record is:

TraceId
ClientRequestId
OpenAIRequestId
Operation
Model
PromptVersion
SchemaVersion
Duration
Status
Usage

Example:

Operation       = ResumeAnalysis
Model           = configured-model
PromptVersion   = 4.1
SchemaVersion   = 2
DurationMs      = 1840
Status          = Success
OpenAIRequestId = ...

This is much more useful than simply logging:

"AI call completed."

OpenAI API and Prompt Injection

The API itself does not remove the need for application security.

Your C# application still needs to distinguish:

trusted application instructions

from:

untrusted user input
untrusted documents
untrusted retrieved content
untrusted tool output

A basic architecture is:

System / Application Rules
        +
User Input
        +
Retrieved Data
        |
        v
OpenAI

and not:

All text
  |
  v
trusted instructions

OpenAI API and Tool Security

Suppose your model requests:

deleteCustomer

The application should still ask:

Is this user authorized?
Is this operation allowed?
Is the customer resource accessible?
Is the request within policy?

The AI is not an authorization layer.

OpenAI API and Sensitive Data

Before sending information to the API, consider:

Do we need this field?
Can it be minimized?
Can it be masked?
Can it be removed?
Should it be logged?
Should it be stored?

The AI API boundary should be deliberate.

OpenAI API and Data Retention

OpenAI's current documentation describes data-retention behavior for different API endpoints and application-state modes. For /v1/responses, response application state has a default retention period when stored, while Zero Data Retention changes this behavior.

Your application should therefore explicitly document:

what you store
what OpenAI stores
how long state persists
how conversations are deleted
which users can access data

Complete Direct-HTTP Responses API Service

Here is a reusable C# service:

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

public sealed class OpenAIResponseService
{
    private readonly HttpClient _httpClient;
    private readonly string _apiKey;

    public OpenAIResponseService(
        HttpClient httpClient,
        string apiKey)
    {
        _httpClient = httpClient;
        _apiKey = apiKey;
    }

    public async Task<string> GenerateAsync(
        string model,
        string input,
        CancellationToken cancellationToken = default)
    {
        using var request =
            new HttpRequestMessage(
                HttpMethod.Post,
                "responses");

        request.Headers.Authorization =
            new AuthenticationHeaderValue(
                "Bearer",
                _apiKey);

        request.Headers.TryAddWithoutValidation(
            "X-Client-Request-Id",
            Guid.NewGuid().ToString());

        request.Content =
            JsonContent.Create(
                new
                {
                    model,
                    input
                });

        using var response =
            await _httpClient.SendAsync(
                request,
                cancellationToken);

        var body =
            await response.Content
                .ReadAsStringAsync(
                    cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException(
                $"OpenAI request failed with " +
                $"{(int)response.StatusCode}.");
        }

        return ExtractText(body);
    }

    private static string ExtractText(
        string json)
    {
        using var document =
            JsonDocument.Parse(json);

        if (!document.RootElement.TryGetProperty(
                "output",
                out var output))
        {
            throw new InvalidOperationException(
                "OpenAI response has no output.");
        }

        foreach (
            var outputItem
            in output.EnumerateArray())
        {
            if (!outputItem.TryGetProperty(
                    "type",
                    out var type) ||
                type.GetString() != "message")
            {
                continue;
            }

            if (!outputItem.TryGetProperty(
                    "content",
                    out var content))
            {
                continue;
            }

            foreach (
                var contentItem
                in content.EnumerateArray())
            {
                if (!contentItem.TryGetProperty(
                        "type",
                        out var contentType) ||
                    contentType.GetString() !=
                        "output_text")
                {
                    continue;
                }

                if (contentItem.TryGetProperty(
                        "text",
                        out var text))
                {
                    return text.GetString()
                        ?? string.Empty;
                }
            }
        }

        return string.Empty;
    }
}

This illustrates the complete flow:

C#
 |
 +-- HttpRequestMessage
 |
 +-- Bearer authentication
 |
 +-- JSON body
 |
 v
/v1/responses
 |
 v
JSON response
 |
 v
JsonDocument
 |
 v
output message
 |
 v
output_text
 |
 v
string

OpenAI SDK Equivalent

The same operation through the official SDK is much shorter:

#pragma warning disable OPENAI001

using OpenAI.Responses;

var client =
    new ResponsesClient(
        apiKey);

var response =
    await client.CreateResponseAsync(
        model,
        input);

var text =
    response.GetOutputText();

#pragma warning restore OPENAI001

This is precisely why SDKs exist: they hide protocol plumbing while exposing a strongly typed C# surface. The current official repository demonstrates this pattern.

Choosing the Right Layer

A good rule is:

Need protocol control?
        |
        v
HttpClient
Need productive C# integration?
        |
        v
OpenAI SDK
Need provider abstraction?
        |
        v
IChatClient / Microsoft.Extensions.AI

These layers can coexist.

OpenAI API with Clean Architecture

A production system can look like:

                        Presentation
                             |
                             v
                    Application Service
                             |
                             v
                    IAIProvider Interface
                             |
              +--------------+--------------+
              |                             |
              v                             v
        OpenAI SDK Adapter            HTTP Adapter
              |                             |
              +--------------+--------------+
                             |
                             v
                         OpenAI API

Your application is then not tied directly to:

OpenAI URL
HTTP headers
JSON payload
SDK version

Example IAIProvider

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

OpenAI implementation:

public sealed class OpenAIProvider
    : IAIProvider
{
    private readonly OpenAIResponseService _service;

    public OpenAIProvider(
        OpenAIResponseService service)
    {
        _service = service;
    }

    public Task<string> GenerateAsync(
        string input,
        CancellationToken cancellationToken = default)
    {
        return _service.GenerateAsync(
            model: "configured-model",
            input,
            cancellationToken);
    }
}

This gives:

Application
      |
      v
IAIProvider
      |
      v
OpenAI

and later:

IAIProvider
|
+-- OpenAI
+-- Azure OpenAI
+-- Ollama
+-- another provider

Practical Project: Build an OpenAI API Wrapper in ASP.NET Core

Build:

POST /api/ai/generate

Request:

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

Architecture:

Browser
   |
   v
ASP.NET Core
   |
   v
IAIProvider
   |
   v
OpenAIResponseService
   |
   v
HttpClient
   |
   v
OpenAI /v1/responses

Features:

authentication
configuration
request IDs
logging
error handling
timeouts
cancellation
rate-limit monitoring

Practical Project: Build a JSON AI API

Endpoint:

POST /api/tickets/analyze

Input:

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

Application:

User Message
    |
    v
Prompt Template
    |
    v
Responses API
    |
    v
Structured JSON
    |
    v
C# DTO
    |
    v
Validation

Output:

{
  "category": "Billing",
  "priority": "High",
  "summary": "Duplicate payment reported."
}

Practical Project: Build a Streaming Chat API

Architecture:

Browser
   |
   v
ASP.NET Core
   |
   v
HttpClient / SDK
   |
   v
OpenAI Streaming API
   |
   +-- event
   +-- event
   +-- event
   |
   v
ASP.NET Core
   |
   v
Browser

Use:

Response streaming
+
Server-Sent Events

for a simple browser experience, or SignalR when your application needs richer bidirectional messaging.

Practical Project: Build an OpenAI Tool Calling API

Build:

POST /api/assistant

The assistant can request:

getOrderStatus
searchProducts
getCustomer

Architecture:

User
 |
 v
ASP.NET Core
 |
 v
OpenAI
 |
 v
Tool Call
 |
 v
.NET Tool Dispatcher
 |
 +-- authorization
 +-- validation
 +-- execution
 |
 v
Tool Result
 |
 v
OpenAI
 |
 v
Final Answer

This prepares you for the later OpenAI Function Calling and Tool Calling topics.

Common Mistakes

Exposing the API key to JavaScript

Never put a long-lived OpenAI API key into browser code. OpenAI's current API reference explicitly warns against exposing API keys in client-side code.

Hard-coding model names

Keep model selection in configuration.

Hard-coding the URL everywhere

Centralize the API base address.

Creating HttpClient repeatedly

Use IHttpClientFactory or the official SDK client lifecycle.

Assuming every response has one text property

Responses can contain multiple item types.

Ignoring response headers

Request IDs and rate-limit headers are valuable for production troubleshooting.

Logging API keys

Never log credentials.

Logging full prompts without considering privacy

Prompt content may contain sensitive data.

Retrying invalid requests

Retry transient failures, not permanent request errors.

Treating model output as trusted application data

Always validate.

Using old Assistants API tutorials for new projects

The Assistants API was sunset on August 26, 2026. Use the Responses API for new integrations.

Assuming Chat Completions and Responses have the same JSON shape

They do not.

Parsing streamed responses as one normal JSON document

Streaming is event-oriented.

Mixing provider logic into controllers

Use an AI service abstraction.

Frequently Asked Questions

What is the OpenAI API?

It is an HTTP API that allows applications to send requests to OpenAI models and receive AI-generated responses.

What is the current OpenAI REST API version?

The current OpenAI API reference identifies the REST API as v1.

What is the main API for new model integrations?

OpenAI's current API overview presents the Responses API as the main general-purpose interface for direct model requests, tool use, multimodal input, and stateful interactions.

Is Chat Completions still available?

Yes. OpenAI's current API reference continues to document the Chat Completions API.

Is the Assistants API still available for new applications?

No. OpenAI's migration documentation says the Assistants API was sunset on August 26, 2026, and recommends Responses for new integrations.

Can I call OpenAI without the SDK?

Yes. The API is an HTTP REST API, so C# can use HttpClient directly. OpenAI's API reference explicitly states that you can call the HTTP API directly or use an official client library.

What header authenticates an API request?

Authorization: Bearer OPENAI_API_KEY

This is documented in the current API reference.

Where should the API key be stored?

Use an environment variable or secure key-management system on the server. Do not expose it in browser or client-side application code.

What is X-Client-Request-Id?

It is a client-supplied unique request identifier that can be used to correlate application requests with OpenAI processing and troubleshooting.

What is x-request-id?

It is the unique identifier returned by OpenAI for an API request. OpenAI recommends logging it in production for troubleshooting.

Why is the Responses API output different from Chat Completions?

Responses uses item-oriented input/output structures, while Chat Completions uses messages and choices. OpenAI documents them as separate API surfaces.

Can I use HttpClient with ASP.NET Core?

Yes. IHttpClientFactory is a good foundation for reusable HTTP-based OpenAI integration.

Can I use the official OpenAI SDK?

Yes. The official .NET SDK provides strongly typed clients for OpenAI API capabilities.

Can I use ResponsesClient from C#?

Yes. The current official SDK provides ResponsesClient. Some Responses APIs are currently marked experimental in the .NET SDK.

Can I use ChatClient instead?

Yes. The official SDK provides ChatClient for Chat Completions.

Can I use OpenAI with IChatClient?

Yes. Microsoft provides adapters that expose OpenAI clients through IChatClient.

Should I return the complete OpenAI API response from my ASP.NET Core API?

Usually no. Return your own application DTO so your public API remains independent of the OpenAI response schema.

Can I store conversation state?

Yes. You can manage state in your application or use supported Responses conversation/state mechanisms. The current API reference includes conversation resources.

Should I use stored Responses state?

It depends on your architecture, privacy, retention, and conversation requirements. OpenAI documents application-state retention behavior for the Responses API.

Can I use image input?

Yes. The current Responses API supports image inputs.

Can I use tools?

Yes. Responses supports built-in tools and function calling.

Can I stream responses?

Yes. The current API reference documents Responses streaming events, and the official .NET SDK provides streaming APIs.

Should I pin model versions?

When consistent behavior matters, OpenAI recommends pinned model versions and evals because prompting behavior can change between snapshots.

Interview Questions

What is the OpenAI REST API?

It is an HTTP-based API used by applications to interact with OpenAI models and related services.

What is the current REST API version?

v1.

What is the Responses API?

The current general-purpose OpenAI interface for model requests, multimodal input, stateful interactions, tools, and function calling.

What is the difference between Responses and Chat Completions?

Responses uses item-based inputs and outputs and supports a broader current capability set, while Chat Completions uses the traditional message/choice structure.

How do you authenticate an OpenAI API request?

Use:

Authorization: Bearer OPENAI_API_KEY

How do you call the API in C# without the SDK?

Use HttpClient:

using var response =
    await httpClient.PostAsJsonAsync(
        "responses",
        request);

What is the advantage of direct HttpClient integration?

It provides low-level control over:

URL
headers
JSON
HTTP behavior
middleware
diagnostics

What is the advantage of the official SDK?

It provides strongly typed clients and higher-level C# abstractions.

What is ResponsesClient?

The official .NET SDK client for the Responses API.

What is ChatClient?

The official .NET SDK client for Chat Completions.

What is OpenAIClient?

The SDK's parent client for creating feature-specific clients that can share underlying implementation details.

Why use IHttpClientFactory?

It integrates HTTP clients with dependency injection, connection management, handlers, resilience, and observability.

What is x-request-id?

The OpenAI-generated request identifier returned in the HTTP response headers.

Why use X-Client-Request-Id?

To supply your own correlation identifier for a request. OpenAI documents it as a troubleshooting aid.

Why inspect rate-limit headers?

They help an application understand request/token capacity and remaining limits.

Should all OpenAI API failures be retried?

No. Retry only failures that are appropriate for retry, such as transient conditions or controlled rate-limit scenarios.

How do you secure OpenAI API keys?

Keep them server-side in environment variables or secure secret-management systems.

Why should model IDs be configurable?

Because model availability and behavior change over time.

Why use pinned model versions?

To reduce unexpected behavior changes between model snapshots when reproducibility matters. OpenAI recommends pinned versions plus evals for consistency.

What happened to the Assistants API?

OpenAI's migration guide states that the Assistants API was sunset on August 26, 2026 and new integrations should use Responses.

Best Practices

Keep the API key server-side

Never expose it to browser or mobile client code.

Centralize configuration

Use:

OpenAI:Model
OpenAI:ApiKey

or equivalent secure configuration.

Use IHttpClientFactory for raw HTTP

This makes the integration easier to maintain.

Use the official SDK for common operations

Do not manually implement protocol logic when the SDK already provides a stable abstraction.

Use raw HTTP when you genuinely need protocol control

Especially for specialized or newly exposed endpoints.

Log request identifiers

Record both:

X-Client-Request-Id
x-request-id

where appropriate.

Monitor rate limits

Track the documented rate-limit headers.

Pass cancellation tokens

Especially from ASP.NET Core request handlers.

Separate provider code from business logic

Use:

Application
   |
   v
AI Interface
   |
   v
OpenAI Infrastructure

Validate AI output

Never treat model-generated information as automatically correct.

Track prompt and model versions

This makes debugging reproducible.

Use evals for important AI behavior

OpenAI recommends evals when consistent prompting and model behavior matter.

Do not build new systems around sunset APIs

Use the current Responses API for new integrations rather than old Assistants API tutorials.

Keep the public API independent

Do not expose OpenAI-specific response objects directly from your application's public API.

Production Architecture

A robust C# OpenAI API architecture can look like:

                         ASP.NET Core
                              |
                              v
                      Application Service
                              |
                              v
                       IAIProvider
                              |
                +-------------+-------------+
                |                           |
                v                           v
         OpenAI SDK Adapter             HTTP Adapter
                |                           |
                +-------------+-------------+
                              |
                              v
                       HttpClient / SDK
                              |
                              v
                    OpenAI REST API v1
                              |
             +----------------+----------------+
             |                |                |
             v                v                v
        Responses        Chat Completions   Realtime
             |
       +-----+------+
       |            |
       v            v
    Models        Tools
       |
       v
    Response
       |
       v
 Validation / Mapping
       |
       v
  Domain Application
       |
       +---- SQL Server
       +---- Redis
       +---- Queue
       +---- External API

Practical Exercise

Build a C# console application that calls the Responses API through raw HttpClient.

Requirements:

1. Read OPENAI_API_KEY from environment.
2. Read AI_MODEL from environment.
3. Create HttpClient.
4. Send POST /v1/responses.
5. Add Authorization header.
6. Send JSON request.
7. Handle non-success HTTP status codes.
8. Read x-request-id.
9. Read the JSON response.
10. Extract generated text.

Architecture:

Environment
    |
    v
C# Configuration
    |
    v
HttpClient
    |
    v
POST /v1/responses
    |
    v
OpenAI
    |
    v
JSON
    |
    v
Text Extraction

Advanced Exercise

Create an ASP.NET Core service:

POST /api/ai/generate

Add:

IHttpClientFactory
OpenAI authentication handler
request correlation ID
structured logging
timeout
retry policy
rate-limit logging
custom exception
response parsing
application DTO

Then implement:

POST /api/ai/structured

that returns a strongly typed JSON result.

Advanced Project

Build a reusable:

OpenAI API Gateway

with:

                    Client Applications
                           |
                           v
                   ASP.NET Core Gateway
                           |
             +-------------+-------------+
             |                           |
             v                           v
      Authentication              Rate Limiting
             |                           |
             +-------------+-------------+
                           |
                           v
                    OpenAI Service
                           |
                +----------+----------+
                |                     |
                v                     v
             HttpClient              SDK
                |                     |
                +----------+----------+
                           |
                           v
                      OpenAI API

Features:

API key protection
tenant isolation
prompt management
model routing
usage tracking
request correlation
rate limiting
retries
timeouts
structured output
logging
metrics
caching
audit events

Learning Path

The OpenAI API with C# path can be learned in this order:

1. HTTP and REST basics
        |
        v
2. OpenAI authentication
        |
        v
3. /v1/responses
        |
        v
4. HttpClient in C#
        |
        v
5. Request JSON
        |
        v
6. Response JSON
        |
        v
7. System.Text.Json
        |
        v
8. Chat Completions
        |
        v
9. Responses API
        |
        v
10. Streaming
        |
        v
11. Structured Outputs
        |
        v
12. Function Calling
        |
        v
13. Tools
        |
        v
14. Conversations
        |
        v
15. Production API architecture

Key Takeaways

The OpenAI API is fundamentally an HTTP API.

At the lowest level:

C#
 |
 v
HTTP POST
 |
 v
https://api.openai.com/v1/responses
 |
 v
JSON
 |
 v
OpenAI Model
 |
 v
JSON Response
 |
 v
C#

Authentication uses:

Authorization: Bearer OPENAI_API_KEY

and API keys should remain server-side.

The current API architecture is centered around the Responses API for new general-purpose integrations. OpenAI's API reference describes Responses as the advanced interface for direct model requests, multimodal input, stateful interactions, tools, and function calling.

Chat Completions remains available as a separate API surface.

The REST API version is currently:

v1

The two main C# implementation choices are:

HttpClient
    =
direct API control

Official OpenAI SDK
    =
strongly typed convenience

The best architecture often combines those choices behind an application abstraction.

For production observability, record:

OpenAI Request ID
Client Request ID
Model
Operation
Latency
Usage
Status
Prompt Version
Schema Version

OpenAI's current API reference specifically documents request IDs, rate-limit headers, and the recommendation to log request IDs for production troubleshooting.

The most important architectural pattern is:

ASP.NET Core
      |
      v
Application Service
      |
      v
AI Provider Interface
      |
      v
OpenAI SDK / HttpClient
      |
      v
OpenAI API

That keeps OpenAI-specific protocol details inside infrastructure rather than spreading them across your application.


With that foundation, the next topic—OpenAI SDK with .NET—can focus specifically on the official OpenAI NuGet package, its client classes, dependency injection, typed requests, streaming, tools, structured outputs, and production SDK architecture.

Post a Comment

Previous Post Next Post