
# Text tools

Eleven in-process string & document utilities — pure JavaScript, the same open-source libraries you could run yourself ([`jsdiff`](https://github.com/kpdecker/jsdiff), [`node-diff3`](https://github.com/bhousel/node-diff3), [`marked`](https://marked.js.org/), [`sanitize-html`](https://github.com/apostrophecms/sanitize-html), [`html-to-text`](https://github.com/html-to-text/node-html-to-text)). Every op is a `POST https://api.relaystation.ai/v1/text/<op>` and returns a small **JSON object** — there is no file output.

## Inputs

Most ops take **either** an inline `text` string **or** a `file` input source — `{ "inline": "<base64>" }` (≤ 4 MB) or `{ "inputKey": "..." }` (from `POST /v1/cputools/upload-url`, ≤ 50 MB) — exactly one. `diff` takes two strings `a` / `b`; `apply-patch` takes `source` + `patch`; `merge3` takes `base` / `yours` / `theirs`; `template` takes a `template` string + a `data` object.

## Billing

Every op bills **per MB of input** (min 1 MiB) at `cputools.price.text.<op>.per_mb_micros` (launch default $0.0002 / MB — a placeholder the operator tunes). The live `402` challenge is authoritative.

| Op | Billed on | Rate |
|---|---|---|
| `case` | input MB | $0.0002 / MB |
| `slugify` | input MB | $0.0002 / MB |
| `diff` | input MB (a+b) | $0.0002 / MB |
| `apply-patch` | input MB (source+patch) | $0.0002 / MB |
| `merge3` | input MB (base+yours+theirs) | $0.0002 / MB |
| `count` | input MB | $0.0002 / MB |
| `regex-extract` | input MB | $0.0002 / MB |
| `template` | input MB | $0.0002 / MB |
| `markdown-to-html` | input MB | $0.0002 / MB |
| `html-to-text` | input MB | $0.0002 / MB |
| `sanitize-html` | input MB | $0.0002 / MB |

## case

Recase text. `to` is one of `upper` / `lower` / `title` / `camel` / `snake` / `kebab`. Returns `{ result }`.

```json
POST /v1/text/case
{ "text": "hello world", "to": "snake" }
```

## slugify

URL-slugify: lowercase, strip diacritics, collapse non-alphanumeric to the separator (default `-`). Returns `{ slug }`.

```json
POST /v1/text/slugify
{ "text": "Héllo, World!" }
```

## diff

Unified diff of two strings (`a` vs `b`). Returns `{ patch, changed }`.

```json
POST /v1/text/diff
{ "a": "line1\nline2\n", "b": "line1\nLINE2\n" }
```

## apply-patch

The inverse of `diff`: apply a unified diff (`patch`) to a `source` string. Pairs with `text/diff` — `diff` produces the patch, `apply-patch` consumes it. Returns `{ result }`. If the patch doesn't apply cleanly (context mismatch), the call returns `422 PATCH_CONFLICT` and the per-MB charge is refunded.

```json
POST /v1/text/apply-patch
{ "source": "line1\nline2\n", "patch": "--- a\n+++ b\n@@ -1,2 +1,2 @@\n line1\n-line2\n+LINE2\n" }
```

## merge3

Three-way merge of two divergent edits (`yours`, `theirs`) against a common ancestor (`base`) — the same model `git merge` uses. Returns `{ merged, conflict }`. A clean merge returns the combined text with `conflict: false`; overlapping edits return the merged text **with conflict markers** (`<<<<<<<` / `|||||||` / `=======` / `>>>>>>>`) and `conflict: true` — the markers **are** the useful output, not an error.

```json
POST /v1/text/merge3
{ "base": "a\nb\nc\n", "yours": "a\nB\nc\n", "theirs": "a\nb\nC\n" }
```

## count

Count characters / words / lines + reading time. Returns `{ characters, charactersNoSpaces, words, lines, readingTimeMinutes }` (`wpm` default 200).

```json
POST /v1/text/count
{ "text": "one two three" }
```

## regex-extract

Extract regex matches. The pattern is **ReDoS-guarded**: a catastrophic-backtracking pattern is rejected (`422 UNSAFE_REGEX`), the input is byte-capped, and the match count is bounded. Returns `{ matches, count, truncated }`.

```json
POST /v1/text/regex-extract
{ "text": "a1 b2 c3", "pattern": "([a-z])(\\d)" }
```

## template

Render a **logic-less** mustache-subset template — **no eval, no expression evaluation**: `{{var}}` (HTML-escaped), `{{{raw}}}`, `{{#section}}…{{/section}}`, `{{^inverted}}…{{/inverted}}`. Lookups are own-property only (no prototype access). Returns `{ result }`.

```json
POST /v1/text/template
{ "template": "Hi {{name}}", "data": { "name": "Ada" } }
```

## markdown-to-html

Render Markdown (GFM) → HTML, **always run through the whitelist sanitizer** — a `<script>` in your markdown can never reach the output. Returns `{ html }`.

```json
POST /v1/text/markdown-to-html
{ "text": "# Title\n\n**bold**" }
```

## html-to-text

Convert HTML → plain text. Returns `{ text }` (`wordwrap` default 80; pass `false` to disable).

```json
POST /v1/text/html-to-text
{ "text": "<h1>Hi</h1><p>there</p>" }
```

## sanitize-html

Sanitize HTML against a safe whitelist — drops `<script>`, event handlers, and `javascript:`/`data:` schemes (http/https/mailto only). Returns `{ html }`.

```json
POST /v1/text/sanitize-html
{ "text": "<p onclick=\"x()\">ok</p><script>bad()</script>" }
```
