RTF to HTML API
POST an .rtf file to /v2/convert/rtf/to/html and get a single HTML file: headings as h1 and h2, tables with th and colspan, ol and ul lists, and the RTF's pictures embedded as data URIs.
- Endpoint
- POST /v2/
convert/ rtf/ to/ html - Accepts
- rtf
- Sample timing
- 23 ms median of 5 runsHow we measure
- Request size
- Up to 10 MB on the Free plan or 100 MB on paid plans, all files together
Request and response
Every sample below ran against the API before this page was published. Set PDFCONVERSIONAPI_KEY to your key and run it as is.
curl -sS -X POST \
https://api.pdfconversionapi.com/v2/convert/rtf/to/html \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@repair-estimate.rtf \
-o response.json
# FileData is base64. Save the first file in converted/,
# or print the JSON error.
if jq -e '.Files' response.json > /dev/null; then
mkdir -p converted
name=$(jq -r '.Files[0].FileName' response.json)
jq -r '.Files[0].FileData' response.json \
| base64 --decode > "converted/$name"
else
cat response.json
fiimport base64
import os
import requests
key = os.environ["PDFCONVERSIONAPI_KEY"]
response = requests.post(
"https://api.pdfconversionapi.com/v2/convert/rtf/to/html",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("repair-estimate.rtf", "rb"))],
timeout=180,
)
result = response.json()
if not response.ok:
raise SystemExit(f"{result['Code']}: {result['Message']}")
os.makedirs("converted", exist_ok=True)
for file in result["Files"]:
path = os.path.join("converted", file["FileName"])
with open(path, "wb") as out:
out.write(base64.b64decode(file["FileData"]))
print(path, file["FileSize"], "bytes")
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
const form = new FormData();
const file = "repair-estimate.rtf";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/rtf/to/html";
const key = process.env.PDFCONVERSIONAPI_KEY;
const response = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: form,
});
const result = await response.json();
if (!response.ok) {
throw new Error(`${result.Code}: ${result.Message}`);
}
await mkdir("converted", { recursive: true });
for (const file of result.Files) {
const path = join("converted", file.FileName);
await writeFile(path, Buffer.from(file.FileData, "base64"));
console.log(path, file.FileSize, "bytes");
}
{
"ConversionCost": 1,
"ConversionTime": 0.023,
"Files": [
{
"FileName": "repair-estimate.html",
"FileExt": "html",
"FileSize": 12092,
"FileData": "PCFET0NUWVBFIGh0bWw+Cjxo…"
}
]
}Response from our test run on 2026-11-03. ConversionTime shows the median of 5 runs. FileData is shortened here; the real response carries the whole file as base64.
The sample, converted
The same file the code sends. Download the input and the output and compare them yourself.
repair-estimate.html, first 30 of 50 lines
<!DOCTYPE html><html lang="en-US"><head><meta charset="utf-8"><title>Service estimate 2027-0418</title><meta name="author" content="Cedar Lane Appliance Repair"><meta name="keywords" content="estimate"><meta name="subject" content="Refrigerator repair"><style>body{font-family:Aptos,Calibri,'Segoe UI',Arial,sans-serif;font-size:12pt;line-height:1.15;max-width:48em;margin:2em auto;padding:0 1em}table{border-collapse:collapse;margin:0.5em 0}blockquote{margin:0.5em 2em;font-style:italic;color:#404040}pre{background:#f5f5f5;padding:0.5em;font-family:'Courier New',monospace;font-size:10pt}</style></head><body><p><img src="data:image/png;base64,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" alt="" width="160" height="64"></p><p><span style="font-size:11pt;font-family:'Times New Roman'">April 18, 2027</span></p><p><span style="font-size:11pt;font-family:'Times New Roman'">Harbor View Apartments, unit 4B</span></p><p><span style="font-size:11pt;font-family:'Times New Roman'">22 Harbor View Road, Millbrook</span></p><p><span style="font-size:11pt;font-family:'Times New Roman'">Dear resident,</span></p><h1><span style="color:#1F4E79;font-family:'Arial'">Estimate for refrigerator repair</span></h1><p><span style="font-size:11pt;font-family:'Times New Roman'">Our technician inspected your refrigerator on April 16. The fresh-food section held 52°F against a set point of 37°F because the evaporator fan motor has failed and the defrost heater is open. Both parts are in stock at our shop, and the repair takes about 1½ hours.</span></p><table style="border-collapse:collapse"><tr><th style="border:1px solid #999;padding:4px 6px;vertical-align:top;background-color:#C8DAF0"><strong><span style="font-size:10pt;font-family:'Times New Roman'">Item</span></strong></th><th style="border:1px solid #999;padding:4px 6px;vertical-align:top;background-color:#C8DAF0;text-align:right"><strong><span style="font-size:10pt;font-family:'Times New Roman'">Qty</span></strong></th><th style="border:1px solid #999;padding:4px 6px;vertical-align:top;background-color:#C8DAF0;text-align:right"><strong><span style="font-size:10pt;font-family:'Times New Roman'">Unit price</span></strong></th><th style="border:1px solid #999;padding:4px 6px;vertical-align:top;background-color:#C8DAF0;text-align:right"><strong><span style="font-size:10pt;font-family:'Times New Roman'">Amount</span></strong></th></tr><tr><td style="border:1px solid #999;padding:4px 6px;vertical-align:top"><span style="font-size:10pt;font-family:'Times New Roman'">Evaporator fan motor</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">1</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">$86.40</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">$86.40</span></td></tr><tr><td style="border:1px solid #999;padding:4px 6px;vertical-align:top"><span style="font-size:10pt;font-family:'Times New Roman'">Defrost heater</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">1</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">$42.15</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">$42.15</span></td></tr><tr><td style="border:1px solid #999;padding:4px 6px;vertical-align:top"><span style="font-size:10pt;font-family:'Times New Roman'">Labor (hours)</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">1.5</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">$95.00</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">$142.50</span></td></tr><tr><td style="border:1px solid #999;padding:4px 6px;vertical-align:top"><span style="font-size:10pt;font-family:'Times New Roman'">Service call (paid April 16)</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">1</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">$65.00</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">$65.00</span></td></tr><tr><td colspan="3" style="border:1px solid #999;padding:4px 6px;vertical-align:top"><span style="font-size:10pt;font-family:'Times New Roman'">Subtotal</span></td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right"><span style="font-size:10pt;font-family:'Times New Roman'">$336.05</span></td></tr>
- Input
- repair-estimate.rtf (14 KB)
- Output
- repair-estimate.html (12 KB)
- Source
- A two-page repair estimate letter from a fictional appliance shop, with a logo, a priced table, lists, a link, a footnote and a page-numbered footer. We wrote it for these pages in Rich Text Format. (CC0-1.0). How it was made
- Made by
- 2026-11-04, API build
9eeeb0640aba
Parameters
Send these as form fields next to the file. Names are case-insensitive. If you set one of the documented options below that we don't support yet, the request fails with a 400 that names it instead of returning a file made without it.
| Parameter | What it does |
|---|---|
FileNamestring | Name of the HTML file without extension. The sample comes back as repair-estimate.html; send FileName=estimate-0418 for estimate-0418.html. |
StoreFileboolean, default false | Return a 3-hour download link to the HTML instead of the file in the JSON. |
IncludeMarkupboolean, default false | Would add a list of comments at the end of the page, but RTF annotations are not read yet; our test file's comment did not appear with true. |
Not supported yet
Setting one of these to anything but its default returns 400.
| Option | What to do instead |
|---|---|
InlineImages | false returns 400 "InlineImages is not supported yet for RTF → HTML". Pictures are always embedded as data URIs. |
What carries over, and what doesn't
Kept
- Heading 1 and heading 2 as h1 and h2, with their blue color and Arial font as inline styles
- The priced table with th header cells shaded #C8DAF0, amounts right-aligned, and colspan="3" on the subtotal, tax and total rows
- The logo PNG byte for byte (2,090 bytes) as a data URI at 160 × 64
- Bulleted and numbered lists as ul and ol, and the approval link with its URL
- Courier New text as a code element, plus bold, italic, the red warning and the yellow highlight
- The page break before "Terms" as a break-before style, and the title, author, subject and keywords in the head
Lost or not yet supported
- The header, the footer and the footnote: none of their text is in the page.
- Alt text: the logo gets alt="", because RTF pictures carry no description.
- Heading size and weight set in the RTF (15 pt bold for heading 1, 12 pt bold for heading 2). No font-size or font-weight is written, so headings take the browser's h1 and h2 styles.
- Border color: every table border is 1px solid #999 instead of the RTF's blue.
- Compact markup. Most runs of text sit in a span with font-size and font-family: the sample has 62 spans, and style attributes take 5,445 of its 12,092 bytes for about 1,500 characters of text.
Limits
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- A conversion can take up to 120 seconds before a 504.
- Pictures are stored as base64, a third larger than the image itself.
Where teams use it
Showing RTF letters and notes in a browser
Case notes, letters and messages saved as RTF by desktop software can be shown in a web app without a viewer plugin. Tables, bullets and the link work as normal HTML.
Moving RTF content into a CMS or help center
Convert, take the body element, and paste it into the editor of your CMS. Because the RTF sets fonts on every run, expect inline spans you may want to strip with your sanitizer.
Questions
Why is every piece of text wrapped in a span?
RTF sets the font and size on each paragraph and run (here \f1\fs22, Times New Roman 11 pt) instead of in a style, and the converter keeps that formatting where it was set. The result is spans like font-size:11pt;font-family:'Times New Roman' around most text. Strip them, or keep them if the page should look like the letter.
Can I get the pictures as separate files?
Not from this route: InlineImages=false returns 400. Decode the data URIs in the img elements; each holds the original picture bytes, a PNG in the sample.
Which language and title does the page declare?
The html element gets lang="en-US" from the RTF's default language (\deflang1033), and the title element takes the RTF's \title, "Service estimate 2027-0418" in the sample. Author, subject and keywords become meta tags.
Convert your first RTF file in a minute.