> ## 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 - API Reference

> Complete reference for CortiClient, CustomAuthClient, and SDK types

The `Corti.Sdk` package exports two main entry points:

| Export                                  | Namespace      | Purpose                                                                  |
| :-------------------------------------- | :------------- | :----------------------------------------------------------------------- |
| [`CortiClient`](#corticlient)           | `using Corti;` | Full API client with all resource groups, authentication, and WebSockets |
| [`CustomAuthClient`](#customauthclient) | `using Corti;` | Standalone authentication client for token management and OAuth flows    |

***

## CortiClient

The main client for all Corti API operations.

### Constructors

```csharp theme={null}
// 1. Tenant + environment + auth (most common)
new CortiClient(string tenantName, CortiEnvironmentInput environment, CortiClientAuth auth, CortiRequestOptions? requestOptions = null)

// 2. Bearer only — tenant and environment decoded from the JWT
new CortiClient(CortiClientBearerAuth auth, CortiRequestOptions? requestOptions = null)

// 3. Environment only — proxy / passthrough, no SDK-managed auth
new CortiClient(CortiEnvironmentInput environment, CortiRequestOptions? requestOptions = null)
```

`CortiEnvironmentInput` accepts a region `string`, a `CortiClientEnvironment`, or `CortiEnvironments.FromBaseUrl(...)` via implicit conversion.

Overload 2 decodes tenant and environment from the access token JWT. If the token cannot be decoded, use overload 1 instead.

Overload 3 sends no `Authorization` header. `CreateStreamApiAsync` / `CreateTranscribeApiAsync` require a tenant and will throw. See [Proxy / passthrough](/sdk/dotnet/authentication#proxy-passthrough-mode).

#### CortiRequestOptions

| Property            | Type                                          | Default | Description                                            |
| :------------------ | :-------------------------------------------- | :------ | :----------------------------------------------------- |
| `MaxRetries`        | `int?`                                        | `2`     | Number of automatic retries on `408`, `429`, and `5xx` |
| `Timeout`           | `TimeSpan?`                                   | 30s     | Default request timeout                                |
| `HttpClient`        | `HttpClient?`                                 | `null`  | Custom `HttpClient` instance                           |
| `AdditionalHeaders` | `IEnumerable<KeyValuePair<string, string?>>?` | `null`  | Headers sent with every request                        |

***

### Request options

Every HTTP method accepts an optional `RequestOptions` parameter:

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

await client.Interactions.ListAsync(
    new InteractionsListRequest(),
    new RequestOptions
    {
        Timeout = TimeSpan.FromSeconds(30),
        MaxRetries = 3,
        AdditionalHeaders = new Dictionary<string, string?>
        {
            ["X-Request-Id"] = "abc123",
        },
    });
```

| Property                    | Type                                         | Description                                             |
| :-------------------------- | :------------------------------------------- | :------------------------------------------------------ |
| `BaseUrl`                   | `string?`                                    | Override the base URL for this request                  |
| `HttpClient`                | `HttpClient?`                                | Override the HttpClient for this request                |
| `MaxRetries`                | `int?`                                       | Override the default retry count                        |
| `Timeout`                   | `TimeSpan?`                                  | Override the default timeout                            |
| `AdditionalHeaders`         | `IEnumerable<KeyValuePair<string, string?>>` | Additional HTTP headers                                 |
| `AdditionalQueryParameters` | `IEnumerable<KeyValuePair<string, string>>`  | Additional query string parameters                      |
| `AdditionalBodyProperties`  | `object?`                                    | Additional JSON properties merged into the request body |

***

### Resource groups

The client exposes the following resource groups as properties:

| Group                                  | Description                                           | API endpoints                                                                |
| :------------------------------------- | :---------------------------------------------------- | :--------------------------------------------------------------------------- |
| [`client.Interactions`](#interactions) | Manage patient encounters and interaction sessions    | [Interactions API](/api-reference/interactions/list-all-interactions)        |
| [`client.Recordings`](#recordings)     | Upload and retrieve audio recordings for interactions | [Recordings API](/api-reference/recordings/list-recordings)                  |
| [`client.Transcripts`](#transcripts)   | Create and retrieve transcriptions of recordings      | [Transcripts API](/api-reference/transcripts/list-transcripts)               |
| [`client.Documents`](#documents)       | Generate and manage AI-produced clinical documents    | [Documents API](/api-reference/documents/list-documents)                     |
| [`client.Facts`](#facts)               | Extract and manage structured clinical facts          | [Facts API](/api-reference/facts/list-facts)                                 |
| [`client.Templates`](#templates)       | List and retrieve document generation templates       | [Templates API](/api-reference/templates/list-templates)                     |
| [`client.Codes`](#codes)               | Predict medical codes from clinical data              | [Codes API](/api-reference/codes/predict-codes)                              |
| [`client.Agents`](#agents)             | Create and interact with AI agents                    | [Agents API](/agentic/agents/list-agents)                                    |
| [`client.Auth`](#auth)                 | OAuth token management and authorization URLs         | [Auth API](/api-reference/admin/auth/authenticate-user-and-get-access-token) |

WebSocket APIs are created via factory methods:

| Method                                           | Description                                           | API endpoint                                |
| :----------------------------------------------- | :---------------------------------------------------- | :------------------------------------------ |
| [`CreateStreamApiAsync(interactionId)`](#stream) | Real-time WebSocket streaming (transcription + facts) | [Stream API](/api-reference/streams)        |
| [`CreateTranscribeApiAsync()`](#transcribe)      | Real-time WebSocket speech-to-text (standalone)       | [Transcribe API](/api-reference/transcribe) |

***

### Interactions

Manage interaction sessions that group recordings, transcripts, documents, and facts together.

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

// List interactions (pager — see Pagination)
var pager = await client.Interactions.ListAsync(new InteractionsListRequest());

// Create a new interaction
var response = await client.Interactions.CreateAsync(
    new InteractionsCreateRequest
    {
        Encounter = new InteractionsEncounterCreateRequest
        {
            Identifier = "my-encounter-id",
            Status = InteractionsEncounterStatusEnum.Planned,
            Type = InteractionsEncounterTypeEnum.FirstConsultation,
        },
    });

var interactionId = response.InteractionId;

// Get a specific interaction
var interaction = await client.Interactions.GetAsync(interactionId);

// Update an interaction
await client.Interactions.UpdateAsync(
    interactionId,
    new InteractionsUpdateRequest
    {
        Encounter = new InteractionsEncounterUpdateRequest
        {
            Status = InteractionsEncounterStatusEnum.InProgress,
        },
    });

// Delete an interaction
await client.Interactions.DeleteAsync(interactionId);
```

| Method                      | Parameters                       | Returns                    |
| :-------------------------- | :------------------------------- | :------------------------- |
| `ListAsync(request)`        | Filter and pagination options    | `Pager` (async enumerable) |
| `CreateAsync(request)`      | Interaction definition           | Created interaction        |
| `GetAsync(id)`              | Interaction UUID                 | Single interaction         |
| `UpdateAsync(id, request?)` | Interaction UUID + update fields | Updated interaction        |
| `DeleteAsync(id)`           | Interaction UUID                 | `void`                     |

***

### Recordings

Upload and manage audio recordings within an interaction.

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

// Upload a recording
var recording = await client.Recordings.UploadAsync(
    interactionId,
    File.OpenRead("audio.mp3"));

var recordingId = recording.RecordingId;

// List recordings for an interaction
var recordings = await client.Recordings.ListAsync(interactionId);

// Download a recording
var stream = await client.Recordings.GetAsync(interactionId, recordingId);

// Delete a recording
await client.Recordings.DeleteAsync(interactionId, recordingId);
```

| Method                                    | Parameters                        | Returns            |
| :---------------------------------------- | :-------------------------------- | :----------------- |
| `UploadAsync(interactionId, file)`        | Interaction UUID + file stream    | Created recording  |
| `ListAsync(interactionId)`                | Interaction UUID                  | List of recordings |
| `GetAsync(interactionId, recordingId)`    | Interaction UUID + recording UUID | Audio stream       |
| `DeleteAsync(interactionId, recordingId)` | Interaction UUID + recording UUID | `void`             |

***

### Transcripts

Create transcriptions from uploaded recordings and retrieve results.

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

// Create a transcript from a recording
var transcript = await client.Transcripts.CreateAsync(
    interactionId,
    new TranscriptsCreateRequest
    {
        RecordingId = recordingId,
        PrimaryLanguage = "en",
    });

var transcriptId = transcript.Id;

// Check transcription status
var status = await client.Transcripts.GetStatusAsync(interactionId, transcriptId);

// List transcripts for an interaction
var transcripts = await client.Transcripts.ListAsync(interactionId);

// Get a specific transcript
var result = await client.Transcripts.GetAsync(interactionId, transcriptId);

// Delete a transcript
await client.Transcripts.DeleteAsync(interactionId, transcriptId);
```

| Method                                        | Parameters                               | Returns              |
| :-------------------------------------------- | :--------------------------------------- | :------------------- |
| `CreateAsync(interactionId, request)`         | Interaction UUID + transcript definition | Created transcript   |
| `GetStatusAsync(interactionId, transcriptId)` | Interaction UUID + transcript UUID       | Transcription status |
| `ListAsync(interactionId, request?)`          | Interaction UUID + optional filters      | List of transcripts  |
| `GetAsync(interactionId, transcriptId)`       | Interaction UUID + transcript UUID       | Single transcript    |
| `DeleteAsync(interactionId, transcriptId)`    | Interaction UUID + transcript UUID       | `void`               |

***

### Documents

Generate AI-produced clinical documents (e.g. SOAP notes) from interaction data.

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

// Generate a document
var document = await client.Documents.CreateAsync(
    interactionId,
    new DocumentsCreateRequestWithTemplateKey
    {
        TemplateKey = "corti-soap",
        OutputLanguage = "en",
        Context = new[]
        {
            new DocumentsContextWithFacts
            {
                Type = DocumentsContextWithFactsType.Facts,
                Data = new[]
                {
                    new FactsContext { Text = "Patient reports persistent headache for 3 days", Source = CommonSourceEnum.Core },
                    new FactsContext { Text = "No history of migraines", Source = CommonSourceEnum.User },
                },
            },
        },
    });

var documentId = document.Id;

// List documents
var documents = await client.Documents.ListAsync(interactionId);

// Get a specific document
var doc = await client.Documents.GetAsync(interactionId, documentId);

// Update a document
await client.Documents.UpdateAsync(
    interactionId,
    documentId,
    new DocumentsUpdateRequest { Name = "Updated note" });

// Delete a document
await client.Documents.DeleteAsync(interactionId, documentId);
```

| Method                                             | Parameters                                       | Returns            |
| :------------------------------------------------- | :----------------------------------------------- | :----------------- |
| `CreateAsync(interactionId, request)`              | Interaction UUID + document generation options   | Generated document |
| `ListAsync(interactionId)`                         | Interaction UUID                                 | List of documents  |
| `GetAsync(interactionId, documentId)`              | Interaction UUID + document UUID                 | Single document    |
| `UpdateAsync(interactionId, documentId, request?)` | Interaction UUID + document UUID + update fields | Updated document   |
| `DeleteAsync(interactionId, documentId)`           | Interaction UUID + document UUID                 | `void`             |

***

### Facts

Extract structured clinical facts from text or manage facts on an interaction.

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

// Extract facts from text (standalone)
var extracted = await client.Facts.ExtractAsync(
    new FactsExtractRequest
    {
        Context = new[]
        {
            new CommonTextContext
            {
                Type = CommonTextContextType.Text,
                Text = "Patient has a temperature of 38.5°C and reports headache.",
            },
        },
        OutputLanguage = "en",
    });

// List facts for an interaction
var facts = await client.Facts.ListAsync(interactionId);

// Create facts on an interaction
var created = await client.Facts.CreateAsync(
    interactionId,
    new FactsCreateRequest
    {
        Facts = new[]
        {
            new FactsCreateInput { Text = "Temperature 38.5°C", Group = "vitals" },
        },
    });

// Update a single fact
await client.Facts.UpdateAsync(
    interactionId,
    factId,
    new FactsUpdateRequest { Text = "Temperature 39.0°C" });

// List available fact groups
var groups = await client.Facts.FactGroupsListAsync();
```

| Method                                         | Parameters                                    | Returns                          |
| :--------------------------------------------- | :-------------------------------------------- | :------------------------------- |
| `ExtractAsync(request)`                        | Extraction options (context, output language) | Extracted facts                  |
| `ListAsync(interactionId)`                     | Interaction UUID                              | List of facts                    |
| `CreateAsync(interactionId, request)`          | Interaction UUID + fact definitions           | Created facts                    |
| `BatchUpdateAsync(interactionId, request)`     | Interaction UUID + fact updates               | Updated facts                    |
| `UpdateAsync(interactionId, factId, request?)` | Interaction UUID + fact ID + update fields    | Updated fact                     |
| `FactGroupsListAsync()`                        | None                                          | Available fact group definitions |

***

### Templates

List and inspect document generation templates available to your tenant.

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

// List all templates
var templates = await client.Templates.ListAsync(new TemplatesListRequest());

// Get a specific template by key
var template = await client.Templates.GetAsync("corti-soap");

// List template sections
var sections = await client.Templates.SectionListAsync(new TemplatesSectionListRequest());
```

| Method                      | Parameters           | Returns                   |
| :-------------------------- | :------------------- | :------------------------ |
| `ListAsync(request)`        | List filters         | List of templates         |
| `GetAsync(key)`             | Template key string  | Single template           |
| `SectionListAsync(request)` | Section list filters | List of template sections |

***

### Codes

Predict medical codes from clinical data.

```csharp title="C# .NET" theme={null}
var response = await client.Codes.PredictAsync(new CodesGeneralPredictRequest
{
    System = [CommonCodingSystemEnum.Icd10CmInpatient],
    Context =
    [
        new CommonTextContext
        {
            Type = CommonTextContextType.Text,
            Text = "Progress Note — Day 3: Patient continues on IV vancomycin for MRSA bacteremia. Blood cultures from yesterday still pending. Acute kidney injury improving — creatinine down to 1.8 from 2.4. Patient also has history of CHF, currently euvolemic on home dose of furosemide. Diabetes managed with insulin sliding scale, glucose well controlled.",
        },
    ],
});
```

| Method                  | Parameters                                       | Returns          |
| :---------------------- | :----------------------------------------------- | :--------------- |
| `PredictAsync(request)` | `CodesGeneralPredictRequest` (text, code system) | Code predictions |

***

### Agents

Create and interact with AI agents (Agentic Framework).

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

// List agents
var agents = await client.Agents.ListAsync(new AgentsListRequest());

// Create an agent
var agent = await client.Agents.CreateAsync(
    new AgentsCreateAgent
    {
        Name = "My Agent",
        Description = "A helpful assistant",
    });

// Get agent details
var details = await client.Agents.GetAsync(agent.Id);

// Send a message to an agent
var response = await client.Agents.MessageSendAsync(
    agent.Id,
    new AgentsMessageSendParams
    {
        Message = new AgentsMessage
        {
            Role = AgentsMessageRole.User,
            Kind = AgentsMessageKind.Message,
            MessageId = $"msg-{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}",
            Parts = new[]
            {
                AgentsPart.FromAgentsTextPart(
                    new AgentsTextPart { Kind = AgentsTextPartKind.Text, Text = "Hello" }),
            },
        },
    });

// Update an agent
await client.Agents.UpdateAsync(agent.Id, new AgentsUpdateAgent { Description = "Updated" });

// Delete an agent
await client.Agents.DeleteAsync(agent.Id);
```

| Method                                     | Parameters                 | Returns                 |
| :----------------------------------------- | :------------------------- | :---------------------- |
| `ListAsync(request)`                       | List filters               | List of agents          |
| `CreateAsync(request)`                     | Agent definition           | Created agent           |
| `GetAsync(id)`                             | Agent ID                   | Agent details           |
| `GetCardAsync(id)`                         | Agent ID                   | Agent card (A2A format) |
| `MessageSendAsync(id, request)`            | Agent ID + message payload | Agent response          |
| `GetTaskAsync(id, taskId, request?)`       | Agent ID + task ID         | Task details            |
| `GetContextAsync(id, contextId, request?)` | Agent ID + context ID      | Context details         |
| `GetRegistryExpertsAsync(request?)`        | Optional filters           | Registry experts        |
| `UpdateAsync(id, request?)`                | Agent ID + update fields   | Updated agent           |
| `DeleteAsync(id)`                          | Agent ID                   | `void`                  |

***

### Stream

Real-time WebSocket connection for combined transcription, fact extraction, and more -- tied to an interaction.

```csharp title="C# .NET" expandable theme={null}
var stream = await client.CreateStreamApiAsync(interactionId);

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

stream.StreamFactsMessage.Subscribe(_ =>
{
    Console.WriteLine("Facts received");
});

await stream.ConnectAsync(new StreamConfig
{
    Transcription = new StreamConfigTranscription
    {
        PrimaryLanguage = "en",
        Participants = new[]
        {
            new StreamConfigParticipant { Channel = 0, Role = StreamConfigParticipantRole.Doctor },
        },
    },
    Mode = new StreamConfigMode
    {
        Type = StreamConfigModeType.Facts,
        OutputLocale = "en",
    },
});

await stream.Send(audioBytes);
```

#### Factory method

| Parameter       | Type     | Required | Description                              |
| :-------------- | :------- | :------- | :--------------------------------------- |
| `interactionId` | `string` | Yes      | Interaction UUID to attach the stream to |

Returns `Task<IStreamApi>`.

#### `ConnectAsync` parameters

| Parameter           | Type                | Required | Description                                                                                                                                                                                                                   |
| :------------------ | :------------------ | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `configuration`     | `StreamConfig`      | No       | Stream configuration (language, participants, mode). When provided, the SDK automatically sends the config message once the socket opens and waits for `CONFIG_ACCEPTED`. If omitted, you must send a config message yourself |
| `cancellationToken` | `CancellationToken` | No       | Cancellation support                                                                                                                                                                                                          |

#### Events

Subscribe to events using the `.Subscribe()` method on each `Event<T>` field:

| Event                       | Type                               | Description                      |
| :-------------------------- | :--------------------------------- | :------------------------------- |
| `Connected`                 | `Event<Connected>`                 | WebSocket connection established |
| `Closed`                    | `Event<Closed>`                    | WebSocket connection closed      |
| `ExceptionOccurred`         | `Event<Exception>`                 | WebSocket error occurred         |
| `Reconnecting`              | `Event<ReconnectionInfo>`          | Reconnection attempt in progress |
| `StreamTranscriptMessage`   | `Event<StreamTranscriptMessage>`   | Transcript data received         |
| `StreamFactsMessage`        | `Event<StreamFactsMessage>`        | Facts data received              |
| `StreamFlushedMessage`      | `Event<StreamFlushedMessage>`      | Audio buffer flushed             |
| `StreamEndedMessage`        | `Event<StreamEndedMessage>`        | Stream session ended             |
| `StreamUsageMessage`        | `Event<StreamUsageMessage>`        | Usage metrics                    |
| `StreamDeltaUsageMessage`   | `Event<StreamDeltaUsageMessage>`   | Incremental usage metrics        |
| `StreamErrorMessage`        | `Event<StreamErrorMessage>`        | Server-side error                |
| `StreamConfigStatusMessage` | `Event<StreamConfigStatusMessage>` | Config accepted or denied        |
| `UnknownMessage`            | `Event<JsonElement>`               | Unrecognised message type        |

#### Methods

| Method                                             | Description                                                                                       |
| :------------------------------------------------- | :------------------------------------------------------------------------------------------------ |
| `ConnectAsync(configuration?, cancellationToken?)` | Open the WebSocket connection                                                                     |
| `Send(byte[])`                                     | Send binary audio data                                                                            |
| `Send(StreamFlushMessage)`                         | Request the server to flush buffered audio                                                        |
| `Send(StreamEndMessage)`                           | Signal end of audio stream                                                                        |
| `Send(StreamConfigMessage)`                        | Send a configuration message manually                                                             |
| `CloseAsync(cancellationToken?)`                   | Close the connection. Call `Send(new StreamEndMessage())` first and wait for `StreamEndedMessage` |
| `DisposeAsync()`                                   | Dispose the WebSocket resources                                                                   |

***

### Transcribe

Real-time WebSocket speech-to-text without an interaction context.

```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);
```

#### `ConnectAsync` parameters

| Parameter           | Type                | Required | Description                                                                                                                                                                                                                          |
| :------------------ | :------------------ | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `configuration`     | `TranscribeConfig`  | No       | Transcribe configuration (language, punctuation, commands). When provided, the SDK automatically sends the config message once the socket opens and waits for `CONFIG_ACCEPTED`. If omitted, you must send a config message yourself |
| `cancellationToken` | `CancellationToken` | No       | Cancellation support                                                                                                                                                                                                                 |

#### Events

| Event                           | Type                                   | Description                      |
| :------------------------------ | :------------------------------------- | :------------------------------- |
| `Connected`                     | `Event<Connected>`                     | WebSocket connection established |
| `Closed`                        | `Event<Closed>`                        | WebSocket connection closed      |
| `ExceptionOccurred`             | `Event<Exception>`                     | WebSocket error occurred         |
| `Reconnecting`                  | `Event<ReconnectionInfo>`              | Reconnection attempt in progress |
| `TranscribeTranscriptMessage`   | `Event<TranscribeTranscriptMessage>`   | Transcript data received         |
| `TranscribeCommandMessage`      | `Event<TranscribeCommandMessage>`      | Command detected                 |
| `TranscribeFlushedMessage`      | `Event<TranscribeFlushedMessage>`      | Audio buffer flushed             |
| `TranscribeEndedMessage`        | `Event<TranscribeEndedMessage>`        | Session ended                    |
| `TranscribeUsageMessage`        | `Event<TranscribeUsageMessage>`        | Usage metrics                    |
| `TranscribeDeltaUsageMessage`   | `Event<TranscribeDeltaUsageMessage>`   | Incremental usage metrics        |
| `TranscribeErrorMessage`        | `Event<TranscribeErrorMessage>`        | Server-side error                |
| `TranscribeConfigStatusMessage` | `Event<TranscribeConfigStatusMessage>` | Config accepted or denied        |
| `UnknownMessage`                | `Event<JsonElement>`                   | Unrecognised message type        |

#### Methods

| Method                                             | Description                                                                                               |
| :------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- |
| `ConnectAsync(configuration?, cancellationToken?)` | Open the WebSocket connection                                                                             |
| `Send(byte[])`                                     | Send binary audio data                                                                                    |
| `Send(TranscribeFlushMessage)`                     | Request the server to flush buffered audio                                                                |
| `Send(TranscribeEndMessage)`                       | Signal end of audio stream                                                                                |
| `Send(TranscribeConfigMessage)`                    | Send a configuration message manually                                                                     |
| `CloseAsync(cancellationToken?)`                   | Close the connection. Call `Send(new TranscribeEndMessage())` first and wait for `TranscribeEndedMessage` |
| `DisposeAsync()`                                   | Dispose the WebSocket resources                                                                           |

The key differences from [Stream](#stream) are:

* **No interaction ID** -- Transcribe operates standalone via `CreateTranscribeApiAsync()` (no `interactionId` parameter)
* **Different message types** -- Transcribe delivers transcript, command, flushed, ended, usage, `delta_usage`, and error messages, while Stream delivers transcript, facts, flushed, ended, usage, `delta_usage`, and error

***

### Auth

OAuth token management via `client.Auth`. In most cases you won't call these directly -- the SDK handles tokens automatically based on the `auth` option you pass to the constructor. These methods are useful for advanced scenarios like generating authorization URLs for browser-based flows.

| Method                                          | Description                                |
| :---------------------------------------------- | :----------------------------------------- |
| `GetTokenAsync(OAuthTokenRequest)`              | Get a token via client credentials         |
| `GetTokenAsync(OAuthRopcTokenRequest)`          | Get a token via ROPC flow                  |
| `GetTokenAsync(OAuthAuthCodeTokenRequest)`      | Exchange an authorization code for a token |
| `GetTokenAsync(OAuthPkceTokenRequest)`          | Exchange a PKCE code for a token           |
| `GetTokenAsync(OAuthRefreshTokenRequest)`       | Refresh an expired token                   |
| `AuthorizeUrlAsync(clientId, redirectUri, ...)` | Generate an authorization URL              |

All `GetTokenAsync` overloads also accept `*WithScopes` request variants (e.g. `OAuthTokenRequestWithScopes`) to request [scoped tokens](/sdk/dotnet/authentication#scoped-tokens).

<Tip>For detailed usage and end-to-end examples, see the [Authentication Guide](/sdk/dotnet/authentication). For standalone use without `CortiClient`, see [CustomAuthClient](#customauthclient) below.</Tip>

***

## CustomAuthClient

A standalone authentication client for when you need OAuth token management without the full `CortiClient`. Useful for proxy servers, custom backends, or applications that handle tokens separately from API calls.

```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 auth = CustomAuthClient.Create(
    new CortiAuthClientOptions
    {
        TenantName = TENANT,
        Environment = ENVIRONMENT,
    });

var tokenResponse = await auth.GetTokenAsync(
    new OAuthTokenRequest
    {
        ClientId = CLIENT_ID,
        ClientSecret = CLIENT_SECRET,
    });

Console.WriteLine(tokenResponse.AccessToken);
```

### Constructor options

| Property         | Type                               | Required | Description                      |
| :--------------- | :--------------------------------- | :------- | :------------------------------- |
| `TenantName`     | `string`                           | Yes      | Your Corti tenant name           |
| `Environment`    | `string \| CortiClientEnvironment` | Yes      | API region or custom environment |
| `RequestOptions` | `CortiRequestOptions?`             | No       | Default request options          |

### Methods

| Method                                                              | Description                                | Returns             |
| :------------------------------------------------------------------ | :----------------------------------------- | :------------------ |
| `GetTokenAsync(OAuthTokenRequest)`                                  | Get a token via client credentials         | `AuthTokenResponse` |
| `GetTokenAsync(OAuthRopcTokenRequest)`                              | Get a token via ROPC flow                  | `AuthTokenResponse` |
| `GetTokenAsync(OAuthAuthCodeTokenRequest)`                          | Exchange an authorization code for a token | `AuthTokenResponse` |
| `GetTokenAsync(OAuthPkceTokenRequest)`                              | Exchange a PKCE code for a token           | `AuthTokenResponse` |
| `GetTokenAsync(OAuthRefreshTokenRequest)`                           | Refresh an expired token                   | `AuthTokenResponse` |
| `AuthorizeUrlAsync(clientId, redirectUri, codeChallenge?, scopes?)` | Generate an authorization URL              | `string`            |

All `GetTokenAsync` overloads also accept `*WithScopes` request variants to request [scoped tokens](/sdk/dotnet/authentication#scoped-tokens).

<Tip>For end-to-end examples of each method, see the [Authentication Guide](/sdk/dotnet/authentication).</Tip>

***

## Authentication types

The `CortiClientAuth` abstract record has the following concrete variants used with the `CortiClient` constructor:

| Variant                               | Parameters                                                                                            | Description                                                                                 |
| :------------------------------------ | :---------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ |
| `CortiClientAuth.ClientCredentials`   | `clientId`, `clientSecret`                                                                            | Server-to-server ([guide](/sdk/dotnet/authentication#client-credentials))                   |
| `CortiClientAuth.AuthorizationCode`   | `clientId`, `clientSecret`, `code`, `redirectUri`                                                     | Interactive login ([guide](/sdk/dotnet/authentication#authorization-code-flow))             |
| `CortiClientAuth.Pkce`                | `clientId`, `code`, `redirectUri`, `codeVerifier`                                                     | Public client login ([guide](/sdk/dotnet/authentication#pkce-flow))                         |
| `CortiClientAuth.Ropc`                | `clientId`, `username`, `password`                                                                    | Username/password ([guide](/sdk/dotnet/authentication#resource-owner-password-credentials)) |
| `CortiClientAuth.Bearer`              | `accessToken`, `clientId?`, `refreshToken?`, `expiresIn?`, `refreshExpiresIn?`, `refreshAccessToken?` | Existing token ([guide](/sdk/dotnet/authentication#bearer-token))                           |
| `CortiClientAuth.BearerCustomRefresh` | `refreshAccessToken`, `accessToken?`, `expiresIn?`, `refreshToken?`, `refreshExpiresIn?`              | Custom refresh logic ([guide](/sdk/dotnet/authentication#custom-refresh))                   |

***

## Error handling

The SDK throws typed exceptions you can catch and inspect:

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

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}");
}
```

| Exception                  | Status code | When it's thrown                                                  |
| :------------------------- | :---------- | :---------------------------------------------------------------- |
| `BadRequestError`          | 400         | Malformed request                                                 |
| `UnauthorizedError`        | 401         | Missing or invalid authentication                                 |
| `ForbiddenError`           | 403         | Insufficient permissions                                          |
| `NotFoundError`            | 404         | Resource not found                                                |
| `UnprocessableEntityError` | 422         | Validation failed (includes `AgentsValidationErrorResponse` body) |
| `InternalServerError`      | 500         | Server error                                                      |
| `BadGatewayError`          | 502         | Gateway error                                                     |
| `GatewayTimeoutError`      | 504         | Gateway timeout                                                   |

All inherit from `CortiClientApiException`, which inherits from `CortiClientException`.

***

## Environments

Use `CortiEnvironments` to create custom environments:

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

// Built-in region
// var env = CortiEnvironments.FromRegion("eu");

// Custom proxy base URL
var env = CortiEnvironments.FromBaseUrl("https://your-proxy.com/api");

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

| Method                 | Description                                          |
| :--------------------- | :--------------------------------------------------- |
| `FromRegion(region)`   | Create an environment from a region string           |
| `FromBaseUrl(baseUrl)` | Create an environment pointing at a custom proxy URL |
