> Turn any messy data or URL into a clean, typed table. Use it before analysing a CSV, Excel, JSON or XML file. Every column is typed, every cell is normalised, and each cell carries a status, so nothing is changed silently.
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
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.
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.
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.
X-API-Key header; the limit is lifted to the keyed cap and the datetime endpoints open. A request above the keyed cap is refused with 413./column, /value, /type and /types, and required for /datetime.X-API-Key header is preferred. 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.Keys are issued by hand. Write to egaere (at) ethz.ch with a line on what you want to clean.
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: BINARY, BOOK_NAME, COUNTRY, CREDIT_CARD, CURRENCY, DATE, DATETIME, FLOAT, GEOMETRY_GEOJSON, GEOMETRY_WKB, HEX_ID, 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, YEAR, YEAR_MONTH, YEAR_QUARTER, YEAR_WEEK, YEAR_WEEK_DAY. A hint outside this set types the values and returns them as text.
| Method | Path | What it does | Key |
|---|---|---|---|
| POST | https://data-cleaner.com/clean/v1/column | Clean a column of values | optional |
| GET | https://data-cleaner.com/clean/v1/value | Clean one value | optional |
| GET | https://data-cleaner.com/clean/v1/type | Infer the type of one value without cleaning it | optional |
| POST | https://data-cleaner.com/clean/v1/types | Infer the common type of an array of values without cleaning them | optional |
| GET | https://data-cleaner.com/clean/v1/datetime | Clean one datetime string | required |
| POST | https://data-cleaner.com/clean/v1/datetime | Clean a column of datetime strings | required |
| GET | https://data-cleaner.com/clean/v1/health | Liveness: this process is up | none |
| GET | https://data-cleaner.com/clean/v1/info | The two caps, the accepted entity hints and the auth scheme | none |
A wrong key is served as anonymous, not refused; only the key-required endpoints answer 401 to a missing key.
Every cleaned cell carries na_code: 0 valid, 1 missing, 2 invalid, 3 error, 4 warning.
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:
| Name | Type | Required | Description |
|---|---|---|---|
api_key | string or null | no | API 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.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
api_key | string or null | no | API key for clients that cannot set headers; the X-API-Key header is preferred | |
column_name | string | no | "column" | A label for the column; appears in logs only |
entity | string or null | no | Entity type hint, case-insensitive (e.g. "date", "currency", "number"); omit to auto-detect. GET /info lists the accepted values. | |
values | array of string | yes | The values to clean, one string per cell |
Response 200 (JSON):
CleanResult: Full cleaning result for a column.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
entity_confidence | number or null | no | Inference confidence in [0, 1]; null when a hint skipped inference | |
entity_type | string or null | no | The entity type the column was cleaned as: inferred, or the hint when it was honoured | |
results | array of CleanedCell | yes | One 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 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:
| Name | Type | Required | Description |
|---|---|---|---|
value | string | yes | Value to clean |
entity | string or null | no | Entity type hint, case-insensitive (e.g. "date", "currency", "number"); omit to auto-detect. GET /info lists the accepted values. |
format | string or null | no | Reserved; ignored |
api_key | string or null | no | API 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.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
clean | string | yes | The cleaned value; the input unchanged when nothing was cleaned | |
confidence | number or null | no | Parser confidence in [0, 1], when available | |
is_ambiguous | boolean | yes | True when more than one reading is plausible | |
message | string or null | no | Human-readable status | |
na_code | integer | yes | Status code: 0 valid, 1 missing, 2 invalid, 3 error, 4 warning | |
type | string | yes | The 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 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:
| Name | Type | Required | Description |
|---|---|---|---|
value | string | yes | Value for which type must be inferred |
format | string or null | no | Reserved; ignored |
api_key | string or null | no | API 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).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
inference | object | yes | The full inference block: predicted_class, class_p and the alternatives considered | |
type | string | yes | The 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 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:
| Name | Type | Required | Description |
|---|---|---|---|
api_key | string or null | no | API 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).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
api_key | string or null | no | API key for clients that cannot set headers; the X-API-Key header is preferred | |
column_id | string or null | no | ||
column_name | string | no | "range" | A label for the array; appears in logs only |
data | array of string | yes | The values whose common type is to be inferred | |
format | string or null | no | Reserved; ignored | |
job_uuid | string or null | no | ||
page_num | integer | no | 0 | |
session_uuid | string or null | no | ||
skip_cleaning | boolean | no | false |
Response 200 (JSON):
TypeResult: Type inference result (GET /type, POST /types).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
inference | object | yes | The full inference block: predicted_class, class_p and the alternatives considered | |
type | string | yes | The 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 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:
| Name | Type | Required | Description |
|---|---|---|---|
value | string | yes | Datetime string to clean |
api_key | string or null | no | API 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.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
clean | string | yes | The cleaned value; the input unchanged when nothing was cleaned | |
confidence | number or null | no | Parser confidence in [0, 1], when available | |
is_ambiguous | boolean | yes | True when more than one reading is plausible | |
message | string or null | no | Human-readable status | |
na_code | integer | yes | Status code: 0 valid, 1 missing, 2 invalid, 3 error, 4 warning | |
type | string | yes | The 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 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:
| Name | Type | Required | Description |
|---|---|---|---|
api_key | string or null | no | API 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.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
entity_confidence | number or null | no | Inference confidence in [0, 1]; null when a hint skipped inference | |
entity_type | string or null | no | The entity type the column was cleaned as: inferred, or the hint when it was honoured | |
results | array of CleanedCell | yes | One 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 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.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
prefix | string | yes | The URL prefix every endpoint lives under | |
service | string | yes | Service name | |
status | string | yes | "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 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.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
anonymous_max_values | integer | yes | Values per request without a key; 0 means a key is mandatory | |
auth | object | yes | How a key travels: header (preferred) and query parameter | |
cleaning_entities | array of string | yes | The subset with a dedicated cleaner; a hint outside it types the values and returns them as text | |
cors | object | yes | Browser policy: any origin may call, GET and POST | |
entities | array of string | yes | Accepted values of the entity hint, as the fleet registers them; matched case-insensitively | |
key_required | array of string | yes | Paths under the prefix that refuse an anonymous caller (401) | |
keyed_max_values | integer | yes | Values per request with a valid key | |
openapi | string | yes | Path of the OpenAPI document under the prefix | |
prefix | string | yes | The URL prefix every endpoint lives under | |
service | string | yes | Service name | |
version | string | yes | Service 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())
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-cleaner is the state of the art in real-time data-cleaning technologies.
data-cleaner is built and maintained by Edward Gaere at ETH Zurich. The research behind it, with Prof. Dr. Florian von Wangenheim, creates new benchmarks and real-time cleaning models for real-world data as it actually appears, not after it has been tidied. The benchmarks measure how leading machine-learning and language models handle that data. data-cleaner is designed to handle the majority of surface forms arising in practice: bytes, encodings, file formats and their versions, dialects, morphology, locales, aliases, component permutations, and so on.
Benchmarks on real-world data: addresses, datetimes and files.
MESSY STREETS: A Benchmark for Geocoding Real-World Addresses. Edward Gaere and Florian von Wangenheim. Accepted as a short paper at ACM SIGSPATIAL 2026, Riverside, 3 to 6 November 2026.
A benchmark of addresses taken verbatim from the web, with parts that are missing, repeated or malformed, and with reference locations from OpenAddresses and OpenStreetMap. Twelve commercial and open-source geocoders are compared on it. Commercial geocoders find far more of these addresses, and most of the gap comes from addresses that are not written in canonical form. That is the case for cleaning an address before geocoding it.
PRIMETIME: Limits of LLMs in Temporal Primitives. Edward Gaere and Florian von Wangenheim. The preprint of MESSY TIMES.
Language models are tested on each basic datetime operation in isolation. The operations are individually unreliable across models, and learnable by fine-tuning on generated data.
Forthcoming.
Edward Gaere, ETH Zurich. egaere (at) ethz.ch