# OpenAI の base_url 変更：優先順位とパスの書き方

> 送信先は引数、OPENAI_BASE_URL、既定値の順で決まります。base は /v1 や /openai/v1 までで、/chat/completions は書きません。

- URL: https://blog.laozhang.ai/ja/posts/openai-base-url-override
- Published: 2026-09-26
- Updated: 2026-09-26
- Author: LaoZhang AI Team (https://blog.laozhang.ai/ja/about)
- Category: APIガイド
- Tags: OpenAI API, base_url, OPENAI_BASE_URL, OpenAI 互換 API, Azure OpenAI, Cursor

---
OpenAI の公式 SDK で送信先を変えるには、クライアントを作るときの `base_url`（Node では `baseURL`）か、環境変数 `OPENAI_BASE_URL` を使います。両方あれば引数が勝ち、どちらもなければ `https://api.openai.com/v1` に送られます。SDK はこの base の後ろに `/chat/completions` や `/responses` をつなげるだけなので、base には接続先が求めるパスの前半（`/v1`、`/openai/v1`、`/v1beta/openai` など）まで書き、エンドポイント名そのものは書きません。

以下の SDK の挙動は、2026年9月26日時点の最新版である openai-python 3.19.2 と openai（npm）7.23.0 のソースコード、およびローカルのエコーサーバーで実際のリクエストパスを記録した結果に基づきます。base_url が正しくても、接続先が呼び出し中の API を実装していなければ 404 になるため、Chat Completions と Responses のどちらを呼んでいるかも合わせて確認します。

## 優先順位：引数、環境変数、data residency

Python と Node で共通の基本は「引数 → `OPENAI_BASE_URL` → 既定値」です。ただし空文字の扱いなど、細部で両者は違います。

| 設定の状態 | Python（openai 3.19.2） | Node（openai 7.23.0） |
| --- | --- | --- |
| 引数だけ指定 | 引数の URL | 引数の URL |
| `OPENAI_BASE_URL` だけ指定 | 環境変数の URL | 環境変数の URL |
| 引数と環境変数の両方 | 引数が優先 | 引数が優先 |
| どちらもなし | `https://api.openai.com/v1` | `https://api.openai.com/v1` |
| `OPENAI_BASE_URL=""`（空文字） | base が空のまま残り、送信時に `APIConnectionError` | 未設定と同じ扱いで `https://api.openai.com/v1` に送る |
| 旧名の `OPENAI_API_BASE` だけ指定 | 無視され、`https://api.openai.com/v1` | 無視され、`https://api.openai.com/v1` |
| `data_residency="eu"` と環境変数 | `https://eu.api.openai.com/v1`（環境変数より優先） | `dataResidency: 'eu'` で同じ結果 |
| `baseURL: null` と環境変数 | 該当なし | 環境変数を読まず既定値 |

いちばん気づきにくいのは、Node で `OPENAI_BASE_URL` が空文字になっているケースです。エラーにならずに `api.openai.com` へ送られるため、ゲートウェイや他社のキーがそのまま OpenAI に届きます。OpenAI はそのキーを受け付けないので、キーの不備に見える 401 になりがちです。401 を見てキーを疑う前に、送信先を確かめる価値があります。

`OPENAI_API_BASE` と `openai.api_base` は v1 より前の openai-python で使われていた名前で、現行の SDK は読みません。古いサンプルからコピーした設定で送信先が変わらないときは、ここを最初に疑います。フレームワークやツールが自前で環境変数を読んでいる場合は、SDK と同じ名前・同じ優先順位とは限らないので、そのツールのドキュメントを確認してください。Codex CLI は独自の設定（`openai_base_url` と `model_providers`）を使うので、[Codex カスタムプロバイダー設定：APIキーとBase URLの選び方](https://blog.laozhang.ai/ja/posts/codex-config-toml)を参照してください。

OpenAI のリージョン別エンドポイントは、URL を直接書くより `data_residency`（Node は `dataResidency`）で指定します。SDK に登録されている値は `us`、`eu`、`ae` で、それぞれ `https://us.api.openai.com/v1` のような URL に展開されます。`base_url` と同時に渡すとクライアントの生成時点でエラーになります。自分のアカウントでそのリージョンを使えるかどうかは、SDK の一覧からは分かりません。

呼び出しごとに送信先を変えたい場合は、`client.with_options(base_url=...)`（Node は `client.withOptions({ baseURL })`）が使えます。変わるのはその呼び出しだけで、クライアント本体の base はそのまま残ります。

引数で指定する最小の例として、Gemini API の OpenAI 互換エンドポイントに向けるコードを示します。

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
    api_key=os.environ["GEMINI_API_KEY"],
)
print(client.base_url)  # 実際に使われる base を表示
```

```js
import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai/',
  apiKey: process.env.GEMINI_API_KEY,
});
console.log(client.baseURL);
```

## base_url にはどこまで書くか

SDK は base の後ろに `chat/completions` や `responses` といった相対パスをつなげて、1本の URL にします（Python は base の末尾にスラッシュを補います）。したがって、接続先のドキュメントに載っている完全な URL から、末尾のエンドポイント名（`/chat/completions`、`/responses`、`/embeddings` など）を取り除いた残りが、そのまま base_url になります。

ローカルのエコーサーバーに `chat.completions.create()` を送ったとき、サーバーが受け取ったパスは Python と Node で同じでした。

| base_url に書いた値 | サーバーが受け取ったパス | 判定 |
| --- | --- | --- |
| `http://[::1]:8765` | `/chat/completions` | `/v1` 配下で待ち受けるサーバーでは 404 |
| `http://[::1]:8765/v1` | `/v1/chat/completions` | 正しい |
| `http://[::1]:8765/v1/` | `/v1/chat/completions` | 正しい。スラッシュは二重にならない |
| `http://[::1]:8765/v1/chat/completions` | `/v1/chat/completions/chat/completions` | エンドポイント名が二重になり 404 |

末尾のスラッシュの有無は結果に影響しません。影響するのは、途中のパスを書き漏らすか、エンドポイント名まで書いてしまうかの2点です。

![base_url に書いた値へ SDK が chat/completions をつなげた結果の比較。/v1 までなら正しいパス、/v1 が抜けると /chat/completions、エンドポイント名まで書くと二重になって 404](https://blog.laozhang.ai/posts/ja/openai-base-url-override/img/base-url-path-join.webp)

接続先ごとの書き方は次のとおりです。キーと `model` に渡す値も接続先に合わせて変わるので、base_url だけを差し替えて終わりにはなりません。

| 接続先 | base_url | API キー | `model` に渡す値 | 注意点 |
| --- | --- | --- | --- | --- |
| OpenAI（既定） | `https://api.openai.com/v1` | OpenAI の API キー | OpenAI のモデル ID | 何も指定しなければこれ |
| OpenAI のリージョン別 | `data_residency` で `us`・`eu`・`ae` を指定 | OpenAI の API キー | OpenAI のモデル ID | `base_url` と併用不可 |
| Azure OpenAI（v1 API） | `https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/` | Azure OpenAI の API キー、または Entra ID のトークンプロバイダー | デプロイ名 | `OpenAI()` を使い、`api-version` は不要 |
| Gemini API の OpenAI 互換 | `https://generativelanguage.googleapis.com/v1beta/openai/` | Gemini API キー | Gemini のモデル ID | ライブラリ対応はベータ版。Responses API の記載なし |
| OpenAI 互換ゲートウェイ（例：LaoZhang API） | `https://api.laozhang.ai/v1` | ゲートウェイの API キー | ゲートウェイのモデル一覧にある ID | Responses は GPT-6 系のみ明記 |
| ローカル・自前のサーバー | 互換エンドポイントの URL から末尾のエンドポイント名を除いたもの | サーバーの設定による | サーバーが読み込んでいるモデル名 | Cursor からは直接届かない（後述） |

Azure OpenAI は、Microsoft Learn の v1 API の説明（2026年5月13日更新）で、エンドポイントに `/openai/v1` を付けて `base_url` に渡すよう案内されています。`*.services.ai.azure.com/openai/v1/` の形も使え、`OPENAI_BASE_URL` と `OPENAI_API_KEY` を設定すれば引数なしの `OpenAI()` でも動きます。SDK には従来の `AzureOpenAI(azure_endpoint=..., api_version=...)` も残っていますが、こちらは別の方式なので、v1 の base_url と混ぜないようにします。v1 の GA 版が対応するのは機能の一部だと同じページに書かれています。Azure で 429 が出る場合は [Azure OpenAI の TPM 制限を切り分ける：使用量が少なくても出る 429 の対処法](https://blog.laozhang.ai/ja/posts/azure-openai-tpm-rate-limit)が参考になります。

Gemini API の互換ドキュメント（2026年9月2日更新）は、コードを3行更新すれば OpenAI のライブラリから使えると説明しています。その3行は API キー、base_url、モデル名のことで、動く範囲はドキュメントに載っている機能（Chat Completions、ストリーミング、関数呼び出し、構造化出力、埋め込み、画像、バッチなど）に限られます。

LaoZhang API のドキュメントでは、OpenAI SDK 用の base_url は `https://api.laozhang.ai/v1` で、Google Gen AI SDK と Anthropic SDK はホスト直下の `https://api.laozhang.ai` を使うとされています。同じゲートウェイでも SDK によって base の書き方が変わる、という点でも分かりやすい例です。2026年9月のお知らせで Chat Completions と Responses の両方から呼べると明記されているのは GPT-6 Astra、Sol、Luna の3モデルで、それ以外のモデルで Responses を使えるかは各モデルの説明を確認してください。

## Chat Completions と Responses：互換の範囲を確かめる

`client.responses.create()` は `POST {base}/responses` に送られます。base_url が正しくても、接続先がこのパスを実装していなければ 404 です。「OpenAI 互換」と書かれていても、どの API までが互換なのかはサービスごとに違います。

2025年11月には、OpenAI のコミュニティフォーラムで、Gemini の互換 base_url に対して `responses.create()` を呼んだら 404 になったという報告がありました。そこに付いた利用者の回答は「他社は一般に Chat Completions の互換だけを提供している」というものです。Gemini のドキュメントにも Responses API の記載はありません。記載がないことは「絶対に動かない」という意味ではありませんが、前提にはできません。

一方、Azure OpenAI の v1 API のサンプルは `responses.create()` を使っています。判断の手順は単純で、接続先のドキュメントに `/responses` が載っていれば Responses を、載っていなければ `chat.completions.create()` を使います。Responses を前提にしたエージェントのコードを互換サービスに移すなら、先にその対応を確かめてください。Assistants API のコードを移している途中なら、[Assistants APIからResponses APIへ移行する実装手順【停止日・状態・ロールバック】](https://blog.laozhang.ai/ja/posts/openai-assistants-api-to-responses-api)も合わせて確認できます。

## プロキシと base_url は別の設定

base_url は「どのサービスの API を呼ぶか」という API の起点です。HTTP プロキシは「そのリクエストがどの経路を通るか」という通信の経路です。日本語ではどちらも「プロキシ」と呼ばれがちなので、手元の設定がどちらなのかを先に決めます。

- 相手から専用の URL と API キーを渡され、その URL に API として送る場合は、ゲートウェイです。base_url に書きます。
- `http://proxy.example.com:8080` のようなアドレスを経由して api.openai.com などに届く場合は、HTTP プロキシです。HTTP クライアントの設定に書きます。

HTTP プロキシのアドレスを base_url に入れると、SDK はそこへ `/chat/completions` を API リクエストとして直接送るため、意図した経路にはなりません。

![base_url は SDK からゲートウェイや API サービスへ直接向かう API の起点、HTTP プロキシは SDK から接続先の API へ向かう途中の経路であることを示す図](https://blog.laozhang.ai/posts/ja/openai-base-url-override/img/base-url-vs-proxy.webp)

Python（3.19.2）では、README にあるとおり `http_client` にプロキシを渡します。古いバージョンではクラス名が `DefaultHttpxClient` なので、インストール済みのバージョンの README を確認してください。

```python
from openai import OpenAI, DefaultHttpx2Client

client = OpenAI(
    base_url="https://api.openai.com/v1",  # API の起点
    http_client=DefaultHttpx2Client(proxy="http://proxy.example.com:8080"),  # 経路
)
```

Node では undici の `fetch` と `ProxyAgent` を `fetchOptions` の `dispatcher` に渡します。

```js
import OpenAI from 'openai';
import { fetch, ProxyAgent } from 'undici';

const client = new OpenAI({
  fetch,
  fetchOptions: { dispatcher: new ProxyAgent('http://proxy.example.com:8080') },
});
```

応答がまったく返らずに `APIConnectionError` になる場合は、リクエストが接続先に届いていないので、[OpenAI API 接続エラー: APIConnectionError はまず経路から切り分ける](https://blog.laozhang.ai/ja/posts/openai-api-error-connection-error)の手順が使えます。

## 実際の送信先を確かめる

設定を変えたら、リクエストが本当にどこへ向かっているかを確認します。軽い順に次の4つです。

1. クライアントを作った直後に `print(client.base_url)`（Node は `console.log(client.baseURL)`）で、最終的な base を表示する。
2. 環境変数 `OPENAI_LOG=debug` で SDK のログを出す。Node ではクライアントの `logLevel: 'debug'` でも指定でき、環境変数より優先されます。ログの形式はバージョンによって変わります。
3. ローカルのエコーサーバーに向けて、SDK が組み立てたパスをそのまま見る。
4. 接続先のダッシュボードやログに、そのリクエストが記録されているかを見る。記録がなければ、リクエストはそこに届いていません。

3 は、本物の接続先を呼ばずにパスの組み立てだけを確かめる方法です。Python の標準ライブラリだけで動きます。

```python
# echo_server.py
import json
import socket
from http.server import BaseHTTPRequestHandler, HTTPServer

class Echo(BaseHTTPRequestHandler):
    def do_POST(self):
        body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
        model = json.loads(body or b"{}").get("model")
        print(f"{self.command} {self.path} model={model}", flush=True)
        reply = {
            "id": "chatcmpl-echo", "object": "chat.completion", "created": 0,
            "model": model or "echo",
            "choices": [{"index": 0, "finish_reason": "stop",
                         "message": {"role": "assistant", "content": self.path}}],
        }
        data = json.dumps(reply).encode()
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(data)))
        self.end_headers()
        self.wfile.write(data)

    def log_message(self, *args):
        pass

class V6Server(HTTPServer):
    address_family = socket.AF_INET6

V6Server(("::1", 8765), Echo).serve_forever()
```

```python
# check.py
from openai import OpenAI

client = OpenAI(api_key="sk-dummy", max_retries=0)
print("base_url:", client.base_url)
r = client.chat.completions.create(
    model="test-model",
    messages=[{"role": "user", "content": "ping"}],
)
print("server saw:", r.choices[0].message.content)
```

別のターミナルで `python echo_server.py` を起動し（IPv6 のループバックアドレス `::1` で待ち受けます）、`OPENAI_BASE_URL=http://[::1]:8765/v1 python check.py` を実行すると、`server saw: /v1/chat/completions` と表示されます。`OPENAI_BASE_URL` に `/chat/completions` まで含めると `/v1/chat/completions/chat/completions` が表示されます。本番のコードと同じ方法で base_url を渡してから試すと、フレームワーク経由の設定が効いているかも分かります。

キーはダミーで構いません。base_url の向け先には、Authorization ヘッダーで API キーがそのまま届きます。見知らぬサーバーやテスト用のサーバーに本物のキーを送らないようにしてください。

## 症状から直す場所を決める

エラーの種類と、実際に送られたパスを組み合わせると、直すべき層が決まります。

| 症状 | 送信先・パスの状態 | 直す場所 |
| --- | --- | --- |
| 404、パスが `/v1/chat/completions/chat/completions` | base にエンドポイント名まで入っている | base_url から `/chat/completions` を削る |
| 404、パスが `/chat/completions`（`/v1` などがない） | base に途中のパスが足りない | `/v1`、`/openai/v1`、`/v1beta/openai` などを足す |
| `/responses` だけが 404 | 接続先が Responses API を実装していない | `chat.completions.create()` に切り替えるか、対応している接続先を使う |
| 他社のキーなのに OpenAI の `Incorrect API key provided` | リクエストが `api.openai.com` に届いている | 旧名の環境変数、Node の空文字、環境変数が実行プロセスに渡っていない、などを確認 |
| model not found、存在しないモデル | 接続先には届いている | `model` を接続先の ID に直す。Azure はデプロイ名 |
| `APIConnectionError`、応答なし | 接続先に届いていない | Python の空文字の `OPENAI_BASE_URL`、ホストとポート、プロキシ、ファイアウォール |
| 429 | 接続先には届いている | base_url ではなく、接続先のレート制限とクォータ |

401 が返っていて送信先が正しい場合は、キー自体の問題です。OpenAI に直接つなぐ場合にどのキーや ID が必要なのかは、[OpenAI API Key と Organization ID: 2026年に本当に必要なもの](https://blog.laozhang.ai/ja/posts/openai-api-key-organization-id)にまとまっています。

## Cursor の "Override OpenAI Base URL"

Cursor では Settings > Models の API キー欄に "Override OpenAI Base URL" という項目があり、OpenAI 互換の別サービスにチャットを向けられます。Cursor の公式ヘルプページは自分の API キーの登録方法を説明していますが、この項目の挙動は載っていません。以下は2026年8〜9月の Cursor スタッフによるフォーラム回答に基づくもので、一部は修正時期が未定の既知の問題です。

設定は次の順で行います（2026年8月25日・9月10日のスタッフ回答）。

1. OpenAI API Key の欄に接続先のキーを貼り、"Use OpenAI API key" のトグルをオンにする。キーを貼ることとトグルを入れることは別の操作で、トグルがオフのあいだは base URL も使われません。
2. Override OpenAI Base URL をオンにする。オンにした直後は `https://api.openai.com/v1` が入っているので、接続先の base（例：Fireworks なら `https://api.fireworks.ai/inference/v1`）に書き換える。
3. Enter を押すか欄の外をクリックして保存し、Settings を閉じて開き直し、値が残っていることを確かめる。トグルをオフにしてからオンに戻すと、既定値にリセットされます。
4. "+ Add Custom Model" で接続先の実際のモデル ID を追加し、新しいチャットで送る。

うまくいかないときは、症状から次のように切り分けます。

| 症状 | スタッフ回答での原因 | 対処 |
| --- | --- | --- |
| 他社のキーが OpenAI に `Incorrect API key provided` で拒否される | base URL が既定値の `https://api.openai.com/v1` のまま（2026年9月10日） | 手順2〜3をやり直す |
| Unauthorized User API key が出るが、接続先のログに記録がない | モデル ID が Cursor の管理するモデル名（例：kimi-k3、kimi-k2.6）と重なり、base URL を使わずに Cursor 側で処理された（2026年8月19日・9月10日） | 名前が重ならない、接続先の実際のモデル ID を使う |
| BAD_MODEL_NAME、AI Model Not Found | "Use OpenAI API key" がオフで、キーが付かずに送られた（2026年8月25日） | トグルをオンにして新しいチャットを開く |
| Composer や Grok で "This model does not support custom API keys" | この設定は Claude と Gemini 以外のすべてのモデルに効き、Cursor 上で動くモデルは自分のキーを受け付けない（2026年9月23日） | Cursor のモデルを使うあいだは OpenAI のキーをオフにする |
| 手元の PC のローカルアドレスや社内ネットワークのアドレスに届かない | リクエストは Cursor のサーバーでプロンプトを組み立ててから送られるため、公開された HTTPS が必要（2026年2月22日・4月2日・5月22日） | ngrok や Cloudflare Tunnel で公開 URL を作る |

オンにしているあいだは、内蔵のモデル一覧から選んだ OpenAI 系のモデルも含めて、Claude と Gemini 以外のリクエストがこの URL に向かいます。自前のモデルと Cursor のモデルを同時に使い分ける機能は検討中で、時期は示されていません（2026年8月21日・9月15日）。今のところは、使うモデルに合わせてトグルを切り替えるしかありません。

トンネルでローカルのモデルサーバーを公開すると、その URL を知っていれば誰でも届きます。公開する前に、サーバー側で認証を有効にしておくのが安全です。

公式ヘルプページには、ほかにも判断に関わる条件があります。自分のキーが使われるのはチャットモデルだけで、Tab 補完は Cursor のモデルのままです。自分のキーを使う場合、Cursor のゼロデータ保持ポリシーは適用されません。Team と Enterprise では、自分のキーで送ったリクエストにも Cursor Token 料金（入力・出力・キャッシュを含め100万トークンあたり$0.25）がかかります。また、自分のキーでは接続先のレート制限とコンテキスト上限がそのまま効くため、長い会話では制限を超えやすくなります。

なお、Cursor 3.15.6 では OpenAI API Key と Override OpenAI Base URL の入力欄がクリックで選べない不具合がありました。スタッフはトグルを入れてから Tab キーで欄に移動する回避策を案内し、修正は後続の 3.15 系の更新で配布されると告知しています。
