https://txtfetch.com/solutions/invoice-and-receipt-processing/
Turn invoices and receipts into text your AP tools can use.
Inbound invoices arrive as PDFs, phone photos, scans, spreadsheets, and email attachments. txtfetch turns each one into text for your own rules or an LLM to parse. Table cells come back as text.
the-problem
Accounts payable teams receive invoices in every shape. A vendor emails a PDF, a contractor texts a photo, a supplier uploads a scan to a portal. Each format needs its own reader before any approval logic can run. Teams that only handle PDFs miss the photos and scans, and those invoices sit in a manual queue.
how-txtfetch-solves-it
txtfetch reads the invoice, whatever the format, and returns the text. It does not return a typed total field or a typed vendor field. Pair it with your own rules or an LLM to pull those values out. For a Word, Excel or HTML invoice, pass format=markdown on the same request to get the line-item table as a pipe table. A PDF invoice comes back as lines of text, one line for each row.
- OCR runs automatically on photographed and scanned invoices, with no separate vision service.
- format=markdown returns a Word, Excel or HTML line-item table as a pipe table. A PDF invoice comes back as lines of text.
- One endpoint reads a vendor's PDF, a phone photo of a receipt, a spreadsheet, and an email attachment.
- Async job and webhook mode keeps large batch runs from blocking approval queues.
- Idempotency-Key support stops a retried upload from double-processing an invoice.
- The response includes the text and the table rows, ready for your own totals logic.
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@invoice.pdfimport os
import requests
with open("invoice.pdf", "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"])import { readFile } from "node:fs/promises";
const file = new Blob([await readFile("invoice.pdf")]);
const form = new FormData();
form.append("file", file, "invoice.pdf");
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);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("invoice.pdf")
if err != nil {
panic(err)
}
defer f.Close()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, err := writer.CreateFormFile("file", "invoice.pdf")
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)
}{
"status": "success",
"extracted_text": "..."
}faq
- Does txtfetch return a total or vendor field from an invoice?
- No. It returns the invoice as text. Pull a typed total or vendor field with your own rules or an LLM.
- Can txtfetch read a photographed invoice from a phone?
- Yes. OCR runs automatically on the photo, and the response has the same shape as a digital-native PDF.
- How do I keep the line-item table readable for my parser?
- For a Word, Excel or HTML invoice, add format=markdown to the request. The table comes back as a pipe table. A PDF invoice has no table in the output. Each row is a line of space-separated text. See /docs/tables.
related-reading
Start on the free plan.
Run your own documents through it before you commit. The Hobby plan needs no card.
Get started →