# Input\_message doesn't support role as assistant

**URL:** <https://community.openai.com/t/input-message-doesnt-support-role-as-assistant/1364269>\
**Category:** Bugs\
**Tags:** api\
**Created:** [October 28, 2025, 6:08pm UTC](https://community.openai.com/t/input-message-doesnt-support-role-as-assistant/1364269 "2025-10-28T18:08:54Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![key4ng](https://avatars.discourse-cdn.com/v4/letter/k/b5ac83/32.png) [@key4ng](https://community.openai.com/u/key4ng)\
**Post date:** [October 28, 2025, 6:08pm UTC](https://community.openai.com/t/input-message-doesnt-support-role-as-assistant/1364269/1 "2025-10-28T18:08:54Z")

</div>

Hi experts,

I recently found the input\_message doesn’t support `input_text` as a type for role of `assistant`.

example:

```auto
curl https://api.openai.com/v1/responses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-5-nano",
"input":[
        {
            "role": "assistant",
            "content": [
                { "type": "input_text", "text": "what is in this image?" }
            ],
            "type": "message"
        }
    ]
}'
{
  "error": {
    "message": "Invalid value: 'input_text'. Supported values are: 'output_text' and 'refusal'.",
    "type": "invalid_request_error",
    "param": "input[0].content[0]",
    "code": "invalid_value"
  }
}

```

I remember it used to be supported. In the spec, it also shows supporting role as `assistant`, the only type we could choose for input message is `input_text`, I’m thinking if there is bug introduced recently for the field validation.

Could we remove the unnecessary check for the `input_text` type of assistant role  
related thread:

```auto
https://community.openai.com/t/input-text-field-removed-from-response-api/1363365/6?u=key4ng

```

---

<div class="post-metadata">

**Author:** ![jlvanhulst](https://sea2.discourse-cdn.com/openai1/user_avatar/community.openai.com/jlvanhulst/32/195305_2.png) [@jlvanhulst](https://community.openai.com/u/jlvanhulst)\
**Post date:** [October 28, 2025, 6:17pm UTC](https://community.openai.com/t/input-message-doesnt-support-role-as-assistant/1364269/2 "2025-10-28T18:17:57Z")

</div>

> I think that is becasue for input\_text you are supposed to use ‘developer” role ? From the API docss: A message input to the model with a role indicating instruction following hierarchy. Instructions given with the `developer` or `system` role take precedence over instructions given with the `user` role. Messages with the `assistant` role are presumed to have been generated by the model in previous interactions.

---

<div class="post-metadata">

**Author:** ![aprendendo.next](https://sea2.discourse-cdn.com/openai1/user_avatar/community.openai.com/aprendendo.next/32/721758_2.png) [@aprendendo.next](https://community.openai.com/u/aprendendo.next)\
**Post date:** [October 28, 2025, 6:59pm UTC](https://community.openai.com/t/input-message-doesnt-support-role-as-assistant/1364269/3 "2025-10-28T18:59:13Z")

</div>

While I had already agreed that this is a bit too strict, there is actually a much simpler alternative syntax for just plain text:

```python
response = client.responses.create(
    model="gpt-4.1-mini",
    input=[
        {
            "role": "assistant",
            "content": "I am chattygpt" 
        },
        {
            "role": "user",
            "content": "chatty what?"
        }
    ]
)

print(response.output_text)

```

Depending on your use case, perhaps this might help to simplify things.

---

<div class="post-metadata">

**Author:** ![\_j](https://sea2.discourse-cdn.com/openai1/user_avatar/community.openai.com/_j/32/766292_2.png) [@\_j](https://community.openai.com/u/_j)\
**Post date:** [October 28, 2025, 9:00pm UTC](https://community.openai.com/t/input-message-doesnt-support-role-as-assistant/1364269/4 "2025-10-28T21:00:29Z")

</div>

To put it simply: “assistant” role messages with content are type “`output_text`”.

This message type is meant to replay a previous response object also received with that type, and you would simulate that if not using an actual assistant turn as received.

Output is an array of many types, from reasoning (with both encrypted content and useless summary items), tool calls, and the final seen output. OpenAI has taken the basic production of tokens an AI produces, and made server-side parsing into objects, and a strict formatting and sequence is then required in replay.

Each type still takes client-side understanding and rewriting for placement back as input.

What follows is AI production from a lossless input of prepared and curated documentation and iterative turns re-collected, along with task instructions and understanding. This conversation input, BTW, looks like:

user: 181888 tokens  
assistant: 6041 tokens  
user: 645 tokens  
assistant: reasoning + this text before review and edit

* * *

Below is reference you can use to understand better the stateless conversation replay with the OpenAI Responses API. It covers:

- What streaming events to collect and how they assemble into response output items
- What the completed response.output contains (object types)
- What the API accepts in the next request’s input
- A clear output-to-input mapping table that you can implement directly
- Special handling for encrypted reasoning

The forum has limited width for the tabular data, so you may unfortunately need to scroll around or pick the large table view.

* * *

## How to think about streaming collection

- Stream events arrive as “start → deltas → done” for each content part or tool call. Only the “done” events contain the authoritative finalized string.
- The final response.completed event contains a normalized output array that is equivalent to non-streaming mode. It’s the easiest single source to mirror when you build the next turn’s input for stateless conversation.
- If you are using stateless mode (store=false or zero data retention), you must replay the entire conversation context as input in the correct order. If you use conversation:{id} and let OpenAI store state, you usually just send the new user message (no replay needed).

* * *

## Table 1. Streaming event lifecycles → what to collect for the final output array

| output kind (in response.completed.output) | lifecycle events to watch | what to collect into the item |
| --- | --- | --- |
| Assistant message (text/refusal) | response.output\_item.added (message)  
response.content\_part.added (output\_text or refusal)  
response.output\_text.delta → response.output\_text.done  
response.refusal.delta → response.refusal.done  
response.content\_part.done  
response.output\_item.done | Finalized assistant OutputMessage with content array of {type: output\_text, text, annotations} and/or {type: refusal, refusal}. |
| Reasoning summary (preview) | response.output\_item.added (reasoning)  
response.reasoning\_summary\_part.added  
response.reasoning\_summary\_text.delta → response.reasoning\_summary\_text.done  
response.reasoning\_summary\_part.done  
response.output\_item.done | Reasoning item with summary parts (summary\_text). This is for display/debug; it’s not needed for model context replay. |
| Reasoning text (OSS only) | response.content\_part.added (reasoning\_text)  
response.reasoning\_text.delta → response.reasoning\_text.done  
response.content\_part.done | Reasoning item content parts with reasoning\_text (plain text). You’ll use encrypted reasoning instead (see Table 5). |
| Function tool call (model-issued) | response.output\_item.added (function\_call)  
response.function\_call\_arguments.delta → response.function\_call\_arguments.done  
response.output\_item.done | Output item: {type:function\_call, call\_id, name, arguments}. You’ll include this item in next input (to preserve the dialog) and separately send your tool result as function\_call\_output. |
| Custom tool call (model-issued) | response.output\_item.added (custom\_tool\_call)  
response.custom\_tool\_call\_input.delta → response.custom\_tool\_call\_input.done  
response.output\_item.done | Output item: {type: custom\_tool\_call, call\_id, name, input}. Include this item in next input; separately send custom\_tool\_call\_output with your tool’s output. |
| MCP tool call | response.output\_item.added (mcp\_call)  
response.mcp\_call\_arguments.delta → .done  
response.mcp\_call.in\_progress → .completed/.failed  
response.output\_item.done | Output item: {type:mcp\_call, server\_label, name, arguments, output?, error?, status}. Include it to preserve context. |
| File search call | response.output\_item.added (file\_search\_call)  
response.file\_search\_call.in\_progress → .searching → .completed  
response.output\_item.done | Output item: {type:file\_search\_call, status, queries, results?}. Include it to preserve context. |
| Web search call | response.output\_item.added (web\_search\_call)  
response.web\_search\_call.in\_progress → .searching → .completed  
response.output\_item.done | Output item: {type:web\_search\_call, status, action:{search/open\_page/find…}}. Include to preserve context. |
| Code interpreter call | response.output\_item.added (code\_interpreter\_call)  
response.code\_interpreter\_call.in\_progress → .interpreting  
response.code\_interpreter\_call\_code.delta → .done  
response.code\_interpreter\_call.completed  
response.output\_item.done | Output item: {type:code\_interpreter\_call, status, container\_id, code?, outputs?}. Include to preserve context. |
| Image generation call | response.output\_item.added (image\_generation\_call)  
response.image\_generation\_call.in\_progress → .generating → partial\_image\* → .completed  
response.output\_item.done | Output item: {type:image\_generation\_call, status, result?}. Include to preserve context. |
| Audio output (TTS style) | response.audio.delta → response.audio.done  
response.audio.transcript.delta → .done | Audio bytes and transcript are not typically re-sent. Will likely need a prior ID, like Chat Completions. If you want the transcript in context, add it yourself next turn as output\_text. |

* * *

## Table 2. Output array (response.completed.output) — items you may see

| output item.type | description | key fields to store | needed for next-turn context? |
| --- | --- | --- | --- |
| message (assistant) | Final assistant message content | id, role=assistant, content of output\_text/annotations or refusal | Yes, include the assistant OutputMessage as-is. |
| reasoning | Reasoning preview/text | id, summary, content?, encrypted\_content? | Include only if you plan to supply encrypted\_content back (see Table 5). Do not include summary in input. |
| function\_call | Model called your function | id, call\_id, name, arguments (JSON string) | Yes (to preserve what model did). You will also send function\_call\_output with your tool’s result. |
| function\_call\_output | Your function result (only appears if you sent it) | id?, call\_id, output (string or content list) | Send from your app as input in the next turn (not created by the model). Include to preserve tool outputs. |
| custom\_tool\_call | Model called your custom tool | id, call\_id, name, input | Yes; also send custom\_tool\_call\_output with your tool’s result. |
| custom\_tool\_call\_output | Your custom tool result (if you sent it) | id?, call\_id, output (string or content list) | Send from your app as input (not created by model). Include to preserve tool outputs. |
| mcp\_call | Model invoked a remote MCP tool | id, server\_label, name, arguments, output?, error?, status | Yes; include to preserve context. |
| mcp\_list\_tools | List of tools on MCP server | id, server\_label, tools, error? | Yes; if you want the model to remember the discovered tools. |
| mcp\_approval\_request | MCP approval requested | id, server\_label, name, arguments | Include if you want to preserve the approval interaction in context. |
| mcp\_approval\_response | Human approval response (if you sent it) | id?, approval\_request\_id, approve, reason? | Send from your app as input; include later for context. |
| file\_search\_call | Built-in file search call | id, status, queries, results? | Yes; include. |
| web\_search\_call | Built-in web search call | id, status, action:{search/open\_page/find} | Yes; include. |
| code\_interpreter\_call | Built-in Python tool call | id, status, container\_id, code?, outputs? | Yes; include. |
| local\_shell\_call | Built-in local shell call | id, call\_id, action:{exec}, status | Yes; include. |
| local\_shell\_call\_output | Built-in local shell result (if you sent it) | id, call\_id, output, status? | Send from your app as input; include later for context. |
| image\_generation\_call | Built-in image generation | id, status, result? (base64) | Yes; include. |

Notes:

- OutputMessage includes annotations you should keep (citations, file\_path).
- The presence of function\_call\_output/custom\_tool\_call\_output/local\_shell\_call\_output in output depends on whether you previously submitted them as input during an earlier turn or within a multi-step flow.

* * *

## Table 3. What the API accepts as input (request.input)

The input field accepts either a string (equivalent to a user message) or a list of input items. The list allows original user content and replayed prior assistant/tool items.

| input variant | subtype/type | purpose | minimal shape summary |
| --- | --- | --- | --- |
| string | n/a | Simple user text | “Hello, world” (equivalent to user role message). |
| Easy message (string) | message (role=user/assistant/system/developer) | Add a role-annotated message. | {type:“message”, role, content:string} only (not typed) |
| Input text content | input\_text | Text content part | {type:“input\_text”, text}. |
| Input image content | input\_image | Vision input | {type:“input\_image”, image\_url or file\_id, detail:“low” |
| Input file content | input\_file | File/PDF input | {type:“input\_file”, file\_id? filename? file\_url? file\_data?}. |
| Input audio | input\_audio | Audio input to model | {type:“input\_audio”, input\_audio:{data: base64, format:“mp3” |
| Assistant message (replay) | message (role=assistant) or OutputMessage | Preserve the assistant’s last reply | Best: include the OutputMessage object you received, as-is (id, role, content). Alternatively send Easy message with role=assistant. |
| Reasoning (encrypted) | reasoning | Preserve model chain-of-thought (encrypted) | {type:“reasoning”, id, encrypted\_content}. Only if you included it in previous output. Do not pass summary. |
| Tool call (function) | function\_call | Replay what model did | Copy the prior item: {type:“function\_call”, call\_id, name, arguments}. |
| Tool output (function) | function\_call\_output | Return your function result | {type:“function\_call\_output”, call\_id, output:string or content}. |
| Tool call (custom) | custom\_tool\_call | Replay what model did | Copy prior item: {type:“custom\_tool\_call”, call\_id, name, input}. |
| Tool output (custom) | custom\_tool\_call\_output | Return your custom tool result | {type:“custom\_tool\_call\_output”, call\_id, output:string or content}. |
| MCP tool call | mcp\_call | Replay MCP call | Copy prior item: {type:“mcp\_call”, id, server\_label, name, arguments, output?, error?, status}. |
| MCP list tools | mcp\_list\_tools | Replay MCP discovery | Copy prior item: {type:“mcp\_list\_tools”, id, server\_label, tools, error?}. |
| MCP approval request | mcp\_approval\_request | Replay request | Copy prior item: {type:“mcp\_approval\_request”, id, server\_label, name, arguments}. |
| MCP approval response | mcp\_approval\_response | Supply approval decision | {type:“mcp\_approval\_response”, approval\_request\_id, approve, reason?}. |
| File search call | file\_search\_call | Replay call/results | Copy prior item: {type:“file\_search\_call”, id, status, queries, results?}. |
| Web search call | web\_search\_call | Replay call | Copy prior item: {type:“web\_search\_call”, id, status, action{…}}. |
| Code interpreter call | code\_interpreter\_call | Replay call/results | Copy prior item: {type:“code\_interpreter\_call”, id, status, container\_id, code?, outputs?}. |
| Local shell call | local\_shell\_call | Replay call | Copy prior item: {type:“local\_shell\_call”, id, call\_id, action{exec}, status}. |
| Local shell result | local\_shell\_call\_output | Return your shell output | {type:“local\_shell\_call\_output”, id?, call\_id, output, status?}. |
| Image generation call | image\_generation\_call | Replay call/result | Copy prior item: {type:“image\_generation\_call”, id, status, result?}. |
| Item reference | item\_reference | Refer to an earlier item by id | {type:“item\_reference”, id}. Useful if you persist items and want to reference rather than inline. |

Tips:

- For replaying assistant replies, you can embed the exact OutputMessage objects you received (no transformation).
- For user messages with images/files/audio, use input\_\* content parts inside a message item with role=user.

* * *

## Table 4. Output → Input mapping (stateless conversation replay)

This is the “do-this-in-your-code” table. Every row shows how an output item from the last turn becomes one or more input items for the next turn.

| last-turn output item (from response.completed.output) | next-turn input item to add | transform needed? | notes |
| --- | --- | --- | --- |
| message (assistant) | OutputMessage (same object) | No | Include id, role=assistant, content, annotations intact. This preserves the assistant response verbatim. |
| message (assistant) with refusal | OutputMessage (same object) | No | Keep refusal content part as-is to preserve context of refusal. |
| reasoning (with encrypted\_content) | reasoning | Yes (drop summary) | Only include if encrypted\_content is present (you requested include: [“reasoning.encrypted\_content”]). Set {type:“reasoning”, id, encrypted\_content}. Do not include summary or plaintext content. |
| reasoning (no encrypted\_content) | — | — | Do not include. Reasoning summary/text is not useful input. |
| function\_call (model issued) | function\_call | No | Copy {type, call\_id, name, arguments}. Also prepare to send function\_call\_output with your tool’s result. |
| — (your tool ran) | function\_call\_output | You create this | {type:“function\_call\_output”, call\_id (from function\_call), output:string or content}. |
| custom\_tool\_call (model issued) | custom\_tool\_call | No | Copy {type, call\_id, name, input}. Also prepare custom\_tool\_call\_output. |
| — (your custom tool ran) | custom\_tool\_call\_output | You create this | {type:“custom\_tool\_call\_output”, call\_id, output}. |
| mcp\_call | mcp\_call | No | Copy the entire item including arguments, output?, error?, status; keeps the MCP interaction in context. |
| mcp\_list\_tools | mcp\_list\_tools | No | Copy the item; helps the model remember tool inventory. |
| mcp\_approval\_request | mcp\_approval\_request | No | Copy the item if you need the approval in context. |
| — (you approved/rejected) | mcp\_approval\_response | You create this | {type:“mcp\_approval\_response”, approval\_request\_id, approve, reason?}. |
| file\_search\_call | file\_search\_call | No | Copy; results help with continuity and citations. |
| web\_search\_call | web\_search\_call | No | Copy; action details maintain trace of browsing. |
| code\_interpreter\_call | code\_interpreter\_call | No | Copy; includes container\_id, code, outputs. |
| local\_shell\_call | local\_shell\_call | No | Copy; action.exec, status preserved. |
| — (your shell executed) | local\_shell\_call\_output | You create this | {type:“local\_shell\_call\_output”, call\_id, output}. |
| image\_generation\_call | image\_generation\_call | No | Copy; include result if present for downstream reference. |

Important:

- Order matters. Build input by concatenating, in order, all items from previous turns (user → assistant → tool calls → tool outputs → …), then append the new user request for this turn.
- If your org is stateless/zero-retention, always replay everything you want the model to “remember”, including tool calls and their outputs.

* * *

## Table 5. Reasoning tokens: what to return vs. what to ignore

| you saw in output | what to include next turn | why |
| --- | --- | --- |
| reasoning item with encrypted\_content (you set include:[“reasoning.encrypted\_content”]) | Include {type:“reasoning”, id, encrypted\_content}. | This preserves the chain-of-thought for the model under encryption; only OpenAI can decrypt it. |
| reasoning.summary (summary\_text parts) | Do NOT include in input. | Summaries are for UI/debug only; they are not used by the model. |
| reasoning.content (reasoning\_text) | Do NOT include in input. | Plaintext reasoning is not accepted/needed; prefer encrypted form. |
| No reasoning item present | Nothing to include. | Not all models/requests return reasoning items. |

* * *

## Table 6. Allowed original user inputs (to add alongside replayed context)

| role | content parts | example uses |
| --- | --- | --- |
| user | input\_text | General instructions/questions. |
| user | input\_image (image\_url or file\_id; detail) | Vision tasks (describe image, OCR, etc.). |
| user | input\_file (file\_id/filename/file\_url/file\_data) | PDF/doc analysis, RAG ingestion. |
| user | input\_audio (data, format=mp3 | wav) |
| developer/system | input\_text/image/file/audio | Policy or tool guidance higher in instruction hierarchy. |
| assistant | Generally you replay the OutputMessage you got | Keep exact assistant message for continuity. |

* * *

## Recommended request-building order (stateless replay)

- If you are not using conversation storage:

- If you are using conversation:{id} and store:true:

* * *

## Practical notes

- response.completed has the authoritative final output list; do your output→input mapping from there rather than by stitching deltas yourself.
- For function/custom/local shell tools: the output objects with type \*\_output are produced by you and must be sent back as input, keyed by call\_id.
- Only response.completed contains usage; if you must meter, read it there.
- Audio is unannounced, only breadcrumbs are there. Chat Completions currently has a short-life audio ID server-side that must be re-sent (you are not allowed to simulate the AI producing audio). Responses likely will also deny you self-management.
- Avoid passing reasoning summaries back; they are not used by the model.

* * *

## Minimal example strategy (conceptual)

- After collecting response.completed:
  - For each element in response.output:
    - If type in {message, function\_call, custom\_tool\_call, mcp\_call, mcp\_list\_tools, file\_search\_call, web\_search\_call, code\_interpreter\_call, local\_shell\_call, image\_generation\_call} → copy item as-is into next input.
    - If type=reasoning and encrypted\_content is present → add {type:“reasoning”, id, encrypted\_content}. Ignore summary/content.

  - Then append your next user message.

This approach ensures your next turn’s input mirrors the previous turn’s output in both type and order, preserving conversational coherence even without server-side conversation storage.

This is incomplete documentation for just one of the fields of “Responses” - not even getting into 53 SSE streaming events for you to create parsers for in your automatic iterator for functions, including error event that would have multiple statuses to take action on.

---

<div class="post-metadata">

**Author:** ![key4ng](https://avatars.discourse-cdn.com/v4/letter/k/b5ac83/32.png) [@key4ng](https://community.openai.com/u/key4ng)\
**Post date:** [October 29, 2025, 12:03am UTC](https://community.openai.com/t/input-message-doesnt-support-role-as-assistant/1364269/5 "2025-10-29T00:03:43Z")

</div>

Thanks for the quick reply!

totally agreed the type of assistant role in `items` should be `output_text` - it’s just copied from the previous response call’s output field. However, the issue I mentioned is in `input_message`, it’s really difficult to understand the input\_message couldn’t support `input_text`as a type for assistant role.

If the assistant role only support output\_text, I feel it might be better to remove the assistant value in the document, because it’s causing confusion - I have the assistant as an option but I’m never able to use it

 ![Screenshot 2025-10-28 at 4.53.14 PM](https://us1.discourse-cdn.com/openai1/original/4X/4/1/6/4168a9f7491f72d1b78ca03c4c7b0399c7074039.png)

In addition, I doubt it used to be accepting the input\_text as type for assistant role. I’m looking at XAI’s response api as well, which should following the openai spec to achieve compatibility. They only accept input\_text as type for assistant role

`https://docs.x.ai/docs/api-reference#create-new-response`

---

<div class="post-metadata">

**Author:** ![\_j](https://sea2.discourse-cdn.com/openai1/user_avatar/community.openai.com/_j/32/766292_2.png) [@\_j](https://community.openai.com/u/_j)\
**Post date:** [October 29, 2025, 1:10am UTC](https://community.openai.com/t/input-message-doesnt-support-role-as-assistant/1364269/6 "2025-10-29T01:10:30Z")

</div>

> [@key4ng](#):
>
> I doubt it used to be accepting the input\_text as type for assistant role

You are correct - for the blocking of objects. The Responses API always had this behavior. If you want no-nonsense, you have to use Chat Completions.

You can read the “role” enums in the OpenAI OpenAPI specification for the “input” array of type “message” if an “object” if you really want to get to the nitty-gritty of why you are blocked from using `assistant` as a role for more than a string…

```yaml
description: |
An item representing part of the context for the response to be
generated by the model. Can contain text, images, and audio inputs,
as well as previous assistant responses and tool call outputs.
discriminator:
propertyName: type
anyOf:
- x-resolved-from: '#/components/schemas/InputMessage'
  type: object
  title: Input message
  description: |
    A message input to the model with a role indicating instruction following
    hierarchy. Instructions given with the `developer` or `system` role take
    precedence over instructions given with the `user` role.
  properties:
    type:
      type: string
      description: |
        The type of the message input. Always set to `message`.
      enum:
        - message
      x-stainless-const: true
    role:
      type: string
      description: |
        The role of the message input. One of `user`, `system`, or `developer`.
      enum:
        - user
        - system
        - developer
    status:
      type: string
      description: |
        The status of item. One of `in_progress`, `completed`, or
        `incomplete`. Populated when items are returned via API.
      enum:
        - in_progress
        - completed
        - incomplete

```

Probably an easy validation shortcut to block the assistant from “pretending” to make images or other multimodal content itself, while giving you more simple string inputs for “content” as multi-shot.

The same is enforced on “instructions”, which also can be a complex array of messages and even example function calls.
