API reference

Format and validate JSON

Check that text is valid JSON (RFC 8259), pretty-print or minify it, and find duplicate keys.

GET POST /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

Credentials
Remember these credentials

Not saved: you will paste them again next time.

No credentials yet? Get a client ID and secret. They are sent only to this site, in an Authorization: Basic header.

POST /api/json/format with a JSON body

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}"
    }
  }
}