HomeBlog → Speech to text in C#: connecting from .NET in five minutes

Speech to text in C#: connecting from .NET in five minutes

Published 2026-08-20 · 5 min read

In .NET shops the need usually starts with calls: telephony records conversations, the CRM stores deals, and text is the missing link between them. Connecting takes five minutes, either through the official client or with a bare HttpClient.

Using the official client

If the OpenAI package is already in the project, only the endpoint changes:

var options = new OpenAIClientOptions { Endpoint = new Uri("https://voicesscribe.com/v1") };
var client = new OpenAIClient(new ApiKeyCredential(Environment.GetEnvironmentVariable("VS_KEY")), options);

var audio = client.GetAudioClient("whisper-1");
var result = await audio.TranscribeAudioAsync("call.ogg");

Console.WriteLine(result.Value.Text);

Everything else stays the same: parameters, response parsing and exception types are unchanged.

Without third-party packages

When you would rather not add a dependency, HttpClient is enough:

using var http = new HttpClient { Timeout = TimeSpan.FromMinutes(3) };
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("VS_KEY"));

using var form = new MultipartFormDataContent();
using var stream = File.OpenRead("call.ogg");
var file = new StreamContent(stream);
file.Headers.ContentType = new MediaTypeHeaderValue("audio/ogg");

form.Add(file, "file", "call.ogg");
form.Add(new StringContent("whisper-1"), "model");

var response = await http.PostAsync("https://voicesscribe.com/v1/audio/transcriptions", form);
var json = await response.Content.ReadAsStringAsync();

if (!response.IsSuccessStatusCode)
    throw new InvalidOperationException($"error {(int)response.StatusCode}: {json}");

using var doc = JsonDocument.Parse(json);
Console.WriteLine(doc.RootElement.GetProperty("text").GetString());

The filename in form.Add is required — it is what identifies the audio format.

Handling a stream of calls

Telephony delivers recordings in batches, so the work belongs in a background service. A practical shape: a job queue, a concurrency cap through SemaphoreSlim, retries with growing delays for temporary failures, and the result written back to the deal record.

Create one HttpClient per application — through IHttpClientFactory or a static field. A new instance per request exhausts sockets under load, and that is the classic .NET mistake.

Details that bite

Try it on your own recordings. Sign-up takes a minute, and the free minutes are enough to judge the quality.

Get a free API key

Frequently asked questions

Is the official client required?

No, HttpClient from the standard library is entirely sufficient. The client is convenient if the project already uses it.

Why does it time out on long files?

HttpClient waits one hundred seconds by default. For files at the size limit set two or three minutes.

How do I get timed segments?

Add a response_format field with the value verbose_json and the response includes each utterance with start and end times.

Is this suitable for a background service?

Yes, the usual pattern is a hosted service with a queue and a concurrency limit matched to your plan.

Related reading