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 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