DEV Community

T. Alam
T. Alam

Posted on

The Azure Bug That Cost Us an Afternoon Was One String Not Matching Another String

We migrated a feature from calling OpenAI's API directly to going through Azure AI instead — same underlying model family, just routed through our Azure subscription for reasons that had nothing to do with the model itself. Should've been a config change. Instead it turned into an afternoon of checking API keys, checking network rules, checking whether our Azure subscription had actually provisioned correctly, before someone finally found the actual problem: a string that didn't match another string, in a place none of us thought to look first.

Writing this up because we're fairly sure we won't be the last team to lose an afternoon to it.

What's actually different about Azure

Every other provider we'd connected before this — OpenAI, Anthropic, Gemini, Hugging Face — you get an API key and you call model names directly. gpt-4o means gpt-4o. Azure AI doesn't work that way. You have to have already deployed a model inside your own Azure resource before anything can call it, and Azure doesn't let you address that model by its public name. You address it by whatever name you (or whoever set up the resource) gave that deployment when it was created in the Azure portal.

That one fact is responsible for basically all of the confusion in this story.

Two things have to exist before this works at all: an Azure resource, and a model actually deployed inside it.

The setup, which looked completely normal

In the DNotifier portal: Projects → AI Studio → Providers → Azure AI. We plugged in the resource endpoint (something like https://your-resource.openai.azure.com), an API key, and the deployment list. Installed the SDK:

npm install @dnotifier-realtime/dnotifier
Enter fullscreen mode Exit fullscreen mode

Took our existing OpenAI-calling code, swapped the provider field, and shipped it:

const notifier = new DNotifier({ apiKey: process.env.DNOTIFIER_API_KEY });

const response = await notifier.sendAI({
  senderId: USER_ID,
  provider: "azure_ai",
  model: "gpt-4o", // <-- this is the line that broke everything
  message: {
    messages: [{ role: "user", content: "Summarize this quarter's key metrics." }],
  },
  saveHistory: true,
});
Enter fullscreen mode Exit fullscreen mode

Looks completely reasonable. It is completely reasonable, if your Azure deployment happens to be named gpt-4o. Ours wasn't.

What actually failed, and why nothing pointed at the real cause

Every call came back with a 404-ish deployment error. Not "invalid API key," not "network unreachable" — a deployment error, which in hindsight is exactly the right error and in the moment told us almost nothing, because none of us had internalized yet that model here doesn't mean what it means for every other provider.

We checked the API key. Fine. We checked the resource endpoint URL for typos. Fine. We checked whether the Azure resource had finished provisioning. Fine. Somewhere around the twenty-minute mark, someone actually opened the Azure portal and looked at the resource's deployment list instead of staring at our own code one more time.

Azure Portal → your-resource → Deployments

Deployment name          Model                Status
chatapp-gpt4o-prod        gpt-4o               Succeeded
Enter fullscreen mode Exit fullscreen mode

The error looks like an integration problem. It's almost always a naming mismatch, checked against the Azure portal's deployment list.

There it was. Whoever set up this Azure resource — months earlier, a different engineer, long since moved to a different team — had named the deployment chatapp-gpt4o-prod, not gpt-4o. Reasonable choice on their part, especially in an org with more than one deployment floating around. Just not the string our code was sending.

The fix, once we knew what to look for

const response = await notifier.sendAI({
  senderId: USER_ID,
  provider: "azure_ai",
  model: "chatapp-gpt4o-prod", // the actual deployment name, not the model's public name
  message: {
    messages: [{ role: "user", content: "Summarize this quarter's key metrics." }],
  },
  saveHistory: true,
});
Enter fullscreen mode Exit fullscreen mode

One line. Worked immediately, no other changes needed anywhere. The fix took about two minutes once someone knew to look at deployment names specifically. Finding that out took the other twenty-plus.

The rule we now follow, no exceptions

The model field in a sendAI() call against Azure AI is whatever your deployment is named in the Azure (or Microsoft Foundry) portal, full stop. It can match the underlying model's public name — Azure's default deployment name sometimes does — but "sometimes" is exactly the trap. We stopped assuming and started checking the actual deployment list every single time we point new code at an Azure resource, especially one we didn't personally set up.

Azure Portal → [your resource] → Model deployments

Whatever appears here, verbatim, is what belongs in the `model` field.
Enter fullscreen mode Exit fullscreen mode

Not the model's public name. Not an abbreviation. Not what you assume it "should" be called based on every other provider's convention.

One resource, several model families — the decision is which lane a task actually belongs in, not which model sounds most impressive.

It's not just the name — deployment type is its own decision

Once we'd fixed the immediate bug, we ran into the second layer of this: Azure's model catalog spans OpenAI's GPT family, Claude, Llama, Mistral, DeepSeek, and others, all inside the same Foundry resource, and picking a model family is only half the decision. Deployment type — Standard, Global Standard, Data Zone Standard, Provisioned Throughput — behaves differently on pricing, data residency, and latency consistency under load. A task with hard data-residency requirements might need Data Zone Standard regardless of which model family you picked. A high-volume, latency-sensitive feature might justify Provisioned Throughput even for a mid-tier model.

We'd initially deployed our most capable available model for a document-summarization tool, assuming quality would scale with size. Running the same representative prompts through DNotifier's Prompt Testing Studio against a smaller, cheaper deployment from the same model family showed statistically indistinguishable output quality for our specific documents — the task just didn't need the bigger model. Switching cut per-request cost substantially with zero measurable drop in quality, and we wouldn't have made that switch with any real confidence without the side-by-side comparison sitting in front of us.

// same task, two deployments, tested against the same real prompts
// before committing production traffic to either one
const resultA = await notifier.sendAI({
  provider: "azure_ai",
  model: "chatapp-gpt4o-prod",
  message: { messages: [{ role: "user", content: testPrompt }] },
});

const resultB = await notifier.sendAI({
  provider: "azure_ai",
  model: "chatapp-gpt4o-mini-prod",
  message: { messages: [{ role: "user", content: testPrompt }] },
});
// compare quality, latency, and cost before picking one for real traffic
Enter fullscreen mode Exit fullscreen mode

What we'd tell anyone setting this up fresh

Before you write a single line pointing at an Azure deployment: open the portal, find the actual deployment name, and use that string exactly. Don't assume it matches the model's public name just because every other provider you've touched works that way — Azure is genuinely different here, not being difficult for no reason, just structured around your own resource rather than a shared public catalog. And once it's working, don't stop at "which model family" — test the actual deployment type and tier against real prompts before you commit production traffic to it. Twenty minutes of confusion over a naming mismatch is annoying. Overpaying for capability a task never needed is the quieter, more expensive version of the same mistake.

Top comments (0)