Azure provider: cannot target chat/completions (404 on responses); need explicit config/docs for Azure wire and path construction

Resolved 💬 17 comments Opened Aug 8, 2025 by santiago-afonso Closed Nov 28, 2025

What version of Codex is running?

codex-cli 0.19.0

Which model were you using?

gpt-5

What platform is your computer?

Linux 6.6.87.1-microsoft-standard-WSL2 x86_64 x86_64

What steps can reproduce the bug?

Environment

  • Codex CLI: v0.19 (Rust)
  • OS: Linux (x86-64)
  • Auth: Entra ID (AAD) access token via client credentials (raw JWT in env var)
  • Azure target: Azure OpenAI fronted by API Management (APIM)
  • Config method: ~/.codex/config.toml + --profile
  • Model: Azure deployment name (not base model)

Summary
Codex CLI’s Azure provider appears to call the Responses API path:

/openai/deployments/{deployment}/responses?api-version=...

Our APIM only exposes Chat Completions:

/openai/deployments/{deployment}/chat/completions?api-version=...

Result: Codex returns 404 Not Found consistently, while curl and the Python SDK succeed using chat/completions.

What works

  • Mint Entra ID access token (client credentials).
  • curl to POST .../openai/deployments/<DEPLOY>/chat/completions?api-version=2025-04-01-preview returns 200 OK with a normal response.
  • Python AzureOpenAI + chat.completions.create(...) returns 200 OK against the same endpoint.

What fails

  • Codex CLI (with Azure provider + profile) returns 404 Not Found repeatedly.
  • Hitting .../openai/deployments/<DEPLOY>/responses?api-version=... with curl also returns 404 on our APIM (that path isn’t exposed).

Minimal (sanitized) config

# ~/.codex/config.toml
model_provider = "azure"
model = "<AZURE_DEPLOYMENT_NAME>"

[model_providers.azure]
name = "Azure"
# Base ends in /openai so provider appends /deployments/{model}/...
base_url = "https://<YOUR-APIM-DOMAIN>/openai"
query_params = { api-version = "2025-04-01-preview" }
# We need to force Chat Completions here, but there’s no documented knob.
# (Tried variations; CLI still hits /responses.)

[profiles.az-qa]
model_provider = "azure"
model = "<AZURE_DEPLOYMENT_NAME>"

Invocation

export AZURE_OPENAI_API_KEY="<RAW_JWT_FROM_AAD>"   # no "Bearer " prefix
codex --profile az-qa -m "<AZURE_DEPLOYMENT_NAME>" "say hello"
# => 404 Not Found

Logs (excerpt)

... INFO BackgroundEvent: stream error: unexpected status 404 Not Found: {"statusCode":404,"message":"Resource not found"}
... WARN stream disconnected - retrying ...

Repro (working vs failing)

# Working (Chat Completions)
curl -sS -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -X POST "https://<YOUR-APIM-DOMAIN>/openai/deployments/<DEPLOY>/chat/completions?api-version=2025-04-01-preview" \
  -d '{"messages":[{"role":"user","content":"ping"}]}'
# => 200 OK

# Failing (Responses)
curl -sS -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -X POST "https://<YOUR-APIM-DOMAIN>/openai/deployments/<DEPLOY>/responses?api-version=2025-04-01-preview" \
  -d '{"input":[{"role":"user","content":"ping"}]}'
# => 404 Not Found

Expected

  • Either:
  1. A documented configuration option to select the Azure wire:
  • e.g., wire_api = "chat.completions" vs wire_api = "responses" in the Azure provider block; or
  1. A path-template override (e.g., path_template = "/deployments/{model}/chat/completions")
  • And documentation that clearly shows:
  • base_url must end with /openai.
  • model must be the Azure deployment name.
  • How to set api-version via query_params.
  • How to provide an AAD access token (raw JWT vs "Bearer "), plus whether API keys are supported and which header the CLI sends.

Actual

  • CLI appears to target /responses with no way (documented) to force chat/completions, leading to 404s on Azure setups that only expose Chat Completions via APIM.

Requests

  1. Bug/feature: Add a documented way to select the Azure wire (Chat Completions vs Responses) or to override the path template for Azure.
  2. Docs: Provide an end-to-end Azure section with:
  • Minimal TOML for Azure.
  • AAD Device Code and Client Credentials examples that mint a token and run Codex.
  • Exact path shapes Codex will call for each wire.
  • api-version configuration.
  • Troubleshooting guidance (401 vs 404, where logs live, how to confirm the final URL).

Why this matters
Many Azure/APIM deployments expose Chat Completions but not Responses. Without a documented knob to choose the wire (or override the path), Codex can’t interoperate with otherwise standard Azure OpenAI setups.

Thanks!

What is the expected behavior?

_No response_

What do you see instead?

_No response_

Additional information

_No response_

View original on GitHub ↗

17 Comments

sepawi01 · 11 months ago

I have exactly this problem aswell. Very nicely formulated.

pl-shernandez · 11 months ago

I get a '🖐 builder error' but curls just fine because I'm using chat completions with API keys

santiago-afonso · 11 months ago
I have exactly this problem aswell. Very nicely formulated.

Guess who wrote it

jepio · 11 months ago

@santiago-afonso your config.toml section needs to look something like this:

[model_providers.azure]
name = "Azure"
base_url = "https://<YOUR-APIM-DOMAIN>/openai/deployments/<AZURE_DEPLOYMENT_NAME>"
env_key = "AZURE_OPENAI_API_KEY"
query_params = { "api-version" = "2025-04-01-preview" }
wire_api = "chat"

[profiles.az-qa]
model_provider = "azure"
model = "<AZURE_DEPLOYMENT_NAME>"

The model value in the profiles block is only used internally to the cli to lookup model parameters (like context window size), so i recommend it matches one of the values from codex-rs/core/src/openai_model_info.rs.

ckr0 · 11 months ago

Does not work. Though there are no 400/404 errors with this config, the commands do not do anything. Input a command - it shows working for a couple of second and dies out. This is a consistent problem with azure. Looks like use of azure is being discouraged.

santiago-afonso · 11 months ago
@santiago-afonso your config.toml section needs to look something like this: `` [model_providers.azure] name = "Azure" base_url = "https://<YOUR-APIM-DOMAIN>/openai/deployments/<AZURE_DEPLOYMENT_NAME>" env_key = "AZURE_OPENAI_API_KEY" query_params = { "api-version" = "2025-04-01-preview" } wire_api = "chat" [profiles.az-qa] model_provider = "azure" model = "<AZURE_DEPLOYMENT_NAME>" ` The model value in the profiles block is only used internally to the cli to lookup model parameters (like context window size), so i recommend it matches one of the values from codex-rs/core/src/openai_model_info.rs`.

Thanks for your comment. My organization works with bearer tokens, not API keys, so this wouldn't solve the issue.

jepio · 11 months ago

This works with bearer tokens too (https://github.com/openai/codex/blob/main/codex-rs/core/src/model_provider_info.rs#L98-L117), the value of env_key is passed with Authorization: Bearer <token>.
I've been using this for several weeks.

jepio · 11 months ago
Does not work. Though there are no 400/404 errors with this config, the commands do not do anything. Input a command - it shows working for a couple of second and dies out. This is a consistent problem with azure. Looks like use of azure is being discouraged.

I don't get the feeling that it's discouraged, there are definitely still occasional issues and documentation is lacking. Codex is still a research preview. If you enable RUST_LOG=trace env variable you might get more helpful output.

pl-shernandez · 11 months ago

Still stuck probably holding it wrong. I've tried multiple permutations of the variables I've seen in other closed issues using Azure Open AI with chat completions and a geographical region subdomain. Always this builder error though.

<img width="1081" height="154" alt="Image" src="https://github.com/user-attachments/assets/5ef65904-d722-42fa-902a-f4880292f56d" />


#it auto adds this section on launch of codex tui
model_provider = "azure"
projects = { "/fake/project/path" = { trust_level = "trusted" } }


[model_providers.azure]
name = "Azure"
base_url = "https://MYREGION.api.cognitive.microsoft.com/openai/deployments/DEPLOYMENTNAME" #tried permutations of this with and without deployment (mind is named the same as the model) tried "https://MYREGION.api.cognitive.microsoft.com/openai/
env_key = "AZURE_OPENAI_API_KEY" #set in my zsh profile for certain there with `echo $AZURE_OPENAI_API_KEY`
query_params = { api-version = "2025-04-01-preview"} #tried "2025-01-01-preview"
wire_api = "chat" #tried "responses"

[profiles.azure] #tried with and without this
model_provider = "azure"
model = "o3"

using RUST_LOG=1 codex
cat ~/.codex/log/codex-tui.log

2025-08-08T19:04:27.511109Z  WARN stream disconnected - retrying turn (1/5 in 184ms)...
2025-08-08T19:04:27.511305Z  INFO BackgroundEvent: stream error: builder error; retrying 1/5 in 184ms…
2025-08-08T19:04:30.570229Z  WARN stream disconnected - retrying turn (2/5 in 371ms)...
2025-08-08T19:04:30.570339Z  INFO BackgroundEvent: stream error: builder error; retrying 2/5 in 371ms…
2025-08-08T19:04:33.929918Z  WARN stream disconnected - retrying turn (3/5 in 871ms)...
2025-08-08T19:04:33.930078Z  INFO BackgroundEvent: stream error: builder error; retrying 3/5 in 871ms…
2025-08-08T19:04:37.788611Z  WARN stream disconnected - retrying turn (4/5 in 1.701s)...
2025-08-08T19:04:37.788681Z  INFO BackgroundEvent: stream error: builder error; retrying 4/5 in 1.701s…
2025-08-08T19:04:42.569739Z  WARN stream disconnected - retrying turn (5/5 in 2.984s)...
2025-08-08T19:04:42.569815Z  INFO BackgroundEvent: stream error: builder error; retrying 5/5 in 2.984s…

I get a '🖐 builder error'

curl works and returns a "Hello how can I help..."

curl -X POST "https://MYREGION.api.cognitive.microsoft.com/openai/deployments/DEPLOYMENTNAME/chat/completions?api-version=2025-04-01-preview" \
  -H "Content-Type: application/json" \
  -H "api-key: $AZURE_OPENAI_API_KEY" \
  -d '{
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "Hello!"}
    ]
  }'
santiago-afonso · 11 months ago

I am on the same boat, tried countless permutations, curl works, codex doesn't. First, it took hours of trial and error to get codex to post to the correct URL (thanks @jepio for the RUST_LOG tip). Below is the writeup.

Codex CLI v0.20 ↔ Azure OpenAI via APIM (Entra ID): What Worked vs. What Didn’t

TL;DR (most relevant):
Codex CLI succeeds only when the request path contains /openai/deployments/{deployment}/chat/completions and an explicit api-version. Transport/auth work; the TUI render is flaky (completion arrives, but nothing is painted). Use plain stdout as a workaround.

---

Working configurations (observed)

1) Direct HTTP (control tests)

  • curl against APIM with bearer JWT and the deploymented path:

POST <APIM_BASE>/openai/deployments/<DEPLOYMENT>/chat/completions?api-version=...
200 OK with model: gpt-5-YYYY-MM-DD.

  • AzureOpenAI Python SDK (chat.completions.create) with the same deploymented path + JWT.

200 OK.

2) Codex CLI v0.20 (transport/auth OK; TUI flaky)

  • Provider config where base_url already includes /openai/deployments/<DEPLOYMENT>, plus:
  • wire_api = "chat"
  • query_params = { "api-version" = "<API_VERSION>" } (key must be quoted)
  • requires_openai_auth = false
  • env_key = "AZURE_OPENAI_API_KEY" and/or

env_http_headers = { "Authorization" = "Bearer ${AZURE_OPENAI_API_KEY}" }

  • Logs show:
  • Correct POST .../deployments/<DEPLOYMENT>/chat/completions?api-version=...
  • Streaming SSE chunks received
  • Output item ... "Hello! How can I help today?"
  • But: Codex TUI did not paint the assistant message even though it was emitted (render bug).

Minimal known-good provider snippet (redacted/templated):

# ~/.codex/config.toml
projects = { "/path/to/project" = { trust_level = "trusted" } }

[model_providers.your-org]
name                 = "Azure"
base_url             = "<APIM_BASE>/openai/deployments/<DEPLOYMENT>"
wire_api             = "chat"
requires_openai_auth = false
env_key              = "AZURE_OPENAI_API_KEY"
# Belt-and-suspenders to force Bearer:
env_http_headers     = { "Authorization" = "Bearer ${AZURE_OPENAI_API_KEY}" }
query_params         = { "api-version" = "<API_VERSION>" }

[profiles.az-qa]
model_provider = "your-org"
model          = "gpt-5"

Runtime:

export AZURE_OPENAI_API_KEY="$(your-token-command)"
codex --profile az-qa -m gpt-5 "say hello"

---

Not working (observed) & symptoms

  1. Missing deployment segment

base_url = "<APIM_BASE>/openai" (no /deployments/<DEPLOYMENT>)
404 Not Found from APIM.

  1. Wrong wire

wire_api = "responses"
404 (APIM does not expose that route here).

  1. Missing api-version

Omit query param or mis-name the key (unquoted api-version)
404. (Quoting the hyphenated key is required.)

  1. Profile vs. top-level provider precedence pitfalls
  • Removing the top-level provider while leaving model_provider = "..." at top level

“Model provider … not found.”

  • Defining both top-level and profile-scoped azure providers (or a stub at top) sometimes caused Codex to fall back to api.openai.com

401 Unauthorized (“You didn’t provide an API key”), even though a valid JWT was present.

  1. TUI render bug (Codex v0.20 cut)

Transport succeeds (SSE, “Output item …”), but no text is shown in the UI.
→ This is display-only; completions are produced.

---

Known-good patterns (keep these)

  • Bake the deployment into base_url:

.../openai/deployments/<DEPLOYMENT>

  • Pin the version with a quoted key:

query_params = { "api-version" = "<API_VERSION>" }

  • JWT bearer, not OpenAI API key:

requires_openai_auth = false and (optionally) env_http_headers.Authorization = "Bearer ${AZURE_OPENAI_API_KEY}".

  • Avoid ambiguous merges: give the provider a unique id (e.g., your-org) and define it once at top level; reference it from the profile.

---

Negative controls (useful for repro issues)

  • Set base_url = "<APIM_BASE>/openai" (no /deployments/...) → expect 404.
  • Remove/rename api-version → expect 404.
  • Leave OPENAI_* envs set or let Codex pick the OpenAI default base → expect 401 to api.openai.com.

---

Checklist to keep it green

  • [ ] base_url includes /openai/deployments/<DEPLOYMENT>
  • [ ] wire_api = "chat"
  • [ ] query_params."api-version" present and quoted
  • [ ] requires_openai_auth = false
  • [ ] Authorization: Bearer <JWT> is sent (via env_key and/or env_http_headers)
  • [ ] No conflicting provider blocks; no stray OPENAI_* env vars
  • [ ] If TUI is blank, use one of the plain-output workarounds above

This reflects only what we actually tried and observed.

ckr0 · 11 months ago
> Does not work. Though there are no 400/404 errors with this config, the commands do not do anything. Input a command - it shows working for a couple of second and dies out. This is a consistent problem with azure. Looks like use of azure is being discouraged. I don't get the feeling that it's discouraged, there are definitely still occasional issues and documentation is lacking. Codex is still a research preview. If you enable RUST_LOG=trace env variable you might get more helpful output.

I respect your feeling. but the fact of the matter is azure support was broken in:
0.05
0.10
0.15 and still in
0.20

so make of it whatever you will

imxj · 10 months ago
I am on the same boat, tried countless permutations, curl works, codex doesn't. First, it took hours of trial and error to get codex to post to the correct URL (thanks @jepio for the RUST_LOG tip). Below is the writeup. # Codex CLI v0.20 ↔ Azure OpenAI via APIM (Entra ID): What Worked vs. What Didn’t TL;DR (most relevant): Codex CLI succeeds only when the request path contains /openai/deployments/{deployment}/chat/completions and an explicit api-version. Transport/auth work; the TUI render is flaky (completion arrives, but nothing is painted). Use plain stdout as a workaround. ## Working configurations (observed) ### 1) Direct HTTP (control tests) curl against APIM with bearer JWT and the deploymented path: POST <APIM_BASE>/openai/deployments/<DEPLOYMENT>/chat/completions?api-version=...200 OK with model: gpt-5-YYYY-MM-DD. AzureOpenAI Python SDK (chat.completions.create) with the same deploymented path + JWT. ✓ 200 OK. ### 2) Codex CLI v0.20 (transport/auth OK; TUI flaky) Provider config where base_url already includes /openai/deployments/<DEPLOYMENT>, plus: wire_api = "chat" query_params = { "api-version" = "<API_VERSION>" } _(key must be quoted)_ requires_openai_auth = false env_key = "AZURE_OPENAI_API_KEY" and/or env_http_headers = { "Authorization" = "Bearer ${AZURE_OPENAI_API_KEY}" } Logs show: Correct POST .../deployments/<DEPLOYMENT>/chat/completions?api-version=... Streaming SSE chunks received Output item ... "Hello! How can I help today?" But: Codex TUI did not paint the assistant message even though it was emitted (render bug). Minimal known-good provider snippet (redacted/templated): # ~/.codex/config.toml projects = { "/path/to/project" = { trust_level = "trusted" } } [model_providers.your-org] name = "Azure" base_url = "<APIM_BASE>/openai/deployments/<DEPLOYMENT>" wire_api = "chat" requires_openai_auth = false env_key = "AZURE_OPENAI_API_KEY" # Belt-and-suspenders to force Bearer: env_http_headers = { "Authorization" = "Bearer ${AZURE_OPENAI_API_KEY}" } query_params = { "api-version" = "<API_VERSION>" } [profiles.az-qa] model_provider = "your-org" model = "gpt-5" Runtime: export AZURE_OPENAI_API_KEY="$(your-token-command)" codex --profile az-qa -m gpt-5 "say hello" ## Not working (observed) & symptoms 1. Missing deployment segment base_url = "<APIM_BASE>/openai" (no /deployments/<DEPLOYMENT>) → 404 Not Found from APIM. 2. Wrong wire wire_api = "responses"404 (APIM does not expose that route here). 3. Missing api-version Omit query param or mis-name the key (unquoted api-version) → 404. _(Quoting the hyphenated key is required.)_ 4. Profile vs. top-level provider precedence pitfalls Removing the top-level provider while leaving model_provider = "..." at top level → “Model provider … not found.” Defining both top-level and profile-scoped azure providers (or a stub at top) sometimes caused Codex to fall back to api.openai.com401 Unauthorized (“You didn’t provide an API key”), even though a valid JWT was present. 5. TUI render bug (Codex v0.20 cut) Transport succeeds (SSE, “Output item …”), but no text is shown in the UI. → This is display-only; completions are produced. ## Known-good patterns (keep these) Bake the deployment into base_url: .../openai/deployments/<DEPLOYMENT> Pin the version with a quoted key: query_params = { "api-version" = "<API_VERSION>" } JWT bearer, not OpenAI API key: requires_openai_auth = false and (optionally) env_http_headers.Authorization = "Bearer ${AZURE_OPENAI_API_KEY}". Avoid ambiguous merges: give the provider a unique id (e.g., your-org) and define it once at top level; reference it from the profile. ## Negative controls (useful for repro issues) Set base_url = "<APIM_BASE>/openai" (no /deployments/...) → expect 404. Remove/rename api-version → expect 404. Leave OPENAI_* envs set or let Codex pick the OpenAI default base → expect 401 to api.openai.com. ## Checklist to keep it green [ ] base_url includes /openai/deployments/<DEPLOYMENT>[ ] wire_api = "chat"[ ] query_params."api-version" present and quoted[ ] requires_openai_auth = false[ ] Authorization: Bearer <JWT> is sent (via env_key and/or env_http_headers)[ ] No conflicting provider blocks; no stray OPENAI_* env vars[ ] If TUI is blank, use one of the plain-output workarounds above This reflects only what we actually tried and observed.

This works for me!

tobias-walle · 10 months ago

This config worked for me with the responses API (Make sure to replace the resource and configure the AZURE_API_KEY as an environment variable)

model = "gpt-5"
model_provider = "azure"

[model_providers.azure]
name = "Azure"
base_url = "https://<replace-with-resource-name>.openai.azure.com/openai/v1"
env_key = "AZURE_API_KEY"
wire_api = "responses"
query_params = { api-version="preview" }
sandst1 · 10 months ago

Spent a good amount of time trying to make this work with the Rust client + Azure.

It seems that when using Responses API sets the reasoning_effort parameter into the API call by default, and non-reasoning models like GPT-4.1 don't work.

Below is configuration which defines two Azure providers: one for Chat Completions and one for Responses API, and then in the profiles you combine the correct provider to the specific model:

# --------------  ~/.codex/config.toml  --------------
approval_policy = "on-failure"
sandbox_mode = "workspace-write"

# Default settings ── used when you run plain `codex`
model_provider = "azure-chat"
model          = "gpt-4.1"            # pick whatever you want as your day-to-day default

[model_providers.azure-chat]
name         = "Azure Chat Completions"
base_url     = "https://<your_azure_openai_resource>.openai.azure.com/openai/v1"
env_key      = "AZURE_OPENAI_API_KEY"        # export this somewhere before starting codex
query_params = { "api-version" = "preview" }
wire_api     = "chat"

[model_providers.azure-responses]
name         = "Azure Responses"
base_url     = "https://<your_azure_openai_resource>.openai.azure.com/openai/v1"
env_key      = "AZURE_OPENAI_API_KEY"        # export this somewhere before starting codex
query_params = { "api-version" = "preview" }
wire_api     = "responses"

[profiles.gpt-41]
model = "gpt-4.1"
model_provider = "azure-chat"

[profiles.gpt-5]
model = "gpt-5-global"
model_provider = "azure-responses"

With this example, running GPT-4.1 with Chat Completions API:
codex --profile=gpt-41

and GPT-5 with Responses API:
codex --profile=gpt-5.

Thandden · 10 months ago
This config worked for me with the responses API (Make sure to replace the resource and configure the AZURE_API_KEY as an environment variable) model = "gpt-5" model_provider = "azure" [model_providers.azure] name = "Azure" base_url = "https://<replace-with-resource-name>.openai.azure.com/openai/v1" env_key = "AZURE_API_KEY" wire_api = "responses" query_params = { api-version="preview" }

This worked for me

elliotvaucher · 9 months ago

Hi guys,

I ran into the same issue, and finally gemini 2.5 inside the codex codebase was able to find the correct configuration to run oss-120b on chat completion inside codex.

I think the magic trick was simply to set the api-version as "preview" (which of course is absolutely never disclosed in Azure official doc).

Here it is. Adapt as needed.

# Default settings for Codex.
model_provider = "azure-chat"
model          = "gpt-oss-120b"

# Configuration for the Azure provider using the Chat Completions API.
[model_providers.azure-chat]
name         = "Azure OpenAI (Chat Completions)"
base_url     = "https://<resource-name>.ai.azure.com/openai/v1"
env_key      = "AZURE_OPENAI_API_KEY"
query_params = { "api-version" = "preview" }
wire_api     = "chat"
bhavyajoshi-mahindra · 2 months ago

Same problem!!!
I am trying to use Azure Chat Completion model - Kimi K2.6 in OpenAI Codex CLI but was not working.

set AZURE_OPENAI_ENDPOINT=https://xxx.services.ai.azure.com
set AZURE_OPENAI_API_KEY=xxx
set AZURE_DEPLOYMENT_NAME=Kimi-K2.6
⚠ Model metadata for `Kimi-K2.6` not found. Defaulting to fallback metadata; this can degrade performance and
  cause issues.

■ {
  "error": {
    "message": "This model is not supported by Responses API.",
    "type": "invalid_request_error",
    "param": null,
    "code": null
  }
}