https://txtfetch.com/extract/html/
Any web page, fetched and reduced to clean text.
Pass a URL, not a file. txtfetch fetches the page server-side and hands back clean text, with the markup, scripts, and styles gone. Deciding what's chrome (nav, ads, cookie banners) versus article is a caller-side call. See the free tool below to check yours.
the-problem
Turning a web page into usable text usually means standing up a scraper. You fetch the HTML yourself, strip scripts and styles, and guess at which <div> is the actual content versus navigation and ads. You also handle redirects and encoding along the way. That's infrastructure most teams don't want to own just to get plain text into a RAG index.
one-request-solution
Skip the fetch-and-strip pipeline. Pass a url parameter to txtfetch, and it retrieves the page server-side, runs it through Apache Tika's HTML parser, and returns clean plain text. No headless browser, no boilerplate code to maintain.
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@landing-page.htmlimport os
import requests
with open("landing-page.html", "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("landing-page.html")]);
const form = new FormData();
form.append("file", file, "landing-page.html");
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("landing-page.html")
if err != nil {
panic(err)
}
defer f.Close()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, err := writer.CreateFormFile("file", "landing-page.html")
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 -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/blog/2024-annual-report" \
-H "Authorization: Bearer $TXTFETCH_KEY"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/blog/2024-annual-report"},
)
print(r.json()["extracted_text"])const endpoint = new URL("https://api.txtfetch.com/v1/extract");
endpoint.searchParams.set("url", "https://example.com/blog/2024-annual-report");
const res = await fetch(endpoint, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
});
const { extracted_text } = await res.json();
console.log(extracted_text);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/blog/2024-annual-report")
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
That is the shape. This is the text. A real .html from the benchmark corpus, word-diffed against the raw parser output. Nothing here was written for this page. Every character comes from a committed fixture.
txtfetch Pricing Page (HTML)
raw parser output
Pricing
Turn any document into clean plain text over a simple HTTP API.
pricing tiers diagram
Plans
Plan Price Quota
Hobby $0 500 docs/mo
Developer $19 10,000 docs/mo
Scale $99 100,000 docs/mo
Questions? Use the contact form & skip the 'support' email.
the human-checked ideal
Pricing
Turn any document into clean plain text over a simple HTTP API.
pricing tiers diagram
Plans
Plan Price Quota
Hobby $0 500 docs/mo
Developer $19 10,000 docs/mo
Scale $99 100,000 docs/mo
Questions? Use the contact form & skip the 'support' email.See all ten documents, with the full explanation →
try-your-own
Drop your own .html file below. Your browser reads it. Nothing uploads, and txtfetch never sees it.
Stays in your browser. Up to 25 MB.
What's not in this text:
Your browser read this file. No request left your browser, so txtfetch never received it. The API returns this same text as { status, extracted_text }.
See the real API response in the playground. Need many files, a folder, or a .zip? Use file-to-text instead.
formats-covered
.html.htm.xhtml
faq
- How do I extract text from a web page URL?
- POST to https://api.txtfetch.com/v1/extract?url=<page-url> with your API key in the Authorization header. txtfetch fetches the page server-side and returns { "status": "success", "extracted_text": "..." }.
- Does it strip navigation, ads, and boilerplate?
- It strips markup, not boilerplate. Tika's HTML parser removes scripts, styles, and tags. But nav, footer, and cookie-banner text is still text, so it comes back in the response like any other paragraph. Deciding what's chrome versus article is a deliberate, caller-side step. See /tools/html-to-text for a free tool that shows you exactly what your pages' chrome looks like before you index them.
- Can I upload a local .html file instead of a URL?
- Yes. The same endpoint accepts a multipart file upload for local HTML files, same as any other format.
go-further
- Drop an .html file, paste HTML, or paste a copied page — free, in your browser →
- The fetch rules behind a bare URL, redirects, SSRF blocks, and timeouts →
- Delimited data lives on the CSV side →
- Structured/config markup lives on the XML side →
- Links in your HTML? The href values do not come back →
- API quickstart →
- How the pipeline works →
- Extract it from your language →
- Get an API key →
Web & data
Send a real .html through it.
One HTTP call returns the text. Read one in your browser first, for free.
Get an API key →