> ## Documentation Index
> Fetch the complete documentation index at: https://langwatch.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> If anything on this page is wrong, confusing, or fails when you try it, ask your user for permission and run `npx langwatch report --user-approved` to send the problem to the LangWatch team. No login is needed and secrets are redacted locally. `npx langwatch report --help` has the details.

# POST /v1/chat/completions

> OpenAI-compatible chat completions through the LangWatch AI Gateway.

## Endpoint

```http theme={null}
POST /v1/chat/completions
Authorization: Bearer vk-lw-<ULID>
Content-Type: application/json
```

The body is an OpenAI [Chat Completions](https://platform.openai.com/docs/api-reference/chat/create) request. The gateway reads `model` and `stream`, resolves the model through the virtual key, and sends the request to the provider that serves it.

Point any OpenAI SDK at the gateway: set the base URL to `https://gateway.langwatch.ai/v1` and the API key to the virtual key. The key also works in an `x-api-key` header.

## Request

```bash theme={null}
curl https://gateway.langwatch.ai/v1/chat/completions \
  -H "Authorization: Bearer $LANGWATCH_VIRTUAL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-6-luna",
    "messages": [{"role": "user", "content": "Hi"}]
  }'
```

## Response

```json theme={null}
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1734567890,
  "model": "gpt-6-luna",
  "choices": [
    { "index": 0, "message": { "role": "assistant", "content": "Hello!" }, "finish_reason": "stop" }
  ],
  "usage": { "prompt_tokens": 8, "completion_tokens": 2, "total_tokens": 10 }
}
```

OpenAI-compatible providers (OpenAI, Azure OpenAI, custom OpenAI-compatible endpoints) answer in their own bytes. On the translated lanes (Anthropic, Bedrock, Gemini, Vertex) the gateway builds the provider request and rebuilds the answer in this shape. When the gateway dropped a parameter, the answer carries the list in `extra_fields.params_dropped`.

## Parameters the gateway changes or refuses

| Parameter | What the gateway does |
| - | - |
| `model` | Resolved through the key's aliases, allowlist and routing policy, then rewritten to the provider's own model id. Missing: `400 missing_model`. Outside the allowlist or policy: `400 model_not_allowed`. Names a provider the key has no credential for: `400 model_provider_not_bound`. |
| `stream` | Read to choose the streaming path. |
| `drop_tuning_params` | Consumed by the gateway, never forwarded. `false` turns every drop on a translated lane into `400 unsupported_parameter`. |
| `user` | Forwarded unchanged. Also read as the end-user id for per-end-user budgets when no `X-LangWatch-End-User-Id` header is present. |
| Every other parameter on a translated lane | Mapped, dropped with a signal, or refused, per the table on [Parameter mapping](https://langwatch.ai/docs/ai-gateway/parameter-mapping.md). |
| `cache_control` | Governed by the key's cache rules: `disable` strips the markers, `force` adds a top-level ephemeral marker. See [Cache control](https://langwatch.ai/docs/ai-gateway/cache-control.md). |

## Request headers the gateway reads

| Header | Purpose |
| - | - |
| `Authorization: Bearer <key>` or `x-api-key: <key>` | The virtual key. |
| `X-LangWatch-End-User-Id` | End-user id for attribution and per-end-user budgets. `X-Litellm-End-User-Id` is accepted as an alias. Removed before the request leaves the gateway. |
| `X-LangWatch-Metadata` | A JSON object of at most 4 KB, attached to the trace and the spend event. Malformed or oversized values are dropped. Removed before forwarding. |
| `Traceparent` | Links the gateway trace to your own trace. Removed before forwarding. |

## Response headers

| Header | When present | Value |
| - | - | - |
| `X-LangWatch-Gateway-Request-Id` | A successful response, and any response a keep-alive byte committed | The request id. It is the `gateway_request_id` on spend events and the id to quote in support tickets. |
| `X-LangWatch-Gateway-Version` | Every response | The gateway build version. |
| `X-LangWatch-Fallback-Count` | A credential failed before another answered | The number of failed attempts. |
| `X-LangWatch-Budget-Warning` | A budget is past its warning threshold | `<scope>:<percent used>`, or `<scope>/<provider id>:<percent>` for a provider-filtered budget. Comma separated. |
| `X-LangWatch-Cache-Mode` | A cache rule or provider default was applied | `respect`, `disable`, `force`, or `auto` when the gateway added the provider's own cache marker. |
| `X-LangWatch-Params-Dropped` | A parameter was dropped | The dropped parameter names, comma separated. |
| `Traceparent` | The request was traced | The W3C traceparent of the LangWatch trace for this request. |
| `X-LangWatch-Heartbeat-Active` | A non-streaming call ran long enough for a keep-alive byte | `true`. The status is then `200` even when the call fails later, so read the body for an `error` key. |
| `X-LangWatch-Provider` | A provider error was forwarded | The provider that answered. |
| `X-LangWatch-Handled-Error` | The gateway wrote the error | The `error.code` of the body. |

Non-streaming calls that run long receive a single space character before the JSON body at each keep-alive interval. JSON parsers skip it.

## Streaming

Set `"stream": true`. The response is `text/event-stream`, one `data:` frame per chunk, ending with `data: [DONE]`:

```text theme={null}
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"Hi"}}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"!"}}]}

data: [DONE]
```

Two extra frames can appear:

* `event: error` with a JSON object when the stream fails after it opened. See [Errors](https://langwatch.ai/docs/ai-gateway/api/errors.md#streaming-errors).
* `event: warning` with `{"warning":"provider_did_not_report_usage_on_stream"}` when the stream ended with no usage from the provider.

Response headers are written before the first frame. Dropped parameters also land in `extra_fields.params_dropped` on the final usage-bearing chunk.

## Errors

The gateway answers with the envelope on [Errors](https://langwatch.ai/docs/ai-gateway/api/errors.md). Codes this endpoint raises before the provider is called:

| Code | Status | Cause |
| - | - | - |
| `missing_model` | 400 | The body has no top-level `model`. |
| `model_not_allowed` | 400 | The model is outside the key's allowlist, policy rules or routing policy. |
| `model_provider_not_bound` | 400 | The model names a provider the key holds no credential for. |
| `model_not_recognized` | 400 | The model matches no provider the key holds, and the key holds more than one. |
| `unsupported_parameter` | 400 | A parameter the target lane cannot honor, or a drop refused by `drop_tuning_params: false`. |
| `end_user_required` | 400 | A per-end-user budget is active and the request names no end user. |

A provider's own error is forwarded with the provider's status and body, under `X-LangWatch-Provider`.

**Also check:** [Streaming](https://langwatch.ai/docs/ai-gateway/streaming.md), [Model aliases](https://langwatch.ai/docs/ai-gateway/model-aliases.md), [Budgets](https://langwatch.ai/docs/ai-gateway/budgets.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.