Integrationsmodel providers
Azure OpenAI models
Call your Azure OpenAI deployments through Azure's v1 API with @mayurajs/provider-azure: deployment ids, Global prices, Entra ID tokens and errors.
- Package
- @mayurajs/provider-azure
- Install
- npm install mayura @mayurajs/provider-azure
- Vendor
- Microsoft Azure ↗
Azure OpenAI serves OpenAI's models from an Azure resource you own, as deployments you
name. @mayurajs/provider-azure is a provider package for the model registry: every
deployment gets an id such as azure/prod-chat, which you price, bound and grant like any other registry model. It
calls Azure's v1 API (the Responses API) through the official openai SDK, by way of responsesProvider from
@mayurajs/provider-openai, its only dependency (mayura is a peer). Everything that package
promises holds here: strict schemas, streaming, bounded responses, no SDK retries and nothing read from the
environment. The package uses no Node built-in modules and sends every request through fetch; its engines field
names Node.js 22 (22.12 or later) and 24 (24.14.1 or later), and its tests run on Node.
Install
npm install mayura @mayurajs/provider-azureCredentials
Give the package one of:
- The resource's API key, from your Azure OpenAI resource's Keys and Endpoint page in the Azure portal, or from Azure AI Foundry.
- A Microsoft Entra ID token source: a function that returns an access token for Azure OpenAI (scope
https://cognitiveservices.azure.com/.default). It is called before every request, so tokens stay fresh. The identity needs a role that may call the resource's models, such as Cognitive Services OpenAI User.
Either one is sent as Authorization: Bearer. The package reads nothing from the environment: not
AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT or OPENAI_BASE_URL. Pass the resource, key and deployment in
yourself, for example from AZURE_OPENAI_RESOURCE, AZURE_OPENAI_API_KEY and AZURE_OPENAI_DEPLOYMENT.
Use it
import { createModels, createRuntime, defineAgent, z } from 'mayura';
import { azure } from '@mayurajs/provider-azure';
const models = createModels({
providers: [azure({
resource: 'contoso', // https://contoso.openai.azure.com/openai/v1
apiKey: process.env.AZURE_OPENAI_API_KEY ?? '',
deployments: { 'prod-chat': { model: 'gpt-5.5', type: 'global' } },
})],
prices: 'catalog', // Azure's Global list prices for the model each deployment runs
maxCallCostMicros: 200_000, // at most $0.20 per model call
});
const agent = defineAgent({
id: 'support', version: '1', instructions: 'Answer the customer in one or two sentences.',
model: models.model('azure/prod-chat', { retry: { attempts: 2 } }), tools: [],
input: z.object({ question: z.string() }), output: z.object({ answer: z.string() }),
});
const runtime = createRuntime({
profile: 'ephemeral',
permissions: { allow: ['model:azure/prod-chat'] },
limits: { maxCostMicros: 1_000_000 }, // at most $1 per run
});
try {
const outcome = await runtime.submit(agent, { input: { question: 'Where is my order?' } }).result();
console.log(outcome.status === 'succeeded' ? outcome.output.answer : outcome.error);
} finally {
await runtime.close();
}The adapter's id is the model id, so the run needs model:azure/prod-chat, and that grant admits no other deployment.
A run without it ends blocked with PERMISSION_DENIED before any request is sent. See
Permissions.
With Microsoft Entra ID instead of a key, pass token, for example from @azure/identity:
const provider = azure({
baseURL: 'https://contoso.cognitiveservices.azure.com/openai/v1',
token: () => getEntraToken('https://cognitiveservices.azure.com/.default'),
});Model ids
A model id is azure/<deployment>: the deployment's name in your resource, not the model's. The name is sent as the
request's model. Names in deployments must be up to 64 letters, digits, ., _ or -, starting with a letter or
digit.
Catalog prices
Catalog prices reach a deployment only through deployments, which says which model each deployment runs. Only
Global deployments have catalog prices: Data Zone and Regional deployments cost more, so give their prices yourself.
A deployment not listed in deployments has no catalog price.
The catalog holds Azure OpenAI's Global list prices as of 2026-09-29, from Azure's pricing page, by model. In US dollars per million tokens:
| Model | Input | Output | Input over 272K | Output over 272K |
|---|---|---|---|---|
gpt-6-astra |
$10 | $50 | $20 | $75 |
gpt-5.5 |
$5 | $30 | $10 | $45 |
gpt-5.4 |
$2.50 | $15 | $5 | $22.50 |
gpt-5.4-mini |
$0.75 | $4.50 | $1.50 | $6.75 |
gpt-5-mini |
$0.25 | $2 | $0.50 | $3 |
gpt-4.1 |
$2 | $8 | $4 | $12 |
The catalog errs towards charging more, so a budget never undercounts:
- A prompt over 272K input tokens is charged the long-context rates (twice the input rate, one and a half times the output rate) for the whole call, on every model, as Azure applies OpenAI's rule.
- Cached input is charged at the full input rate.
- Models whose Azure prices were not yet published on that date (GPT-6 Sol and Luna) are left out.
Prices change: models.list() shows each deployment's catalogAsOf, and the catalog is exported as catalog. A
price you give, in prices or as pricing on one model, always wins, and is the only way to price a Data Zone or
Regional deployment:
const models = createModels({
providers: [azure({ resource: 'contoso', apiKey: process.env.AZURE_OPENAI_API_KEY ?? '' })],
// A Data Zone deployment: use the prices Azure lists for its deployment type and region.
prices: { 'azure/eu-chat': { inputMicrosPerMillionTokens: 5_500_000, outputMicrosPerMillionTokens: 33_000_000 } },
maxCallCostMicros: 200_000,
});Options
| Option | Default | Meaning |
|---|---|---|
resource |
none | Your Azure OpenAI resource name; requests go to https://<resource>.openai.azure.com/openai/v1. Give this or baseURL. |
baseURL |
none | The v1 API's base URL instead, such as a custom domain or a gateway (https only). Give this or resource. |
apiKey |
none | The resource's API key. Give this or token. |
token |
none | A Microsoft Entra ID token source, () => Promise<string>, called for every request. Give this or apiKey. |
deployments |
none | Your Global deployments by name, { model, type: 'global' }, for catalog prices. |
headers |
none | Up to 8 extra headers, such as a gateway's credential. Treated as credentials. They cannot replace Authorization, api-key, OpenAI-Organization, OpenAI-Project, Content-Type, Host or cookies. |
media |
every Mayura media type, and URLs | What the models can see, { types, urls }; false for none. |
maxRequestBytes |
1 MiB | Largest request body, not counting media (the run's maxMediaBytes bounds those). |
maxResponseBytes |
1 MiB | Largest response body; four times that for a stream. A larger one fails without being read into memory. |
timeoutMs |
60,000 | How long one call may take, unless the registry or the model sets timeoutMs. |
fetch |
globalThis.fetch |
The transport, for tests and proxies. Redirects are never followed. |
The registry's timeoutMs (on createModels or on one model, 100 to 3,600,000 ms) wins over the provider's.
What it sends and supports
Requests are OpenAI Responses requests, as @mayurajs/provider-openai sends them:
- Structured output. The agent's output as strict JSON Schema, tools as strict functions, parallel tool calls on. The schemas must follow the strict rules.
- Streaming. With a stream policy on the agent, output text is reported as it arrives.
- Reasoning.
store: false; encrypted reasoning items are kept in the run's private state between calls of one run and never released. That state names the provider, so a run never continues on Azure with state from OpenAI, or the other way round. - Images and PDFs. By default every Mayura media type, as bytes or URLs;
media: falsefor none. See Vision. - Output tokens.
max_output_tokensis the run'slimits.maxOutputTokens(4,096 unless you set it).
Costs
Each call's cost is Azure's reported input_tokens and output_tokens at your prices, rounded up to a whole micro;
the long-context rates apply to the whole call once its input is over the threshold.
maxCallCostMicros (or a model's maxCostMicros) is reserved before every call, and a call that doesn't fit in the
run's budget never starts. Choose it to cover your largest prompt plus maxOutputTokens: on a gpt-5.5 deployment,
20,000 input tokens and 4,096 output tokens cost at most 100,000 + 122,880 = 222,880 micros, so lower maxOutputTokens
or raise the bound for prompts that large. A call whose cost is unknown, such as one that timed out after it was sent,
is charged its full bound. See Costs and budgets.
Retries and fallback
The SDK's own retries are off: one call is one HTTP request. Retries belong to the registry:
const model = models.model('azure/prod-chat', { retry: { attempts: 3, backoffMs: 500 } });retrycalls the model again afterrate_limited,unavailableortimeout; never after the caller cancels, a failure that would repeat, or once streamed text was released.attemptsis 1 to 5, and the model's bound becomesattemptstimes its bound. Every attempt is charged.- Waits double from
backoffMs(default 500 ms).
To fall back to another deployment, or to OpenAI itself during an outage, use a chain. It is a model router granted by the name you choose, and granting it grants every model in it:
import { createModels } from 'mayura';
import { azure } from '@mayurajs/provider-azure';
import { openai } from '@mayurajs/provider-openai';
const models = createModels({
providers: [
azure({ resource: 'contoso', apiKey: process.env.AZURE_OPENAI_API_KEY ?? '', deployments: { 'prod-chat': { model: 'gpt-5.5', type: 'global' } } }),
openai({ apiKey: process.env.OPENAI_API_KEY ?? '' }),
],
prices: 'catalog',
maxCallCostMicros: 200_000,
});
const support = models.chain('support', ['azure/prod-chat', 'openai/gpt-5.5']);
// Grant model:support. Each call reserves both bounds: 400,000 micros.Errors
A failed call ends the run with MODEL_FAILED and a reason (see Errors for the
messages):
reason |
When |
|---|---|
authentication |
HTTP 401 or 403: a wrong key or token, or no access to the deployment; or the token source threw. |
rate_limited |
HTTP 429, or 402 for quota. |
unavailable |
HTTP 408 or 5xx, no connection, or a stream that reported a failure. |
timeout |
No answer within timeoutMs. |
rejected |
Any other HTTP error, such as 404 for an unknown deployment or 400 for a request Azure refuses. |
refused |
The model refused, or the response was incomplete, for example at the output-token limit. |
invalid_response |
An answer that is not the required JSON, a response without usage, one larger than maxResponseBytes, or a run's state from another provider. |
configuration |
A schema that breaks the strict rules, or a token source that returned something other than a single-line string (code INVALID_CONFIG). |
Azure's error text, the token source's error, your key and your headers never appear in outcomes or events. Options
the package cannot use throw INVALID_CONFIG when you call azure(): both or neither of resource and baseURL, both
or neither of apiKey and token, a resource name with characters other than letters, digits and -, an http://
baseURL, a header that would replace api-key, and a deployment that is not type: 'global', runs a model the
catalog does not have, or has a malformed name.
How it is tested
- Unit tests run the package through the OpenAI SDK against a fake Azure v1 API, given through
fetch. It passes the model adapter conformance suite frommayura/testing: response shapes, costs, tool calls, streaming, the reason for each failure, no leaked provider text, cancellation, timeouts and strict schemas. - They also check that requests go to the resource's v1 API by deployment name with nothing from the environment, that a token source is asked for a fresh token on every request and its failures are reported without their text, that only listed Global deployments get catalog prices, and that a run OpenAI started is never continued on Azure.
- It has not been run against a real Azure account yet.
Related
- Model registry: ids, prices, the per-call bound, retries and chains.
- OpenAI: the same models from OpenAI, and
responsesProvider. - Model routing: failover, circuit breakers and accounting.
- Costs and budgets: how a call's cost is reserved and charged.