RTF to TXT API
POST an .rtf file to /v2/convert/rtf/to/txt and get its text without control words: Windows-1252 and Unicode escapes decoded to UTF-8, one line per paragraph with CRLF endings, list markers kept and table cells separated by tabs.
- Endpoint
- POST /v2/
convert/ rtf/ to/ txt - Accepts
- rtf
- Sample timing
- 22 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/txt \
-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/txt",
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/txt";
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.022,
"Files": [
{
"FileName": "repair-estimate.txt",
"FileExt": "txt",
"FileSize": 1599,
"FileData": "DQpBcHJpbCAxOCwgMjAyNw0K…"
}
]
}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.txt, 30 lines
April 18, 2027Harbor View Apartments, unit 4B22 Harbor View Road, MillbrookDear resident,Estimate for refrigerator repairOur 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.Item Qty Unit price AmountEvaporator fan motor 1 $86.40 $86.40Defrost heater 1 $42.15 $42.15Labor (hours) 1.5 $95.00 $142.50Service call (paid April 16) 1 $65.00 $65.00Subtotal $336.05Sales tax on parts (8%) $10.28Total due on approval $346.33Reference: EST-2027-0418. The café cooler in the lobby was checked on the same visit and needs no work.What the estimate does not cover• Food that spoiled before or during the repair• The ice maker, which was not tested• Damage to flooring when the unit is pulled outHow to approve1. Reply to this letter or call the shop before May 18, 2027.2. Or approve online at example.com/cedar-lane/approve.3. We book the repair within two business days of your approval.TermsWarranty. Parts and labor on this repair are covered for 90 days. The warranty does not cover damage from power surges, moving the unit or repairs made by others.Payment. The total is due when the work is done. Estimates over $500 need a 25% deposit. We accept checks and cards at the door.Cancellations. Cancel at least one business day before the booked visit, or a $40.00 trip fee applies.Technician ID 27Cedar Lane Appliance Repair
- Input
- repair-estimate.rtf (14 KB)
- Output
- repair-estimate.txt (1.6 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 for the .txt; the sample comes back as repair-estimate.txt, or estimate-0418.txt with FileName=estimate-0418. |
StoreFileboolean, default false | Return a link to the stored text file (3 hours) instead of embedding it. |
Not supported yet
Setting one of these to anything but its default returns 400.
| Option | What to do instead |
|---|---|
LineEnding | Only crlf is accepted; lf or cr returns 400 "LineEnding is not supported yet for RTF → TXT". |
SaveFormat | Only unicodetext. The text is UTF-8 without a byte order mark. |
InsertLineBreaks | false returns 400, though lines are never wrapped at a fixed width. |
AllowSubstitutions | true returns 400; characters are kept as they are. |
What carries over, and what doesn't
Kept
- Each paragraph on one line, up to 266 characters in the sample
- Characters escaped in the RTF: \'b0, \'bd and \'e9 come out as °, ½ and é in UTF-8
- Bullets as •, numbered steps as 1., 2., 3., each followed by a tab
- Table rows with tabs between cells; the header row reads Item, Qty, Unit price and Amount
- Link text, here example.com/cedar-lane/approve
Lost or not yet supported
- Header, footer and footnote text.
- Link URLs behind the text, and all formatting.
- Column positions in merged rows: "Subtotal\t$336.05" has two fields instead of four.
- The picture, which leaves an empty first line in the sample.
- Deleted text of revision marks, which is left out as if the change were accepted.
Limits
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- 120 seconds per conversion before a 504.
- RTF files can't be encrypted, so
Passwordis accepted and ignored.
Where teams use it
Indexing RTF archives
Systems that saved notes, letters or reports as RTF for years leave a store of files full of control words. Converting each one to text gives the words to index without the {\rtf1 … } markup around them.
Pulling data out of RTF letters
Line items in a table come out as tab-separated lines ("Defrost heater\t1\t$42.15\t$42.15"), which a few lines of code can parse into rows.
Questions
How are special characters handled?
Escapes such as \'b0 are read in the RTF's code page (Windows-1252 here, from \ansicpg1252) and \u escapes as Unicode, then written as UTF-8. In the sample, 52\'b0F, 1\'bd and caf\'e9 become 52°F, 1½ and café.
What does the start of the file look like?
The sample's text starts with an empty line, where the logo picture sat in its own paragraph, followed by the date and the address lines. The file is 1,599 bytes in 30 lines, with CRLF between lines and none after the last.
Can I get LF line endings?
Not from the API yet: LineEnding=lf returns 400. Replace \r\n with \n after you receive the file; in the sample every carriage return is part of a CRLF pair.
Convert your first RTF file in a minute.