Payload Validator

Validate JSON, YAML, XML and CSV — with the exact line, the rule, and the fix.

Validates JSON, YAML, XML and CSV payloads and reports every problem with a 1-based line and column, a stable rule code, and a fix hint. Goes past well-formedness to the failures that parse cleanly and still break: duplicate keys, integers that lose precision past 2^53, YAML values that mean different things under 1.1 and 1.2, ragged CSV rows, and XML doctypes that carry entity-expansion risk. Detects the format when the caller does not know it.

Up to 6,000 characters here, because this form puts the payload in the URL. For anything larger, POST it to the API — the limit there is 1 MB. Nothing is stored either way.

What it looks for

Syntax errors are the easy half, and the half you would have found anyway. The findings worth a round trip are the ones where the payload parses cleanly and still means the wrong thing — a duplicate key whose second value silently wins, an ID that changes as it is read, a bare no that becomes false, an unquoted comma that shifts every column after it. Those do not produce an error anywhere, so nothing tells you until something downstream is quietly wrong.

Built for agents too

The same engine behind this page is a JSON API and an MCP server with one tool per format. Every finding carries a stable rule code, a line and column, and a fix hint.

# point it at a file
curl --data-binary @config.yaml \
  'https://payload-validator.gumballtools.com/api/v1/validate?format=yaml'

API and MCP setup · llms.txt

Questions people actually ask

Why does it call my document invalid when it parses fine?

Because "parses" and "has one meaning" are different questions, and the result reports both separately. `parseable` tells you whether a conforming parser accepts the input. `valid` tells you whether the input is unambiguous. A JSON object with two identical keys is parseable and not valid: every parser accepts it, and they disagree about which value wins — JavaScript and Python keep the last, some Go and Rust decoders error. Calling that valid would defeat the point of the tool, and calling it unparseable would be false, so both booleans are returned and you can use whichever matches your question.

What is the Norway problem?

In YAML 1.1, the unquoted words no, yes, on, off, y and n are booleans. So a list of ISO country codes containing `no` for Norway becomes ["se", false, "dk"] when read by PyYAML, which implements 1.1. YAML 1.2 dropped that rule, so Go's yaml.v3 and the yaml npm package read the same document as ["se", "no", "dk"]. Nothing in the file says which is correct — the answer depends entirely on the loader. Quoting the value pins it to a string, which is exactly what quoting means in YAML.

Why does my 64-bit ID come back as a different number?

JSON numbers are IEEE-754 doubles in nearly every parser, which represent integers exactly only up to 9007199254740991, or 2^53-1. Above that, some integers are not representable and get rounded to the nearest one that is: 9007199254740993 parses as 9007199254740992. Snowflake IDs from Twitter and Discord, and most database bigints, are 64-bit and sit squarely in the lossy range. Nothing errors, so an ID can quietly come to point at a different row. It is why the Twitter API added id_str alongside id. The fix is to send large integers as strings. This validator proves the loss using exact BigInt arithmetic and shows you the value you would actually receive.

Does it check my payload against a JSON Schema, XSD or DTD?

No. It checks that a payload is well-formed and free of silent-corruption hazards, not that it matches a schema. Those are genuinely different jobs: schema validation answers "does this document have the fields my code expects", and this answers "does this document mean one thing". A payload can satisfy a schema perfectly and still have a duplicate key that made the schema check pass by keeping the wrong value. It also never fetches a remote schema or DTD, because doing so is one of the vulnerabilities it reports.

Is my payload stored anywhere?

No. Payloads are validated in memory and discarded when the response is sent. Nothing is written to a database, a log, or a cache. The site records that a call happened — the format, the timestamp, and a coarse caller class for quota accounting — but never the content. If you are validating something you cannot send to a third party at all, the engine is a pure function in an open-source repository and runs offline.

Will it resolve an XML external entity to check whether it works?

No, and that is deliberate. The whole point of the XXE finding is that a parser which resolves external entities can be made to read local files or reach internal services. A validator that resolved them in order to report on them would be performing the attack rather than detecting it. Entity declarations are parsed and reported with the URI they point at; nothing is fetched. The same applies to alias bombs in YAML, which are detected by counting the expansion rather than performing it.

Why does it guess my CSV delimiter, and can I stop it?

Because getting it wrong is silent. Wherever the comma is the decimal separator, CSV exports are semicolon-separated, and reading such a file as comma-separated produces one column of nonsense with no error at all. So the delimiter is sniffed from the header — ignoring quoted regions, so their contents cannot vote — and always reported in the stats, with a warning when the guess was a close call. Pass `delimiter` to remove the guess entirely.

Why not just use JSON.parse in a try/catch?

It answers a narrower question than people think it does. JSON.parse cannot report duplicate keys, because the earlier value is gone before it returns. It cannot report integer precision loss, because the rounding has already happened. It reports one error and stops, so a document with six problems takes six round trips. And its message wording differs between V8, JavaScriptCore and SpiderMonkey, so you cannot branch on it. A scanner reports every finding in one pass, at an exact line and column, with a stable rule code you can assert on.

What does the 1 MB limit mean in practice, and how do I send a large file?

The API accepts up to 1,000,000 bytes, measured as encoded bytes rather than characters, so a document of non-Latin text gets the limit it says. POST the file as the raw request body with any non-JSON content type and put the format in the query string: curl --data-binary @config.yaml '…/api/v1/validate?format=yaml'. The HTML form is capped much lower, at 6,000 characters, because a GET form puts the payload in the URL and requests over roughly 8 KB are rejected before any of our code runs. For a CSV larger than the limit, the first few thousand rows will surface a ragged-row problem just as well as all of them.