ODT to HTML API
Send an .odt file to POST /v2/convert/odt/to/html and get one self-contained HTML page back: h1 and h2 headings, ol and ul lists, a table with th header cells, and every picture embedded in the file itself.
- Endpoint
- POST /v2/
convert/ odt/ to/ html - 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/html \
-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/html",
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/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.022,
"Files": [
{
"FileName": "garden-plot-guide.html",
"FileExt": "html",
"FileSize": 13520,
"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.
garden-plot-guide.html, first 30 of 50 lines
<!DOCTYPE html><html lang="en-US"><head><meta charset="utf-8"><title>Plot holder guide 2027</title><meta name="author" content="Garden committee"><meta name="keywords" content="garden"><meta name="subject" content="Maple Hollow Community Garden"><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><h1>Plot holder guide 2027</h1><p><em><span style="color:#5A5A5A;font-size:14pt;font-family:'Liberation Sans'">Maple Hollow Community Garden, season of April 3 to November 13</span></em></p><p>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.</p><h1>Before opening day</h1><ol><li>Sign the plot agreement and pay the season fee by <strong>March 20</strong>.</li><li>Pick up your gate key at the tool shed on one of the two orientation mornings:<ul><li>Saturday, March 27, 9:00 to 11:00</li><li>Sunday, March 28, 13:00 to 15:00</li></ul></li><li>Clear last year's stakes and twine from your plot before you plant anything.</li></ol><h1>Plot sizes and fees</h1><p>Fees cover water, mulch deliveries and the shared tools. Raised beds are reserved for gardeners who cannot kneel or bend.</p><table style="border-collapse:collapse"><tr><th style="border:1px solid #999;padding:4px 6px;vertical-align:top;background-color:#D9EAD3"><strong>Plot type</strong></th><th style="border:1px solid #999;padding:4px 6px;vertical-align:top;background-color:#D9EAD3"><strong>Size</strong></th><th style="border:1px solid #999;padding:4px 6px;vertical-align:top;background-color:#D9EAD3;text-align:right"><strong>Season fee</strong></th><th style="border:1px solid #999;padding:4px 6px;vertical-align:top;background-color:#D9EAD3"><strong>Plots on the map</strong></th></tr><tr><td style="border:1px solid #999;padding:4px 6px;vertical-align:top">Small plot</td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top">10 × 10 ft</td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right">$35.00</td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top">S2, S3, S11, S12</td></tr><tr><td style="border:1px solid #999;padding:4px 6px;vertical-align:top">Full plot</td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top">10 × 20 ft</td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top;text-align:right">$60.00</td><td style="border:1px solid #999;padding:4px 6px;vertical-align:top">F4 to F9</td></tr>
- Input
- garden-plot-guide.odt (35 KB)
- Output
- garden-plot-guide.html (13 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 |
|---|---|
IncludeMarkupboolean, default false | Meant to append the document's comments as a list at the end of the page. This route does not read comments from ODT files, so the HTML is the same with or without it; we checked with a test file holding one comment. |
FileNamestring | Name of the HTML file without the extension. The sample returns garden-plot-guide.html; FileName=plots-2027 returns plots-2027.html. |
StoreFileboolean, default false | Return a link to the stored HTML instead of its base64 text. Links expire 3 hours after the conversion. |
What carries over, and what doesn't
Kept
- Title and Heading 1 as h1, Heading 2 as h2, and the manual page break as a break-before style on the Plot map heading
- The numbered steps as ol, with the two orientation mornings as a ul inside step 2
- Header cells as th with the green fill (#D9EAD3), right-aligned fees, and colspan="2" on the late sign-up row
- The 6,251-byte map PNG, byte for byte, as a data URI sized 576 × 288 CSS pixels (its 6 × 3 inch placement)
- Bold, italic, the red warning and the yellow highlight as inline tags and styles, and the link with its full URL
- The document title in the title element, author, subject and keywords as meta tags, and lang="en-US" from the file's language
Lost or not yet supported
- The header, the footer and the footnote: none of their text is in the HTML.
- Centering around the map. Its centered paragraph turns into an empty centered paragraph and an empty one, and the picture follows in a separate, uncentered paragraph.
- A space in the alt text: the picture's title and description are joined as "Plot mapMap of the twelve plots…".
- Fonts, sizes and colors from the ODT's heading and body styles. Headings get no color, and the body falls back to the stylesheet in the head (Aptos, Calibri, Segoe UI, Arial at 12 pt) instead of 11 pt Liberation Serif. The subtitle and the caption are the exception: they keep their look as inline styles (gray 14 pt italic Liberation Sans, and 9 pt italic).
- Border styling: every table border becomes 1px solid #999, where the file used thin green lines.
- Comments. Tracked changes are shown as accepted: deleted words are left out and inserted words read as plain text.
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; past that the API answers 504.
- Pictures are base64 text, a third larger than the image: the 6,251-byte map takes 8,336 characters of the 13,520-byte sample.
- A password-protected ODT returns 422 without
Passwordand 400 "password-protected ODT files can't be converted to HTML yet" with it.
Where teams use it
Publishing OpenDocument pages on a website
Committees and teams that write in an OpenDocument editor can upload the .odt and have it appear as a web page. The markup is plain enough for your own stylesheet: headings and body paragraphs carry no classes and no fonts, while colors, highlights and cell fills from the file are written inline.
Previews in a document portal
Show an uploaded .odt in the browser without a plugin. The map, the table and the links work as on any web page, and the text can be searched and copied.
Text for a search index
Split the HTML at h1 and h2 to index a long guide section by section, with table rows still in rows. For words only, /v2/convert/odt/to/txt returns a smaller file.
Questions
Can I get the pictures as separate image files?
Not from this route. Pictures are always embedded, and InlineImages=false is ignored for ODT input rather than refused. Decode the data URIs, or read the originals straight from the .odt, which is a ZIP archive with pictures in its Pictures/ folder (the sample's map is Pictures/10000000000002580000012C318A5922.png).
What is in the head of the page?
A meta charset of utf-8, the title, meta tags for author, keywords and subject, and a short style block that sets the font list, a 48em maximum width with auto margins, and table, blockquote and pre styles. To place the content inside your own layout, take what is inside body and drop the style block.
Why are the header, footer and footnotes missing?
This conversion writes the body of the document only. Footnotes are left out of every ODT output except XML (/v2/convert/odt/to/xml); headers and footers are kept in PDF, DOCX, XML and the page images, but not in HTML. If a footer carries something readers need, such as a version date, put it in the body before converting.
How do tables come out?
As a table element with border-collapse, one tr per row and th for cells in the header row. Each cell gets an inline style with a 1px #999 border, 4px by 6px padding and top alignment, plus a background color and right alignment where the ODT set them. Merged cells keep colspan.
Convert your first ODT file in a minute.