An integration that worked yesterday is throwing today. The stack trace points at your parsing code, which has not changed. Here is a process that finds the cause quickly, and — just as importantly — produces evidence about which side needs to fix it.
1. Capture the actual payload
Not a summary, not a screenshot of the network tab, the bytes. Everything after this step depends on having them.
# Raw body plus status and headers
curl -sS -D headers.txt https://api.example.com/v1/users?page=1 -o body.json
cat headers.txt | head -20
# From a browser: Network tab, right-click the request, Copy > Copy as cURLCheck Content-Type first. A surprising share of “JSON parse errors” are an HTML error page, a login redirect, or a proxy timeout being handed to a JSON parser.
head -c 200 body.json2. Confirm it parses
Paste it into the validator. If it fails, you have your answer, and the line and column tell you where. Truncated responses are common: a body cut off mid-document means a timeout or a dropped connection upstream, not a formatting bug.
3. Look at the shape, not the text
Open it in the viewer. Collapse everything, then expand the branch you care about. Two questions to answer immediately:
- Is the field you are reading actually where you think it is? Wrapper objects move between API versions —
data.itemsbecomesdata.resultsand every client breaks. - Is the array you are iterating actually an array? An API that returns a single object when there is one result, and an array when there are several, is a real and common design mistake.
Copy the path of the value you need — $.data[0].preferences.theme — and compare it against the path your code walks. Half of these bugs end here.
4. Profile it
Run the response through the analyzer. It answers the three questions that cause most integration failures:
- Which fields change type between records? An
idthat is a number in 99.9% of records and a string in the rest will break a strict client on exactly the records you did not test. - Which fields are missing from some records? Optional in practice, whatever the documentation says.
- Which fields are frequently null? A field that is null a third of the time needs a null check, not an optimistic access.
5. Diff against a known-good response
If you kept a working response — and you should; save one per integration in your test fixtures — this is the fastest step in the process. Put the old one in Before and the new one in After in the diff tool.
Read the type changes first, then the removals, then the additions. A removed field or a changed type is a breaking change on their side. A new field is usually harmless, unless your parser rejects unknown properties — which is worth knowing about your own code.
Strip the volatile fields first or they will drown the output:
jq 'del(.meta.generatedAt, .requestId, .traceId)' new.json > new-clean.json6. Extract the evidence
Use JSONPath to produce the minimal reproduction. $..[?(@.id)] to see every record with an id; $.data[*].status to list the values a field actually takes. A bug report that says “$.data[3].id is the string "1004" while every other record has a number” gets fixed. “The API is returning bad data” does not.
Prevent the next one
- Save a golden response per integration and diff against it in CI. You will find breaking changes before your users do.
- Validate at the boundary with a JSON Schema, and log the validation failure with the path — not a generic parse error three layers deeper.
- Never trust `Content-Type` alone. Check the status code before parsing.
- Handle the null and the missing case explicitly for every field the analyzer flagged, rather than assuming the happy path.
- Send large identifiers as strings. Any integer over 2^53 − 1 loses precision in a JavaScript client, silently.