Sanitize HTML
Anywhere users can submit rich text — comments, bios, support tickets, rendered emails — an attacker can try to slip a script tag past you. This endpoint strips executable content and dangerous attributes while leaving the harmless formatting your users actually intended.
Run — free
Runs in your browser. Free, unlimited — your data never leaves this page.
The problem: rich text is also an attack surface
The moment an application lets a user submit HTML, or lets a rich-text editor emit HTML, it has also opened a door for stored cross-site scripting: a script tag, an onerror handler on a broken image, a javascript: link, or an iframe pointing somewhere it shouldn't. Denylisting a handful of tag names by hand misses obscure vectors — event handler attributes, data URIs, SVG payloads — and each miss is a real vulnerability sitting in production. data.html_sanitize is built by people who track that vector list for a living, so your team doesn't have to.
What happens on each request
Send POST /data/html-sanitize with the untrusted HTML, and you get a task_id back immediately while sanitization runs. The cleaned HTML — safe formatting tags preserved, scripts and dangerous attributes removed — arrives through a signed webhook call or waits behind a signed link valid for 24 hours.
Allowlisting, not guessing
Sound sanitization works by allowlist, not denylist: rather than trying to enumerate every dangerous pattern, it defines which tags and attributes are safe and drops everything else, including anything a browser's error-tolerant parser might reinterpret in a way the sanitizer didn't expect. This matters because HTML parsing is notoriously permissive — browsers will render malformed markup that looks harmless in one context and executes in another, which is exactly the kind of quirk that has produced real-world XSS bypasses against naive filters for as long as rich-text input has existed on the web.
How it fits an automated pipeline
Comment systems, support-ticket viewers, CMS content ingestion and any pipeline that renders user-authored HTML back to other users should sanitize on the way in, not hope the frontend framework escapes correctly on the way out. Because the task is billed per request with no charge on failure, it's cheap enough to run on every single submission rather than sampling or trusting client-side validation, and the async webhook flow means a comment form can accept input immediately and swap in the sanitized version once it's ready, without blocking the user on a synchronous scan.
What you can do with it
Comment section moderation
A blog platform sanitizes every submitted comment before storing it, stripping any script or event-handler attribute a commenter tried to inject.
Rich-text support tickets
A helpdesk sanitizes HTML pasted into ticket replies so agents can safely view formatted messages without risking a stored XSS payload.
User bio and profile fields
A community platform sanitizes HTML in user bios, allowing bold, links and line breaks while blocking any executable content.
CMS content from external contributors
A publisher sanitizes HTML submitted by freelance writers before it's rendered on the live site, closing off a common injection vector.
FAQ
How do I sanitize HTML with the API?
POST the untrusted HTML to /data/html-sanitize, keep the returned task_id, and collect the cleaned HTML by webhook or a signed link valid for 24 hours.
Is the HTML sanitizer API free?
No, there is no free tier or trial; it costs $0.002 per request, and a failed request is never charged.
Does it remove all XSS vectors, including event handlers?
Yes, it strips script tags, javascript: links, event-handler attributes and other executable content, keeping only safe formatting tags and attributes.
Will safe formatting like bold text and links survive?
Yes, standard formatting tags and safe attributes are preserved by default so legitimate rich text still renders correctly.
Is this better than a manual tag denylist?
Yes, it works by allowlisting known-safe tags and attributes instead of trying to enumerate every dangerous pattern, which is the approach that misses obscure vectors.
Can I sanitize HTML in bulk?
Yes, submit one async task per document and collect each result by webhook, which suits batch imports of user-generated content.
Is this endpoint live?
Yes, data.html_sanitize is live and accepting requests now.
Is submitted HTML stored afterward?
No, both the submitted and sanitized HTML are deleted after the retention window and never used for training.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/data/html-sanitize \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"input":"…"}'const res = await fetch("https://api.kit.forhosting.com/data/html-sanitize", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"input": "…"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/data/html-sanitize",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"input": "…"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/data/html-sanitize", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"input":"…"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"input":"…"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/data/html-sanitize", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"input": "…"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "data.html_sanitize",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_mb | 25 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |