API and MCP setup
The same engine behind the web page is exposed as markdown, a JSON API, and an MCP server. All four surfaces are thin wrappers over one pure function, so they cannot disagree.
MCP server
Streamable HTTP at https://payload-validator.gumballtools.com/api/mcp. No authentication required.
Claude Code
claude mcp add --transport http payload-validator https://payload-validator.gumballtools.com/api/mcpClaude Desktop or Cursor
{
"mcpServers": {
"payload-validator": {
"type": "http",
"url": "https://payload-validator.gumballtools.com/api/mcp"
}
}
}Verify it works
curl -X POST 'https://payload-validator.gumballtools.com/api/mcp' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'JSON API
Every endpoint accepts GET with query parameters or POST with a JSON body. CORS is open. Full machine-readable description at /.well-known/openapi.json.
# named format
curl 'https://payload-validator.gumballtools.com/api/v1/validate?format=json&input=%7B%22a%22%3A1%2C%22a%22%3A2%7D'
# let it detect the format
curl 'https://payload-validator.gumballtools.com/api/v1/validate?input=country%3A%20no'
# point it at a file — up to 1 MB, no JSON escaping
curl --data-binary @config.yaml 'https://payload-validator.gumballtools.com/api/v1/validate?format=yaml'
# CSV with an explicit delimiter, so nothing is guessed
curl --data-binary @export.csv 'https://payload-validator.gumballtools.com/api/v1/validate?format=csv&delimiter=%3B'Parameters
input— the raw document text, not a parsed object. Required. Up to 1,000,000 bytes, measured as encoded bytes rather than characters.format—json,yaml,xml,csv, orauto. Defaults toauto. Name it when you know it: detection cannot then be corrected out from under you.delimiter— CSV only, a single character. Omit to sniff it from the header.hasHeader— CSV only, defaults to true. Passfalseand rows are compared against the first row instead.
An invalid payload is a 200
Check the response body, not the status code. A 4xx means your request was malformed — no input, or an unknown format. A payload being broken is a successful validation, and conflating the two leaves a caller unable to tell which one happened.
The two booleans
valid means no errors — well-formed and unambiguous. parseable means a conforming parser accepts it. They are not the same question, and the gap between them is the whole point: {"port":1,"port":2} is parseable and not valid, because every parser accepts it and they disagree about which value wins.
Each diagnostic
Carries a rule code that is stable and safe to branch on (messages are not), a 1-based line and column, an excerpt with a caret under the column, a fixHint, and blocksParse — false on an error meaning the document parses and is ambiguous. The list is capped at 200, with stats.diagnosticsOmitted saying how many were dropped.
What it does not do
- No schema validation. It checks that a payload is well-formed and unambiguous, not that it matches a JSON Schema, XSD, DTD or RelaxNG.
- No resolution of XML external entities, and no fetching of DTDs or remote schemas. That is the vulnerability being reported — performing it would be the exploit.
- No alias-bomb expansion. It is detected by counting, not by expanding.
- No storage. Payloads are validated in memory and discarded with the response.
Markdown instead of HTML
Every page has a markdown representation at the same canonical URL. Send Accept: text/markdown or append ?format=md. Responses set Vary: Accept. Do not parse the HTML.
curl -H 'Accept: text/markdown' 'https://payload-validator.gumballtools.com/'Errors
Every failure returns the same shape, with a stable code and a hint describing what to change before retrying.
{
"error": {
"code": "invalid_input",
"message": "...",
"fix_hint": "...",
"docs": "https://payload-validator.gumballtools.com/docs"
}
}Rate limits and pricing
- Free: 250 calls per UTC day per caller, on every surface. Quotas apply to humans and agents alike.
- Past the quota: HTTP 402 with an x402 v1 payment requirement — $0.001 per call in USDC on base.
- Settlement is not live yet. While that is true the 402 body carries
payment_enabled: false, so do not attempt payment — wait for the reset time inquota.resetsAt.