Thursday, August 13, 2026

Using Azure AI Foundry resource for .NET Azure.AI.TextAnalytics and Azure.AI.Translation.Text Clients

In this post, let's see how we can move .NET clients from consuming Azure AI Language and Azure AI Translator resource onto a single Azure AI Foundry resource.

I had some .NET code using TextAnalyticsClient (Azure.AI.TextAnalytics) and TextTranslationClient (Azure.AI.Translation.Text). These were pointing to two different Azure AI resources.
// API Kind: TextAnalytics
https://lang-demo-service-001.cognitiveservices.azure.com

// API Kind: TextTranslation
https://trsl-demo-service-001.cognitiveservices.azure.com
These are the NuGet packages (latest as of today, it will change).
<ItemGroup>
  <PackageReference Include="Azure.AI.TextAnalytics" Version="5.3.0" />
  <PackageReference Include="Azure.AI.Translation.Text" Version="2.0.0" />
  <PackageReference Include="Azure.Identity" Version="1.21.0" />
</ItemGroup>
And this is the existing code.
using Azure;
using Azure.AI.TextAnalytics;
using Azure.AI.Translation.Text;
using Azure.Identity;

DefaultAzureCredential credentials = new DefaultAzureCredential();

// API Kind: TextAnalytics
string languageEndpoint = "https://lang-demo-service-001.cognitiveservices.azure.com";

TextAnalyticsClient textAnalyticsClient = new(new Uri(languageEndpoint), credentials);
Azure.AI.TextAnalytics.DetectedLanguage response =
    await textAnalyticsClient.DetectLanguageAsync("Hello");
Console.WriteLine(response.Name);

// API Kind: TextTranslation
string translatorEndpoint = "https://trsl-demo-service-001.cognitiveservices.azure.com";

TextTranslationClient textTranslationClient = new(credentials, new Uri(translatorEndpoint));
Response<IReadOnlyList<TranslatedTextItem>> translateTextResponse =
    await textTranslationClient.TranslateAsync("fr", "Hello", "en");

TranslatedTextItem? translatedTextItem = translateTextResponse.Value.FirstOrDefault();
Console.WriteLine(translatedTextItem?.Translations.FirstOrDefault()?.Text);
I didn't want to maintain more services when we can have single Microsoft Foundry resource (API Kind: AIServices). It's a multi-service resource, it bundles Language, Translator, Speech, Vision and more, so it's one resource, one credential and one endpoint in configuration.

I thought that should be a simple endpoint change.
string apiEndpoint = "https://aif-demo-service-001.services.ai.azure.com";

DefaultAzureCredential credentials = new DefaultAzureCredential();

// Language
TextAnalyticsClient textAnalyticsClient = new(new Uri(apiEndpoint), credentials);

// Translation
TextTranslationClient textTranslationClient = new(credentials, new Uri(apiEndpoint));

// Omitted for brevity
TextAnalyticsClient seemed to work, it wrote out English. But TextTranslationClient didn't.
English

Unhandled exception. Azure.RequestFailedException: Resource not found Status: 404(Resource Not Found) ErrorCode: 404 Content: { "error":{ "code":"404","message": "Resource not found"} } Headers: apim - request - id: REDACTED Strict-Transport-Security: REDACTED X-Content-Type-Options: REDACTED Date: Tue, 11 Aug 2026 08:57:57 GMT Content-Length: 56 Content - Type: application / json at Azure.AI.Translation.Text.ClientPipelineExtensions.ProcessMessageAsync(HttpPipeline pipeline, HttpMessage message, RequestContext context) at Azure.AI.Translation.Text.TextTranslationClient.TranslateAsync(RequestContent content, String clientTraceId, RequestContext context) at Azure.AI.Translation.Text.TextTranslationClient.TranslateAsync(IEnumerable`1 inputs, CancellationToken cancellationToken) at Program.< Main >$(String[] args) in C: \Users\Jaliya\Desktop\ConsoleApp1\ConsoleApp1\Program.cs:line 45 at Program.<Main>(String[] args)
My first thought was that Translator simply isn't served on the services.ai.azure.com endpoint. That's not it, a raw request against the very same endpoint works.
$token = az account get-access-token `
    --resource https://cognitiveservices.azure.com `
    --query accessToken -o tsv

curl -X POST "https://aif-demo-service-001.services.ai.azure.com/translator/text/v3.0/translate?api-version=3.0&to=fr" `
     -H "Authorization: Bearer $token" `
     -H "Content-Type: application/json" `
     -d "[{'Text':'Hello'}]"

# 200 OK
#[
#    {
#        "detectedLanguage": {
#            "language": "en",
#            "score": 1.0
#        },
#        "translations": [
#            {
#                "text": "Bonjour",
#                "to": "fr"
#            }
#        ]
#    }
#]     
So the host is fine, and note the route, Translator lives under /translator/text. May be something fishy with the SDK.

Looking at the source of Azure.AI.Translation.Text, this is how it decides whether to add that route prefix.
private const string PLATFORM_HOST = "cognitiveservices";

internal static bool IsPlatformHost(this Uri uri)
{
    return uri.Host?.Contains(PLATFORM_HOST) == true;
}
Which is then used in the constructor.
private const string PLATFORM_PATH = "/translator/text";

if (endpoint.IsPlatformHost())
{
    this._endpoint = new Uri(endpoint, PLATFORM_PATH);
}
There it is. The prefix is added only when the endpoint host contains the literal string cognitiveservices.

My old endpoint was trsl-demo-service-001.cognitiveservices.azure.com, which contains it, so the SDK built /translator/text/translate. The new one is aif-demo-service-001.services.ai.azure.com, which doesn't, so the SDK treated it like a global endpoint and posted to /translate. Hence the 404.

The fix is to pass the prefix ourselves when constructing the Uri.
using Azure;
using Azure.AI.Translation.Text;
using Azure.Identity;

string apiEndpoint = "https://aif-inf-sl-tenant1-dev-001.services.ai.azure.com";

DefaultAzureCredential credentials = new DefaultAzureCredential();

// Translation
TextTranslationClient textTranslationClient = new(credentials, new Uri($"{apiEndpoint}/translator/text"));
Response<IReadOnlyList<TranslatedTextItem>> translateTextResponse =
    await textTranslationClient.TranslateAsync("fr", "Hello", "en");

TranslatedTextItem? translatedTextItem = translateTextResponse.Value.FirstOrDefault();
Console.WriteLine(translatedTextItem?.Translations.FirstOrDefault()?.Text);
And now both work off the single endpoint.
English
Bonjour
One thing I wanted to confirm, what happens if a future version of the SDK starts recognizing services.ai.azure.com? Would it add the prefix on top of mine and put me back at a 404? No, because PLATFORM_PATH is rooted, so new Uri(endpoint, PLATFORM_PATH) replaces the path instead of appending to it.
Uri uri = new Uri(new Uri("https://aif-demo-service-001.services.ai.azure.com/translator/text"), "/translator/text");
Console.WriteLine(uri.ToString());

// https://aif-demo-service-001.services.ai.azure.com/translator/text
I have raised this with the SDK team as azure-sdk-for-net#61912, so the behavior might change.

More read:

Hope this helps.

Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment