Data Cleaner: Zero-touch tables

> A table-processing tool for AI agents and people. Drop a file, get a clean table. Or call the cleaning API directly.

Site: https://data-cleaner.com/ API base: https://data-cleaner.com/clean/v1 OpenAPI: https://data-cleaner.com/clean/v1/openapi.json Swagger UI: https://data-cleaner.com/clean/v1/docs

What it does

Data Cleaner takes a messy table and returns a clean one. It infers the type of every column, such as date, number, currency, address or free text, and rewrites each cell in a canonical form, keeping a status per cell so that nothing is changed silently. The same engine answers over a REST API, one value or one column at a time, so a script, a spreadsheet or an agent can use it without the browser.

Two ways to use it

In the browser

Drag a CSV or Excel file onto the page. The columns are typed, the cells are cleaned, and the result is shown as a table you can inspect, correct and download.

Over the REST API

Every endpoint lives under https://data-cleaner.com/clean/v1. Send a value or a column, get JSON back. No key is needed to start; a key lifts the per-request limit and unlocks the datetime endpoints. The OpenAPI document at https://data-cleaner.com/clean/v1/openapi.json is the reference.

Tiers

Limits

Getting a key

Keys are issued by hand. Write to egaere (at) ethz.ch with a line on what you want to clean.

About the API (SerenTec Cleaning API 0.2.0)

Clean a value, a column or a datetime string, or infer the type of a value or an array. Every endpoint lives under /clean/v1. Every call is proxied to the cleaning fleet; this service holds no database and no session state.

Authentication. A key is optional for /column, /value, /type and /types and required for /datetime. Send it in the X-API-Key header. The api_key query parameter exists for Excel WEBSERVICE(), which cannot set headers; a key in a URL lands in server logs and browser history, so prefer the header everywhere else. A wrong key is served as anonymous, not refused.

Tiers. An anonymous caller is served under a per-request cap; a valid key lifts it. A request above its cap is refused, never truncated: 401 for an anonymous caller (a key would lift the cap), 413 for a keyed caller. GET /clean/v1/info reports both caps and the accepted entity hints.

Browsers. CORS allows any origin, methods GET and POST, so a page or a browser-side agent may call this API directly.

Accepted values of the entity hint, matched case-insensitively: BOOK_NAME, COUNTRY, CREDIT_CARD, CURRENCY, DATE, DATETIME, FLOAT, IBAN, INTEGER, ISBN, JOB, JSON, LANGUAGE, NULL, NUMBER, PERSON_NAME, PLACE, PYTHON_OBJECT, QUANTITY, STRING, SWIFT, TEXT, TIME, TSID, URL, UUID, XML, YEAR, YEAR_MONTH, YEAR_QUARTER, YEAR_WEEK, YEAR_WEEK_DAY.

Hints with a dedicated cleaner: CURRENCY, DATE, DATETIME, FLOAT, INTEGER, NUMBER, PERSON_NAME, PLACE, QUANTITY, TIME. A hint outside this set types the values and returns them as text.

Endpoints

MethodPathWhat it doesKey
POSThttps://data-cleaner.com/clean/v1/columnClean a column of valuesoptional
GEThttps://data-cleaner.com/clean/v1/valueClean one valueoptional
GEThttps://data-cleaner.com/clean/v1/typeInfer the type of one value without cleaning itoptional
POSThttps://data-cleaner.com/clean/v1/typesInfer the common type of an array of values without cleaning themoptional
GEThttps://data-cleaner.com/clean/v1/datetimeClean one datetime stringrequired
POSThttps://data-cleaner.com/clean/v1/datetimeClean a column of datetime stringsrequired
GEThttps://data-cleaner.com/clean/v1/healthLiveness: this process is upnone
GEThttps://data-cleaner.com/clean/v1/infoThe two caps, the accepted entity hints and the auth schemenone

Status codes

A wrong key is served as anonymous, not refused; only the key-required endpoints answer 401 to a missing key.

na_code legend

Every cleaned cell carries na_code: 0 valid, 1 missing, 2 invalid, 3 error, 4 warning.

Endpoint reference

POST /column

POST https://data-cleaner.com/clean/v1/column (key: optional)

Clean a column of values. The column's type is inferred from the values, or pinned by the entity hint; every value comes back as a CleanedCell in the input order. A hint the fleet refuses is reported as text with the reason in each cell's message and na_code 4 (warning), so an untouched value is never mistaken for a cleaned one.

Query parameters:

NameTypeRequiredDescription
api_keystring or nullnoAPI key; prefer the X-API-Key header

Headers: X-API-Key (the API key)

Request body (JSON):

CleanRequest: Request to clean a column of values (POST /column). Sent from Excel via VBA, curl or Python.

FieldTypeRequiredDefaultDescription
api_keystring or nullnoAPI key for clients that cannot set headers; the X-API-Key header is preferred
column_namestringno"column"A label for the column; appears in logs only
entitystring or nullnoEntity type hint, case-insensitive (e.g. "date", "currency", "number"); omit to auto-detect. GET /info lists the accepted values.
valuesarray of stringyesThe values to clean, one string per cell

Response 200 (JSON):

CleanResult: Full cleaning result for a column.

FieldTypeRequiredDefaultDescription
entity_confidencenumber or nullnoInference confidence in [0, 1]; null when a hint skipped inference
entity_typestring or nullnoThe entity type the column was cleaned as: inferred, or the hint when it was honoured
resultsarray of CleanedCellyesOne cell per input value, same order

Other status codes:

curl:


curl -X POST https://data-cleaner.com/clean/v1/column -H "Content-Type: application/json" \
     -d '{"values": ["1.1.2024", "2024-02-01", "Feb 3, 2024"], "entity": "date"}'

Python:


import requests

r = requests.post("https://data-cleaner.com/clean/v1/column", json={"values": ["1.1.2024", "2024-02-01", "Feb 3, 2024"], "entity": "date"})
print(r.status_code, r.json())

GET /value

GET https://data-cleaner.com/clean/v1/value (key: optional)

Clean one value. The type is inferred from the value alone, or pinned by the entity hint. Always JSON, so a VBA or Excel client can convert by type. A fleet fault is reported in-band as na_code 3 with the message, not as a 5xx, so a spreadsheet cell shows the reason rather than an error.

Query parameters:

NameTypeRequiredDescription
valuestringyesValue to clean
entitystring or nullnoEntity type hint, case-insensitive (e.g. "date", "currency", "number"); omit to auto-detect. GET /info lists the accepted values.
formatstring or nullnoReserved; ignored
api_keystring or nullnoAPI key; prefer the X-API-Key header

Headers: X-API-Key (the API key)

Response 200 (JSON):

CleanedValue: One cleaned value (GET /value, GET /datetime). Always JSON: VBA needs the type to convert.

FieldTypeRequiredDefaultDescription
cleanstringyesThe cleaned value; the input unchanged when nothing was cleaned
confidencenumber or nullnoParser confidence in [0, 1], when available
is_ambiguousbooleanyesTrue when more than one reading is plausible
messagestring or nullnoHuman-readable status
na_codeintegeryesStatus code: 0 valid, 1 missing, 2 invalid, 3 error, 4 warning
typestringyesThe entity type inferred, or the hint when honoured; "" if unknown

Other status codes:

curl:


curl -G https://data-cleaner.com/clean/v1/value --data-urlencode "value=1.1.2024"

Python:


import requests

r = requests.get("https://data-cleaner.com/clean/v1/value", params={"value": "1.1.2024"})
print(r.status_code, r.json())

GET /type

GET https://data-cleaner.com/clean/v1/type (key: optional)

Infer the type of one value without cleaning it. The inference block carries the predicted class, its probability and the alternatives considered. A fleet fault is reported in-band as na_code 3 with the message, not as a 5xx.

Query parameters:

NameTypeRequiredDescription
valuestringyesValue for which type must be inferred
formatstring or nullnoReserved; ignored
api_keystring or nullnoAPI key; prefer the X-API-Key header

Headers: X-API-Key (the API key)

Response 200 (JSON):

TypeResult: Type inference result (GET /type, POST /types).

FieldTypeRequiredDefaultDescription
inferenceobjectyesThe full inference block: predicted_class, class_p and the alternatives considered
typestringyesThe inferred entity type; "" when none was inferred

Other status codes:

curl:


curl -G https://data-cleaner.com/clean/v1/type --data-urlencode "value=2024-02-01"

Python:


import requests

r = requests.get("https://data-cleaner.com/clean/v1/type", params={"value": "2024-02-01"})
print(r.status_code, r.json())

POST /types

POST https://data-cleaner.com/clean/v1/types (key: optional)

Infer the common type of an array of values without cleaning them. One verdict for the whole array, the way a column is typed. A fleet fault is reported in-band as na_code 3 with the message, not as a 5xx.

Query parameters:

NameTypeRequiredDescription
api_keystring or nullnoAPI key; prefer the X-API-Key header

Headers: X-API-Key (the API key)

Request body (JSON):

TypeArrayRequest: Request to infer the type of an array of values (POST /types).

FieldTypeRequiredDefaultDescription
api_keystring or nullnoAPI key for clients that cannot set headers; the X-API-Key header is preferred
column_idstring or nullno
column_namestringno"range"A label for the array; appears in logs only
dataarray of stringyesThe values whose common type is to be inferred
formatstring or nullnoReserved; ignored
job_uuidstring or nullno
page_numintegerno0
session_uuidstring or nullno
skip_cleaningbooleannofalse

Response 200 (JSON):

TypeResult: Type inference result (GET /type, POST /types).

FieldTypeRequiredDefaultDescription
inferenceobjectyesThe full inference block: predicted_class, class_p and the alternatives considered
typestringyesThe inferred entity type; "" when none was inferred

Other status codes:

curl:


curl -X POST https://data-cleaner.com/clean/v1/types -H "Content-Type: application/json" \
     -d '{"data": ["CH", "DE", "FR"]}'

Python:


import requests

r = requests.post("https://data-cleaner.com/clean/v1/types", json={"data": ["CH", "DE", "FR"]})
print(r.status_code, r.json())

GET /datetime

GET https://data-cleaner.com/clean/v1/datetime (key: required)

Clean one datetime string. Same answer shape as GET /value with entity=datetime, but a valid API key is required.

Query parameters:

NameTypeRequiredDescription
valuestringyesDatetime string to clean
api_keystring or nullnoAPI key; prefer the X-API-Key header

Headers: X-API-Key (the API key)

Response 200 (JSON):

CleanedValue: One cleaned value (GET /value, GET /datetime). Always JSON: VBA needs the type to convert.

FieldTypeRequiredDefaultDescription
cleanstringyesThe cleaned value; the input unchanged when nothing was cleaned
confidencenumber or nullnoParser confidence in [0, 1], when available
is_ambiguousbooleanyesTrue when more than one reading is plausible
messagestring or nullnoHuman-readable status
na_codeintegeryesStatus code: 0 valid, 1 missing, 2 invalid, 3 error, 4 warning
typestringyesThe entity type inferred, or the hint when honoured; "" if unknown

Other status codes:

curl:


curl -G https://data-cleaner.com/clean/v1/datetime -H "X-API-Key: YOUR_API_KEY" --data-urlencode "value=19:34, Tuesday 31 March 2026"

Python:


import requests

r = requests.get("https://data-cleaner.com/clean/v1/datetime", params={"value": "19:34, Tuesday 31 March 2026"}, headers={"X-API-Key": "YOUR_API_KEY"})
print(r.status_code, r.json())

POST /datetime

POST https://data-cleaner.com/clean/v1/datetime (key: required)

Clean a column of datetime strings. Same answer shape as POST /column with entity=datetime, but a valid API key is required. The body is a bare JSON array, so the key travels in the X-API-Key header or the api_key query parameter.

Query parameters:

NameTypeRequiredDescription
api_keystring or nullnoAPI key; prefer the X-API-Key header

Headers: X-API-Key (the API key)

Request body (JSON):

JSON array of string.

Response 200 (JSON):

CleanResult: Full cleaning result for a column.

FieldTypeRequiredDefaultDescription
entity_confidencenumber or nullnoInference confidence in [0, 1]; null when a hint skipped inference
entity_typestring or nullnoThe entity type the column was cleaned as: inferred, or the hint when it was honoured
resultsarray of CleanedCellyesOne cell per input value, same order

Other status codes:

curl:


curl -X POST https://data-cleaner.com/clean/v1/datetime -H "Content-Type: application/json" -H "X-API-Key: YOUR_API_KEY" \
     -d '["19:34, Tuesday 31 March 2026", "2024-02-01 08:00"]'

Python:


import requests

r = requests.post("https://data-cleaner.com/clean/v1/datetime", json=["19:34, Tuesday 31 March 2026", "2024-02-01 08:00"], headers={"X-API-Key": "YOUR_API_KEY"})
print(r.status_code, r.json())

GET /health

GET https://data-cleaner.com/clean/v1/health (key: none)

Liveness: this process is up. Says nothing about the fleet behind it.

Response 200 (JSON):

HealthResult: Liveness (GET /health): this process answers. Says nothing about the fleet behind it.

FieldTypeRequiredDefaultDescription
prefixstringyesThe URL prefix every endpoint lives under
servicestringyesService name
statusstringyes"ok" when this process is up

curl:


curl https://data-cleaner.com/clean/v1/health

Python:


import requests

r = requests.get("https://data-cleaner.com/clean/v1/health")
print(r.status_code, r.json())

GET /info

GET https://data-cleaner.com/clean/v1/info (key: none)

The two caps, the accepted entity hints and the auth scheme. Read it before the first request. No key needed; no addresses, no key material. The entity hints are read from the fleet (CleanColumn's parser registry) on every call, so a parser added to the fleet appears here without a change to this service.

Response 200 (JSON):

InfoResult: What a caller needs before the first request (GET /info): caps, hints, the auth scheme.

FieldTypeRequiredDefaultDescription
anonymous_max_valuesintegeryesValues per request without a key; 0 means a key is mandatory
authobjectyesHow a key travels: header (preferred) and query parameter
cleaning_entitiesarray of stringyesThe subset with a dedicated cleaner; a hint outside it types the values and returns them as text
corsobjectyesBrowser policy: any origin may call, GET and POST
entitiesarray of stringyesAccepted values of the entity hint, as the fleet registers them; matched case-insensitively
key_requiredarray of stringyesPaths under the prefix that refuse an anonymous caller (401)
keyed_max_valuesintegeryesValues per request with a valid key
openapistringyesPath of the OpenAPI document under the prefix
prefixstringyesThe URL prefix every endpoint lives under
servicestringyesService name
versionstringyesService version

Other status codes:

curl:


curl https://data-cleaner.com/clean/v1/info

Python:


import requests

r = requests.get("https://data-cleaner.com/clean/v1/info")
print(r.status_code, r.json())

Excel

WEBSERVICE() cannot set headers, so the key travels in the query string; a key in a URL lands in server logs and browser history.


=WEBSERVICE("https://data-cleaner.com/clean/v1/value?value=" & ENCODEURL(A1))
=WEBSERVICE("https://data-cleaner.com/clean/v1/datetime?value=" & ENCODEURL(A1) & "&api_key=YOUR_API_KEY")

Data handling

Contact

Edward Gaere, ETH Zurich. egaere (at) ethz.ch