https://txtfetch.com/solutions/contract-and-legal-review/
Read a whole contract, attachments and all.
Legal teams review old files: .doc contracts, .msg and .eml threads with attachments, and scanned signature pages inside a digital PDF. txtfetch reads all of them.
the-problem
Legal operations and e-discovery deal with an old corpus. A contract might be a .doc file from 2009, or a .msg thread with three attachments. A signature page is often a scan glued inside an otherwise digital PDF. Generic extraction breaks the reading order on multi-column exhibits and loses the thread on nested email attachments.
how-txtfetch-solves-it
txtfetch reads .doc, .msg, and .eml threads, attachments included, plus scanned signature pages inside a digital PDF. It extracts in reading order for most PDFs, and /fixes/columns-out-of-order covers the multi-column exhibits that still need a check. It does not classify clauses and it does not redact text. Pricing is per document, not per page, which matters when a contract runs to 300 pages.
- One endpoint reads .doc, .msg, and .eml threads with attachments.
- Multi-column exhibits can still interleave, and /fixes/columns-out-of-order covers how to check.
- OCR runs automatically on scanned pages inside an otherwise digital-native PDF.
- Per-document pricing means a 300-page contract costs the same as a one-pager.
- Async job and webhook mode handles large discovery batches.
- The response text keeps attachment boundaries clear, so one email thread never blends into the next.
- The same endpoint reads a native .docx exhibit and a scanned signature page with no format check on your side.
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@contract.docimport os
import requests
with open("contract.doc", "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("contract.doc")]);
const form = new FormData();
form.append("file", file, "contract.doc");
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("contract.doc")
if err != nil {
panic(err)
}
defer f.Close()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, err := writer.CreateFormFile("file", "contract.doc")
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 classify contract clauses?
- No. txtfetch returns the contract as plain text. Clause classification and redaction are up to your own tooling.
- Can txtfetch read an .msg or .eml email thread with attachments?
- Yes. It extracts the message body, headers, and each attachment's text in one call.
- How does per-document pricing help with long contracts?
- A 300-page contract costs the same as a one-page NDA, so long exhibits don't multiply your bill.
related-reading
Start on the free plan.
Run your own documents through it before you commit. The Hobby plan needs no card.
Get started →