OpenAI-compatible APIs now have two main request formats: the long-standing /v1/chat/completions and the newer /v1/responses. GetVibe API accepts both with one key and one base URL, https://getvibeapi.com/v1. Here is how they differ in practice, which one to choose, and the one trap when you move between them.
The short version
| Chat Completions | Responses | |
|---|---|---|
| Endpoint | POST /v1/chat/completions | POST /v1/responses |
| Input | a messages array | input: a string or an array of items |
| System prompt | a message with the system role | the instructions field |
| Answer | choices[0].message | an output array: reasoning, message, function calls |
| Output limit | max_tokens | max_output_tokens |
| Who uses it | most SDKs, frameworks and chat apps | Codex CLI and newer agent SDKs |
The format does not change the model, the price or the token count: you are billed for the model's input and output tokens either way.
The same request in both formats
Chat Completions:
curl https://getvibeapi.com/v1/chat/completions \
-H "Authorization: Bearer $GETVIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-luna",
"messages": [
{"role": "system", "content": "Answer in one sentence."},
{"role": "user", "content": "What does HTTP 429 mean?"}
],
"max_tokens": 300
}'
Responses:
curl https://getvibeapi.com/v1/responses \
-H "Authorization: Bearer $GETVIBE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-luna",
"instructions": "Answer in one sentence.",
"input": "What does HTTP 429 mean?",
"max_output_tokens": 300
}'
In Chat Completions the text is in choices[0].message.content. In Responses it sits inside output: for reasoning models the first item is a reasoning item and the answer is the item of type message. You do not have to walk the array by hand; the Python SDK has an output_text property.
import os
from openai import OpenAI
client = OpenAI(base_url="https://getvibeapi.com/v1", api_key=os.environ["GETVIBE_API_KEY"])
chat = client.chat.completions.create(
model="gpt-6-luna",
messages=[{"role": "system", "content": "Answer in one sentence."},
{"role": "user", "content": "What does HTTP 429 mean?"}],
)
print(chat.choices[0].message.content)
resp = client.responses.create(
model="gpt-6-luna",
instructions="Answer in one sentence.",
input="What does HTTP 429 mean?",
)
print(resp.output_text)
Responses is not limited to OpenAI models. Before publishing we sent the same Responses request to gpt-6-luna, claude-sonnet-5.5 and deepseek-v4.1-flash, and all three answered. DeepSeek returned a reasoning item before its message, exactly as described above.
Conversation history: send it yourself
This is the one thing to know before you switch. OpenAI's own Responses API can keep a conversation on the server, so the next request points at the previous one with previous_response_id. GetVibe API does not store your conversations, so previous_response_id is not supported and store is ignored. When we tried it, the request was rejected with a 400 error.
The working pattern is the same as with Chat Completions: send the whole history in input.
history = [{"role": "user", "content": "My name is Ana. Remember it."}]
first = client.responses.create(model="gpt-6-luna", input=history)
history.append({"role": "assistant", "content": first.output_text})
history.append({"role": "user", "content": "What is my name? One word."})
second = client.responses.create(model="gpt-6-luna", input=history)
print(second.output_text) # Ana
Tools: a different shape
Function calling works in both formats, but the tool definition looks different. In Chat Completions the function fields are nested inside a function object; in Responses they sit directly on the tool. The call arrives as a separate function_call item, and you return the result as a function_call_output item with the same call_id.
import json
tools = [{
"type": "function",
"name": "get_weather",
"description": "Current weather in a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
"additionalProperties": False,
},
}]
items = [{"role": "user", "content": "What's the weather in Denver?"}]
resp = client.responses.create(model="gpt-6-luna", input=items, tools=tools)
for item in resp.output:
if item.type == "function_call":
result = {"city": "Denver", "temp_f": 54, "sky": "overcast"} # your function
items.append({"type": "function_call", "call_id": item.call_id,
"name": item.name, "arguments": item.arguments})
items.append({"type": "function_call_output", "call_id": item.call_id,
"output": json.dumps(result)})
final = client.responses.create(model="gpt-6-luna", input=items, tools=tools)
print(final.output_text)
In our run the model called get_weather with {"city":"Denver"}, and the second request answered that Denver is 54°F and overcast. The Chat Completions version of the same loop is in our function calling guide.
Streaming
Both formats support "stream": true, but the events differ. Chat Completions sends uniform data: lines, with each new piece of text in choices[0].delta.content. Responses sends named events: response.created first, response.output_text.delta with the text pieces, and response.completed at the end. If you parse the stream yourself instead of through an SDK, you need a parser for the specific format.
Which one to choose
- Chat Completions if you already have code on it, or you use a framework, a chat app or a no-code platform. Almost every tool speaks it, and there is no reason to rewrite working code just to change formats.
- Responses if a tool requires it. The main example is Codex CLI, which talks to
/v1/responses; setup is in the Codex CLI docs and in our Codex CLI guide.
Either way, the model list and prices are the same. See the API reference for every field, and the models page for live prices.
