.NET SDK
The official C# client for IVK Match. It wraps the gRPC surface, ships the generated message types, and provides helpers for packing engine payloads.
- Package:
Invokation.Match.Sdk - Source: invokation-games/ivk-match-dotnet-sdk
- Targets .NET 6, .NET 8, and .NET 10
- Apache-2.0
Install
Section titled “Install”dotnet add package Invokation.Match.SdkCreate a client
Section titled “Create a client”using Invokation.Match.Sdk;
using var sdk = MatchSdk.CreateBuilder() .WithBaseUrl("https://match.ivk.dev") .Build();The client is disposable and intended to be long-lived. Create one per process and share it; it holds a gRPC channel, and constructing one per request costs connections and latency.
| Builder option | Required | Purpose |
|---|---|---|
WithBaseUrl(string) | Yes | The endpoint to connect to |
WithRetryConfig(RetryConfig) | No | Retry policy for transient failures |
WithHttpClient(HttpClient) | No | Supply a custom HttpClient, for credentials or custom transport behaviour |
WithLogger(ILogger) | No | gRPC diagnostics |
Authentication
Section titled “Authentication”Wrap an HttpClient in a handler that adds the Authorization header to every call:
sealed class BearerTokenHandler(string token) : DelegatingHandler{ protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { request.Headers.Authorization = new("Bearer", token); return base.SendAsync(request, ct); }}
var httpClient = new HttpClient(new BearerTokenHandler(token){ InnerHandler = new HttpClientHandler(),});
using var sdk = MatchSdk.CreateBuilder() .WithBaseUrl("https://match.ivk.dev") .WithHttpClient(httpClient) .Build();Load the token from a secret store. See Authentication for where credentials belong and how to rotate them.
Create a ticket
Section titled “Create a ticket”using Google.Protobuf;using Matchmaker.Core.V1;using Matchmaker.Engines.Nemesis.V1;
var input = new EngineInput{ Players = { new Player { PlayerId = "alice", Mmr = 0.62, Latencies = { ["eu-central"] = 22, ["eu-west"] = 41 }, }, }, Attributes = { ["playlist"] = new Attribute { Values = { "TDM" } }, ["platform"] = new Attribute { Values = { "pc" }, Accepts = { "pc", "xbox" } }, },};
var ticket = new Ticket{ Id = Guid.NewGuid().ToString(), QueueId = "acme.quickplay", RulesetIds = { "5v5" }, EngineInput = Nemesis.PackInput(input), Metadata = ByteString.CopyFromUtf8(sessionId),};
var ticketId = await sdk.CreateTicketAsync(ticket, ct);Three details in this example are deliberate.
The caller generates the ID, and it must be a UUID. Because the caller controls the ID, retrying after a timeout is idempotent: the same ID cannot produce two pool entries.
It does not set CreatedAt. The server sets it and overwrites any value the client sends. It is the
basis of queue position, so it has to be server-authoritative.
Metadata comes back verbatim. Nothing reads it, and it is included on every outcome about
this ticket. Session and party IDs can be stored there instead of in a separate lookup table.
The return value confirms the ticket is durably queued. It is not a match. Matches arrive asynchronously, over the sink configured on the queue.
Operations
Section titled “Operations”| Method | Purpose |
|---|---|
CreateTicketAsync(ticket, ct) | Put a player or party into a queue |
CancelTicketAsync(queueId, ticketId, ct) | Remove a ticket from matchmaking |
ReactivateTicketsAsync(queueId, ticketIds, ct) | Return resolved tickets to the pool after a match that could not be honoured |
CreateBackfillRequestAsync(backfillRequest, ct) | Ask for players to fill a running match |
CancelBackfillRequestsAsync(queueId, backfillIds, ct) | Withdraw backfill requests |
ClearQueuePoolAsync(queueId, ct) | Empty a queue’s pool. Test tooling; it ejects every waiting player |
Request and response semantics for each are in the Matchmaking API reference.
Engine payload helpers
Section titled “Engine payload helpers”engine_input and engine_output are engine-defined protobuf messages carried as bytes. Every queue
runs Nemesis, and the Nemesis helper packs and unpacks both:
var output = Nemesis.UnpackOutput(match.EngineOutput);
var preferredHost = output.Host.Preferred;var teams = output.Teams;The package contains helpers for other engines. They are being deprecated; use Nemesis.
Consuming outcomes
Section titled “Consuming outcomes”The SDK does not consume outcomes. They are pushed to the sink configured on the queue rather than pulled through the client, so consuming them is a separate path in the game backend: an HTTP handler or a WebSocket consumer, not a client call.
See Outcome Delivery for the sinks, and Integration Flow for how to handle a match when it arrives.
Errors
Section titled “Errors”Failures surface as RpcException carrying the gRPC status codes documented in
Matchmaking API § Error semantics. Two need
specific handling:
Unavailable: retry with backoff rather than surfacing it to the player. It is usually a brief restart window, and tickets already queued survive it.FailedPreconditiononCancelTicketAsync: the player is very likely already in a match whose outcome is in flight. A failed cancel does not mean the player left the queue.