.NET SDK
Official .NET client for ScaiGrid. Targets .NET 8 LTS. Idiomatic
async/await throughout, with IAsyncEnumerable<ChatChunk> for
streaming chat.
Install
| dotnet add package ScaiLabs.ScaiGrid
|
Quickstart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | 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
1
2
3
4
5
6
7
8
9
10
11
12
13 | // 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:
| public interface IAuthProvider
{
ValueTask<string> GetHeaderAsync(CancellationToken cancellationToken = default);
}
|
Resources
1
2
3
4
5
6
7
8
9
10
11
12
13 | // 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
| 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
| 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
| 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:
| 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | 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:
| 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:
| services.AddSingleton<ScaiGridClient>(_ => ScaiGridClient.WithApiKey(
builder.Configuration["ScaiGrid:ApiKey"]!));
|
For tighter control, inject your own HttpClient (e.g. one from
IHttpClientFactory with Polly handlers):
| var http = httpClientFactory.CreateClient("scaigrid");
var client = new ScaiGridClient(
new ScaiGridClientOptions { Auth = new ApiKeyAuth(apiKey) },
http);
|
Download
The current release is on
/downloads under ScaiGrid →
.NET, with SHA-256 verification.
See also