Clean.tools Text API
Text analysis and transformation — deterministic.
Text analysis and transformation — deterministic.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/clean-tools:clean-tools-text-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Clean.tools Text API
version: 0.1.0
summary: Read-only developer, finance, date, and text utilities. No key, no signup.
description: 'Every endpoint is read-only and side-effect-free. Most tools are deterministic: same input, same output. The generator tools (uuid_v4, generate_id, random_number, generate_password) use cryptographic randomness instead. Requests are processed in memory and never written to storage or logs; only anonymous per-tool call counters are kept. The same tools are available over the Model Context Protocol at POST /mcp (mount a subset with ?tools=). Rate limit: about 60 requests/minute per caller — honor Retry-After on 429 and add jitter.'
license:
name: MIT
contact:
name: Clean.tools
url: https://clean.tools/
email: support@clean.tools
servers:
- url: https://api.clean.tools
tags:
- name: Text
description: Text analysis and transformation — deterministic.
paths:
/v1/diff_text:
post:
operationId: diff_text
summary: Use this when you need an exact line-level diff between two blocks of text…
description: 'Use this when you need an exact line-level diff between two blocks of text instead of eyeballing the differences. Deterministic: same input, same output. Computes a longest-common-subsequence diff and returns every line tagged added, removed, or unchanged, plus per-category counts. Example: original "a\nb", modified "a\nB" -> added 1, removed 1, unchanged 1, with lines [{type:"unchanged",text:"a"},{type:"removed",text:"b"},{type:"added",text:"B"}]. Trailing edits produce separate removed+added lines rather than an in-place change. Inputs whose line-count product exceeds 4,000,000 are rejected as too large to diff.'
x-read-only: true
tags:
- Text
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
original:
type: string
description: Original text.
modified:
type: string
description: Modified text.
required:
- original
- modified
example:
original: 'a
b'
modified: 'a
B'
responses:
'200':
description: Structured tool result.
content:
application/json:
schema:
type: object
properties:
tool:
type: string
result:
type: object
additionalProperties: false
required:
- added
- removed
- unchanged
- lines
properties:
added:
type: integer
description: Number of lines present only in the modified text.
removed:
type: integer
description: Number of lines present only in the original text.
unchanged:
type: integer
description: Number of lines common to both texts.
lines:
type: array
description: The full diff in order, one entry per line.
items:
type: object
additionalProperties: false
required:
- type
- text
properties:
type:
type: string
enum:
- added
- removed
- unchanged
description: How this line differs between the two texts.
text:
type: string
description: The line content (without its trailing newline).
example:
tool: diff_text
result:
added: 1
removed: 1
unchanged: 1
lines:
- type: unchanged
text: a
- type: removed
text: b
- type: added
text: B
'400':
description: Invalid input or the tool reported an error.
content:
application/json:
schema:
type: object
required:
- error
properties:
error:
type: string
description: Human-readable, structured error message.
code:
type: string
description: Stable machine-readable error code.
tool:
type: string
description: Tool name, when the error is tool-specific.
'413':
description: Request body exceeds the 128 KB limit.
'429':
description: Rate limit exceeded; the response carries Retry-After and RateLimit-* headers — honor them and add jitter.
/v1/count_text:
post:
operationId: count_text
summary: Use this when you need exact word, character, sentence, and paragraph counts…
description: 'Use this when you need exact word, character, sentence, and paragraph counts instead of estimating. Deterministic: same input, same output. Uses Unicode-aware segmentation (Intl.Segmenter): characters are grapheme clusters, so an emoji or an accented letter counts as one; words are word-like segments; sentence splitting is abbreviation-aware (Dr., etc., won''t end a sentence). charactersNoSpaces counts graphemes after stripping whitespace, and paragraphs are blocks separated by blank lines. Example: "Café 👩💻!" -> words 1, characters 7, charactersNoSpaces 6, sentences 1, paragraphs 1. Empty input returns all zeros.'
x-read-only: true
tags:
- Text
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
text:
type: string
description: Text to analyze.
required:
- text
example:
text: Café 👩💻!
responses:
'200':
description: Structured tool result.
content:
application/json:
schema:
type: object
properties:
tool:
type: string
result:
type: object
additionalProperties: false
required:
- words
- characters
- charactersNoSpaces
- sentences
- paragraphs
properties:
words:
type: integer
description: Count of word-like segments.
characters:
type: integer
description: Count of grapheme clusters (user-perceived characters), including whitespace.
charactersNoSpaces:
type: integer
description: Grapheme count after removing all whitespace.
sentences:
type: integer
description: Sentence count using abbreviation-aware segmentation.
paragraphs:
type: integer
description: Count of non-empty blocks separated by blank lines.
example:
tool: count_text
result:
words: 1
characters: 7
charactersNoSpaces: 6
sentences: 1
paragraphs: 1
'400':
description: Invalid input or the tool reported an error.
content:
application/json:
schema:
type: object
required:
- error
properties:
error:
type: string
description: Human-readable, structured error message.
code:
type: string
description: Stable machine-readable error code.
tool:
type: string
description: Tool name, when the error is tool-specific.
'413':
description: Request body exceeds the 128 KB limit.
'429':
description: Rate limit exceeded; the response carries Retry-After and RateLimit-* headers — honor them and add jitter.
/v1/convert_case:
post:
operationId: convert_case
summary: Use this when you need to re-case text into a specific naming or letter case
description: 'Use this when you need to re-case text into a specific naming or letter case. Given `text` and a target `case` (upper, lower, title, sentence, camel, snake, kebab, or constant), returns the converted string. Smart word tokenization splits camelCase, snake_case, kebab-case, and whitespace, so a phrase in any style re-cases consistently; title case honors an editorial stop-word list and preserves ALL-CAPS acronyms. Empty text returns an empty result. Deterministic: same input, same output. Example: {text: "myVariableName", case: "constant"} -> result "MY_VARIABLE_NAME".'
x-read-only: true
tags:
- Text
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
text:
type: string
description: The input text to convert.
case:
type: string
enum:
- upper
- lower
- title
- sentence
- camel
- snake
- kebab
- constant
description: 'Target case: upper (UPPER CASE), lower (lower case), title (Title Case with stop-words), sentence (Sentence case), camel (camelCase), snake (snake_case), kebab (kebab-case), constant (CONSTANT_CASE).'
required:
- text
- case
example:
text: myVariableName
case: constant
responses:
'200':
description: Structured tool result.
content:
application/json:
schema:
type: object
properties:
tool:
type: string
result:
type: object
properties:
case:
type: string
description: The target case that was applied (echoes the requested `case`).
result:
type: string
description: The converted text. Empty string if the input text was empty.
required:
- case
- result
additionalProperties: false
example:
tool: convert_case
result:
case: constant
result: MY_VARIABLE_NAME
'400':
description: Invalid input or the tool reported an error.
content:
application/json:
schema:
type: object
required:
- error
properties:
error:
type: string
description: Human-readable, structured error message.
code:
type: string
description: Stable machine-readable error code.
tool:
type: string
description: Tool name, when the error is tool-specific.
'413':
description: Request body exceeds the 128 KB limit.
'429':
description: Rate limit exceeded; the response carries Retry-After and RateLimit-* headers — honor them and add jitter.
/v1/fancy_text:
post:
operationId: fancy_text
summary: Use this when you need to style ASCII letters and digits as Unicode glyphs…
description: 'Use this when you need to style ASCII letters and digits as Unicode glyphs (bold-serif, italic-serif, bold-italic-serif, bold-sans, script, fraktur, double-struck, monospace, circled, squared, parenthesized, small-caps) for places that lack font control such as social bios or usernames. Pass a `style` and `text` to get the transformed string; characters outside A-Z, a-z, and 0-9 (spaces, punctuation, emoji) pass through unchanged. Omit `style` to receive the list of valid style keys instead of transforming. Deterministic: same input, same output. Example: {style: "bold-serif", text: "Hello 123"} -> result "𝐇𝐞𝐥𝐥𝐨 𝟏𝟐𝟑".'
x-read-only: true
tags:
- Text
requestBody:
required: false
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
text:
type: string
description: The text to transform. ASCII A-Z, a-z, and 0-9 are mapped to styled glyphs; other characters (spaces, punctuation, emoji) pass through unchanged. Required when a style is given.
style:
type: string
enum:
- bold-serif
- italic-serif
- bold-italic-serif
- bold-sans
- script
- fraktur
- double-struck
- monospace
- circled
- squared
- parenthesized
- small-caps
description: The style to apply. Omit entirely to receive the list of valid style keys instead of transforming.
required: []
example:
style: bold-serif
text: Hello 123
responses:
'200':
description: Structured tool result.
content:
application/json:
schema:
type: object
properties:
tool:
type: string
result:
type: object
properties:
style:
type: string
description: 'Transform mode only: the style that was applied (echoes the requested `style`).'
result:
type: string
description: 'Transform mode only: the styled text, with unsupported characters passed through unchanged.'
styles:
type: array
items:
type: string
description: 'Discovery mode only (when `style` is omitted): the list of valid style keys.'
example:
tool: fancy_text
result:
style: bold-serif
result: 𝐇𝐞𝐥𝐥𝐨 𝟏𝟐𝟑
'400':
description: Invalid input or the tool reported an error.
content:
application/json:
schema:
type: object
required:
- error
properties:
error:
type: string
description: Human-readable, structured error message.
code:
type: string
description: Stable machine-readable error code.
tool:
type: string
description: Tool name, when the error is tool-specific.
'413':
description: Request body exceeds the 128 KB limit.
'429':
description: Rate limit exceeded; the response carries Retry-After and RateLimit-* headers — honor them and add jitter.
/v1/render_markdown:
post:
operationId: render_markdown
summary: Use this when you need to convert lightweight Markdown into a sanitized…
description: 'Use this when you need to convert lightweight Markdown into a sanitized, XSS-safe HTML fragment to preview or embed, matching the Clean.tools markdown preview. Deterministic: same input, same output. Supports headings, bold/italic/strikethrough, inline and fenced code, links and images (http/https/mailto URL schemes only; other schemes become "#"), blockquotes, ordered/unordered/task lists, GFM tables, and horizontal rules. All raw angle brackets are HTML-escaped so the output is safe to inject. Example: "# Hi\n\n**bold**" -> html "
Hi
bold
".'
x-read-only: true
tags:
- Text
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
markdown:
type: string
description: The Markdown source text to render to HTML.
required:
- markdown
example:
markdown: '# Hi
**bold**'
responses:
'200':
description: Structured tool result.
content:
application/json:
schema:
type: object
properties:
tool:
type: string
result:
type: object
properties:
html:
type: string
description: The rendered HTML fragment. Block elements are concatenated with no separators; inline styles are attached to tables, code, blockquotes, images, and task-list items. No wrapping document element.
required:
- html
additionalProperties: false
example:
tool: render_markdown
result:
html: <h1>Hi</h1><p><strong>bold</strong></p>
'400':
description: Invalid input or the tool reported an error.
content:
application/json:
schema:
type: object
required:
- error
properties:
error:
type: string
description: Human-readable, structured error message.
code:
type: string
description: Stable machine-readable error code.
tool:
type: string
description: Tool name, when the error is tool-specific.
'413':
description: Request body exceeds the 128 KB limit.
'429':
description: Rate limit exceeded; the response carries Retry-After and RateLimit-* headers — honor them and add jitter.
/v1/lorem_ipsum:
post:
operationId: lorem_ipsum
summary: Use this when you need placeholder/filler copy for mockups, tests, or layout
description: 'Use this when you need placeholder/filler copy for mockups, tests, or layout. Given a `mode` ("paragraphs", "sentences", "words", or "formatted") and a `count`, cycles a fixed built-in Latin corpus to return the same text every time. `count` is clamped to the mode''s max (paragraphs 20, sentences 100, words 500) and defaults to 1 when missing or below 1; "formatted" ignores `count` and returns a fixed multi-block sample (with count 0). Deterministic: same input, same output. Example: {mode: "words", count: 5} -> text "Lorem ipsum dolor sit amet.", count 5.'
x-read-only: true
tags:
- Text
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
mode:
type: string
enum:
- paragraphs
- sentences
- words
- formatted
description: Unit of generated text. "paragraphs", "sentences", or "words" repeat the corpus up to the requested count; "formatted" returns a fixed multi-block sample as plain text and ignores count.
count:
type: integer
minimum: 1
description: How many units to generate. Clamped to the mode's max (paragraphs 20, sentences 100, words 500); values below 1 or missing default to 1. Unused for "formatted".
required:
- mode
example:
mode: words
count: 5
responses:
'200':
description: Structured tool result.
content:
application/json:
schema:
type: object
properties:
tool:
type: string
result:
type: object
properties:
mode:
type: string
description: The generation mode used (echoes the requested `mode`).
enum:
- paragraphs
- sentences
- words
- formatted
count:
type: integer
description: The effective number of units generated after clamping/defaulting; always 0 for "formatted" mode.
text:
type: string
description: The generated placeholder text. Paragraphs are separated by blank lines; words mode is capitalized and terminated with a period.
required:
- mode
- count
- text
additionalProperties: false
example:
tool: lorem_ipsum
result:
mode: words
count: 5
text: Lorem ipsum dolor sit amet.
'400':
description: Invalid input or the tool reported an error.
content:
application/json:
schema:
type: object
required:
- error
properties:
error:
type: string
description: Human-readable, structured error message.
code:
type: string
description: Stable machine-readable error code.
tool:
type: string
description: Tool name, when the error is tool-specific.
'413':
description: Request body exceeds the 128 KB limit.
'429':
description: Rate limit exceeded; the response carries Retry-After and RateLimit-* headers — honor them and add jitter.
/v1/redact_text:
post:
operationId: redact_text
summary: 'Use this when scrubbing test/dev text: replaces each occurrence of the given…'
description: 'Use this when scrubbing test/dev text: replaces each occurrence of the given terms with block characters (████). Provide `text` plus `terms` (a comma-separated string or an array of strings). By default it matches whole words only using Unicode boundaries (so "ann" will not match inside "annual") and is case-insensitive; set `caseSensitive` to match exactly, `wholeWords: false` to match substrings, or `fixedWidth: true` to hide each term''s length behind a constant-width bar. Returns the redacted text and a replacement count, and never echoes the original terms. Deterministic: same input, same output. Truly sensitive text is better redacted client-side at clean.tools/text-redact/. Example: {text: "Contact Jane Doe", terms: "Jane Doe"} -> redacted "Contact ████████", redactedCount 1.'
x-read-only: true
tags:
- Text
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
text:
type: string
description: The text to redact terms from.
terms:
description: The sensitive terms to redact, given either as a comma-separated string (e.g. "Jane Doe, jane@x.com") or an array of strings.
oneOf:
- type: string
- type: array
items:
type: string
wholeWords:
type: boolean
description: When true (default), only match terms as whole words using Unicode word boundaries (so "ann" will not match inside "annual").
caseSensitive:
type: boolean
description: When true, match terms case-sensitively. Defaults to false (case-insensitive).
fixedWidth:
type: boolean
description: When true, replace every matched term with a constant-width block bar so the redacted length does not leak the original term length. Defaults to false (block length equals term length).
required:
- text
- terms
example:
text: Contact Jane Doe
terms: Jane Doe
responses:
'200':
description: Structured tool result.
content:
application/json:
schema:
type: object
properties:
tool:
type: string
result:
type: object
properties:
redacted:
type: string
description: The text with all matched terms replaced by block characters; identical to the input when no terms matched.
redactedCount:
type: integer
description: The number of replacements made.
required:
- redacted
- redactedCount
additionalProperties: false
example:
tool: redact_text
result:
redacted: Contact ████████
redactedCount: 1
'400':
description: Invalid input or the tool reported an error.
content:
application/json:
schema:
type: object
required:
- error
properties:
error:
type: string
description: Human-readable, structured error message.
code:
type: string
description: Stable machine-readable error code.
tool:
type: string
description: Tool name, when the error is tool-specific.
'413':
description: Request body exceeds the 128 KB limit.
'429':
description: Rate limit exceeded; the response carries Retry-After and RateLimit-* headers — honor them and add jitter.
/v1/slugify:
post:
operationId: slugify
summary: Use this when you need a URL- or filename-safe slug from arbitrary text
description: 'Use this when you need a URL- or filename-safe slug from arbitrary text. Deterministic: same input, same output. Applies Unicode NFKD normalization, strips combining accents, and transliterates non-decomposing letters (ß->ss, æ->ae, œ->oe, ø->o, đ->d, ł->l, þ->th, ð->d, plus uppercase variants), then collapses every run of non-alphanumeric characters to a single separator and trims separators; e.g. "Héllo Wörld!" -> "hello-world". Emoji, CJK, and any other characters with no ASCII form are dropped. Prefer this over transliterating Unicode yourself, which models routinely get wrong. Returns { error } when no URL-safe characters remain.'
x-read-only: true
tags:
- Text
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
text:
type: string
description: Text to slugify (max 10000 characters).
separator:
type: string
maxLength: 1
description: 'Word separator: empty string (join words) or a single character from "-", "_", ".". Default "-".'
lowercase:
type: boolean
description: Lowercase the result (default true).
maxLength:
type: integer
minimum: 0
description: Maximum slug length; 0 means no limit (default). When set and exceeded, the slug is cut at a separator boundary when possible, otherwise hard-cut.
required:
- text
example:
text: Héllo Wörld!
responses:
'200':
description: Structured tool result.
content:
application/json:
schema:
type: object
properties:
tool:
type: string
result:
type: object
additionalProperties: false
required:
- slug
- changed
properties:
slug:
type: string
description: The URL/filename-safe slug.
changed:
type: boolean
description: True when the slug differs from the input text.
example:
tool: slugify
result:
slug: hello-world
changed: true
'400':
description: Invalid input or the tool reported an error.
content:
application/json:
schema:
type: object
required:
- error
properties:
error:
type: string
description: Human-readable, structured error message.
code:
type: string
description: Stable machine-readable error code.
tool:
type: string
description: Tool name, when the error is tool-specific.
'413':
description: Request body exceeds the 128 KB limit.
'429':
description: Rate limit exceeded; the response carries Retry-After and RateLimit-* headers — honor them and add jitter.
externalDocs:
description: LLM guide
url: https://api.clean.tools/llms.txt