We use cookies. Cookies and browser storage keep you signed in and remember your preferences. With your permission we also measure how the site is used. You can any time. See our cookie policy.

C# / .NET

SDK guides

C# / .NET

Connect api14 from C# and .NET with the official OpenAI .NET package, with setup, chat, streaming and error handling examples.

Use the official OpenAI .NET library with api14. Set OpenAIClientOptions.Endpoint to api14 and pass a model id such as openai/gpt-5-mini when you create a client.

Install

dotnet add package OpenAI
export API14_API_KEY="sk-api14-..."

Configure

using System.ClientModel;
using OpenAI;
using OpenAI.Chat;

var credential = new ApiKeyCredential(Environment.GetEnvironmentVariable("API14_API_KEY")!);
var options = new OpenAIClientOptions { Endpoint = new Uri("https://api14.com/api/v1") };

var client = new ChatClient(model: "openai/gpt-5-mini", credential: credential, options: options);

Each ChatClient is bound to one model; create one per model id you use.

Read the key from the environment, user secrets or your secret store, never from appsettings.json committed to source control. In ASP.NET Core, register one client as a singleton; it is thread-safe.

Chat

ChatCompletion completion = await client.CompleteChatAsync("Explain vector databases in two sentences.");
Console.WriteLine(completion.Content[0].Text);

With a system prompt:

ChatCompletion completion = await client.CompleteChatAsync(
[
    new SystemChatMessage("You are terse."),
    new UserChatMessage("What is RAG?"),
]);

Streaming

var claude = new ChatClient("anthropic/claude-sonnet-4.5", credential, options);

await foreach (StreamingChatCompletionUpdate update in claude.CompleteChatStreamingAsync("Write a haiku about APIs."))
{
    foreach (ChatMessageContentPart part in update.ContentUpdate)
        Console.Write(part.Text);
}

See Streaming.

Tool calling

using System.Text.Json;

ChatTool getWeather = ChatTool.CreateFunctionTool(
    functionName: "get_weather",
    functionDescription: "Current weather for a city",
    functionParameters: BinaryData.FromString("""
        { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] }
        """));

var toolOptions = new ChatCompletionOptions { Tools = { getWeather } };
List<ChatMessage> messages = [new UserChatMessage("What's the weather in Istanbul?")];

ChatCompletion completion = await client.CompleteChatAsync(messages, toolOptions);
while (completion.FinishReason == ChatFinishReason.ToolCalls)
{
    messages.Add(new AssistantChatMessage(completion));
    foreach (ChatToolCall call in completion.ToolCalls)
    {
        using JsonDocument args = JsonDocument.Parse(call.FunctionArguments);
        string city = args.RootElement.GetProperty("city").GetString()!;
        messages.Add(new ToolChatMessage(call.Id, GetWeather(city))); // your function
    }
    completion = await client.CompleteChatAsync(messages, toolOptions);
}
Console.WriteLine(completion.Content[0].Text);

See Tool calling.

Structured output

var jsonOptions = new ChatCompletionOptions
{
    ResponseFormat = ChatResponseFormat.CreateJsonSchemaFormat(
        jsonSchemaFormatName: "person",
        jsonSchema: BinaryData.FromString("""
            {
              "type": "object",
              "properties": { "name": { "type": "string" }, "born": { "type": "integer" } },
              "required": ["name", "born"],
              "additionalProperties": false
            }
            """),
        jsonSchemaIsStrict: true),
};

ChatCompletion result = await client.CompleteChatAsync(
    [new UserChatMessage("Extract: Ada Lovelace, born 1815.")], jsonOptions);

using JsonDocument person = JsonDocument.Parse(result.Content[0].Text);
Console.WriteLine(person.RootElement.GetProperty("born").GetInt32());

See Structured outputs.

Image input

var vision = new ChatClient("google/gemini-2.5-flash", credential, options);

ChatCompletion described = await vision.CompleteChatAsync(
[
    new UserChatMessage(
        ChatMessageContentPart.CreateTextPart("What is in this picture?"),
        ChatMessageContentPart.CreateImagePart(new Uri("https://example.com/photo.jpg"))),
]);

For a local file, use ChatMessageContentPart.CreateImagePart(BinaryData.FromBytes(File.ReadAllBytes("photo.png")), "image/png"). See Vision & files.

Embeddings

using OpenAI.Embeddings;

var embeddings = new EmbeddingClient("openai/text-embedding-3-small", credential, options);
OpenAIEmbedding embedding = await embeddings.GenerateEmbeddingAsync("The quick brown fox");
Console.WriteLine(embedding.ToFloats().Length);

Errors

Failed calls throw ClientResultException; the raw response carries the status and headers:

try
{
    await client.CompleteChatAsync("Hi");
}
catch (ClientResultException ex) when (ex.Status == 402)
{
    Console.Error.WriteLine("Balance too low, add funds (insufficient_balance).");
}
catch (ClientResultException ex) when (ex.Status == 429)
{
    string? retryAfter = null;
    ex.GetRawResponse()?.Headers.TryGetValue("Retry-After", out retryAfter);
    Console.Error.WriteLine($"Rate limited, retry after {retryAfter ?? "60"}s.");
}

See Errors.

Timeouts and retries

The library retries 429 and 5xx automatically with exponential backoff. Tune the retry count and network timeout on the options you pass to every client:

using System.ClientModel.Primitives;

var options = new OpenAIClientOptions
{
    Endpoint = new Uri("https://api14.com/api/v1"),
    NetworkTimeout = TimeSpan.FromMinutes(2),
    RetryPolicy = new ClientRetryPolicy(maxRetries: 4),
};

Pass a CancellationToken to any ...Async call to bound the total time across retries. Retrying does not help with daily_limit_reached or insufficient_balance. See Rate limits.

All capabilities

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api14.com/api/v1",
  apiKey: process.env.API14_API_KEY,
});

const result = await client.chat.completions.create({
  "model": "openai/gpt-5-mini",
  "messages": [
    {
      "role": "user",
      "content": "Explain vector databases in two sentences."
    }
  ]
});
console.log(result.choices[0].message.content);