A 2-post thread. 12 blocks. 3,672 characters. One image. Four export buttons.
The four files that came out were 548,977 B, 443,250 B, 405,019 B and 1,225 B. If you judged them by size you would say the PDF kept the most and the TXT kept almost nothing. That is backwards for the text, and exactly right for the image — and the TXT turned out to be missing 79% of the thread.
This post is a measurement report, not a comparison of vendors. Every number below was produced on 2026-10-20 and re-run on 2026-10-21 with byte-identical results, on a public thread and a public long post. The tool used is one I work on, stated up front so you can weigh the sample accordingly.
How the samples were produced
I called the export API directly on the server, not through a browser session. The endpoints belong to TweetArticleSave, the tool whose thread-to-PDF guide documents the same import → export flow; I state that relationship in full at the end.
-
POST /api/parsewith the post URL → returns adocId, pluskind,blocks,chars,imagesOk,scopeGot. -
POST /api/exportfour times with the samedocIdandformatindocx | pdf | md | txt.
Same docId for all four means the containers differ only in rendering — no re-fetch, no re-parse, no chance that sample drift explains a difference. That is the part most comparisons get wrong: export the same document twice from two runs and you are measuring the fetch, not the format.
The same thread, four ways
Sample A: a public two-post thread, kind=thread, scopeGot=2/2, 12 blocks, 3,672 chars, 1 image retrieved, 0 missing.
| Format | Bytes | What is actually inside |
|---|---|---|
| 548,977 | 3 pages, 1 image, 5 embedded Font objects, content stream uses Tj/TJ text operators |
|
| DOCX | 443,250 |
word/media/image1.jpg = 418,440 B, plus word/header1.xml, word/footer1.xml
|
| Markdown | 405,019 (ZIP) |
.md = 4,334 B with YAML front matter, SOURCE.txt = 519 B, images/01.jpg = 418,440 B |
| TXT | 1,225 | 1,212 characters, no image, [image: 01.jpg] placeholder |
Finding 1: the image is 94% of the DOCX, so file size tells you nothing about text
418,440 B of the 443,250 B DOCX is one JPEG — 94.4%. The text, styles and headers live in the remaining 24,810 B. The Markdown .md is 4,334 B for 3,672 characters of body text; the rest of that 405,019 B ZIP is the same JPEG.
Practical consequence: when a "PDF export failed" report arrives with a 500 KB attachment, the file usually did not fail — it is one photo plus two paragraphs. Size is an image counter, not a content counter.
Finding 2: the PDF is text, not a screenshot — and you can check
The PDF contains 5 Font objects and a content stream with Tj/TJ operators. That means the text is selectable, searchable, and copyable, and the file does not scale with your screen resolution. If a PDF export is a screenshot, you will see one large XObject image covering the page and no font objects.
Finding 3: Markdown is the only container that carries provenance
The ZIP holds three things: the .md, a SOURCE.txt, and images/. The SOURCE.txt records title, kind, author, published date, export timestamp, post count, block count, images retrieved vs missing, and the source links. The image bytes are identical to the DOCX media (418,440 B), so nothing is re-encoded on the way out.
The front matter in the .md carries title, author, published, exported, source, kind. If you archive threads for later citation, that metadata is the difference between a folder of text and a record you can defend six months later.
Finding 4: the TXT dropped 2,910 characters, and nothing warned me
This is the one worth the whole post.
The parse reported 3,672 chars. The TXT contained 1,212 characters — including its own headers. Comparing them line by line, the body text in the TXT stopped right after the second post's first sentence:
--- 2/2 · @handle · 2026-10-19 ---
@someone v nice side event https://x.com/...
Everything after that was gone. The source document had 12 blocks; two of them were quoted posts:
| Block | Type | Characters | In TXT? |
|---|---|---|---|
| 6 | quoted post (note) |
286 | no |
| 11 | quoted post (note) |
2,624 | no |
2,624 characters — most of the second post — vanished. The TXT writer emits direct post blocks and silently skips the quoted ones, because quoted content is stored in a different field than paragraph runs. No error, no warning, exit code 0, plausible-looking 1,225-byte file.
The DOCX kept them (3 occurrences of the word "quoted" and the quoted body text are in word/document.xml). The Markdown kept them. The PDF I could not text-extract on that box (pdftotext not installed), so I am not claiming either way.
The rule I now apply to every export tool: compare the character count the tool reports with the character count of the file it produced, before you trust the file.
# for txt
raw = open("export.txt", "rb").read().decode("utf-8-sig")
print(len(raw)) # must be >= the tool's reported char count
# for docx
import zipfile
x = zipfile.ZipFile("export.docx").read("word/document.xml").decode()
print(x.count("quoted")) # 0 here means quoted posts were dropped
A one-line ratio check would have caught a 79% content loss. Nothing in the UI would have.
Direction matters when the sample is a thread
The same thread, linked two ways, produced two different documents:
| Link used | kind |
Blocks | Chars |
|---|---|---|---|
| First post of the chain | post |
7 | 891 |
| Last post of the chain | thread |
12 | 3,672 |
Repeated on a second thread: first post → 3 blocks / 381 chars; last post → 11 blocks / 848 chars. Both runs agree. Pasting the head of a thread exported one post and looked completely normal while doing it.
This is a property of reply-chain reconstruction, not of any one format: the tool walks up the reply chain from the link you gave it. If you start at the top, there is nothing above to walk to.
Images drive page count, not word count
Sample D: a public single post, 498 words, 4 images.
| Format | Bytes | Structure |
|---|---|---|
| 452,647 | 5 pages, 4 images | |
| DOCX | 339,064 | 4 files in word/media
|
| Markdown | 300,863 (ZIP) | 4 images in images/
|
| TXT | 968 | 4 × [image: 0N.jpg] placeholders |
498 words produced 5 PDF pages, while 3,672 characters produced 3. Each image occupies its own block, so a 4-image post paginates faster than a long text thread. If you are estimating print cost or reading length from word count, you will be wrong on any post with images.
Note also the TXT here: 968 B for 4 images and 498 words — the placeholders are the only trace that images existed. That is a reasonable design for plain text, as long as you know it before you delete the other three files.
Limits that decide whether you can trust the file tomorrow
Three values from the running configuration, not from a pricing page:
-
DOC_TTL_SECONDS = 10800— the document and its media are kept about 3 hours by default. Re-downloading from an olddocIdafter that returnsdoc_expired, and no amount of retrying fixes it. -
RATE_LIMIT_EXPORT = (80, 300)— 80 exports per 300 seconds. -
RATE_LIMIT_PARSE = (30, 300)— 30 parses per 300 seconds.
The practical version: export and save in the same sitting. A docId is a short-lived handle, not a permalink.
What I did not test
- Mobile browsers (iOS Safari, Android Chrome). All runs were server-side HTTP.
- Threads longer than 2 posts, and threads where the source withholds some replies (
scopeGot < scopeTotal). - Video posts, protected accounts, deleted posts, paywalled content.
- PDF text extraction on the sample box —
pdftotextwas not installed, so "PDF keeps quoted posts" is unverified, not confirmed. - Any comparison against other tools under identical conditions. Two samples of one tool is a case report, not a benchmark.
If you are choosing a container
- Editing or sending to someone who will edit → DOCX. Keeps quoted posts, keeps images inline, keeps headers and footers.
- Reading, printing, archiving for a human → PDF. Text stays text; check the font objects if you care.
- Archiving for a machine or for citation → Markdown ZIP. Only format that ships provenance and unmodified images.
- Diffing, grepping, pasting into a terminal → TXT, but verify the character count first. In this sample it would have quietly cost you 79% of the text.
The export button is one click; verifying which container lied is the actual work. A character-count comparison takes ten seconds and catches the failures that look like success.
I work on TweetArticleSave, the tool used for these measurements. All samples are public posts; no post text is reproduced beyond the short fragments needed to show where output stopped. Numbers were measured 2026-10-20 and re-run 2026-10-21 with identical byte sizes; the configuration values were read from the running instance on 2026-10-21.
Top comments (0)