# .NET Reference

> Every modality, every method, every option.

The .NET SDK ships **five modality classes** plus a raw MoQT client, all
under the `ClutchCall.SDK` namespace.

```csharp
using ClutchCall.SDK;
// Voice, Streams, Robotics, Games, Data, Moqt
```

All modality clients implement `IAsyncDisposable`.

## Voice

```csharp
var v = new Voice(
    baseUrl: "https://app.clutchcall.dev",
    apiKey:  Environment.GetEnvironmentVariable("CLUTCHCALL_API_KEY"),
    orgId:   "org_abc");
```

### Calls

```csharp
var call = await v.Calls.OriginateAsync(new Voice.OriginateArgs {
    To       = "+15551234567",
    From     = "+15558675309",
    TrunkId  = "trunk_main",
    Agent    = "healthcare-assistant",
});

await v.Calls.GetAsync(sid);
await v.Calls.ListAsync(orgId, cursor, limit);
await v.Calls.TransferAsync(sid, new Voice.TransferArgs { To = "+1..." });
await v.Calls.HangupAsync(sid);
await v.Calls.TerminateAsync(sid);
```

`Call`:

```csharp
call.Sid    : string
call.Status : string
call.From   : string
call.To     : string

await call.RefreshAsync();
await call.HangupAsync();
await call.TransferAsync(args);
call.OnStatus(action);
```

### AudioBridge

```csharp
var bridge = await v.AudioBridge.AttachAsync(call.Sid,
    new Voice.AudioBridgeOpts {
        Codec    = Voice.Codec.Opus,
        OnUplink = (frame, tsUs) => myAsr.Feed(frame),
    });

bridge.PublishUplink(frame);
bridge.PublishDownlink(frame);
bridge.OnUplink(cb);
bridge.OnDownlink(cb);
await bridge.DisposeAsync();
```

`Codec`: `Opus`, `Pcm16`, `G711ULaw`, `G711ALaw`.

## Streams

```csharp
var s = new Streams("https://app.clutchcall.dev", apiKey, "org_abc");
```

### Control plane

```csharp
var created = await s.LiveInputs.CreateAsync(new Streams.CreateLiveInput {
    Name = "My Show",
});
string streamKey = created.StreamKey;          // returned ONCE

await s.LiveInputs.GetAsync(id);
await s.LiveInputs.ListAsync(orgId, cursor, limit);
await s.LiveInputs.RotateStreamKeyAsync(id);
await s.LiveInputs.DeleteAsync(id);

await s.SigningKeys.CreateAsync(orgId, label);
await s.SigningKeys.ListAsync(orgId);
await s.SigningKeys.RetireAsync(id);

await s.ApiKeys.CreateAsync(orgId, label, scopes);
await s.ApiKeys.ListAsync(orgId);
await s.ApiKeys.RevokeAsync(id);

await s.Webhooks.CreateAsync(orgId, url, events);
await s.Webhooks.ListAsync(orgId);
await s.Webhooks.DeleteAsync(id);
await s.Events.ListDeliveriesAsync(webhookId, cursor, limit);

await s.Analytics.ViewerMinutesAsync(orgId, from, to, groupBy);
await s.Analytics.PopCacheAsync(orgId, from, to);
```

`LiveInput`:

```csharp
input.ExternalInputId : string
await input.SignedPlaybackUrlAsync(ttlSeconds, scopes); // → SignedPlaybackUrl
```

### Data plane

```csharp
var pub = await Streams.BroadcastPublisher.OpenAsync(new Streams.PublisherArgs {
    InputId = input.ExternalInputId, StreamKey = streamKey,
    Codecs  = new Streams.Codecs { Video = "avc1.42E01F", Audio = "opus" },
});
pub.Write(chunk);
await pub.CloseAsync(Streams.CloseReason.Complete);

var viewer = await Streams.BroadcastViewer.OpenAsync(signedUrl, new Streams.ViewerOpts {
    OnChunk = (init, chunk) => { /* ... */ },
    OnClose = (reason)      => { /* ... */ },
});
await viewer.DisposeAsync();
```

## Robotics

```csharp
var r = new Robotics(
    relayHost: "relay.clutchcall.dev",
    token:     Environment.GetEnvironmentVariable("CLUTCHCALL_RELAY_TOKEN"),
    robotId:   "turtlebot-7");
```

```csharp
var qos = new Robotics.QoSProfile {
    Reliability = Robotics.Reliability.Reliable,
    Depth       = 10,
};

var pub = await r.PublishTelemetryAsync("odom",
    "nav_msgs/msg/Odometry", qos);
pub.Write(cdrBytes);
await pub.DisposeAsync();

var cmd = await r.PublishCommandAsync("cmd_vel",
    "geometry_msgs/msg/Twist", null);

var sub = await r.SubscribeTelemetryAsync("odom", null,
    (payload, typeName) => { /* ... */ });
await sub.DisposeAsync();
```

`Reliability`: `BestEffort`, `Reliable`.
`Durability`: `Volatile`, `TransientLocal`.

## Games

```csharp
var g = new Games(
    relayHost: "relay.clutchcall.dev",
    token:     token,
    roomId:    "duel-42",
    playerId:  "alice"); // null for authority
```

### Player

```csharp
var stateSub = g.SubscribeState(bytes => Render(bytes));
var input    = await g.PublishInputAsync();
input.Write(SerializeInput(localInput));

var events = await g.PublishEventAsync();
events.Send(new { kind = "chat", text = "gg" });
g.SubscribeEvents((evt, fromPlayerId) => Dispatch(evt));
```

### Authority (playerId: null)

```csharp
var state = await g.PublishStateAsync(new Games.StateOpts { TickHz = 30 });
state.Write(SerializeState(world));

g.SubscribeInputs((playerId, payload) => ApplyInput(playerId, payload));
g.SubscribeEvents((evt, fromPlayerId) => { /* ... */ });
```

## Data

```csharp
var d = new Data(
    relayHost: "relay.clutchcall.dev",
    token:     Environment.GetEnvironmentVariable("CLUTCHCALL_DATA_TOKEN"),
    clientId:  "device-7");
```

```csharp
await d.PublishAsync(
    topic:   "sensors/room1/temperature",
    payload: Encoding.UTF8.GetBytes("23.5"),
    reliable: false,
    retain:   false);

var sub = await d.SubscribeAsync("sensors/+/temperature",
    msg => Console.WriteLine($"{msg.Topic} ← {msg.FromClientId} = " +
                             $"{Encoding.UTF8.GetString(msg.Payload)}"));
await sub.DisposeAsync();
```

`Data.Message`:

```csharp
msg.Topic        : string
msg.Payload      : byte[]
msg.FromClientId : string
msg.Retain       : bool
msg.TsUs         : ulong
```

## Realtime Tracks

See [Realtime Tracks](/concepts/realtime-tracks). Common methods:

```csharp
var client = await Moqt.Client.ConnectAsync("quic://relay.clutchcall.dev", token);

await client.PublishFrameAsync(ns, name, opts);
client.SubscribeFrame(ns, name, (tsUs, prio, data) => {...});
client.PublishAudio(...)  / client.SubscribeAudio(...)
client.PublishVideo(...)  / client.SubscribeVideo(...)
client.PublishText(...)   / client.SubscribeText(...)

client.SubscribeNamespace(prefix, (suffix, active) => {...});
client.ConnectionRttUs : ulong
client.MaxDatagramSize : int
await client.DisposeAsync();
```

## Legacy RPC

The root `ClutchCall.SDK.ClutchCallClient` (`Dial`, `OriginateBulk`,
`Hangup`, `Barge`, `PushAudio`, …) remains for backwards compat. New code
should prefer the [voice modality](#voice).
