## TL;DR
The body you sent is not valid JSON. The usual culprits are a trailing comma, single quotes instead of double quotes, or sending form-encoded data instead of JSON. Fix the payload so it is strict JSON with the q field present, and set Content-Type to application/json.

```text
serper.dev 400 invalid JSON body
```

## Use this when
- POST https://google.serper.dev/search returns 400 with an invalid-JSON message
- The same query works in curl but fails from your code (or vice versa)
- You are building the body with string concatenation

## Not for this skill when
- Serper returns 401/403 (the X-API-KEY header is missing or wrong)
- Serper returns 429 (rate limit; back off instead)
- Results return but are wrong or empty (query wording, not payload shape)

## Steps
1. Print the exact raw body bytes you are sending, before headers. Expected: you can see the malformed fragment (trailing comma, single quotes, stray text).
2. Validate it with json.loads in Python or JSON.parse in Node. Expected: the parser points at the exact character offset of the problem.
3. Rebuild the body with a real serializer (json.dumps / JSON.stringify), never string templates. Expected: valid strict JSON every time.
4. Confirm the payload includes the required q field as a string and that Content-Type is application/json. Expected: 200 with a results array.
5. Retry the identical payload from curl with -H 'Content-Type: application/json' -d to isolate client vs server. Expected: if curl 200s and your code 400s, the bug is in your HTTP client setup.

## Variant phrasings
### serper invalid json body error
Run the body through a strict JSON parser to find the offending character.
### serper.dev 400 bad request search
Check Content-Type first: many 400s are form-encoded bodies sent to a JSON endpoint.

## Tool/version compatibility
Serper search API v1 (google.serper.dev). Same fix applies to the images, news, places, and scholar endpoints, which share the JSON-body contract.

## Why it happens
The endpoint parses the body as strict JSON (RFC 8259): no trailing commas, no comments, double quotes only. Common producers violate this: Python f-string templates, requests.post(data=...) instead of json=..., or curl -d without the JSON content type. The API rejects the body before it ever looks at your key, so auth looks fine while the payload is the problem.

## Edge cases / pitfalls
- Non-ASCII queries must be UTF-8 encoded; some clients double-encode and produce mojibake, which can also 400.
- An empty body or a body that is just whitespace 400s the same way.
- Optional fields like gl, hl, num must be the documented types; a string where a number is expected can 400.
- Read keys via os.environ.get("SERPER_API_KEY") and never log or print the value.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_ktZQF8nOriqScKqCCLdKgg
