ODT to TXT API
POST an OpenDocument text file to /v2/convert/odt/to/txt and get its words back as UTF-8 plain text: one line per paragraph ending in CRLF, list numbers and bullets kept, and table rows with a tab between cells.
- Endpoint
- POST /v2/
convert/ odt/ to/ txt - Accepts
- odt
- 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/odt/to/txt \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@garden-plot-guide.odt \
-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/odt/to/txt",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("garden-plot-guide.odt", "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 = "garden-plot-guide.odt";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/odt/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": "garden-plot-guide.txt",
"FileExt": "txt",
"FileSize": 2023,
"FileData": "UGxvdCBob2xkZXIgZ3VpZGUg…"
}
]
}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.
garden-plot-guide.txt, first 30 of 31 lines
Plot holder guide 2027Maple Hollow Community Garden, season of April 3 to November 13Welcome to the garden. This guide covers what to do before opening day, what each plot costs, where the water taps are and the rules every plot holder agrees to. Keep it with your gloves; the committee updates it each winter.Before opening day1. Sign the plot agreement and pay the season fee by March 20.2. Pick up your gate key at the tool shed on one of the two orientation mornings: ◦ Saturday, March 27, 9:00 to 11:00 ◦ Sunday, March 28, 13:00 to 15:003. Clear last year's stakes and twine from your plot before you plant anything.Plot sizes and feesFees cover water, mulch deliveries and the shared tools. Raised beds are reserved for gardeners who cannot kneel or bend.Plot type Size Season fee Plots on the mapSmall plot 10 × 10 ft $35.00 S2, S3, S11, S12Full plot 10 × 20 ft $60.00 F4 to F9Raised bed 4 × 12 ft $25.00 R1, R10Late sign-up (after March 20) + $10.00 Any open plotPlot mapFigure 1: plots R1 and R10 are the raised beds. The blue circles are the water taps.Garden rules• Water only by hand or with a soaker hose. Sprinklers are not allowed.• Follow the posted watering days; the current schedule is at example.org/maple-hollow/water-schedule.• Keep plants inside your plot and under six feet tall so they do not shade your neighbors.• Put weeds and spent plants in the compost bays, never in the path.• Return shared tools to the shed the same day and lock the gate when you leave.Work hoursEach plot holder gives six hours a season to shared jobs: turning compost, mowing the edges and staffing the fall harvest table. Sign up on the board inside the shed door. Hours not worked by November 1 are billed at $15.00 an hour.Leaving the garden
- Input
- garden-plot-guide.odt (35 KB)
- Output
- garden-plot-guide.txt (2.0 KB)
- Source
- A two-page plot holder guide for a fictional community garden, with a fee table, a plot map, lists, a footnote and a page-numbered footer. We wrote it for these pages as an OpenDocument text file. (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 text file. FileName=plots-2027 returns plots-2027.txt; otherwise the sample comes back as garden-plot-guide.txt. |
StoreFileboolean, default false | Reply with a URL to the stored .txt (deleted after 3 hours) instead of embedding it in the JSON. |
Not supported yet
Setting one of these to anything but its default returns 400.
| Option | What to do instead |
|---|---|
LineEnding | Only crlf, the default, is accepted. lf and cr return 400 "LineEnding is not supported yet for ODT → TXT". |
SaveFormat | Only unicodetext is accepted; the text is always UTF-8 without a byte order mark. |
InsertLineBreaks | false returns 400. Paragraphs are never wrapped at a line length: each one is a single line. |
AllowSubstitutions | true returns 400. Characters such as × and • are written as they are, in UTF-8. |
What carries over, and what doesn't
Kept
- Every body paragraph, heading and caption, each on one line; the longest line in the sample is 232 characters
- List markers: 1., 2. and 3. for the steps, • for the rules and a tab-indented ◦ for the nested sub-items, each followed by a tab
- Table rows as one line each, with a tab between cells
- Non-ASCII characters such as × in "10 × 10 ft" as UTF-8
- The visible text of links (here example.org/maple-hollow/water-schedule)
Lost or not yet supported
- Header, footer and footnote text.
- Link targets: only the words shown in the document are kept, not the URL behind them.
- Column alignment in merged rows. The late sign-up row has three tab-separated fields instead of four, so its fee lands under Size if you split on tabs.
- The picture, which leaves three empty lines between the Plot map heading and the caption.
- All formatting, and the deleted half of tracked changes: the text reads as if every change were accepted.
Limits
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- A conversion stops after 120 seconds with 504.
- A password-protected ODT returns 422 without
Passwordand 400 "password-protected ODT files can't be converted to TXT yet" with it. A file without a password ignoresPassword. - There is no line after the last paragraph: the file ends without a final CRLF.
Where teams use it
Full-text search over a folder of .odt files
Index the text without parsing content.xml yourself. Headings, list items and table rows each land on their own line, which keeps search snippets readable.
Word counts and text checks
Run spell checks, reading-level scores, word counts or a diff between two versions of a document on plain text. The sample's 31 lines come to 2,023 bytes.
Questions
What encoding and line endings does the text use?
UTF-8 with no byte order mark, and CRLF at the end of each line, which is ConvertAPI's default LineEnding as well. Other LineEnding or SaveFormat values return 400 for now, so if you need LF, replace \r\n after you receive the file.
Are long paragraphs wrapped?
No. Each paragraph is one line however long it is. InsertLineBreaks=true, ConvertAPI's default, is accepted, and false returns 400 even though the output never contains wrap breaks.
How can I split the table into columns?
Split each table line on tab characters. Watch for merged cells: a cell spanning two columns gives one field, so that row is one field short. If you need the columns to line up, /v2/convert/odt/to/md pads merged cells with an empty one.
Is the text of headers and footnotes included?
No. The text file holds the body only. Footnotes are left out of every ODT output except XML (/v2/convert/odt/to/xml); header and footer text is printed in PDF and image output.
Convert your first ODT file in a minute.