# DS2API /v1/chat/completions vs /v1/responses: Key Differences Explained

> Understand the key differences between DS2API /v1/chat/completions and /v1/responses. Learn how /v1/responses offers structured payloads and retrieval API for advanced chat applications.

- Repository: [CJACK./ds2api](https://github.com/CJackHwang/ds2api)
- Tags: deep-dive
- Published: 2026-04-26

---

**The `/v1/chat/completions` endpoint returns a standard OpenAI-compatible chat completion with no server-side persistence, while `/v1/responses` provides a structured payload separating reasoning, output text, and function calls, complete with a built-in retrieval API.**

DS2API is an OpenAI-compatible gateway that proxies requests to DeepSeek models, exposing two distinct completion interfaces. Understanding the **difference between `/v1/chat/completions` and `/v1/responses` in DS2API** helps you choose between standard chat interoperability and advanced response decomposition with stateful retrieval.

## Request Normalization

Both endpoints share authentication and session creation logic, but they use different normalization functions to prepare the upstream request.

**Chat Completions** – Uses `NormalizeOpenAIChatRequest` in [`internal/promptcompat/standard_request.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/promptcompat/standard_request.go) to parse the classic `messages` array into a `StandardRequest`.

**Responses** – Uses `NormalizeOpenAIResponsesRequest` in [`internal/promptcompat/request_normalize.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/promptcompat/request_normalize.go) to handle the responses-specific contract, including special processing for `output_text` and function call definitions.

```go
// From handler_chat.go
stdReq, err := promptcompat.NormalizeOpenAIChatRequest(h.Store, req, requestTraceID(r))

// From responses_handler.go
stdReq, err := promptcompat.NormalizeOpenAIResponsesRequest(h.Store, req, traceID)

```

## Handler Logic and Response Construction

After normalization, both handlers call `h.DS.CallCompletion` to reach the DeepSeek model, but they diverge in how they construct the final JSON payload.

### Chat Handler

Located in [`internal/httpapi/openai/chat/handler_chat.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/httpapi/openai/chat/handler_chat.go), this handler invokes `openaifmt.BuildChatCompletion` to generate a `chat.completion` object:

```go
respBody := openaifmt.BuildChatCompletion(completionID, model, finalPrompt,
             finalThinking, finalText, toolNames)

```

The output conforms to the standard OpenAI schema with a `choices` array containing `message` objects.

### Responses Handler

Located in [`internal/httpapi/openai/responses/responses_handler.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/httpapi/openai/responses/responses_handler.go), this handler uses `openaifmt.BuildResponseObject` (defined in [`internal/format/openai/render_responses.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/format/openai/render_responses.go)) to create a richer structure:

```go
responseObj := openaifmt.BuildResponseObject(responseID, model,
             finalPrompt, sanitizedThinking, sanitizedText, toolNames)

```

This produces a `type: "response"` envelope containing an `output` array that separates reasoning blocks, output text, and function-call items, plus a top-level `output_text` field.

## Persistence and Retrieval API

A critical architectural difference is persistence:

- **`/v1/chat/completions`**: Returns the payload directly to the client with **no server-side storage**.
- **`/v1/responses`**: Persists the final response in an in-memory store ([`internal/httpapi/openai/responses/response_store.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/httpapi/openai/responses/response_store.go)) via `h.getResponseStore()`.

The Responses endpoint exposes a retrieval interface:

```bash
GET /v1/responses/{response_id}

```

This allows clients to fetch previously generated responses by ID, a feature absent from the chat completions endpoint.

## Streaming Behavior

Both endpoints support Server-Sent Events (SSE), but the event schemas differ significantly.

**Chat completions** emit standard `delta` messages representing incremental content for the assistant's message.

**Responses** emit a richer event stream defined in [`internal/format/openai/render_stream_events.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/format/openai/render_stream_events.go), including:

- `response.created`
- `response.output_item_added`
- `response.output_item_done`

These events allow granular tracking of reasoning steps versus final output versus tool invocations.

## Practical Code Examples

### Calling the Chat Completions Endpoint

```bash
curl -X POST https://api.example.com/v1/chat/completions \
  -H "Authorization: Bearer $DS2API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "model": "gpt-4o-mini",
        "messages": [{"role":"user","content":"Explain quantum tunneling"}],
        "stream": false
      }'

```

**Response:**

```json
{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1714139205,
  "model": "gpt-4o-mini",
  "choices": [{
    "index": 0,
    "message": {"role":"assistant","content":"Quantum tunneling is ..."},
    "finish_reason":"stop"
  }],
  "usage": {"prompt_tokens":12,"completion_tokens":45,"total_tokens":57}
}

```

### Calling the Responses Endpoint

```bash
curl -X POST https://api.example.com/v1/responses \
  -H "Authorization: Bearer $DS2API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "model": "gpt-4o-mini",
        "messages": [{"role":"user","content":"Give me a summary and a function call"}],
        "stream": false
      }'

```

**Response:**

```json
{
  "id": "resp_4c8e1b2f-7a6d-4f2e-9c3b-b1a5d9c6e5f7",
  "type": "response",
  "object": "response",
  "created_at": 1714139210,
  "status": "completed",
  "model": "gpt-4o-mini",
  "output": [
    {
      "type": "function_call",
      "id": "fc_1d2c3e4f-5a6b-7c8d-9e0f-1234567890ab",
      "call_id": "call_9f8e7d6c-5b4a-3c2d-1e0f-9876543210fe",
      "name": "summarize",
      "arguments": {"text":"Quantum tunneling ..."},
      "status":"completed"
    },
    {
      "type": "message",
      "id": "msg_a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "role": "assistant",
      "content": [
        {"type":"reasoning","text":"I reasoned about the physics first."},
        {"type":"output_text","text":"Quantum tunneling is ..."}
      ]
    }
  ],
  "output_text": "Quantum tunneling is ...",
  "usage": {"prompt_tokens":15,"completion_tokens":60,"total_tokens":75}
}

```

### Retrieving a Stored Response

```bash
curl -X GET https://api.example.com/v1/responses/resp_4c8e1b2f-7a6d-4f2e-9c3b-b1a5d9c6e5f7 \
  -H "Authorization: Bearer $DS2API_KEY"

```

## When to Use Each Endpoint

- **Standard OpenAI SDK compatibility**: Use **`/v1/chat/completions`** for drop-in replacement with existing clients expecting `choices[0].message`.
- **Structured reasoning separation**: Use **`/v1/responses`** when downstream tools need to distinguish between reasoning chains, final text, and tool calls via distinct `output` items.
- **Stateful retrieval**: Use **`/v1/responses`** when you need to fetch generated content later using `GET /v1/responses/{response_id}`.
- **Granular streaming**: Use **`/v1/responses`** for SSE events that track individual output items (`response.output_item_added`) rather than simple text deltas.

## Summary

- **`/v1/chat/completions`** in [`internal/httpapi/openai/chat/handler_chat.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/httpapi/openai/chat/handler_chat.go) provides standard OpenAI compatibility with `choices[0].message` output and no persistence.
- **`/v1/responses`** in [`internal/httpapi/openai/responses/responses_handler.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/httpapi/openai/responses/responses_handler.go) offers decomposed output via `BuildResponseObject`, separating reasoning and text into distinct items.
- Only the Responses endpoint utilizes `responseStore` for in-memory persistence and supports `GET /v1/responses/{response_id}` retrieval.
- Both share the same DeepSeek upstream call (`h.DS.CallCompletion`) and authentication, but diverge in normalization (`NormalizeOpenAIChatRequest` vs `NormalizeOpenAIResponsesRequest`) and streaming event formats.

## Frequently Asked Questions

### Can I retrieve a chat completion later using its ID?

No. According to the source code in [`handler_chat.go`](https://github.com/CJackHwang/ds2api/blob/main/handler_chat.go), the `/v1/chat/completions` endpoint does not persist responses to the `responseStore`. Only `/v1/responses` stores data for later retrieval via `GET /v1/responses/{response_id}`.

### Does the Responses endpoint support standard OpenAI client libraries?

While DS2API maintains OpenAI-compatible request structures, the `/v1/responses` output schema differs from the standard `chat.completion` format. Standard SDKs expecting `choices[0].message` will require custom parsing to handle the `output` array and `output_text` fields generated by `BuildResponseObject`.

### How does streaming differ between the two endpoints?

The chat completions endpoint emits SSE `delta` messages for incremental content, while the responses endpoint emits structured events like `response.created` and `response.output_item_done` as defined in [`internal/format/openai/render_stream_events.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/format/openai/render_stream_events.go), allowing real-time tracking of reasoning versus final output.

### Which endpoint should I use for function calling?

Both endpoints support function calling, but **`/v1/responses`** is architecturally superior for complex agent workflows because it explicitly separates function calls into typed `output` items (e.g., `type: function_call`) distinct from reasoning and text content, making downstream parsing more reliable.