How to check if API returns valid JSON
Save the body, then parse it: curl -s -o body.json https://httpbin.org/json followed by node -e "JSON.parse(require('fs').readFileSync('body.json','utf8'))". Silence means the bytes parse. A SyntaxError naming an unexpected < means the endpoint answered with HTML, whatever its content-type header claimed. Repeat on the error path.
Why check this
Clients call response.json() and crash on a body that never parsed. The report arrives as "the page is blank after login", and the cause is a gateway that returned an HTML error page under a JSON content type. Run this on staging sign-off and after any proxy, WAF or ingress change, on the success path and on at least one error path.
Prerequisites
- curl. Any build works, no HTTP/2 support is needed. See the curl manual.
- Node 18 or later.
JSON.parsefollows RFC 8259, so its verdict is the same one a browser reaches. - A local target for the gateway case, saved as
bad-json-server.jsand started withnode bad-json-server.js. It answers every request the way a failing reverse proxy does:
const http = require('node:http');
const page = '<html>\n<head><title>502 Bad Gateway</title></head>\n<body><h1>502 Bad Gateway</h1><hr>nginx</body>\n</html>\n';
http.createServer((req, res) => {
res.writeHead(502, { 'content-type': 'application/json' });
res.end(page);
}).listen(8081, '127.0.0.1', () => console.log('listening on 127.0.0.1:8081'));
Stop it with Ctrl+C when the run is over.
Steps
- Step 1.
Capture the body and print the three facts that decide how to read it: status, media type, byte count.
curl -s -o body.json -w '%{http_code} %{content_type} %{size_download}\n' https://httpbin.org/json200 application/json 429 - Step 2.
Parse the saved file.
node -e "JSON.parse(require('fs').readFileSync('body.json','utf8')); console.log('valid JSON')"valid JSON - Step 3.
Point the same capture at the local gateway target and read the header it sends.
curl -s -o bad.json -w '%{http_code} %{content_type} %{size_download}\n' http://127.0.0.1:8081/orders/1502 application/json 106The media type says JSON. Nothing so far contradicts it.
- Step 4.
Parse that body and read the error.
node -e "JSON.parse(require('fs').readFileSync('bad.json','utf8'))"SyntaxError: Unexpected token '<', "<html> <he"... is not valid JSONThis is the same message a front end prints in the browser console, which is why testers recognise it faster than they recognise the 502 behind it.
- Step 5.
Turn the check into one pipe that a pipeline can fail on. Point it at a host that answers HTML.
curl -s https://example.com/ | node -e "let b='';process.stdin.on('data',c=>b+=c).on('end',()=>{try{JSON.parse(b);console.log('valid JSON, '+b.length+' bytes')}catch(e){console.error('NOT JSON: '+e.message);process.exit(1)}})"NOT JSON: Unexpected token '<', "<!doctype "... is not valid JSON - Step 6.
Run the same pipe against the JSON endpoint to confirm the guard passes real traffic.
curl -s https://httpbin.org/json | node -e "let b='';process.stdin.on('data',c=>b+=c).on('end',()=>{try{JSON.parse(b);console.log('valid JSON, '+b.length+' bytes')}catch(e){console.error('NOT JSON: '+e.message);process.exit(1)}})"valid JSON, 429 bytes
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| valid JSON and a byte count | The body parses on its own | Move on to the schema, which is the next contract. |
| Unexpected token '<' | The first byte is a tag, so the body is HTML or XML | Read the status code. A gateway, not the service, wrote this body. |
| Unexpected end of JSON input | The body was cut off | Compare size_download with content-length. A truncated response is a transport fault. |
| Unexpected non-whitespace character after JSON | Two documents in one body | Usually a stray debug print, or an NDJSON stream read as one object. |
| Status 200 with a zero byte count | Empty body sold as success | A 204 belongs here. An empty 200 breaks clients that always parse. |
Common mistakes
What to check next
- How to check content-type of API response: the header this check deliberately does not trust.
- How to validate JSON against schema: once the body parses, the field types are the next thing to break.
- How to check HTTP status code: the number that explains who wrote the HTML body.
- How to test API error responses: the paths where unparsable bodies actually live.
- Json schema validator online: paste the captured body and see where it stops parsing.
FAQ
How to validate JSON format without installing anything?
Python is on most build agents: curl -s URL | python -c "import json,sys;json.load(sys.stdin)". It prints nothing on success and raises json.decoder.JSONDecodeError with a line and column on failure. The exit code is 1, so a pipeline can gate on it.
How to check if a response is valid JSON in the browser?
Open DevTools, Network tab, click the request, then the Response tab, which shows raw bytes. The Preview tab renders a tree even when parsing failed, so the raw view is the one that answers the question.
Does a 200 status mean the body is valid JSON?
No. Status and body are set independently, and a proxy can rewrite one without the other. Step 3 returns a 502 with a JSON media type and an HTML body, which is the same decoupling seen from the other direction.
Should an empty body count as valid JSON?
No. An empty string is not a JSON value, and JSON.parse('') throws. An endpoint with nothing to say should answer 204 No Content and set no content type, so the client skips parsing instead of failing at it.
Verified
Verified by Maks Vernycurl 8.21.0node 22.23.2
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
- Checker: json-schema validate JSON against provided schema
- API testing checklist
- All api checks checks
basic5 minpublished updated Maks Verny