ValueError
ValueError: Invalid message format in OpenAI Responses API
Stack trace
Traceback (most recent call last):
File "app.py", line 24, in <module>
response = client.responses.create(
File "/usr/local/lib/python3.11/site-packages/openai/resources/responses.py", line 156, in create
validate_message_format(messages)
File "/usr/local/lib/python3.11/site-packages/openai/lib/responses.py", line 45, in validate_message_format
raise ValueError(f"Invalid role '{msg.get('role')}' in message. Must be 'user' or 'assistant'.")
ValueError: Invalid role 'system' in message. Must be 'user' or 'assistant'." Why it happens
The OpenAI Responses API (stateful conversation mode) uses a simplified message format that differs from the standard Chat Completions API. It does not support 'system' role messages in the messages array: system instructions must be passed via the separate `instructions` parameter. Additionally, the API strictly validates that each message has required fields: 'role' (user/assistant only) and 'content' (string or structured content). Missing, misspelled, or invalid values in these fields trigger validation errors.
Detection
Test your message structure before sending by validating that all messages have role in ['user', 'assistant'] and non-empty content. Log the raw messages dict before calling client.responses.create() to catch format issues during development. Use IDE autocomplete with the openai library's type hints to catch structural issues at coding time.
Causes & fixes
Using 'system' role in the messages array instead of the instructions parameter
Remove system messages from the messages list and pass system instructions via the instructions parameter: client.responses.create(..., instructions='Your system prompt here', messages=[...])
Message missing 'role' field or 'content' field, or role is misspelled
Ensure every message dict has exactly role (as 'user' or 'assistant') and content (non-empty string). Validate: `all('role' in m and 'content' in m for m in messages)`
Using deprecated Chat Completions message format (with system role) in Responses API
Refactor: extract any {'role': 'system', 'content': '...'} messages and pass their content to instructions parameter instead. Keep only user/assistant messages in the messages list.
Content field is empty string, None, or not a string type
Ensure content is always a non-empty string: content must be str(value) and len(content) > 0 before sending
Code: broken vs fixed
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ.get('OPENAI_API_KEY'))
# BROKEN: Passing system role in messages array (Responses API doesn't support this)
messages = [
{'role': 'system', 'content': 'You are a helpful assistant.'}, # ❌ WRONG — no system role in Responses API
{'role': 'user', 'content': 'What is 2+2?'},
{'role': 'assistant', 'content': '2+2 equals 4.'},
{'role': 'user', 'content': 'And 3+3?'} # ❌ WRONG — content field missing
]
response = client.responses.create(
model='gpt-4o-mini',
messages=messages # This will fail validation
)
print(response.output[0].content) import os
from openai import OpenAI
client = OpenAI(api_key=os.environ.get('OPENAI_API_KEY'))
# FIXED: System instructions passed via instructions parameter, not messages array
messages = [
# Only user and assistant roles in Responses API
{'role': 'user', 'content': 'What is 2+2?'},
{'role': 'assistant', 'content': '2+2 equals 4.'},
{'role': 'user', 'content': 'And 3+3?'} # ✅ FIXED — all messages have valid role and content
]
# ✅ FIXED: System instructions moved to instructions parameter
response = client.responses.create(
model='gpt-4o-mini',
instructions='You are a helpful assistant.', # System instructions here, not in messages
messages=messages # Only user/assistant messages
)
print(response.output[0].content) # Success: prints '3+3 equals 6.' Workaround
If you must support legacy code that uses system messages, wrap the messages list in a try/except block, catch ValueError, extract any system messages, rebuild the list with only user/assistant messages, and retry: `try: response = client.responses.create(..., messages=messages) except ValueError: system_msgs = [m for m in messages if m.get('role') == 'system']; system_content = ' '.join(m['content'] for m in system_msgs); user_msgs = [m for m in messages if m.get('role') != 'system']; response = client.responses.create(..., instructions=system_content, messages=user_msgs)`
Prevention
Build a message validation utility that enforces Responses API constraints before sending: `def validate_responses_messages(msgs, instructions): assert instructions, 'instructions parameter is required'; assert all(m.get('role') in ['user', 'assistant'] for m in msgs), 'Only user/assistant roles allowed'; assert all(m.get('content') and isinstance(m['content'], str) for m in msgs), 'All messages must have non-empty string content'`. Call this on every client.responses.create() call. Use TypedDict to document the message structure and catch mismatches in your IDE.