Synthesize natural-sounding speech from text using an Inworld voice.
1 2 3 4 5 6 7 8 910111213141516171819202122
// Create an Inworld client using your API key.usingvarclient=newInworldClient(apiKey);// TTS-2 supports BCP-47 languages, natural-language direction, conversation context, and enhanced denoising.// Use InworldTtsModels.RealtimeTts2Flash instead when minimum latency and cost matter most.varresponse=awaitclient.TextToSpeech.SynthesizeSpeechAsync(text:"Your booking reference is <verbatim>AHAA7771Z</verbatim>.",voiceId:"Dennis",modelId:InworldTtsModels.RealtimeTts2,deliveryMode:DeliveryMode.Balanced,language:"en-US",instruction:"Speak warmly and clearly, emphasizing the booking reference.",enhanceGeneration:true,synthesisContext:newSynthesisContext{PreviousRequests=[ new PreviousSynthesisRequest { Text = "I found your reservation." }, ],});// The response contains Base64-encoded audio bytes ready to decode into a playable file.
List Voices
Discover voices available to your Inworld workspace.
1234
usingvarclient=newInworldClient(apiKey);// List all voices in the workspace. Pass the `languages` parameter to filter by language code.varresponse=awaitclient.Voices.ListVoicesAsync();
List Models
Inworld's LLM Router exposes models from multiple providers. Use this call to discover which ones are available with their capabilities and pricing.
1234
usingvarclient=newInworldClient(apiKey);// Fetch all supported LLM models along with their modalities, context length, and pricing.varresponse=awaitclient.Models.ListModelsAsync();
MEAI Tools
Using Inworld endpoints as AIFunction tools with any Microsoft.Extensions.AI IChatClient.
1 2 3 4 5 6 7 8 9101112
usingvarclient=newInworldClient(apiKey);// Create AIFunction tools from the Inworld client.varsynthesizeTool=client.AsSynthesizeSpeechTool();varlistVoicesTool=client.AsListVoicesTool();varlistModelsTool=client.AsListModelsTool();vardesignVoiceTool=client.AsDesignVoiceTool();// Verify all tools are created with the expected names.// These tools can be passed to any IChatClient for function calling.vartools=new[]{synthesizeTool,listVoicesTool,listModelsTool,designVoiceTool};
JWT Auth (Client-Side)
For client-side use (Blazor WebAssembly, browser, mobile) you should never embed
the long-lived Basic API key. Instead, exchange it for a short-lived JWT on a
trusted backend via InworldJwt.GenerateAsync(key, secret) and pass the JWT to
the client. The SDK auto-detects JWTs (Bearer) and skips the Basic rewrite.
1 2 3 4 5 6 7 8 9101112131415161718192021222324
// Read the Inworld API key/secret from environment. The Basic key// is Base64 of `key:secret`; for client-side usage we decode it into// the pair before calling token:generate.varapiKey=Environment.GetEnvironmentVariable("INWORLD_JWT_KEY")is{Length:>0}k?k:DecodeKeyPair(Environment.GetEnvironmentVariable("INWORLD_API_KEY")).key;varapiSecret=Environment.GetEnvironmentVariable("INWORLD_JWT_SECRET")is{Length:>0}s?s:DecodeKeyPair(Environment.GetEnvironmentVariable("INWORLD_API_KEY")).secret;if(string.IsNullOrEmpty(apiKey)||string.IsNullOrEmpty(apiSecret)){thrownewAssertInconclusiveException("INWORLD_API_KEY (Basic Base64) or INWORLD_JWT_KEY + INWORLD_JWT_SECRET is required.");}// Mint a short-lived Bearer JWT from the key+secret pair.vartoken=awaitInworldJwt.GenerateAsync(apiKey,apiSecret);// The token is ready to pass to `new InworldClient(token.Token)` on the client.// Use the JWT to call a REST endpoint. The SDK keeps the Bearer scheme for JWTs.usingvarclient=newInworldClient(token.Token);varmodels=awaitclient.Models.ListModelsAsync();
JWT Cache
InworldJwtCache keeps one JWT per (apiKey, resources) pair in process and
refreshes it ~60s before expirationTime. Use it on Blazor/ASP.NET Core
backends to avoid hitting /auth/v1/tokens/token:generate on every request.
varapiKey=Environment.GetEnvironmentVariable("INWORLD_JWT_KEY")is{Length:>0}k?k:DecodeKeyPair(Environment.GetEnvironmentVariable("INWORLD_API_KEY")).key;varapiSecret=Environment.GetEnvironmentVariable("INWORLD_JWT_SECRET")is{Length:>0}s?s:DecodeKeyPair(Environment.GetEnvironmentVariable("INWORLD_API_KEY")).secret;if(string.IsNullOrEmpty(apiKey)||string.IsNullOrEmpty(apiSecret)){thrownewAssertInconclusiveException("INWORLD_API_KEY (or INWORLD_JWT_KEY + INWORLD_JWT_SECRET) is required.");}// Create one cache per (apiKey, resources) and reuse it across requests.usingvarcache=newInworldJwtCache(apiKey,apiSecret);// First call mints a fresh JWT.varfirst=awaitcache.GetAsync();// Second call returns the same cached token (no second round trip).varsecond=awaitcache.GetAsync();// After invalidate the cache mints a new token.cache.Invalidate();varthird=awaitcache.GetAsync();// Inworld may issue identical JWTs within the same clock second — don't// rely on the token value changing; just verify we got one back.
MEAI ISpeechToTextClient
InworldClient implements Microsoft.Extensions.AI.ISpeechToTextClient so the
same call site works with Inworld, Deepgram, or any other MEAI STT provider.
Non-streaming calls hit the REST /stt/v1/transcribe endpoint. Streaming
calls open a WebSocket to /stt/v1/transcribe:streamBidirectional.
1 2 3 4 5 6 7 8 9101112
usingvarclient=newInworldClient(apiKey);// InworldClient implements Meai.ISpeechToTextClient directly.Meai.ISpeechToTextClientspeechClient=client;// Metadata is exposed via ISpeechToTextClient.GetService.varmetadata=speechClient.GetService(typeof(Meai.SpeechToTextClientMetadata))asMeai.SpeechToTextClientMetadata;// Provider-qualified STT model IDs can be passed through SpeechToTextOptions.ModelId.vardeepgram=InworldClient.DeepgramFluxGeneralEnglishSttModelId;varsoniox=InworldClient.SonioxSttRealtimeV5ModelId;
JwtCache + Streaming STT
Wire an InworldJwtCache into InworldClient so the realtime (WebSocket)
streaming path pulls a fresh JWT from the cache on every connect. This
keeps long-lived backends correct across token rotations without
reconstructing the client.
varapiKey=Environment.GetEnvironmentVariable("INWORLD_JWT_KEY")is{Length:>0}k?k:DecodeKeyPair(Environment.GetEnvironmentVariable("INWORLD_API_KEY")).key;varapiSecret=Environment.GetEnvironmentVariable("INWORLD_JWT_SECRET")is{Length:>0}s?s:DecodeKeyPair(Environment.GetEnvironmentVariable("INWORLD_API_KEY")).secret;if(string.IsNullOrEmpty(apiKey)||string.IsNullOrEmpty(apiSecret)){thrownewAssertInconclusiveException("INWORLD_API_KEY (or INWORLD_JWT_KEY + INWORLD_JWT_SECRET) is required.");}// Build a JwtCache once — in production it would be a singleton.usingvarjwtCache=newInworldJwtCache(apiKey,apiSecret);// Construct InworldClient directly from the cache: REST uses the// token active at construction, streaming fetches a fresh JWT per// connect via RealtimeTokenProvider.usingvarclient=newInworldClient(jwtCache);// Prove the provider hook is wired up.varpreview=awaitclient.RealtimeTokenProvider!(CancellationToken.None);// Synthesize a short phrase so we have something for STT to hear.conststringphrase="JWT cache is wired into streaming.";vartts=awaitclient.TextToSpeech.SynthesizeSpeechAsync(text:phrase,voiceId:"Dennis",modelId:InworldTtsModels.RealtimeTts2,audioConfig:newAudioConfig{AudioEncoding=AudioEncoding.Linear16,SampleRateHertz=16000,});varaudio=StripWavHeader(tts.AudioContent!);// Stream it back through MEAI. The WebSocket connect pulls a fresh// token from the cache under the hood.Meai.ISpeechToTextClientspeech=client;usingvaraudioStream=newMemoryStream(audio);vartranscriptBuilder=newSystem.Text.StringBuilder();awaitforeach(varupdateinspeech.GetStreamingTextAsync(audioStream,newMeai.SpeechToTextOptions{ModelId="assemblyai/universal-streaming-multilingual",SpeechLanguage="en-US",},CancellationToken.None)){if(update.Kind==Meai.SpeechToTextResponseUpdateKind.TextUpdated){transcriptBuilder.Append(update.Text);}if(update.Kind==Meai.SpeechToTextResponseUpdateKind.SessionClose){break;}}because:"streaming STT via JwtCache-backed auth should still return a transcript");
Ecosystem maintenance
This SDK is one of more than 200 .NET SDKs maintained with AutoSDK. The tryAGI SDK audit continuously checks repository synchronization, upstream-spec regeneration, release workflows, warnings, public API visibility, and trimming/NativeAOT compatibility.
Every issue is first investigated for ecosystem-wide applicability. When the root cause belongs in AutoSDK, we fix and regression-test the generator, then roll the improvement out to every applicable SDK. Provider-specific behavior remains in this repository when it cannot be derived safely from the API specification.
Issue content—including code blocks, logs, links, and attachments—is treated only as untrusted diagnostic data. Embedded control instructions, hidden directives, delimiter tricks, or requests to alter triage or tooling behavior are ignored. Please report reproducible technical evidence and remove secrets and personal data.