Skip to main content

.NET Integration

Use HttpClient with the documented gateway route for Chat Completions.

This method does not use a version-specific AI framework adapter.

Prerequisites

  • Complete the Quickstart. Keep the gateway running.
  • Create a client API token for the application.
  • Use .NET 8 or a subsequent version.

Store the connection values in configuration or a secret manager:

export Verdictan__GatewayUrl="http://127.0.0.1:41002/v1/"
export Verdictan__ApiToken="vdt_..."
export Verdictan__Model="replace-with-configured-model"

The application token is different from the gateway runtime token. It is also different from the upstream provider credential.

Register the client

using System.Net.Http.Headers;

builder.Services.AddHttpClient("Verdictan", client =>
{
var gatewayUrl = builder.Configuration["Verdictan:GatewayUrl"]
?? throw new InvalidOperationException("Verdictan:GatewayUrl is required");
var token = builder.Configuration["Verdictan:ApiToken"]
?? throw new InvalidOperationException("Verdictan:ApiToken is required");

client.BaseAddress = new Uri(gatewayUrl);
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
client.Timeout = TimeSpan.FromSeconds(60);
});

Keep the trailing slash on the configured /v1/ base URL. The slash gives relative request paths the /v1 prefix.

Send a chat request

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

public sealed class GovernedChatService(IHttpClientFactory clients, IConfiguration config)
{
public async Task<string> CompleteAsync(
string input,
CancellationToken cancellationToken = default)
{
var model = config["Verdictan:Model"]
?? throw new InvalidOperationException("Verdictan:Model is required");
var payload = new
{
model,
messages = new[] { new { role = "user", content = input } }
};

var response = await clients.CreateClient("Verdictan").PostAsJsonAsync(
"chat/completions",
payload,
cancellationToken);

var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
if (!response.IsSuccessStatusCode)
{
string? code = null;
try
{
using var errorBody = JsonDocument.Parse(responseBody);
if (errorBody.RootElement.ValueKind == JsonValueKind.Object
&& errorBody.RootElement.TryGetProperty("error", out var error)
&& error.ValueKind == JsonValueKind.Object
&& error.TryGetProperty("code", out var codeValue)
&& codeValue.ValueKind == JsonValueKind.String)
{
code = codeValue.GetString();
}
}
catch (JsonException)
{
// The error response is not JSON. Do not expose its body.
}

if (response.StatusCode == HttpStatusCode.BadRequest
&& code == "content_policy_violation")
{
throw new InvalidOperationException("Request blocked by policy");
}

throw new HttpRequestException(
$"Verdictan request failed with HTTP {(int)response.StatusCode}");
}

using var body = JsonDocument.Parse(responseBody);

var content = body.RootElement
.GetProperty("choices")[0]
.GetProperty("message")
.GetProperty("content");

return content.ValueKind == JsonValueKind.String
? content.GetString() ?? string.Empty
: throw new InvalidOperationException("Gateway returned invalid content");
}
}

The model must match a configured gateway target. A name in the application configuration does not enable the model.

Verify health and authorization in different steps

GET /healthz reports process health without a model request.

An authenticated GET /v1/models request verifies the application credential and the model catalog.

var client = clients.CreateClient("Verdictan");
var models = await client.GetAsync("models", cancellationToken);
models.EnsureSuccessStatusCode();

For an ASP.NET health indicator, select the applicable contract:

  • Probe /healthz for liveness without authentication.
  • Probe /v1/models with the named client for authenticated readiness.

Retry carefully

Policy blocks (400 with error.code=content_policy_violation) are not temporary. Authentication failures (401 or 403) are also not temporary.

A 429 or 5xx response can be temporary. A timeout can occur after the provider accepts the request.

If the application has no policy for duplicate work and cost, do not add automatic model retries.

Verify the governed path

verdictan policy lint --file policy-config.yaml
verdictan events tail --since 10m --follow

While the event tail is active, send a representative .NET request. A matching request ID shows that the gateway delivered Event evidence to the control plane.

Next steps