/api/convert/csv-to-json
MCP tool csv_to_json Reads input exactly as /api/csv/format does (same delimiter and header detection) and converts it to JSON. shape=objects gives one object per row, keyed by the header names (an empty name becomes column_N; a repeated name gets _2, _3, …). shape=arrays gives one array per row, the header row first. auto is objects when there is a header. Without types every value is a string, exactly as written. With types, a column whose values are all integers or numbers becomes JSON numbers (every digit kept, even beyond 2^53), a true/false column becomes booleans, and empty values become null; text, date and mixed columns stay strings. CSV with errors (see the format operation) is rejected rather than guessed at.
Browser version (runs locally): CSV Validate & Format.
Try it
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
input required | string (≤ 50000 chars) | — | The CSV text. Send it as a string field or as a raw text/plain POST body. |
delimiter | auto | comma | semicolon | tab | pipe | auto | Field delimiter of the input: comma, semicolon, tab or pipe. auto picks the one that gives the most consistent field count over the first 50 records, ignoring quoted text, and falls back to comma. |
header | auto | yes | no | auto | Whether the first row is a header. auto says yes when its values are all text (no numbers, booleans or dates), all different, and none repeats in its column below. |
trim_fields | boolean | false | Remove spaces and tabs around unquoted values. Quoted values are always kept exactly. |
shape | auto | objects | arrays | auto | objects (needs a header), arrays, or auto: objects when there is a header, arrays otherwise. |
types | boolean | false | Convert number and boolean columns to JSON numbers and booleans, and empty values to null. |
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 |
|---|---|---|
json | string | The JSON text (2-space indentation). |
value | any | The same data as a JSON value. |
shape | "objects" | "arrays" | The shape used. |
header | array of string | null | The header row; null when there is none. |
rowCount | integer | Data rows converted. |
columns | array of object | One entry per column. |
columns[].name | string | Header name, or Column N without a header. |
columns[].type | "empty" | "integer" | "number" | "boolean" | "date" | "datetime" | "text" | "mixed" | Inferred type of the non-empty values: integer, number (integers and decimals), boolean (true/false), date (YYYY-MM-DD), datetime (ISO 8601), text, mixed, or empty when every value is empty. |
columns[].empty | integer | Empty (or missing) values. |
columns[].distinct | integer | Distinct non-empty values. |
columns[].maxLength | integer | Longest value, in Unicode code points. |
dialect | object | How the input was read. |
dialect.delimiter | "," | ";" | " " | "|" | The delimiter used to read the input (a tab is "\t"). |
dialect.delimiterDetected | boolean | True when the delimiter was detected rather than given (false when detection fell back to comma). |
dialect.lineEnding | "lf" | "crlf" | "cr" | "mixed" | "none" | Line breaks between rows (line breaks inside quoted values are not counted). |
dialect.hasHeader | boolean | True when the first row is treated as the header. |
dialect.headerDetected | boolean | True when that decision came from the heuristic (header=auto). |
dialect.bom | boolean | True when the input starts with a byte-order mark. |
Text output (format=text, text/plain): The JSON text.
Errors
Errors are JSON: {"error": "<message>", "code": "<code>", "param": "<name>"?}.
| Status | Code | When |
|---|---|---|
422 | unprocessable_input | The CSV has an error (unclosed or stray quote, text after a quote, a row with the wrong number of fields; the message gives line and column), or shape=objects and there is no header. |
400 | invalid_parameter | input is longer than 50,000 characters, or an option is not a listed value. |
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.
Objects with typed columns
curl -X POST 'https://util.caruana.me/api/convert/csv-to-json' \
-H 'content-type: application/json' \
-d '{"input":"id,name,price,in_stock,added\n1,Widget,9.99,true,2024-03-01\n2,\"Gadget, mini\",12,false,\n3,Gizmo,,true,2024-04-15\n","types":true}'
Response 200
{
"json": "[\n {\n \"id\": 1,\n \"name\": \"Widget\",\n \"price\": 9.99,\n \"in_stock\": true,\n \"added\": \"2024-03-01\"\n },\n {\n … (211 more characters)",
"value": [
{
"id": 1,
"name": "Widget",
"price": 9.99,
"in_stock": true,
"added": "2024-03-01"
},
{
"id": 2,
"name": "Gadget, mini",
"price": 12,
"in_stock": false,
"added": null
},
{
"id": 3,
"name": "Gizmo",
"price": null,
"in_stock": true,
"added": "2024-04-15"
}
],
"shape": "objects",
"header": [
"id",
"name",
"price",
"in_stock",
"added"
],
"rowCount": 3,
"columns": [
{
"name": "id",
"type": "integer",
"empty": 0,
"distinct": 3,
"maxLength": 1
},
{
"name": "name",
"type": "text",
"empty": 0,
"distinct": 3,
"maxLength": 12
},
{
"name": "price",
"type": "number",
"empty": 1,
"distinct": 2,
"maxLength": 4
},
{
"name": "in_stock",
"type": "boolean",
"empty": 0,
"distinct": 2,
"maxLength": 5
},
{
"name": "added",
"type": "date",
"empty": 1,
"distinct": 2,
"maxLength": 10
}
],
"dialect": {
"delimiter": ",",
"delimiterDetected": true,
"lineEnding": "lf",
"hasHeader": true,
"headerDetected": true,
"bom": false
}
}
Same call as an MCP tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "csv_to_json",
"arguments": {
"input": "id,name,price,in_stock,added\n1,Widget,9.99,true,2024-03-01\n2,\"Gadget, mini\",12,false,\n3,Gizmo,,true,2024-04-15\n",
"types": true
}
}
}Arrays from tab-separated data, every value a string
curl -X POST 'https://util.caruana.me/api/convert/csv-to-json' \
-H 'content-type: application/json' \
-d '{"input":"x\ty\n1\t2\n3\t4\n","shape":"arrays"}'
Response 200
{
"json": "[\n [\"x\", \"y\"],\n [\"1\", \"2\"],\n [\"3\", \"4\"]\n]",
"value": [
[
"x",
"y"
],
[
"1",
"2"
],
[
"3",
"4"
]
],
"shape": "arrays",
"header": [
"x",
"y"
],
"rowCount": 2,
"columns": [
{
"name": "x",
"type": "integer",
"empty": 0,
"distinct": 2,
"maxLength": 1
},
{
"name": "y",
"type": "integer",
"empty": 0,
"distinct": 2,
"maxLength": 1
}
],
"dialect": {
"delimiter": "\t",
"delimiterDetected": true,
"lineEnding": "lf",
"hasHeader": true,
"headerDetected": true,
"bom": false
}
}
Same call as an MCP tools/call
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "csv_to_json",
"arguments": {
"input": "x\ty\n1\t2\n3\t4\n",
"shape": "arrays"
}
}
}