> Source: https://txtfetch.com/output
> Plain-text twin — every page on txtfetch.com has one. https://txtfetch.com/text
---
# /formats is what goes in. This is what comes back.
One endpoint gives you three response shapes and two quality tiers. The same document renders as plain text, structured markdown, or typed JSON. Pick the shape your pipeline needs.
## one-document-three-shapes
The same report, extracted three ways. `format` defaults to `text` when omitted. The default shape doesn't change.
format=text (default)
```text
{
"status": "success",
"extracted_text": "Quarterly Report\n\nQ3 revenue grew 34% year over year, driven by expansion in EMEA.\n\nRegion\tRevenue\nNorth America\t$4.2M\nEMEA\t$3.1M\n",
"metadata": { "content_type": "application/pdf", "bytes": 482913, "chars": 812, "ocr": false }
}
```
format=markdown
```markdown
{
"status": "success",
"markdown": "# Quarterly Report\n\nQ3 revenue grew 34% year over year, driven by expansion in EMEA.\n\n| Region | Revenue |\n| --- | --- |\n| North America | $4.2M |\n| EMEA | $3.1M |\n",
"metadata": {
"content_type": "application/pdf",
"bytes": 482913,
"chars": 812,
"ocr": false,
"format": "markdown",
"tier": "standard",
"pages": 4,
"vlm": false
}
}
```
format=json
```json
{
"status": "success",
"elements": [
{ "type": "heading", "text": "Quarterly Report", "level": 1, "page": 1, "offset": 0, "bbox": null },
{ "type": "paragraph", "text": "Q3 revenue grew 34% year over year, driven by expansion in EMEA.", "page": 1, "offset": 18, "bbox": null },
{
"type": "table",
"text": "Region\tRevenue\nNorth America\t$4.2M\nEMEA\t$3.1M",
"markdown": "| Region | Revenue |\n| --- | --- |\n| North America | $4.2M |\n| EMEA | $3.1M |",
"html": "
| Region | Revenue |
|---|
| North America | $4.2M |
| EMEA | $3.1M |
",
"cells": [
[{ "text": "Region", "colspan": 1, "rowspan": 1, "header": true }, { "text": "Revenue", "colspan": 1, "rowspan": 1, "header": true }],
[{ "text": "North America", "colspan": 1, "rowspan": 1, "header": false }, { "text": "$4.2M", "colspan": 1, "rowspan": 1, "header": false }],
[{ "text": "EMEA", "colspan": 1, "rowspan": 1, "header": false }, { "text": "$3.1M", "colspan": 1, "rowspan": 1, "header": false }]
],
"rows": 3,
"cols": 2,
"page": 1,
"offset": 84,
"bbox": null
}
],
"metadata": {
"content_type": "application/pdf",
"bytes": 482913,
"chars": 812,
"ocr": false,
"format": "json",
"tier": "standard",
"pages": 4,
"vlm": false
}
}
```
## which-shape-do-i-want
| format= | Best for | Learn more |
| --- | --- | --- |
| `text` | Search indexing, classification, and plain prompting all just want the words. A flat extracted\_text string is the smallest, fastest response to parse. It's also the one every existing integration already handles. This is the default: omit format entirely and you get this shape. | [Response shape in the docs](https://txtfetch.com/docs#response) |
| `markdown` | RAG chunking needs headings and tables to survive the splitter. A structure-aware or recursive chunker keys off Markdown's headings and blank-line paragraph breaks. A flat text dump doesn't carry those breaks. Tables render as real GFM pipe tables when every cell is a simple 1×1 cell. | [See it chunk in the previewer](https://txtfetch.com/tools/chunk-preview), [Chunking strategies for RAG](https://txtfetch.com/blog/chunking-strategies-for-rag) |
| `json` | Layout-aware pipelines need per-element page, offset, and type data. A typed elements array lets you filter to just tables, walk headings for a table of contents, or chunk per element. You don't need to re-derive structure from a Markdown string. | [Full element schema in the docs](https://txtfetch.com/docs#response-formats), [OpenAPI spec](https://txtfetch.com/openapi.json) |
## tables-survive
A flattened text dump loses column alignment once it's copied out of a table. Reconstructing it downstream means guessing where one column ends and the next begins. `format=markdown` keeps the grid:
Before: format=text
```
Region Revenue
North America $4.2M
EMEA $3.1M
```
After: format=markdown
```
| Region | Revenue |
| --- | --- |
| North America | $4.2M |
| EMEA | $3.1M |
```
Merged cells are the honest exception. GFM has no way to express a `colspan`/`rowspan` greater than 1. A table with a merged cell renders instead as a sanitized inline ``, with only `table`/`thead`/`tbody`/`tr`/`th`/`td` tags, numeric spans, and escaped text. It's still valid GFM, since GFM allows raw HTML blocks. A table with no merged cells always renders as a plain pipe table.
## quality-modes
| quality= | Best for |
| --- | --- |
| `standard` | The default for every request. Apache Tika parses the document's real structure; Tesseract OCR runs automatically on pages with no text layer. Fast enough to stay synchronous for the vast majority of documents. |
| `premium` | Documents where layout matters more than raw text, like dense multi-column tables or scanned pages, need this tier. Tika's structural recovery is weakest here. This tier always routes async, even for a one-page PDF, since VLM latency can exceed the sync budget. Expect a 202 and a job\_id to poll. |
`quality` is independent of `format`. Pick any response shape with either tier. **standard** (Tika) is the default and stays synchronous. **premium** (a vision-language model) always routes async, even for a one-page PDF. VLM latency can exceed the sync request budget. Expect a `202` and a `job_id` to poll. Premium degrades rather than failing. On a cost, timeout, or size guardrail breach, or a VLM error, it falls back to the standard Tika baseline. It still returns a `success`, with `metadata.tier_downgraded: true` and a `metadata.downgrade_reason` explaining why. The one exception is a document the Tika baseline can't extract either. With no successful result to fall back to, you get the normal extraction error instead of a downgraded success.
Submit (premium)
```submit
curl -X POST "https://api.txtfetch.com/v1/extract?quality=premium" \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@quarterly-report.pdf
# {"status": "processing", "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"}
# quality=premium always routes async, even for a one-page PDF.
```
Premium result
```premium-success
{
"status": "success",
"extracted_text": "Quarterly Report\n\nQ3 revenue grew 34% year over year, driven by expansion in EMEA.\n\nRegion\tRevenue\nNorth America\t$4.2M\nEMEA\t$3.1M\n",
"metadata": {
"content_type": "application/pdf",
"bytes": 482913,
"chars": 812,
"ocr": false,
"format": "text",
"tier": "premium",
"pages": 4,
"vlm": true,
"usage": { "model": "claude-sonnet-5", "input_tokens": 2140, "output_tokens": 612 }
}
}
```
Guardrail downgrade
```downgraded
{
"status": "success",
"extracted_text": "Quarterly Report\n\nQ3 revenue grew 34% year over year, driven by expansion in EMEA.\n\nRegion\tRevenue\nNorth America\t$4.2M\nEMEA\t$3.1M\n",
"metadata": {
"content_type": "application/pdf",
"bytes": 482913,
"chars": 812,
"ocr": false,
"format": "text",
"tier": "standard",
"pages": 4,
"vlm": false,
"tier_downgraded": true,
"downgrade_reason": "max_cost"
}
}
```
| downgrade\_reason | Meaning |
| --- | --- |
| `vlm_disabled` | the VLM\_ENABLED ops flag is off |
| `max_bytes` | a pre-flight size guardrail breach, so the VLM was never called |
| `max_pages` | a pre-flight page-count guardrail breach, so the VLM was never called |
| `max_cost` | a pre-flight cost guardrail breach, so the VLM was never called |
| `vlm_error` | an HTTP/timeout/parse failure calling the VLM |
| `truncated` | the VLM hit its own max\_tokens before finishing |
There's no plan-level gate on `quality=premium`. Every plan can request it, and pricing is unchanged either way (see [pricing](https://txtfetch.com/pricing)). Full guardrails and the measured accuracy climb between tiers: [benchmarks →](https://txtfetch.com/benchmarks) and [the docs →](https://txtfetch.com/docs#premium-quality).
## honest-limits
- **No schema-defined field extraction.** Structured Markdown and element JSON ship today; pulling typed fields per your own schema (invoice number, total, dates) is on the roadmap, not shipped.
- **Structured text, not a visual layout reconstruction.** Element JSON and Markdown preserve reading order and block structure. They don't reproduce the document's visual page layout.
- **OCR accuracy tracks scan quality.** A clean scan OCRs well; a low-resolution or skewed scan degrades like any OCR pipeline's would.
- **`bbox` is always `null` on the standard path.** The field is reserved for a future VLM-populated version. It isn't coordinates today, on either tier.
- **Premium is async-only.** There's no synchronous VLM path, regardless of document size.
## faq
**Does txtfetch return Markdown, not just plain text?**: Yes. Pass ?format=markdown and the response carries a markdown field instead of extracted_text. Headings, nested lists, and paragraphs keep reading order, joined by blank lines. Tables render as GFM pipe tables. It ships today, using the same endpoint and auth as the default text shape.
**Can I get structured JSON with per-element position info?**: Yes. ?format=json returns an elements array of typed heading/paragraph/list/table/image/code nodes. Each one carries a page, offset, and bbox value. bbox is always null on the standard Tika path. It's reserved for the premium VLM path, which doesn't populate it yet either. See the element JSON schema in the docs for the full field list.
**Do merged table cells survive in Markdown?**: GFM has no way to express a merged cell. A table with any colspan or rowspan greater than 1 renders as a sanitized inline instead of a pipe table. The sanitized table keeps only table/thead/tbody/tr/th/td tags, numeric colspan/rowspan values, and HTML-escaped text. It's still valid GFM, since GFM allows raw HTML blocks. A table with no merged cells always renders as a plain pipe table.
**Is quality=premium synchronous, like the standard tier?**: No. Premium always routes async, even for a one-page PDF. Vision-language model latency can exceed the synchronous request budget. Expect a 202 and a job_id to poll, the same job lifecycle as any other async request.
**What happens if the premium tier fails or costs too much?**: A VLM failure never fails your request. It degrades instead. On a cost, timeout, or size guardrail breach, or a VLM error, premium falls back to the standard Tika baseline. It still returns status: success, with metadata.tier_downgraded: true and a metadata.downgrade_reason explaining why. The one exception is a document the Tika baseline couldn't extract either. Then there's no successful result to fall back to, so you get the normal extraction error instead.
**Can I request typed fields — invoice number, total, dates — out of a document?**: Not yet. Structured Markdown and element-JSON document output ship today. Schema-defined field extraction, pulling typed fields per your own schema, is on the roadmap. It hasn't shipped yet.
## Stop parsing. Start shipping.
Create an account and get an API key in minutes. The free Hobby plan needs no card.
[Get started →](https://app.txtfetch.com/signup)