I’m trying to get Gemini to analyze a simple receipt image using the Laravel Http facade, but I keep hitting a 400 “Invalid JSON payload” error.
The request works fine for text-only prompts, but adding the image part breaks it. Here is the snippet:
$res = Http::post($url, [
'contents' => [[
'parts' => [
['text' => 'What is the total?'],
['inline_data' => [
'mime_type' => 'image/jpeg',
'data' => base64_encode(file_get_contents($path))
]]
]
]]
]);
Google returns: Unknown name "inline_data": Field 'inline_data' could not be found in request messages.
I’ve triple-checked the nesting based on the docs. Is this an array structure issue, or am I forced to use the /upload/ File API even for small 150kb images?
Any ideas?
The error is a camelCase/snake_case mismatch. The Gemini REST API uses proto3 JSON mapping, which serialises all field names in camelCase. inline_data and mime_type are the protobuf field names; their JSON wire representation is inlineData and mimeType. Laravel’s Http::post() passes the array directly through json_encode(), so snake_case keys hit the API verbatim and fail validation.
This is a silent trap because some Google documentation examples show snake_case (proto notation), and the error message itself is misleading — it says the field “could not be found” rather than “wrong case”.
The Fix
$res = Http::withHeaders(['Content-Type' => 'application/json'])
->post($url, [
'contents' => [[
'parts' => [
['text' => 'What is the total?'],
['inlineData' => [ // ← camelCase
'mimeType' => 'image/jpeg', // ← camelCase
'data' => base64_encode(file_get_contents($path))
]]
]
]]
]);
That is the only structural change required. 150 KB is well within the 20 MB inline limit for the generateContent endpoint, so you do not need the File API for this use case.
