背景
DeepSeek Harness に外部モデルを追加するとき、API アドレスと API キーを入力したあと、「API プロトコル」というプルダウンで手が止まります。選択肢は OpenAI Chat Completions、OpenAI Responses、Anthropic Messages の 3 つで、それぞれに説明は付いていません。
本記事では、2026 年 10 月 20 日に 3 つのエンドポイントを実際に叩いて確認した結果と、401 / 404 / 400 の切り分け手順をまとめます。結論としては、迷ったら OpenAI Chat Completions を選ぶのが無難です。
スクリーンショットは 2026 年 10 月時点の UI です。キー入力欄は画面外にあります。
環境
| 項目 | 内容 |
|---|---|
| OS | Windows 10 / 11 |
| クライアント | DeepSeek Harness デスクトップ版(2026 年 10 月時点の UI) |
| 接続先 | OpenAI 互換 / Anthropic 互換の任意のサービス(本記事は Crazyrouter を例として使用) |
| ベース URL |
https://api.crazyrouter.com/v1(/v1 まで。以降のパスはクライアントが付与します) |
| 検証日 | 2026-10-20 |
| 使用モデル |
deepseek-v4-pro、claude-opus-4-5
|
| 認証 |
Authorization: Bearer(環境変数 CRAZYROUTER_API_KEY から読み込み) |
前提:3 つは難易度ではなく「方言」
このプルダウンは、モデルの性能差や料金差を選ぶものではありません。リクエストの書き方(方言)を選ぶものです。実際に変わるのは次の 4 点です。
- 文脈(コンテキスト)を誰が保持するか:Chat Completions と Messages は毎回すべて再送、Responses はサーバ側保存が可能
- システム指示の置き場所:
messagesの先頭、instructionsフィールド、トップレベルのsystemフィールド - ツール呼び出しの往復形式:
tool_calls、function_call、tool_use - 応答テキストの位置:
choices、output、content
もう 1 点、「OpenAI」という名前は通信形式の名前であり、モデルのブランド名ではありません。どのモデルを使うかは、別の欄に入力するモデル ID で決まります。
手順
1. 設定画面での入力
- 「設定 → モデル → モデルプロバイダーを追加」を開き、「カスタムモデル API」を選びます
- Provider ID:小文字の識別子を任意で設定します(例:
crazyrouter) - 表示名:メニュー上で識別するための名前です(例:
crazyrouter) - API アドレス:利用するサービスのベース URL を
/v1まで入力します - API プロトコル:迷ったら
OpenAI Chat Completionsを選びます - API キー:同じサービスのキーを貼り付けます
- モデルカタログ:「利用可能なモデルを取得」を押すか、モデル ID を手入力します
- 保存後、新しいセッションで追加したモデルを選びます
2. 3 ステップで疎通確認
$base = "https://api.crazyrouter.com/v1" # 例:Crazyrouter
$headers = @{ Authorization = "Bearer $env:CRAZYROUTER_API_KEY" }
# ステップ1:モデル一覧(アドレスとキーだけを確認)
Invoke-RestMethod -Uri "$base/models" -Headers $headers |
Select-Object -ExpandProperty data | Select-Object -First 5 -Property id
# ステップ2:3 つのエンドポイントを叩き、ステータスコードだけを見る
foreach ($p in @("/chat/completions", "/responses", "/messages")) {
$r = Invoke-WebRequest -Uri "$base$p" -Method Post -Headers $headers `
-ContentType "application/json" -Body "{}" -SkipHttpErrorCheck
Write-Output ("{0,-20} {1}" -f $p, $r.StatusCode)
}
# ステップ3:最小のリクエスト(ここで初めてフィールド名が問題になります)
$body = @{
model = "deepseek-v4-pro"
messages = @(@{ role = "user"; content = "OKとだけ返信してください" })
} | ConvertTo-Json -Depth 5
Invoke-RestMethod -Uri "$base/chat/completions" -Method Post `
-Headers $headers -ContentType "application/json" -Body $body |
Select-Object -ExpandProperty choices
ステップ 2 は API キーがなくても実行できます。$headers を外して空のボディで送るだけです。401 なら経路は存在し、404 なら経路自体が存在しません。
実測結果(2026-10-20)
1. 認証なしでも入口の有無は判定できる
| リクエスト | ステータスコード |
|---|---|
GET /v1/models |
401 |
POST /v1/chat/completions |
401 |
POST /v1/responses |
401 |
POST /v1/messages |
401 |
POST /v1/no-such-endpoint-probe(対照) |
404 |
401 の本文は 未提供令牌、404 の本文は Invalid endpoint path でした。このサービスはトークンより先に経路を判定しているため、キーを用意する前に入口の有無を確認できます。
2. 3 つのエンドポイントはすべて 200
| エンドポイント | モデル | ステータス | 応答のトップレベルフィールド |
|---|---|---|---|
/chat/completions |
deepseek-v4-pro | 200 | choices,id,model,object,usage |
/responses |
deepseek-v4-pro | 200 | output,output_text,status,object |
/messages |
claude-opus-4-5 | 200 | content,stop_reason,type,role,usage |
所要時間は 2〜4 秒でした。違いは最後の列で、テキストの位置がそれぞれ choices、output、content になります。
3. 404 には 2 種類ある
存在しないモデル ID を指定した場合も 404 ですが、本文は model_not_found でモデル名が示されます。パスの誤りであれば Invalid endpoint path です。ステータスコードだけで判断せず、本文を読む必要があります。
4. Messages は公式仕様より緩い
公式では max_tokens が必須ですが、このサービスでは省略しても 200 が返りました。また x-api-key と Authorization: Bearer の両方を受け付けます。一方、OpenAI 形式のエンドポイントは Bearer のみで、x-api-key では 401 でした。同じルールが別のサービスで通用するとは限りません。
5. ツール呼び出しは 3 形式すべて動作しますが、書き方が違います
| プロトコル | リクエスト側 | 応答側 |
|---|---|---|
| Chat Completions | tools[].function.parameters |
message.tool_calls[]、finish_reason=tool_calls
|
| Messages | tools[].input_schema |
content[0].type=tool_use、stop_reason=tool_use
|
| Responses | tools[].type=function |
output[] 内の function_call
|
6. Responses の組み込みツールは動きますが、会話の継続は効きませんでした
tools=[{"type":"web_search"}] を渡すと実際に検索が行われ、応答に web_search_call 項目が 5 つ現れました。instructions も反映されます。しかし 2 段階のテストで、2 回目に previous_response_id を付けても、1 回目の内容を踏まえた回答にはなりませんでした。フィールドが受理されることと、機能が実装されていることは別です。
排錯:401 / 404 / 400 の切り分け
-
401:キーが違うか、置き場所が違います。OpenAI 形式は
Authorization: Bearer、Messages はx-api-key(サービスによってはBearerも可) -
404:経路がない(
Invalid endpoint path)か、モデル ID が違う(model_not_found)。本文を必ず読みます -
400 / 422:経路はあります。フィールド不足が原因で、
modelがない場合はModel name is requiredが返ります -
200 でも反映されない:Anthropic 形式のトップレベル
systemを Chat Completions に送ると 200 が返りますが、指示は無視されます。「エラーが出ない=効いている」ではありません
確認の順序は「アドレス → キー → 経路 → フィールド」です。1 つずつ潰すと、どこで止まっているかが分かります。
切り分けの実例
実際に遭遇した順に並べます。あるとき Anthropic Messages を選び、キーを x-api-key に置いて送ったところ 401 が返りました。本文は「未提供令牌」で、キー自体は正しいものです。原因は打ち先で、そのリクエストは OpenAI 形式のエンドポイントに送られていました。Authorization: Bearer に戻すと 200 になります。同じキーでも、ヘッダーの置き場所が違うだけで通らなくなります。
次に、存在しないモデル ID を指定すると 404 が返りましたが、本文は model_not_found でした。同じ 404 でも、パスの誤りであれば Invalid endpoint path です。この 2 つを見分けずにプロトコルを切り替えると、原因が別の場所にあるまま設定だけが変わっていきます。
最後に、Anthropic 形式のトップレベル system を Chat Completions に送ってみました。返ってきたのは 200 で、エラーはありません。しかし応答内容を見る限り、その指示は反映されていませんでした。エラーが出ないことと、設定が効いていることは別です。
まとめ
- 迷ったら OpenAI Chat Completions。互換実装が最も多く、情報も探しやすいです
- 相手が
/v1/messagesしか提供しない、または Claude 系ツールを接続する場合は Anthropic Messages - Responses は、サーバ側の機能が本当に実装されていると確認できた場合だけ選びます
判断に使う質問は 3 つです。接続先はどのサービスか。そのドキュメントにはどのエンドポイントが書かれているか。今回必要なのは通常会話か、会話の継続や組み込みツールか。
これらの確認には Crazyrouter を使っています。1 つのキーで複数ベンダーのモデルを呼べ、OpenAI 互換と Anthropic 互換の入口が分かれているため、上記の 3 ステップを同じアカウントで試せます。
参考
- OpenAI API ドキュメント:Chat Completions と Responses のフィールド定義
-
Anthropic Messages API:リクエストヘッダー、
systemフィールド、ツールブロックの書き方 - DeepSeek Harness 公式サイト:クライアントのダウンロードとモデル設定
- Crazyrouter ドキュメント:OpenAI 互換と Anthropic 互換の 2 種類の接続方法


