Skip to main content
Lesson 12 of 12 · Chat Completions: OpenAI Compatibility

Gradual Migration to the Responses API

Lesson 12 of 12 Updated November 3, 2026 Developer
On this page
  1. Why and when to migrate
  2. Step 1: Identify the features you use
  3. Step 2: Migrate basic calls
  4. Step 3: Migrate multi-turn conversations
  5. Step 4: Take advantage of new features
  6. Recommended migration strategy
  7. Key takeaways
  8. Test your knowledge

Updated November 3, 2026: Parameter mapping previously indicated that max_tokens keeps its name in the Responses API; xAI’s table changes it to max_output_tokens, and usage counters also change names. Source: Comparison with Chat Completions.

Why and when to migrate

The Chat Completions endpoint is marked legacy. It will continue to work, but new features will not be added to it. If your application needs server-side tools (web search, code execution), conversation storage, or encrypted reasoning, migrating to the Responses API becomes necessary.

The good news: this migration can happen gradually. You do not need to rewrite everything at once.

Step 1: Identify the features you use

Before migrating, take stock of what your application uses with Chat Completions:

# Audit your existing code
# Look for these patterns:

# 1. Basic calls (simple text)
client.chat.completions.create(model=..., messages=...)

# 2. Streaming
client.chat.completions.create(..., stream=True)

# 3. Function calling
client.chat.completions.create(..., tools=[...])

# 4. Vision (images)
# messages with content as array [text, image_url]

# 5. Generation parameters
# temperature, max_tokens, top_p, stop, seed

Basic calls and streaming are the simplest to migrate. Function calling requires adapting the format. Vision works similarly.

Step 2: Migrate basic calls

Migrating a simple call follows this pattern:

# BEFORE — Chat Completions
response = client.chat.completions.create(
    model="grok-4.20-0309-reasoning",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Hello!"}
    ]
)
texte = response.choices[0].message.content

# AFTER — Responses API
response = client.responses.create(
    model="grok-4.20-0309-reasoning",
    instructions="You are a helpful assistant.",
    input="Hello!"
)
texte = response.output_text

Field mapping

The main changes are:

  • messages with role system → instructions (dedicated parameter)
  • messages with role user → input (simple string)
  • response.choices[0].message.content → response.output_text
  • max_tokens → max_output_tokens, according to xAI’s mapping table; temperature keeps its name
  • response.usage.prompt_tokens and completion_tokens → input_tokens and output_tokens (details: input_tokens_details.cached_tokens, output_tokens_details.reasoning_tokens)

Step 3: Migrate multi-turn conversations

This is where the Responses API simplifies things. Instead of reconstructing history with every request, you chain them by ID:

# BEFORE — Chat Completions (manage history yourself)
historique = [
    {"role": "system", "content": "You are a helpful assistant."}
]

def envoyer_message(texte: str) -> str:
    historique.append({"role": "user", "content": texte})
    response = client.chat.completions.create(
        model="grok-4.20-0309-reasoning",
        messages=historique
    )
    reponse = response.choices[0].message.content
    historique.append({"role": "assistant", "content": reponse})
    return reponse

# AFTER — Responses API (server manages history)
dernier_id = None

def envoyer_message(texte: str) -> str:
    response = client.responses.create(
        model="grok-4.20-0309-reasoning",
        instructions="You are a helpful assistant.",
        input=texte,
        previous_response_id=dernier_id
    )
    dernier_id = response.id
    return response.output_text

The code is simpler and you no longer have to manage the messages array manually.

Step 4: Take advantage of new features

Once migrated, you can leverage features exclusive to the Responses API:

response = client.responses.create(
    model="grok-4.20-0309-reasoning",
    input="What are the latest developments in generative AI?",
    tools=[{"type": "web_search"}]
)
# The response includes citations with source URLs

Response storage

response = client.responses.create(
    model="grok-4.20-0309-reasoning",
    input="Analyze this document...",
    store=True  # Keeps for 30 days
)

# Retrieve later
stored = client.responses.retrieve(response.id)

Encrypted reasoning

response = client.responses.create(
    model="grok-4.20-0309-reasoning",
    input="Solve this complex problem...",
    include=["reasoning.encrypted_content"]
)
# The encrypted reasoning can be reused in subsequent requests

Rather than migrating everything at once, proceed in steps:

  1. Identify Chat Completions calls in your code
  2. Classify them by complexity (basic → streaming → function calling)
  3. Migrate first new endpoints or features (greenfield)
  4. Then migrate the simplest existing calls
  5. Keep Chat Completions for third-party integrations that require it (LangChain, etc.)

Temporary coexistence

It is perfectly acceptable to run both endpoints side by side during the transition:

# Wrapper that abstracts the endpoint used
class GrokClient:
    def __init__(self, use_responses: bool = False):
        self.use_responses = use_responses
        self.client = OpenAI(
            api_key=os.getenv("XAI_API_KEY"),
            base_url="https://api.x.ai/v1"
        )

    def generate(self, system: str, user_message: str) -> str:
        if self.use_responses:
            response = self.client.responses.create(
                model="grok-4.20-0309-reasoning",
                instructions=system,
                input=user_message
            )
            return response.output_text
        else:
            response = self.client.chat.completions.create(
                model="grok-4.20-0309-reasoning",
                messages=[
                    {"role": "system", "content": system},
                    {"role": "user", "content": user_message}
                ]
            )
            return response.choices[0].message.content

This approach lets you switch gradually by testing each migration individually.

Key takeaways

  • Migration can be gradual: no need to rewrite everything at once
  • Main changes: messages → input/instructions, choices[0].message.content → output_text
  • Multi-turn conversations move from a messages array to a simple previous_response_id
  • The Responses API gives access to exclusive features (web_search, storage, encrypted reasoning)
  • Run both endpoints side by side during the transition and migrate by order of complexity

Test your knowledge

The legacy API understood—and migration prepared.

1. What does the legacy and stateless status of Chat Completions mean?

Answer: The endpoint remains supported but no longer evolves: stateless, it requires resending the entire history with every call—the Responses API is the modern path.

2. Which roles structure the messages?

Answer: system (and developer), user, assistant—the framework, the requests, the responses: the full history restarts with every request.

3. How do you use existing OpenAI code with Grok?

Answer: By changing base_url to https://api.x.ai/v1 and the API key—format compatibility handles the rest, including OpenAI SDK and Vercel AI SDK.

4. What do you read in the response and usage?

Answer: choices carries responses with finish_reason; usage details tokens, including reasoning_tokens for reasoning models—the basis for cost tracking.

5. How do you migrate cleanly to the Responses API?

Answer: Gradually: new developments in Responses, existing endpoints converted one by one with prompt re-testing—comparing the two formats guides the conversion.

Understand the legacy, build for the modern: the course’s gradual migration plan is your roadmap—without a big bang.