ODT to Markdown API
POST an .odt file to /v2/convert/odt/to/md and get Markdown in the GitHub style: # headings, nested lists, pipe tables with alignment markers, and links with their URLs. Pictures become image links, but the image files themselves are not returned.
- Endpoint
- POST /v2/
convert/ odt/ to/ md - 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/md \
-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/md",
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/md";
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.md",
"FileExt": "md",
"FileSize": 2280,
"FileData": "IyBQbG90IGhvbGRlciBndWlk…"
}
]
}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.md, first 30 of 46 lines
# Plot holder guide 2027*Maple Hollow Community Garden, season of April 3 to November 13*Welcome 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 map** || --- | --- | ---: | --- || Small plot | 10 × 10 ft | $35.00 | S2, S3, S11, S12 || Full plot | 10 × 20 ft | $60.00 | F4 to F9 || Raised bed | 4 × 12 ft | $25.00 | R1, R10 || Late sign-up (after March 20) | | + $10.00 | Any open plot |## Plot map*Figure 1: plots R1 and R10 are the raised beds. The blue circles are the water taps.*
- Input
- garden-plot-guide.odt (35 KB)
- Output
- garden-plot-guide.md (2.2 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 .md file. Left out, the sample is returned as garden-plot-guide.md. |
StoreFileboolean, default false | Get a link to the Markdown file, kept for 3 hours, instead of the file data in the JSON. |
What carries over, and what doesn't
Kept
- Title and Heading 1 as # lines and Heading 2 as ##, in document order
- The subtitle and the map caption as italic lines
- The numbered steps with the orientation mornings as a sub-list indented by four spaces
- The fee table as a pipe table with a ---: marker on the fee column, so it stays right-aligned when rendered
- The merged late sign-up cell, followed by an empty cell so the row keeps four columns
- Bold and italic words, and the link as [example.org/maple-hollow/water-schedule](https://example.org/maple-hollow/water-schedule)
Lost or not yet supported
- The picture file. The map becomes , but no image.png is returned with the Markdown, so the link is broken until you supply the file.
- Header, footer and footnote text.
- Colors and the highlight: "Sprinklers are not allowed." keeps its bold but not its red, and "six hours a season" is plain text.
- The difference between the title and a chapter heading, since both are #. Header cells are wrapped in ** ** as well as being the table's header row.
- A space in the alt text, which runs the picture's title into its description ("Plot mapMap of the twelve plots…").
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, then 504.
- Password-protected ODT files return 422 without
Passwordand 400 "password-protected ODT files can't be converted to MD yet" with it.
Where teams use it
Moving OpenDocument manuals into a docs repository
Teams that switch from .odt manuals to docs-as-code can convert each file once and commit the result. Headings, steps and tables come through as Markdown that renders on GitHub and in site generators that support pipe tables.
Text for language models and retrieval
Markdown keeps the outline (# and ##), list nesting and table columns that plain text loses, in a 2,280-byte file for the two-page sample. That structure helps when a document is split into chunks for embeddings or passed to a model as context.
Questions
Where does image.png come from?
Nowhere yet: the converter writes the image reference with the fixed name image.png, but the response holds only the .md file. The .odt is a ZIP archive, so you can take the picture from its Pictures/ folder, save it as image.png next to the Markdown, or change the link. If you need the images inside the result, /v2/convert/odt/to/html embeds them as data URIs.
Which Markdown flavor is it?
GitHub Flavored Markdown: pipe tables with alignment markers such as ---: on right-aligned columns, - for bullets, 1. for numbered items and four spaces of indent for nested items. The file is UTF-8 without a byte order mark, with LF line endings and a blank line between blocks.
Is the document title kept as front matter?
No. The title only appears as the first # heading, and the file's other properties (author, subject, keywords) are not written. Add YAML front matter yourself if your site generator needs it.
What happens to tracked changes and comments?
The Markdown shows the text with every change accepted, so deleted words disappear and inserted words read as normal. This route does not read comments from ODT files (only ODT to XML keeps them), so IncludeMarkup, which adds a comment list for some inputs, adds nothing here.
Convert your first ODT file in a minute.