Malformed, or just mismatched
Two failure families share this status. The first is the boring one: a typo in a field name, a required field left out, a string where a number belongs. You'll find these by reading your payload against the reference docs, slowly, the way you'd proofread a contract.
The second family is sneakier, and it's the one that eats afternoons. The body is perfectly legal — on a different API version. Google ships new capabilities to v1beta before v1, so a feature copied from one doc page can be unknown to the endpoint another doc page told you to call. Mixed SDK examples, copied snippets, and an unpinned default version blend into a request that's valid everywhere except where you sent it.
The response
A representative body, in the standard Google envelope where the numeric code repeats the HTTP status and status holds the gRPC name:
{
"error": {
"code": 400,
"message": "The request body is malformed.",
"status": "INVALID_ARGUMENT"
}
}
Exact wording differs by failure, and the message rarely names the guilty field. That silence is why the rebuild method below beats staring at logs.
Rebuild from minimal
Prove the plumbing first. The smallest valid generateContent call puts the model in the URL path and sends one contents entry:
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash:generateContent" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [
{ "parts": [ { "text": "Say hello." } ] }
]
}'
If this passes, your schema drifted somewhere above it: add your real fields back one at a time and the 400 will reappear on the exact addition that breaks. If even the minimal call fails, suspect the URL before the body — the model name and the version segment cause more of these than any payload does.
Pin your versions
Three habits make this error rare. Put the API version in the URL explicitly and treat it as config, so every environment provably calls the same endpoint. Validate payloads against a schema before sending; a small JSON Schema check in CI catches drifting field names years before a user does. And when you move between model lines, reread the release notes instead of trusting that the request shape carried over — fields appear, rename, and change types across generations. If a switch to Gemini 3.1 Pro is what surfaced the error, the tracker shows what else changed around the model you just adopted.