---
title: .NET SDK
path: sdks/dotnet
status: published
---

# .NET SDK — `ScaiLabs.ScaiGrid`

Official .NET client for ScaiGrid. Targets .NET 8 LTS. Idiomatic
`async`/`await` throughout, with `IAsyncEnumerable<ChatChunk>` for
streaming chat.

## Install

```bash
dotnet add package ScaiLabs.ScaiGrid
```

## Quickstart

```csharp
using ScaiLabs.ScaiGrid;
using ScaiLabs.ScaiGrid.Models;

using var client = ScaiGridClient.WithApiKey("sgk_...");

var models = await client.Models.ListAsync(modality: "chat");
foreach (var m in models)
{
    Console.WriteLine(m.GetProperty("slug").GetString());
}

var request = new ChatRequest
{
    Model = "anthropic/claude-haiku-4-5",
    Messages = new() { new() { Role = "user", Content = "Hello!" } },
    Metadata = new() { ["agentId"] = "..." },
};
await foreach (var chunk in client.Inference.ChatStreamAsync(request))
{
    Console.Write(chunk.Delta?.Content);
}
Console.WriteLine();
```

## Authentication

```csharp
// 1. Static API key (most common)
using var client = ScaiGridClient.WithApiKey("sgk_...");

// 2. Pre-minted JWT
using var client = ScaiGridClient.WithBearerToken(jwt);

// 3. Custom IAuthProvider — implement OAuth client_credentials or
//    federated tokens yourself.
using var client = new ScaiGridClient(new ScaiGridClientOptions
{
    Auth = new MyAuthProvider(),
    BaseUrl = new Uri("https://scaigrid.example.com"),
});
```

The base interface is small:

```csharp
public interface IAuthProvider
{
    ValueTask<string> GetHeaderAsync(CancellationToken cancellationToken = default);
}
```

## Resources

```csharp
// Models
await client.Models.ListAsync(modality: "chat");
await client.Models.GetAsync("anthropic/claude-haiku-4-5");

// Inference — chat (Task), ChatStreamAsync (IAsyncEnumerable), embed
await client.Inference.ChatAsync(request);
await foreach (var chunk in client.Inference.ChatStreamAsync(request)) { }
await client.Inference.EmbedAsync(model: "...", input: new[] { "..." });

// Modules — generic proxy
await client.Modules.ProxyAsync(
    "scaimatrix", HttpMethod.Post, "/collections",
    jsonBody: new { name = "...", embedding_model = "..." });
```

## Module sub-clients

### ScaiBot

```csharp
var bot = await client.ScaiBot.Bots.CreateAsync(
    name: "support",
    model: "anthropic/claude-haiku-4-5",
    displayName: "Support Bot");

var calls = await client.ScaiBot.Voice.ListCallsAsync(limit: 20);
var transcript = await client.ScaiBot.Voice.GetTranscriptAsync(callId);
var cost = await client.ScaiBot.Voice.GetCostAsync(callId);
```

### ScaiSpeak

```csharp
var result = await client.ScaiSpeak.SynthesizeAsync(
    voiceId: "voice_...",
    text: "Hello, world!");
// result.Audio is a byte[] of decoded audio.

var voices = await client.ScaiSpeak.Voices.ListAsync(language: "nl");
```

### ScaiDial

```csharp
var extensions = await client.ScaiDial.Extensions.ListAsync();
await client.ScaiDial.Extensions.CreateAsync(
    number: "1042", type: "bot", targetRef: bot.Id);

await client.ScaiDial.Dialplans.AddRuleAsync(
    dialplanId,
    actionType: "goto_extension",
    actionParams: new Dictionary<string, object?> { ["extension_number"] = "1042" });
```

## Streaming

`ChatStreamAsync` returns `IAsyncEnumerable<ChatChunk>`. Compose with
`await foreach` and cancellation tokens:

```csharp
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

await foreach (var chunk in client.Inference.ChatStreamAsync(request)
    .WithCancellation(cts.Token))
{
    Console.Write(chunk.Delta?.Content);
    if (chunk.FinishReason is not null)
    {
        Console.WriteLine($"  [finished: {chunk.FinishReason}]");
    }
}
```

## Errors

Every exception extends `ScaiGridException` with a `Code` mirroring the
server:

```csharp
using ScaiLabs.ScaiGrid.Exceptions;

try
{
    await client.Inference.ChatAsync(request);
}
catch (RateLimitedException ex)
{
    Console.WriteLine($"retry after {ex.RetryAfter}");
}
catch (BudgetExceededException)
{
    // Tenant or user budget cap hit.
}
catch (ScaiGridException ex)
{
    // Catch-all; ex.Code, ex.HttpStatus, ex.RequestId all populated.
}
```

Full taxonomy: `AuthException`, `AuthTokenInvalidException`,
`AuthTokenExpiredException`, `PermissionDeniedException`,
`NotFoundException`, `ConflictException`, `ValidationException`,
`RateLimitedException`, `BudgetExceededException`, `BackendException`,
`BackendTimeoutException`, `InternalServerException`,
`TimeoutException`, `TransportException`.

## Request IDs + observability

Every method takes an optional `requestId`. Forwarded as `X-Request-ID`:

```csharp
await client.Inference.ChatAsync(
    request,
    requestId: "req-batch-job-7");
```

## Dependency injection

The client is `IDisposable` and safe to register as a singleton — the
underlying `HttpClient` is reused across requests:

```csharp
services.AddSingleton<ScaiGridClient>(_ => ScaiGridClient.WithApiKey(
    builder.Configuration["ScaiGrid:ApiKey"]!));
```

For tighter control, inject your own `HttpClient` (e.g. one from
`IHttpClientFactory` with Polly handlers):

```csharp
var http = httpClientFactory.CreateClient("scaigrid");
var client = new ScaiGridClient(
    new ScaiGridClientOptions { Auth = new ApiKeyAuth(apiKey) },
    http);
```

## Download

The current release is on
[`/downloads`](https://www.scailabs.ai/downloads) under **ScaiGrid →
.NET**, with SHA-256 verification.

## See also

- [Authentication](/docs/scaigrid/getting-started/authentication)
- [Chat completions](/docs/scaigrid/api-guides/chat-completions)
- [SDKs overview](/docs/scaigrid/sdks)
