OCR that's just another curl request.

Scanned receipts, faxed contracts, photographed whiteboards. Tesseract OCR runs automatically, with no separate OCR pipeline to stand up.

the-problem

Scans and photos aren't 'documents' to most extraction stacks. They're a special case. Handling them usually means a separate OCR service, with its own SDK and its own error handling. It also means a branch in your ingestion code, to detect when a file needs OCR instead of ordinary parsing. Standing that up, and keeping it patched, is its own project.

one-request-solution

In txtfetch, OCR isn't a separate code path. It's just what happens when pixels arrive. POST a PNG, JPG, or TIFF (or a scanned PDF), and Tesseract OCR runs automatically behind the same /v1/extract endpoint. You get back the same { status, extracted_text } shape as any other format.

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

with open("scanned-invoice.png", "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("scanned-invoice.png")]);
const form = new FormData();
form.append("file", file, "scanned-invoice.png");

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("scanned-invoice.png")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)
	part, err := writer.CreateFormFile("file", "scanned-invoice.png")
	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/uploads/receipt-photo.jpg" \
  -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/uploads/receipt-photo.jpg"},
)

print(r.json()["extracted_text"])
JavaScript
const endpoint = new URL("https://api.txtfetch.com/v1/extract");
endpoint.searchParams.set("url", "https://example.com/uploads/receipt-photo.jpg");

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/uploads/receipt-photo.jpg")
	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": "..."
}

what-comes-back

The corpus behind /diff has no recorded .png document yet, so we have nothing honest to show you here. Run one of your own instead. The free converter reads the file in your browser, and nothing is uploaded.

formats-covered

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

faq

How do I OCR a scanned document via API?
POST the image as multipart form data to https://api.txtfetch.com/v1/extract. Tesseract OCR runs automatically. The response is { "status": "success", "extracted_text": "..." }, with no OCR-specific parameters needed.
Which image formats are supported for OCR?
PNG, JPG/JPEG, TIFF, BMP, and GIF are all routed through Tesseract OCR automatically, the same as scanned PDF pages with no text layer.
Do I need to tell txtfetch that a file needs OCR?
No. Format detection is automatic. Any file with no extractable text layer, image or PDF, is OCR'd without any extra flag or parameter.

go-further

Send a real .png through it.

One HTTP call returns the text. Read one in your browser first, for free.

Get an API key →

Open the free .png reader →