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

# Migrate from Chat Completions to Responses API

> Step-by-step guide to migrate your Mavera integration from the Chat Completions API to the Responses API format

Mavera has migrated from the OpenAI **Chat Completions** format to the OpenAI **Responses API** format. This guide walks you through every change — endpoints, SDK methods, request/response shapes, streaming, tool calling, and structured outputs.

<Info>
  The Responses API is **not** a breaking version bump — it's a new endpoint format. Your existing Chat Completions code will continue to work during the transition period, but all new features and documentation target the Responses API.
</Info>

***

## What Changed

| Concept                | Chat Completions (old)                              | Responses API (new)                                                                   |
| ---------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------- |
| **Endpoint**           | `POST /api/v1/chat/completions`                     | `POST /api/v1/responses`                                                              |
| **SDK method**         | `client.chat.completions.create()`                  | `client.responses.create()`                                                           |
| **Input**              | `messages: [{role, content}]`                       | `input: "string"` or `input: [{role, content}]`                                       |
| **System message**     | `{role: "system", content: "..."}` in messages      | `instructions` parameter                                                              |
| **Output text**        | `response.choices[0].message.content`               | `response.output[0].content[0].text`                                                  |
| **Streaming (Python)** | `stream=True` + `for chunk in stream:`              | `client.responses.stream()` context manager                                           |
| **Streaming (JS)**     | `stream: true` + `for await`                        | `client.responses.stream()` + `.on()` events                                          |
| **Tool format**        | `{type, function: {name, description, parameters}}` | `{type, name, description, parameters}` (flat)                                        |
| **Tool call output**   | `response.choices[0].message.tool_calls`            | Items in `response.output` with `type == "function_call"`                             |
| **Tool result**        | `{role: "tool", tool_call_id, content}`             | `{type: "function_call_output", call_id, output}`                                     |
| **Structured output**  | `response_format: {type, json_schema}`              | `text: {format: {type, json_schema}}` via `extra_body`                                |
| **Parsed output**      | `response.choices[0].message.parsed`                | `response.parsed`                                                                     |
| **Response ID**        | `chatcmpl_...`                                      | `resp_...`                                                                            |
| **Response object**    | `"chat.completion"`                                 | `"response"`                                                                          |
| **Token fields**       | `prompt_tokens` / `completion_tokens`               | `input_tokens` / `output_tokens`                                                      |
| **Finish signal**      | `choices[0].finish_reason: "stop"`                  | `status: "completed"`                                                                 |
| **SSE events**         | Generic `data:` chunks                              | Named events (`response.created`, `response.output_text.delta`, `response.completed`) |
| **Credits**            | `usage.credits_used`                                | `usage.credits_used` (no change)                                                      |

***

## Step-by-Step Migration

### 1. Update the SDK Method

The SDK method changes from `chat.completions.create()` to `responses.create()`. The input format also changes from `messages` to `input`. No SDK version change is required — the OpenAI SDK already supports both.

<CodeGroup>
  ```python Before (Python) theme={"dark"}
  response = client.chat.completions.create(
      model="mavera-1",
      messages=[
          {"role": "system", "content": "You are a helpful assistant."},
          {"role": "user", "content": "How do Gen Z consumers view sustainability?"}
      ],
      extra_body={"persona_id": "YOUR_PERSONA_ID"},
  )
  print(response.choices[0].message.content)
  ```

  ```python After (Python) theme={"dark"}
  response = client.responses.create(
      model="mavera-1",
      input="How do Gen Z consumers view sustainability?",
      instructions="You are a helpful assistant.",
      extra_body={"persona_id": "YOUR_PERSONA_ID"},
  )
  print(response.output[0].content[0].text)
  ```
</CodeGroup>

<CodeGroup>
  ```javascript Before (JavaScript) theme={"dark"}
  const response = await client.chat.completions.create({
    model: "mavera-1",
    messages: [
      { role: "system", content: "You are a helpful assistant." },
      { role: "user", content: "How do Gen Z consumers view sustainability?" },
    ],
    persona_id: "YOUR_PERSONA_ID",
  });
  console.log(response.choices[0].message.content);
  ```

  ```javascript After (JavaScript) theme={"dark"}
  const response = await client.responses.create({
    model: "mavera-1",
    input: "How do Gen Z consumers view sustainability?",
    instructions: "You are a helpful assistant.",
    // @ts-ignore - Mavera custom field
    persona_id: "YOUR_PERSONA_ID",
  });
  console.log(response.output[0].content[0].text);
  ```
</CodeGroup>

<CodeGroup>
  ```bash Before (cURL) theme={"dark"}
  curl -X POST https://app.mavera.io/api/v1/chat/completions \
    -H "Authorization: Bearer mvra_live_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{"model": "mavera-1", "persona_id": "YOUR_PERSONA_ID", "messages": [{"role": "user", "content": "..."}]}'
  ```

  ```bash After (cURL) theme={"dark"}
  curl -X POST https://app.mavera.io/api/v1/responses \
    -H "Authorization: Bearer mvra_live_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{"model": "mavera-1", "persona_id": "YOUR_PERSONA_ID", "input": "..."}'
  ```
</CodeGroup>

***

### 2. Move System Messages to `instructions`

System messages are no longer part of the `messages` array. Use the top-level `instructions` parameter instead. Instructions are appended to the persona's built-in system prompt.

<CodeGroup>
  ```python Before theme={"dark"}
  response = client.chat.completions.create(
      model="mavera-1",
      messages=[
          {"role": "system", "content": "You are a market research analyst."},
          {"role": "user", "content": "Analyze brand loyalty trends."}
      ],
      extra_body={"persona_id": "YOUR_PERSONA_ID"},
  )
  ```

  ```python After theme={"dark"}
  response = client.responses.create(
      model="mavera-1",
      input="Analyze brand loyalty trends.",
      instructions="You are a market research analyst.",
      extra_body={"persona_id": "YOUR_PERSONA_ID"},
  )
  ```
</CodeGroup>

***

### 3. Update Streaming

The streaming interface changes from a flag-based approach to a dedicated stream method with named events.

<CodeGroup>
  ```python Before (Python) theme={"dark"}
  stream = client.chat.completions.create(
      model="mavera-1",
      messages=[{"role": "user", "content": "Write a product description"}],
      extra_body={"persona_id": "YOUR_PERSONA_ID"},
      stream=True,
  )

  for chunk in stream:
      delta = chunk.choices[0].delta.content
      if delta:
          print(delta, end="", flush=True)
  ```

  ```python After (Python) theme={"dark"}
  with client.responses.stream(
      model="mavera-1",
      input="Write a product description",
      extra_body={"persona_id": "YOUR_PERSONA_ID"},
  ) as stream:
      for event in stream:
          if event.type == "response.output_text.delta":
              print(event.delta, end="", flush=True)
  ```
</CodeGroup>

<CodeGroup>
  ```javascript Before (JavaScript) theme={"dark"}
  const stream = await client.chat.completions.create({
    model: "mavera-1",
    messages: [{ role: "user", content: "Write a product description" }],
    persona_id: "YOUR_PERSONA_ID",
    stream: true,
  });

  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta?.content || "";
    process.stdout.write(delta);
  }
  ```

  ```javascript After (JavaScript) theme={"dark"}
  const stream = client.responses.stream({
    model: "mavera-1",
    input: "Write a product description",
    // @ts-ignore - Mavera custom field
    persona_id: "YOUR_PERSONA_ID",
  });

  stream.on("response.output_text.delta", (event) => {
    process.stdout.write(event.delta);
  });

  const finalResponse = await stream.finalResponse();
  ```
</CodeGroup>

***

### 4. Update Structured Outputs

The `response_format` parameter is replaced by `text` format configuration passed via `extra_body`. The parsed result moves from `response.choices[0].message.parsed` to `response.parsed`.

<CodeGroup>
  ```python Before theme={"dark"}
  response = client.chat.completions.create(
      model="mavera-1",
      messages=[{"role": "user", "content": "Review this product: Great quality!"}],
      extra_body={
          "persona_id": "YOUR_PERSONA_ID",
          "response_format": {
              "type": "json_schema",
              "json_schema": {
                  "name": "product_review",
                  "strict": True,
                  "schema": {
                      "type": "object",
                      "properties": {
                          "sentiment": {"type": "string"},
                          "score": {"type": "number"},
                          "summary": {"type": "string"}
                      },
                      "required": ["sentiment", "score", "summary"]
                  }
              }
          }
      },
  )
  data = response.choices[0].message.parsed
  ```

  ```python After theme={"dark"}
  response = client.responses.create(
      model="mavera-1",
      input="Review this product: Great quality!",
      extra_body={
          "persona_id": "YOUR_PERSONA_ID",
          "text": {
              "format": {
                  "type": "json_schema",
                  "json_schema": {
                      "name": "product_review",
                      "strict": True,
                      "schema": {
                          "type": "object",
                          "properties": {
                              "sentiment": {"type": "string"},
                              "score": {"type": "number"},
                              "summary": {"type": "string"}
                          },
                          "required": ["sentiment", "score", "summary"]
                      }
                  }
              }
          }
      },
  )
  data = response.parsed
  ```
</CodeGroup>

***

### 5. Update Tool Calling

Tool definitions change from a nested format to a **flat format**. Tool call results change from `{role: "tool"}` messages to `{type: "function_call_output"}` items.

#### Tool Definitions

<CodeGroup>
  ```python Before theme={"dark"}
  tools = [
      {
          "type": "function",
          "function": {
              "name": "get_weather",
              "description": "Get the current weather",
              "parameters": {
                  "type": "object",
                  "properties": {
                      "location": {"type": "string"}
                  },
                  "required": ["location"]
              }
          }
      }
  ]
  ```

  ```python After theme={"dark"}
  tools = [
      {
          "type": "function",
          "name": "get_weather",
          "description": "Get the current weather",
          "parameters": {
              "type": "object",
              "properties": {
                  "location": {"type": "string"}
              },
              "required": ["location"]
          }
      }
  ]
  ```
</CodeGroup>

#### Reading Tool Calls

<CodeGroup>
  ```python Before theme={"dark"}
  if response.choices[0].finish_reason == "tool_calls":
      for tc in response.choices[0].message.tool_calls:
          print(tc.function.name, tc.function.arguments)
  ```

  ```python After theme={"dark"}
  for item in response.output:
      if item.type == "function_call":
          print(item.name, item.arguments)
  ```
</CodeGroup>

#### Sending Tool Results

<CodeGroup>
  ```python Before theme={"dark"}
  follow_up = client.chat.completions.create(
      model="mavera-1",
      messages=[
          {"role": "user", "content": "What's the weather?"},
          {"role": "assistant", "content": None, "tool_calls": [...]},
          {
              "role": "tool",
              "tool_call_id": tool_call.id,
              "content": json.dumps(result)
          }
      ],
      extra_body={"persona_id": "YOUR_PERSONA_ID"},
  )
  ```

  ```python After theme={"dark"}
  follow_up = client.responses.create(
      model="mavera-1",
      input=[
          *response.output,
          {
              "type": "function_call_output",
              "call_id": item.call_id,
              "output": json.dumps(result)
          }
      ],
      tools=tools,
      extra_body={"persona_id": "YOUR_PERSONA_ID"},
  )
  ```
</CodeGroup>

***

### 6. Update Response Parsing

The response shape changes significantly. Update all code that reads from the response object.

| Chat Completions (old)                  | Responses API (new)                       |
| --------------------------------------- | ----------------------------------------- |
| `response.choices[0].message.content`   | `response.output[0].content[0].text`      |
| `response.choices[0].message.parsed`    | `response.parsed`                         |
| `response.choices[0].finish_reason`     | `response.status`                         |
| `response.usage.prompt_tokens`          | `response.usage.input_tokens`             |
| `response.usage.completion_tokens`      | `response.usage.output_tokens`            |
| `response.usage.credits_used`           | `response.usage.credits_used` (unchanged) |
| `response.id` (prefix `chatcmpl_`)      | `response.id` (prefix `resp_`)            |
| `response.object` (`"chat.completion"`) | `response.object` (`"response"`)          |

***

### 7. Update Error Handling

Error responses use the same format, but the retry logic should reference the new SDK method.

<CodeGroup>
  ```python Before theme={"dark"}
  def chat_with_retry(messages, persona_id, max_retries=3):
      for attempt in range(max_retries):
          try:
              return client.chat.completions.create(
                  model="mavera-1",
                  messages=messages,
                  extra_body={"persona_id": persona_id},
              )
          except RateLimitError:
              time.sleep(2 ** attempt)
  ```

  ```python After theme={"dark"}
  def respond_with_retry(input_text, persona_id, max_retries=3):
      for attempt in range(max_retries):
          try:
              return client.responses.create(
                  model="mavera-1",
                  input=input_text,
                  extra_body={"persona_id": persona_id},
              )
          except RateLimitError:
              time.sleep(2 ** attempt)
  ```
</CodeGroup>

***

## Migration Checklist

<AccordionGroup>
  <Accordion title="Migration checklist">
    Use this checklist to verify your migration is complete:

    * [ ] Replace `client.chat.completions.create()` with `client.responses.create()`
    * [ ] Replace `messages` parameter with `input` parameter
    * [ ] Move system messages from `messages` array to `instructions` parameter
    * [ ] Update streaming to use `client.responses.stream()` and named events
    * [ ] Update `response_format` to `text: {format: {...}}` in `extra_body`
    * [ ] Flatten tool definitions (remove `function` wrapper)
    * [ ] Update tool result messages to `{type: "function_call_output", call_id, output}`
    * [ ] Update response parsing: `.choices[0].message.content` to `.output[0].content[0].text`
    * [ ] Update parsed access: `.choices[0].message.parsed` to `.parsed`
    * [ ] Update token field references: `prompt_tokens` to `input_tokens`, `completion_tokens` to `output_tokens`
    * [ ] Update finish detection: `finish_reason == "stop"` to `status == "completed"`
    * [ ] Update cURL endpoints from `/chat/completions` to `/responses`
    * [ ] Update error handling and retry logic to use new SDK method
    * [ ] Test all code paths (basic, streaming, tools, structured outputs, analysis mode)
  </Accordion>
</AccordionGroup>

## Common Gotchas

<AccordionGroup>
  <Accordion title="System messages are ignored in the input array">
    The Responses API does not support `{role: "system"}` in the `input` array. Use the `instructions` parameter instead. If you include a system message in `input`, it will be ignored or cause an error.
  </Accordion>

  <Accordion title="Tool definitions must be flat">
    The old `{type: "function", function: {name, ...}}` nested format will not work. Use the flat format: `{type: "function", name: "...", description: "...", parameters: {...}}`.
  </Accordion>

  <Accordion title="Tool results use call_id, not tool_call_id">
    The field name changed from `tool_call_id` to `call_id`, and the message type changed from `{role: "tool"}` to `{type: "function_call_output"}`.
  </Accordion>

  <Accordion title="Streaming uses a context manager in Python">
    You can no longer use `stream=True` as a parameter. Instead, use `client.responses.stream()` which returns a context manager. Iterate over events and check `event.type == 'response.output_text.delta'`.
  </Accordion>

  <Accordion title="Structured output config key changed">
    `response_format` is replaced by `text` in `extra_body`. The schema structure inside is the same, but the wrapping key is different: `text: {format: {type: "json_schema", json_schema: {...}}}`.
  </Accordion>

  <Accordion title="Response shape is different">
    There are no more `choices` — the output is in `response.output[]`. Each output item has a `type` field (`"message"` for text, `"function_call"` for tool calls). Text content is at `response.output[0].content[0].text`.
  </Accordion>

  <Accordion title="persona_id passing is unchanged">
    In Python, `persona_id` is still passed via `extra_body`. In JavaScript, it's still a top-level field with `// @ts-ignore`. This did not change.
  </Accordion>
</AccordionGroup>

## See Also

<CardGroup cols={2}>
  <Card title="Responses API" icon="bolt" href="/features/responses">
    Full Responses API reference and usage guide
  </Card>

  <Card title="Migrate OpenAI to Mavera" icon="arrow-right" href="/cookbooks/migrate-openai-to-mavera">
    Migrate from OpenAI to Mavera (base URL + persona)
  </Card>

  <Card title="Streaming Guide" icon="stream" href="/guides/streaming">
    Deep dive into streaming patterns
  </Card>

  <Card title="Function Calling Guide" icon="wrench" href="/guides/function-calling">
    Tool calling patterns and best practices
  </Card>
</CardGroup>
