> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corti.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# C# .NET SDK

> Install, configure, and use the Corti C# .NET SDK

The `Corti.Sdk` NuGet package is the official C# .NET SDK for the Corti API. It provides full coverage of every REST endpoint and WebSocket connection, with built-in authentication, automatic token refresh, async/await support, and typed exceptions.

<Info>
  **Package:** [Corti.Sdk on NuGet](https://www.nuget.org/packages/Corti.Sdk) | **Examples:** [corti-examples](https://github.com/corticph/corti-examples)
</Info>

## Prerequisites

* **.NET 8** or later (also supports .NET Framework 4.6.2+ and .NET Standard 2.0)
* A Corti API client from the [Corti Console](https://console.corti.app/)

## Installation

<CodeGroup>
  ```bash .NET CLI theme={null}
  dotnet add package Corti.Sdk
  ```

  ```xml PackageReference theme={null}
  <PackageReference Include="Corti.Sdk" Version="*" />
  ```
</CodeGroup>

## Initialization

The simplest way to get started is with **client credentials** (server-side):

```csharp title="C# .NET" theme={null}
using Corti;

// Replace these with your values
const string CLIENT_ID = "<your-client-id>";
const string CLIENT_SECRET = "<your-client-secret>";
const string ENVIRONMENT = "<eu-or-us>";
const string TENANT = "<your-tenant-name>";

var client = new CortiClient(
    tenantName: TENANT,
    environment: ENVIRONMENT,
    auth: CortiClientAuth.ClientCredentials(
        clientId: CLIENT_ID,
        clientSecret: CLIENT_SECRET)
);
```

<Warning>Client credentials authentication is intended for **backend / server-side** applications only. Never expose your client secret in client-side code. For frontend scenarios, use [bearer tokens](/sdk/dotnet/authentication#bearer-token) or pass tokens from your backend.</Warning>

<Tip>Create a single `CortiClient` instance and reuse it throughout your application. The SDK handles token refresh automatically -- there is no need to re-initialize for each request.</Tip>

## Authentication

The SDK supports six authentication methods to cover different deployment scenarios:

| Method                               | Environment        | Use case                                          |
| :----------------------------------- | :----------------- | :------------------------------------------------ |
| **Client Credentials** (recommended) | Backend only       | Server-to-server integrations                     |
| **Authorization Code**               | Backend            | Interactive user login (confidential clients)     |
| **PKCE**                             | Frontend & Backend | Interactive user login (public clients)           |
| **ROPC**                             | Backend only       | Username/password for trusted apps                |
| **Bearer Token**                     | Frontend & Backend | When you already have a token from another source |
| **Custom Refresh**                   | Frontend & Backend | You manage token refresh yourself                 |

<Warning>**Client Credentials is the recommended authentication method.** Your backend should handle all data access checks and never expose the service-account token or client secret to the browser. The other flows (Authorization Code, PKCE, ROPC) are primarily intended for [Embedded Assistant](/assistant/authentication) use cases where end-user login is required.</Warning>

Each method is covered in detail in the [Authentication Guide](/sdk/dotnet/authentication).

## Usage examples

### Create an interaction

```csharp title="C# .NET" theme={null}
// Replace these with your values
const string ASSIGNED_USER_ID = "<uuid-of-your-choosing>";
const string IDENTIFIER = "<id-of-your-choosing>";

var now = DateTime.UtcNow;

var interaction = await client.Interactions.CreateAsync(new InteractionsCreateRequest
{
    AssignedUserId = ASSIGNED_USER_ID,
    Encounter = new InteractionsEncounterCreateRequest
    {
        Identifier = IDENTIFIER,
        Status = InteractionsEncounterStatusEnum.Planned,
        Type = InteractionsEncounterTypeEnum.FirstConsultation,
        Period = new InteractionsEncounterPeriod
        {
            StartedAt = now,
            EndedAt = now,
        },
        Title = "Consultation",
    },
    Patient = new InteractionsPatient
    {
        Identifier = "<string>",
        Name = "<string>",
        Gender = InteractionsGenderEnum.Male,
        BirthDate = new DateTime(1990, 1, 15, 0, 0, 0, DateTimeKind.Utc),
        Pronouns = "<string>",
    },
});
```

<Tip>See the full request specification in the [API Reference](/api-reference/interactions/create-interaction).</Tip>

### Real-time transcription (WebSocket)

```csharp title="C# .NET" theme={null}
var transcribe = await client.CreateTranscribeApiAsync();

transcribe.TranscribeTranscriptMessage.Subscribe(message =>
{
    Console.WriteLine($"Transcript: {message.Data.Text}");
});

await transcribe.ConnectAsync(new TranscribeConfig
{
    PrimaryLanguage = "en",
    AutomaticPunctuation = true,
});

// Send audio data (e.g. from a microphone stream)
await transcribe.Send(audioBytes);
```

### Text generation

```csharp title="C# .NET" expandable theme={null}
var document = await client.Documents.CreateAsync(
    interactionId,
    new DocumentsCreateRequestWithTemplateKey
    {
        Context =
        [
            DocumentsContext.FromDocumentsContextWithTranscript(
                new DocumentsContextWithTranscript
                {
                    Type = DocumentsContextWithTranscriptType.Transcript,
                    Data = new CommonTranscriptRequest
                    {
                        Text =
                            "tell me a bit about what's been going on? When did you first start feeling unwell?\n\n- Erm, I think it was probably about a week ago.\n\n- Right, okay. And when you say achy, was it more like muscle aches, or joint pain?",
                    },
                }
            ),
        ],
        TemplateKey = "corti-brief-clinical-note",
        Name = "test brief note from transcript",
        OutputLanguage = "en",
    }
);
```

## Error handling

The SDK throws typed exceptions you can catch and inspect:

```csharp theme={null}
try
{
    await client.Interactions.GetAsync("non-existent-id");
}
catch (CortiClientApiException ex)
{
    Console.WriteLine($"Status: {ex.StatusCode}");
    Console.WriteLine($"Message: {ex.Message}");
    Console.WriteLine($"Body: {ex.Body}");
}
```

## Advanced configuration

### Retries

The SDK retries failed requests with exponential backoff when the response is retryable and the attempt count is below the limit (default: **2**). A response is retryable when the status code is **408**, **429**, or any **5xx**.

Override per request:

```csharp theme={null}
await client.Interactions.CreateAsync(
    new InteractionsCreateRequest { /* ... */ },
    new RequestOptions { MaxRetries = 0 }
);
```

Set defaults for all requests via `CortiRequestOptions` on the client:

```csharp theme={null}
var client = new CortiClient(
    tenantName: "YOUR_TENANT",
    environment: "YOUR_ENVIRONMENT_ID",
    auth: CortiClientAuth.ClientCredentials(
        clientId: "YOUR_CLIENT_ID",
        clientSecret: "YOUR_CLIENT_SECRET"
    ),
    requestOptions: new CortiRequestOptions
    {
        MaxRetries = 3,
        Timeout = TimeSpan.FromMinutes(2),
    }
);
```

### Timeouts

The default request timeout is **30 seconds**. Override per request:

```csharp theme={null}
await client.Interactions.CreateAsync(
    new InteractionsCreateRequest { /* ... */ },
    new RequestOptions { Timeout = TimeSpan.FromSeconds(120) }
);
```

Or set a default timeout together with other options using `CortiRequestOptions` — see the example above.

### Raw responses

Methods that return `WithRawResponseTask<T>` let you await **either** the parsed model **or** parsed data plus HTTP metadata. Call `.WithRawResponse()` on the task to get status code, URL, and headers alongside `Data`:

```csharp theme={null}
var result = await client.Interactions.GetAsync("YOUR_INTERACTION_ID").WithRawResponse();

var interaction = result.Data;

Console.WriteLine(result.RawResponse.StatusCode);
Console.WriteLine(result.RawResponse.Url);

if (result.RawResponse.Headers.TryGetValue("X-Request-Id", out var requestId))
{
    Console.WriteLine($"Request ID: {requestId}");
}
```

<Note>Paginated `ListAsync` helpers return a `Pager` and do not expose `.WithRawResponse()`. Use a non-paginated endpoint method that returns `WithRawResponseTask<T>` when you need raw HTTP metadata.</Note>

### Pagination

List endpoints are paginated. The SDK returns a pager you can iterate with `await foreach`:

```csharp theme={null}
var pager = await client.Interactions.ListAsync(new InteractionsListRequest());

await foreach (var interaction in pager)
{
    Console.WriteLine(interaction.InteractionId);
}
```

### Additional headers

```csharp theme={null}
await client.Interactions.CreateAsync(
    new InteractionsCreateRequest { /* ... */ },
    new RequestOptions
    {
        AdditionalHeaders = new Dictionary<string, string?>
        {
            ["X-Custom-Header"] = "custom-value",
        },
    });
```

### Additional query parameters

```csharp theme={null}
await client.Interactions.ListAsync(
    new InteractionsListRequest(),
    new RequestOptions
    {
        AdditionalQueryParameters = new Dictionary<string, string>
        {
            ["custom_param"] = "custom-value",
        },
    });
```

### Forward-compatible enums

Enum-like types in the SDK accept unknown API values without failing deserialization. You can use built-in values, custom string values from the API, and inspect `.Value` in a switch:

```csharp theme={null}
var sort = InteractionsListRequestSort.Id;
var custom = InteractionsListRequestSort.FromCustom("future-sort-key");

switch (sort.Value)
{
    case InteractionsListRequestSort.Values.Id:
        Console.WriteLine("Id");
        break;

    default:
        Console.WriteLine($"Unknown or forward-compatible: {sort.Value}");
        break;
}

string asString = (string)InteractionsListRequestSort.Id;
InteractionsListRequestSort fromString = (InteractionsListRequestSort)"id";
```

### Custom HttpClient

Supply your own `HttpClient` for advanced networking (defaults, handlers, proxies):

```csharp theme={null}
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("X-Custom", "value");

var client = new CortiClient(
    tenantName: "YOUR_TENANT",
    environment: "YOUR_ENVIRONMENT_ID",
    auth: CortiClientAuth.ClientCredentials(
        clientId: "YOUR_CLIENT_ID",
        clientSecret: "YOUR_CLIENT_SECRET"
    ),
    requestOptions: new CortiRequestOptions
    {
        HttpClient = httpClient,
    });
```

## Proxy / passthrough mode

For scenarios where your server proxies requests to Corti and handles auth externally, you can create a client with only an environment (no credentials):

```csharp theme={null}
var env = CortiEnvironments.FromBaseUrl("https://your-proxy.com/api");

var client = new CortiClient(environment: env);
```

No `Authorization` header is sent by default. Add your own headers per request using `RequestOptions.AdditionalHeaders`.

See the [Authentication Guide](/sdk/dotnet/authentication#proxy-passthrough-mode) for details.

## Resources

* **[NuGet package](https://www.nuget.org/packages/Corti.Sdk)** -- latest version and install info
* **[Examples repository](https://github.com/corticph/corti-examples)** -- working code for authentication, streaming, text generation, and more
* **[API Reference](/sdk/dotnet/reference)** -- `CortiClient` resource groups, methods, and options
* **[Authentication Guide](/sdk/dotnet/authentication)** -- all auth methods with code examples
* **[API Endpoint Reference](/api-reference/welcome)** -- full endpoint specifications

***

<Note>For support or questions, reach out through [help.corti.app](https://help.corti.app)</Note>
