C#
The C# SDK is an idiomatic .NET client emitted from the same GlottoIR as every other target. It
uses file-scoped namespaces, using-imported System.* types, and XML /// docs generated from
the operation prose in the spec.
Quickstart
dotnet add package Petstore
using Petstore;
var client = new Petstore.Client(token: Environment.GetEnvironmentVariable("PETSTORE_TOKEN"));
// every method takes a CancellationToken
var pet = await client.Pets.CreatePet(newPet, cancellationToken);
// paging is an IAsyncEnumerable
await foreach (var pet in client.Pets.ListPets())
{
Console.WriteLine(pet.Name);
}
Injectable HttpClient
The Client takes an HttpClient you supply rather than constructing (and leaking) one per
instance, so it slots into IHttpClientFactory, Polly resilience handlers, and test handlers the
standard .NET way — no socket-exhaustion anti-pattern.
Resource accessors
Operations are PascalCase methods on resource accessors — client.Pets.Get(id),
client.Pets.Photos.Add(...) — and every method accepts a CancellationToken so calls honor
cooperative cancellation.
Typed errors
Non-2xx responses throw a typed ApiError carrying the parsed error body; discriminated-union
response bodies resolve to the right concrete type. See Errors.
Pagination
List methods return an async stream (await foreach) that walks every page, advancing the cursor
for you. See Pagination.
Retries & backoff
Transient failures (5xx, 429, transport errors) retry with exponential backoff and jitter,
configurable per client. See Retries & timeouts, Streaming,
and Authentication.
Unknown response fields
Your API can add a response field without it being a breaking change — but a generated record has no member to put it in, and System.Text.Json discards what it does not recognize. The generated
models keep it instead: a field the SDK wasn’t generated from is retained on decode, readable
through an accessor, and written back out when the model is re-serialized.
var pet = await client.Pets.CreatePet(body);
// A field your API started returning after this SDK was generated.
if (pet.ExtraFields.TryGetValue("species", out var species))
{
Console.WriteLine(species.GetString());
}
// Re-encoding preserves it — a read-modify-write never silently drops it.
var json = JsonSerializer.Serialize(pet);
ExtraFields is an IReadOnlyDictionary<string, JsonElement>, so nested objects and arrays
survive intact, and retention is recursive.
Your existing construction still compiles: the retention storage is a record body member, not a positional parameter, so the record’s constructor signature is unchanged.
The retained fields are read-only by design. To send a field your spec doesn’t model yet, use the per-call extra-body escape hatch rather than writing to the retained bag.
Pages, request controls, and files
Paginated methods also expose manual pages, so you can inspect one response and request its successor explicitly. Request overrides apply to the initial call and to each requested next page; see Retries & timeouts for precedence and cancellation.
Binary downloads return an owned byte response with metadata, a bounded read helper, and incremental consumption. Close the response when you stop early. Your generated README demonstrates these operations with your API’s names and the language’s native calling conventions.