Back to guides
Troubleshooting·August 28, 2026·6 min read

Gemini API 400 INVALID_ARGUMENT — “please use a valid role” and the empty-contents family

Nearly every INVALID_ARGUMENT of this shape comes from posting OpenAI-style messages to Google's native generateContent endpoint. It has different role names, a separate home for system instructions, and no tolerance for an empty turn.

Last reviewed on .

Nearly every INVALID_ARGUMENT of this shape comes from posting OpenAI-style messages to Google's native generateContent endpoint. It has different role names, a separate home for system instructions, and no tolerance for an empty turn.

The error

two members of the same family (HTTP 400)
{"error":{"code":400,
  "message":"Please use a valid role: user, model.",
  "status":"INVALID_ARGUMENT"}}

{"error":{"code":400,
  "message":"* GenerateContentRequest.contents: contents is not specified\n",
  "status":"INVALID_ARGUMENT"}}

Causes and fixes at a glance

CauseFix
OpenAI-shaped messages sent to the native endpointassistant → model, and system moves to systemInstruction. Nothing else is legal.
Empty contents arrayEvery message got filtered out — often a tool turn with content: null.
A turn with an empty parts arraySubstitute an empty text part rather than emitting a part-less turn.
Inline image data without a mimeTypeinline_data needs both mime_type and base64 data.

Work out which API you are actually on

The same SDK name fronts two incompatible schemas: Google's native generateContent, which takes contents with user and model roles, and an OpenAI-compatible /v1/chat/completions, which takes messages with system, user and assistant. Every error in this family is a payload written for one and sent to the other.

Map the roles if you are on the native one

system and developer messages fold into a separate systemInstruction field. assistant becomes model. user stays user. Any other role has no equivalent and must be dropped or merged before sending.

to_gemini.py
def to_gemini(messages):
    system, contents = [], []
    for m in messages:
        if m["role"] in ("system", "developer"):
            system.append(m["content"])
            continue
        if m["role"] not in ("user", "assistant"):
            continue  # no Gemini equivalent
        parts = [{"text": m["content"] or ""}]  # never emit empty parts
        contents.append({
            "role": "model" if m["role"] == "assistant" else "user",
            "parts": parts,
        })
    req = {"contents": contents}
    if system:
        req["systemInstruction"] = {"parts": [{"text": "\n\n".join(system)}]}
    return req

Never emit a turn with no parts

A message whose content was null or filtered to nothing produces a part-less turn, which is rejected. Substituting an empty text part keeps the turn valid and preserves the alternation the model expects.

Validate before sending

Assert that contents is non-empty, that every role is user or model, and that every turn has at least one part. Three lines at the call site, versus a 400 from the network.

If you’re calling through Kunavo

Calling Gemini through Kunavo's OpenAI-compatible /v1/chat/completions means you never hand-build Google's contents array at all: you send OpenAI-shaped messages and the gateway does the mapping — system and developer messages folded into systemInstruction, assistant rewritten to model, roles with no equivalent dropped, and an empty text part substituted rather than a part-less turn emitted. So this whole class of INVALID_ARGUMENT does not arise from message shape. What it cannot save you from is an argument that is genuinely invalid — an unsupported response_format, or a malformed inline image — which will still be rejected on its merits. Per-token rates for the Gemini shelf are in our Gemini pricing guide.

FAQ

What are Gemini's valid roles?

On the native API, exactly two: user and model. There is no system role — system instructions go in the separate systemInstruction field.

Can I send a system message?

Not as a turn. Move it to systemInstruction, or use an OpenAI-compatible endpoint that does that for you.

Why does the identical payload work against OpenAI?

Because OpenAI's schema defines system and assistant roles, and Gemini's native schema does not. The payload is valid — for the other API.

Related guides

More error semantics live in the error reference; getting a key takes a minute via sign up and the authentication docs.