https://txtfetch.com/integrations/n8n/
Extract text in n8n, no plugin required.
txtfetch ships no n8n node of its own. The HTTP Request node already does the job. Point it at a link, add one header, and the rest of your workflow gets clean text.
the-problem
An n8n workflow often needs to read a PDF, a scanned form, or an email attachment before it can act on it. The HTTP Request node can forward a file, but binary data adds a Move Binary Data step first. It also risks a truncated or corrupted body on a large upload. Long-running OCR jobs are worse: n8n's own HTTP node has a request timeout, and a slow extraction can trip it before the response ever arrives.
how-to-wire-it-up
txtfetch ships no n8n plugin or connector. You wire it up with n8n's own HTTP Request node, calling the same REST API a curl command would. Pass the document as a link with the url query parameter, instead of a binary body. n8n fetches nothing itself; txtfetch fetches the document server-side, and the node never touches raw file bytes. For a document that's large or slow to OCR, set async to true, or leave it out and let txtfetch decide. Either way, add webhook_url and a Webhook node, so n8n picks up the result on its own trigger instead of holding a request open.
- Pass ?url= instead of a binary body, so the HTTP Request node never has to forward raw file bytes through n8n.
- Automatic OCR runs on scanned pages and photographed documents, in the same request as a digital-native file.
- Async mode returns a job_id immediately, so a slow OCR job never trips the HTTP Request node's timeout.
- A Webhook node catches webhook_url callbacks directly, so the workflow can branch off a finished extraction instead of polling.
- Idempotency-Key support means a retried n8n execution never re-runs and re-bills the same extraction.
pass-a-link-not-a-file
curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/report.pdf" \
-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/report.pdf"},
)
print(r.json()["extracted_text"])const endpoint = new URL("https://api.txtfetch.com/v1/extract");
endpoint.searchParams.set("url", "https://example.com/report.pdf");
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/report.pdf")
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": "..."
}large-or-slow-documents
POST /v1/extract returns 202 with a job_id whenever the document is large or slow to process, or whenever async=true is set. Poll GET /v1/extract/{job_id} for the result, or set webhook_url and have txtfetch push it instead. No single request on this page is safe to assume will always finish synchronously.
curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/report.pdf&async=true" \
-H "Authorization: Bearer $TXTFETCH_KEY"
# {"status": "processing", "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"}curl "https://api.txtfetch.com/v1/extract/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-H "Authorization: Bearer $TXTFETCH_KEY"
# {"status": "processing", "job_id": "..."} while running, then the same
# {"status": "success", "extracted_text": "...", "metadata": {...}} shape
# POST /v1/extract would have returned synchronously.curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/report.pdf" \
-H "Authorization: Bearer $TXTFETCH_KEY" \
--data-urlencode "webhook_url=https://example.com/webhooks/txtfetch"setup-steps
- Add an HTTP Request node to the workflow.
- Set Method to POST.
- Set URL to https://api.txtfetch.com/v1/extract, then add a query parameter named url set to the document's link.
- Under Authentication, add a header named Authorization with the value Bearer, followed by your API key.
- For a large or slow document, add a query parameter async set to true. Add another parameter named webhook_url, pointing at a Webhook node's test or production URL.
- Execute the node once. The response body carries extracted_text, ready for the next step in the workflow.
faq
- Does txtfetch have an official n8n node?
- No. There is no txtfetch node in the n8n node library. The built-in HTTP Request node calls the same REST API directly, with no plugin to install.
- Can the HTTP Request node send a file straight from a previous n8n step?
- Yes, as a binary body, but a link is simpler and safer for large files. Pass the document's URL with the url query parameter and let txtfetch fetch it server-side instead.
- What happens if OCR takes longer than the HTTP Request node's timeout?
- Set async to true, or let txtfetch route it automatically for a large input. The workflow gets a job_id right away, and a Webhook node picks up the result through webhook_url with no timeout risk.
- Does the HTTP Request node need an IF node to check for errors?
- Yes, that's good practice. An error response still carries a machine-readable error.code. An IF node can branch on it before the workflow tries to use a missing extracted_text field.
related-reading
other-integrations