# Protocols (/docs/api/protocols)

Any chat model can be called through any of the four chat protocols, so you can call Claude from the OpenAI SDK, or GPT from the Google Gen AI SDK, by changing only the model ID. Pick the protocol your SDK or tool already speaks:

| You use | Call | Page |
|---|---|---|
| OpenAI SDK, Vercel AI SDK, LangChain, opencode, Cursor | `POST /v1/chat/completions` | [Chat Completions](/docs/api/chat-completions) |
| Codex, OpenAI SDK `client.responses` | `POST /v1/responses` | [Responses](/docs/api/responses) |
| Anthropic SDK, Claude Code | `POST /v1/messages` | [Messages](/docs/api/messages) |
| Google Gen AI SDK | `POST /v1/models/{model}:generateContent` | [Gemini](/docs/api/gemini) |

The same model on two protocols:

```bash
curl https://api.ohmygpt.com/v1/chat/completions \
  -H "Authorization: Bearer $OHMYGPT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "anthropic/claude-haiku-4-5", "max_tokens": 256,
       "messages": [{"role": "user", "content": "Explain QUIC in two sentences."}]}'

curl https://api.ohmygpt.com/v1/messages \
  -H "x-api-key: $OHMYGPT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "anthropic/claude-haiku-4-5", "max_tokens": 256,
       "messages": [{"role": "user", "content": "Explain QUIC in two sentences."}]}'
```

The first answers with a `chat.completion` object, the second with an Anthropic `message`.

## Native protocol [#native-protocol]

Each model has a native protocol: Messages for Claude, Gemini for Gemini models, Chat Completions for GPT, DeepSeek, Qwen, and most others. `GET /v1/models` marks it with `"native": true`:

```bash
curl -s https://api.ohmygpt.com/v1/models \
  | jq -r '.data[] | select(.id == "anthropic/claude-haiku-4-5") | .protocols[] | [.path, (.native // false)] | @tsv'
```

```text
/v1/chat/completions	false
/v1/responses	false
/v1/messages	true
/v1/models/{id}:generateContent	false
```

On its native protocol, a model receives your request body as you sent it, so vendor-specific fields and beta features work as the vendor documents them. OhMyGPT sets only these:

| Field | What OhMyGPT does |
|---|---|
| `model` | Resolves your model ID to the vendor's |
| `stream` | Keeps your choice |
| `stream_options` | Chat Completions streams always end with a usage chunk |
| `provider` | Reads your [routing preferences](/docs/provider-routing) on Chat Completions and Responses |
| `safetySettings` | Gemini requests without one get the four adjustable categories set to `OFF`; see [Gemini](/docs/api/gemini#safety-settings) |
| `anthropic-version` | Set to `2023-06-01` on Messages if you send none; other `anthropic-*` headers, such as `anthropic-beta`, are passed on |

## Other protocols [#other-protocols]

On any other protocol, OhMyGPT converts your request to the model's native protocol and converts the answer back. Messages, system prompts, images, tools, tool choice, token limits, structured output, and [reasoning controls](/docs/api/thinking) all carry over, and so does every sampling and output parameter the model has an equivalent for. The response comes back in your protocol's shape, with the finish reason in its vocabulary.

A sampling or output parameter the model has no equivalent for, such as `seed` or `logprobs` on Claude, isn't sent, and the model answers with its own default for that setting. [Parameters](/docs/api/parameters) shows which parameters apply to each model family. The same goes for `enable_thinking` and `thinking_budget` on Chat Completions (use the [reasoning controls](/docs/api/thinking) instead), and `max_tool_calls`, `truncation`, and `top_logprobs` on Responses.

A few fields carry input that only the model's native protocol can deliver, such as stored context, server-side tools, or prompt templates. Send them there:

| Protocol | Fields to send on the native protocol |
|---|---|
| Chat Completions | `functions` and `function_call` (or use `tools` and `tool_choice` on any model) |
| Responses | `background`, `conversation`, `prompt` |
| Messages | `container`, `mcp_servers`, and server tools such as `web_search_20250305` |
| Gemini | `cachedContent` |

If one of these reaches a model on another protocol, the request returns 400 before it reaches the model, so nothing is charged and you never get an answer that silently skipped part of your input:

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "unsupported_parameter",
    "param": "background",
    "message": "The model `mock` does not support the `background` parameter."
  }
}
```

Fields set to `null`, `false`, `""`, `[]`, or `{}` count as unset, so SDK defaults never trigger this.

## Errors [#errors]

Errors come back in the format of the protocol you called, so your SDK's error types work unchanged.

<Tabs items={['OpenAI', 'Anthropic', 'Gemini']}>
<Tab value="OpenAI">
```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "unsupported_endpoint",
    "param": "model",
    "message": "The model `ohmygpt/mock` does not support this endpoint. `GET /v1/models` lists the endpoints each model supports."
  }
}
```
</Tab>
<Tab value="Anthropic">
```json
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "The `Authorization` and `x-api-key` headers contain different API keys. Send only one, or make them match."
  }
}
```
</Tab>
<Tab value="Gemini">
```json
{
  "error": {
    "code": 404,
    "status": "NOT_FOUND",
    "message": "The model `google/gemini-0-nonexistent` does not exist. List available models with `GET /v1/models`.",
    "details": [
      { "@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "MODEL_NOT_FOUND", "domain": "ohmygpt.com" }
    ]
  }
}
```
</Tab>
</Tabs>

On OpenAI-format errors, `code` is a stable value to branch on. [Errors](/docs/errors) lists the codes and what to do for each status.
