/api/json/format
MCP tool json_format Parses input strictly as RFC 8259 JSON and reports the first syntax error with line, column, offset, JSON Pointer and an excerpt; messages name the usual mistakes (trailing commas, single quotes, unquoted keys, comments, NaN, leading zeros) and say how to fix them. Invalid JSON is a successful response with valid: false, so check valid. For valid JSON it returns the document re-indented or minified, optionally with keys sorted, plus statistics. Only whitespace (and, with sort_keys, member order) changes: strings and numbers are copied exactly as written, so 1.0, 1e5 and 20-digit integers survive. Duplicate keys, which JSON.parse silently collapses, are listed with JSON Pointers; the output keeps the last value at the first position, as JSON.parse does. Nesting deeper than 500 levels is rejected.
Browser version (runs locally): JSON Format & Validate.
Try it
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
input required | string (≤ 500000 chars) | — | The JSON text. Send it as a string field ({"input": "{\"a\":1}"}) or as a raw text/plain POST body. A byte order mark is reported as an error. |
indent | 2 | 4 | tab | none | 2 | JSON output indentation: 2 or 4 spaces, tab, or none for minified output (no whitespace at all). |
sort_keys | boolean | false | Sort object keys recursively, by UTF-16 code units (the order RFC 8785 uses). Array order never changes. |
Also accepted on every endpoint: format (json | text).
Parameters may come from the query string, a JSON object body, an urlencoded or multipart form,
or (for input) a text/plain body.
Global parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
format | json | text | json | json returns the documented object. text returns only the primary value as text/plain (see "Text output"). |
Response 200 application/json
| Field | Type | Description |
|---|---|---|
valid | boolean | True when input is one well-formed JSON value. |
error | object | null | The first syntax error. |
error.message | string | What is wrong and how to fix it. |
error.line | integer | 1-based line number. |
error.column | integer | 1-based column, counted in Unicode code points. |
error.offset | integer | 0-based offset from the start of the input, in Unicode code points. |
error.pointer | string | JSON Pointer (RFC 6901) of the value being read when the error was found; "" is the root. |
error.excerpt | string | The offending line (windowed to about 80 characters, tabs shown as spaces) and a second line with a caret under the error. |
type | string | null | Type of the top-level value: object, array, string, number, boolean or null. |
output | string | null | The formatted JSON; null when invalid. |
duplicateKeys | array of object | Every repeated key after its first occurrence in the same object. |
duplicateKeys[].key | string | The repeated key. |
duplicateKeys[].pointer | string | JSON Pointer of the repeated member. |
duplicateKeys[].line | integer | 1-based line number. |
duplicateKeys[].column | integer | 1-based column, counted in Unicode code points. |
duplicateKeys[].offset | integer | 0-based offset of the repeat, in code points. |
duplicateKeys[].firstLine | integer | Line of the first occurrence. |
duplicateKeys[].firstColumn | integer | Column of the first occurrence. |
warnings | array of object | Valid-but-risky findings: integers beyond 2^53 − 1, numbers that overflow a double, unpaired surrogate escapes, duplicate keys. At most 100 are listed. |
warnings[].message | string | Plain-language finding. |
warnings[].pointer | string | JSON Pointer of the value concerned. |
warnings[].line | integer | 1-based line number. |
warnings[].column | integer | 1-based column, counted in Unicode code points. |
warnings[].offset | integer | 0-based offset in code points. |
stats | object | null | Counts for valid input; null when invalid. |
stats.depth | integer | Maximum nesting depth: 0 for a scalar, 1 for a flat object or array. |
stats.objects | integer | Number of objects. |
stats.arrays | integer | Number of arrays. |
stats.members | integer | Number of object members, duplicates included. |
stats.strings | integer | Number of string values (keys are not counted). |
stats.numbers | integer | Number of numbers. |
stats.booleans | integer | Number of true/false values. |
stats.nulls | integer | Number of nulls. |
stats.inputBytes | integer | Size of input in UTF-8 bytes. |
stats.outputBytes | integer | Size of output in UTF-8 bytes. |
Text output (format=text, text/plain): The formatted JSON, or for invalid input one line: invalid: line L, column C: message.
Errors
Errors are JSON: {"error": "<message>", "code": "<code>", "param": "<name>"?}.
| Status | Code | When |
|---|---|---|
400 | invalid_parameter | input is longer than 500,000 characters, or indent is not one of the listed values. Invalid JSON is NOT an error: it returns 200 with valid: false. |
400 | missing_parameter | A required parameter is absent. |
400 | unknown_parameter | A parameter name is not in the table above (typos are rejected, not ignored). |
413 | payload_too_large | The request body exceeds 1 MiB. |
405 | method_not_allowed | A method other than GET, POST or OPTIONS. |
Behaviour
- Idempotent: the same arguments always return the same result.
-
Requires a client ID and secret (authentication). Each call is
recorded as one metadata row against your client ID; inputs and outputs are never stored or
logged. Responses carry
cache-control: no-store. - CORS: any origin may call this endpoint from a browser, using your credentials.
Examples
Responses below were produced by running each request through this code at build time. Long strings are shortened.
Pretty-print with sorted keys
curl -X POST 'https://util.caruana.me/api/json/format' \
-H 'content-type: application/json' \
-d '{"input":"{\"b\":[1,2,{\"d\":1.0,\"c\":null}],\"a\":\"x\"}","sort_keys":true}'
Response 200
{
"valid": true,
"error": null,
"type": "object",
"output": "{\n \"a\": \"x\",\n \"b\": [\n 1,\n 2,\n {\n \"c\": null,\n \"d\": 1.0\n }\n ]\n}",
"duplicateKeys": [],
"warnings": [],
"stats": {
"depth": 3,
"objects": 2,
"arrays": 1,
"members": 4,
"strings": 1,
"numbers": 3,
"booleans": 0,
"nulls": 1,
"inputBytes": 38,
"outputBytes": 86
}
}
Same call as an MCP tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "json_format",
"arguments": {
"input": "{\"b\":[1,2,{\"d\":1.0,\"c\":null}],\"a\":\"x\"}",
"sort_keys": true
}
}
}Minify
curl -X POST 'https://util.caruana.me/api/json/format' \
-H 'content-type: application/json' \
-d '{"input":"{\n \"a\": 1,\n \"b\": [true, false]\n}","indent":"none"}'
Response 200
{
"valid": true,
"error": null,
"type": "object",
"output": "{\"a\":1,\"b\":[true,false]}",
"duplicateKeys": [],
"warnings": [],
"stats": {
"depth": 2,
"objects": 1,
"arrays": 1,
"members": 2,
"strings": 0,
"numbers": 1,
"booleans": 2,
"nulls": 0,
"inputBytes": 34,
"outputBytes": 24
}
}
Same call as an MCP tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "json_format",
"arguments": {
"input": "{\n \"a\": 1,\n \"b\": [true, false]\n}",
"indent": "none"
}
}
}A syntax error (trailing comma)
curl -X POST 'https://util.caruana.me/api/json/format' \
-H 'content-type: application/json' \
-d '{"input":"{\n \"name\": \"demo\",\n \"tags\": [\"a\", \"b\",]\n}"}'
Response 200
{
"valid": false,
"error": {
"message": "Trailing comma before \"]\": JSON does not allow a comma after the last element. Remove it.",
"pointer": "/tags",
"line": 3,
"column": 21,
"offset": 40,
"excerpt": " \"tags\": [\"a\", \"b\",]\n ^"
},
"type": null,
"output": null,
"duplicateKeys": [],
"warnings": [],
"stats": null
}
Same call as an MCP tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "json_format",
"arguments": {
"input": "{\n \"name\": \"demo\",\n \"tags\": [\"a\", \"b\",]\n}"
}
}
}Duplicate keys and a large integer
curl -X POST 'https://util.caruana.me/api/json/format' \
-H 'content-type: application/json' \
-d '{"input":"{\"id\": 12345678901234567890, \"id\": 2}"}'
Response 200
{
"valid": true,
"error": null,
"type": "object",
"output": "{\n \"id\": 2\n}",
"duplicateKeys": [
{
"key": "id",
"pointer": "/id",
"line": 1,
"column": 30,
"offset": 29,
"firstLine": 1,
"firstColumn": 2
}
],
"warnings": [
{
"message": "Integer 12345678901234567890 is beyond ±(2^53 − 1); parsers that use IEEE 754 doubles (JavaScript, many others) read it … (68 more characters)",
"pointer": "/id",
"line": 1,
"column": 8,
"offset": 7
}
],
"stats": {
"depth": 1,
"objects": 1,
"arrays": 0,
"members": 2,
"strings": 0,
"numbers": 2,
"booleans": 0,
"nulls": 0,
"inputBytes": 37,
"outputBytes": 13
}
}
Same call as an MCP tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "json_format",
"arguments": {
"input": "{\"id\": 12345678901234567890, \"id\": 2}"
}
}
}