Every page of the TIFF, not just the first.

A multi-page fax TIFF is a chain of image directories, not one picture. Read the file the way most image loaders do and you silently lose every page after the first.

the-problem

Unlike PNG or JPEG, TIFF is a container format: a chain of Image File Directories (IFDs), each pointing to the next, and each holding one page's worth of image data. A generic image-loading library built around 'one file, one image' reads the first IFD, produces a picture, and stops — the rest of a multi-page fax or scanned document simply never gets read, with no error to signal that pages are missing. Fax-originated TIFFs add compression variety on top: bitonal (1-bit) pages compressed with CCITT Group 3 or Group 4 fax encoding, at asymmetric resolutions like 200×100 dpi (twice the horizontal detail of vertical) that a generic OCR pipeline tuned for square-pixel photos doesn't expect. Other TIFFs use LZW compression or embed a full JPEG per page instead — several different encodings that all still need to decode correctly before OCR ever runs.

one-request-solution

txtfetch's image path walks the full IFD chain, decoding every page — CCITT G3/G4 fax-compressed, LZW, or JPEG-in-TIFF — and runs OCR on each one, not just the first. Because detection classifies TIFF as an image, OCR always runs automatically; there's no flag to set to make multi-page decoding or OCR happen.

curl
curl -X POST https://api.txtfetch.com/v1/extract \
  -H "Authorization: Bearer $TXTFETCH_KEY" \
  -F file=@faxed-purchase-order.tiff
Python
import os
import requests

with open("faxed-purchase-order.tiff", "rb") as f:
    r = requests.post(
        "https://api.txtfetch.com/v1/extract",
        headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
        files={"file": f},
    )

print(r.json()["extracted_text"])
JavaScript
import { readFile } from "node:fs/promises";

const file = new Blob([await readFile("faxed-purchase-order.tiff")]);
const form = new FormData();
form.append("file", file, "faxed-purchase-order.tiff");

const res = await fetch("https://api.txtfetch.com/v1/extract", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
  body: form,
});

const { extracted_text } = await res.json();
console.log(extracted_text);
Go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
)

type extractResponse struct {
	Status        string `json:"status"`
	ExtractedText string `json:"extracted_text"`
}

func main() {
	f, err := os.Open("faxed-purchase-order.tiff")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)
	part, err := writer.CreateFormFile("file", "faxed-purchase-order.tiff")
	if err != nil {
		panic(err)
	}
	if _, err := io.Copy(part, f); err != nil {
		panic(err)
	}
	writer.Close()

	req, err := http.NewRequest("POST", "https://api.txtfetch.com/v1/extract", &body)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("TXTFETCH_KEY"))
	req.Header.Set("Content-Type", writer.FormDataContentType())

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var result extractResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		panic(err)
	}
	fmt.Println(result.ExtractedText)
}

Or skip the download — pass a url parameter and txtfetch fetches the document server-side:

curl
curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/scans/multi-page-fax.tif" \
  -H "Authorization: Bearer $TXTFETCH_KEY"
Python
import os
import requests

r = requests.post(
    "https://api.txtfetch.com/v1/extract",
    headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
    params={"url": "https://example.com/scans/multi-page-fax.tif"},
)

print(r.json()["extracted_text"])
JavaScript
const endpoint = new URL("https://api.txtfetch.com/v1/extract");
endpoint.searchParams.set("url", "https://example.com/scans/multi-page-fax.tif");

const res = await fetch(endpoint, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
});

const { extracted_text } = await res.json();
console.log(extracted_text);
Go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"os"
)

type extractResponse struct {
	Status        string `json:"status"`
	ExtractedText string `json:"extracted_text"`
}

func main() {
	endpoint, err := url.Parse("https://api.txtfetch.com/v1/extract")
	if err != nil {
		panic(err)
	}
	q := endpoint.Query()
	q.Set("url", "https://example.com/scans/multi-page-fax.tif")
	endpoint.RawQuery = q.Encode()

	req, err := http.NewRequest("POST", endpoint.String(), nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("TXTFETCH_KEY"))

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var result extractResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		panic(err)
	}
	fmt.Println(result.ExtractedText)
}
{
  "status": "success",
  "extracted_text": "..."
}

formats-covered

  • .tiff
  • .tif
  • .jpg
  • .png
  • .bmp
  • .webp
  • .gif

faq

Why am I only getting the first page of a multi-page TIFF?
TIFF stores multiple pages as a chain of Image File Directories (IFDs). Many generic image loaders are built around 'one file, one image' and only read the first IFD. txtfetch walks the full chain and OCRs every page.
Does it handle CCITT fax-compressed TIFFs, not just plain images?
Yes — bitonal fax scans compressed with CCITT Group 3 or Group 4 encoding, including asymmetric resolutions like 200×100 dpi common on faxed documents, decode and OCR correctly.
Do I need to set an OCR flag for TIFF files?
No — detection classifies any TIFF as an image, and images always run through Tesseract OCR automatically. There's no ocr parameter to set.
What about LZW-compressed or JPEG-in-TIFF files?
Both are supported — the parser decodes whichever compression scheme the file actually uses (CCITT, LZW, or embedded JPEG per page) before handing pixels to OCR.

go-further

Stop parsing. Start shipping.

Create an account and get an API key in minutes — the free Hobby plan needs no card.

Get started →