> 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
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: 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.
| 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")
Edward Gaere, ETH Zurich. egaere (at) ethz.ch