> Source: https://txtfetch.com/docs > Plain-text twin — every page on txtfetch.com has one. https://txtfetch.com/text --- # The whole API fits on this page. One endpoint, two ways to call it, one response shape. If you can curl, you've already integrated. Prefer to see it first? [Try the playground →](https://txtfetch.com/playground) ## Authenticate Every request carries your API key in the `Authorization` header. [Create a key in your dashboard](https://app.txtfetch.com/signup). ``` export TXTFETCH_KEY="tf_live_..." ``` A missing key returns `401`. An invalid or revoked key returns `403`. Both come back as API Gateway's platform `{"message": "…"}` body, not the typed error shape below. See [the error reference](https://txtfetch.com/docs/errors) for the full auth-failure semantics. ## Extract a file POST the bytes as multipart form data. Any of the [supported formats](https://txtfetch.com/formats) works. 615 of the 1,683 Tika detects have a real parser, [checked against the exact build we run](https://txtfetch.com/formats/coverage). txtfetch reads the real type from the bytes, not the extension. curl ```curl curl -X POST https://api.txtfetch.com/v1/extract \ -H "Authorization: Bearer $TXTFETCH_KEY" \ -F file=@quarterly-report.pdf ``` Python ```python import os import requests with open("quarterly-report.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"]) ``` JavaScript ```javascript import { readFile } from "node:fs/promises"; const file = new Blob([await readFile("quarterly-report.pdf")]); const form = new FormData(); form.append("file", file, "quarterly-report.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); ``` Go ```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("quarterly-report.pdf") if err != nil { panic(err) } defer f.Close() var body bytes.Buffer writer := multipart.NewWriter(&body) part, err := writer.CreateFormFile("file", "quarterly-report.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) } ``` ## Extract from a URL Or skip the download: pass a `url` parameter and txtfetch fetches the document server-side. curl ```curl curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/whitepaper.docx" \ -H "Authorization: Bearer $TXTFETCH_KEY" ``` Python ```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/whitepaper.docx"}, ) print(r.json()["extracted_text"]) ``` JavaScript ```javascript const endpoint = new URL("https://api.txtfetch.com/v1/extract"); endpoint.searchParams.set("url", "https://example.com/whitepaper.docx"); 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 ```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/whitepaper.docx") 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) } ``` ## Response Success is always the same shape. Scanned documents and images go through OCR automatically. It's the same request and the same response. ``` { "status": "success", "extracted_text": "Q3 revenue grew 34% year over year, driven by...", "metadata": { "content_type": "application/pdf", "bytes": 482913, "chars": 812, "ocr": false } } ``` ## Response formats Need structure instead of flat text? Pass `?format=markdown` for GFM output, tables included, or `?format=json` for a typed element tree. The endpoint, auth, and error shape stay the same. `format` defaults to `text` (the shape above) when omitted. ``` curl -s -X POST "https://api.txtfetch.com/v1/extract?format=markdown" \ -H "Authorization: Bearer $TXTFETCH_KEY" \ -F file=@quarterly-report.pdf ``` ``` { "status": "success", "markdown": "# Quarterly Report\n\n...\n\n| Region | Revenue |\n| --- | --- |\n| EMEA | 34% |\n", "metadata": { "content_type": "application/pdf", "bytes": 482913, "chars": 812, "format": "markdown", "tier": "standard", "pages": 4, "vlm": false } } ``` ``` curl -s -X POST "https://api.txtfetch.com/v1/extract?format=json" \ -H "Authorization: Bearer $TXTFETCH_KEY" \ -F file=@quarterly-report.pdf ``` ``` { "status": "success", "elements": [ { "type": "heading", "text": "Quarterly Report", "level": 1, "page": 1, "offset": 0, "bbox": null }, { "type": "table", "text": "Region\tRevenue\nEMEA\t34%", "markdown": "| Region | Revenue |\n| --- | --- |\n| EMEA | 34% |", "html": "...
", "cells": "[[…]] — one row per array entry, each cell carries text/colspan/rowspan/header", "rows": 2, "cols": 2, "page": 1, "offset": 1, "bbox": null } ], "metadata": { "content_type": "application/pdf", "bytes": 482913, "chars": 812, "format": "json", "tier": "standard", "pages": 4, "vlm": false } } ``` The full element schema is part of the published contract. It covers every type, required field, and the offset/page monotonicity guarantee. See the [OpenAPI spec →](https://txtfetch.com/openapi.json) Deciding which of the three to request? [See the same document rendered all three ways →](https://txtfetch.com/output) ## Premium quality Pass `?quality=premium` to run a vision-language model instead of Tika, for documents where layout matters more than raw text. Premium always routes async, even for a one-page PDF, since VLM latency can exceed the sync budget. Expect a `202` and a `job_id` to poll, same as above. ``` curl -s -X POST "https://api.txtfetch.com/v1/extract?quality=premium" \ -H "Authorization: Bearer $TXTFETCH_KEY" \ -F file=@quarterly-report.pdf ``` ``` { "status": "success", "extracted_text": "Q3 revenue grew 34% year over year, driven by...", "metadata": { "content_type": "application/pdf", "bytes": 482913, "chars": 812, "format": "text", "tier": "premium", "pages": 4, "vlm": true, "usage": { "model": "claude-sonnet-5", "input_tokens": 2140, "output_tokens": 612 } } } ``` Premium never fails your request outright. If it hits a cost/timeout/size guardrail, it falls back safely to the same Tika baseline as the standard tier. The response is still a `success`, with `metadata.tier_downgraded: true` and a `metadata.downgrade_reason` explaining why. ``` { "status": "success", "extracted_text": "Q3 revenue grew 34% year over year, driven by...", "metadata": { "content_type": "application/pdf", "bytes": 482913, "chars": 812, "format": "text", "tier": "standard", "pages": 4, "vlm": false, "tier_downgraded": true, "downgrade_reason": "vlm_disabled" } } ``` There's no plan-level gate on `quality=premium`. Every plan can request it. Full contract, guardrails, and every `downgrade_reason`: [OpenAPI spec →](https://txtfetch.com/openapi.json) Conceptual guide to both tiers, plus the async routing and downgrade caveat: [what comes back →](https://txtfetch.com/output) ## Errors Failures are explicit and machine-readable. They're never a silent empty string. Failed extractions don't count against your quota. See the [complete error reference](https://txtfetch.com/docs/errors) for every code. ``` { "status": "error", "error": { "code": "extraction_failed", "message": "extraction produced no text" } } ``` ## Limits and quotas Every metered response carries `X-RateLimit-*`(per-minute) and `X-Quota-*` (monthly) headers. Going over either returns a typed `429`. `rate_limited` vs `quota_exceeded` is disambiguated by `error.code`. A `Retry-After` header tells you exactly when to retry. Failed extractions don't count against your monthly quota, only successful ones. ``` { "status": "error", "error": { "code": "quota_exceeded", "message": "monthly quota exceeded for this API key" } } ``` Full breakdown of both limits, the header contract, and the auth-failure statuses: [Error reference →](https://txtfetch.com/docs/errors) ## Works with everything The response is plain JSON, so txtfetch drops into any stack. Pipe it straight into your chunker, embedder, or index: ``` curl -s -X POST https://api.txtfetch.com/v1/extract \ -H "Authorization: Bearer $TXTFETCH_KEY" \ -F file=@contract.pdf | jq -r .extracted_text ``` ## Async jobs Large uploads or documents are automatically routed to an async job path instead of blocking the request. Pass `?async=true` to force it for any request. Either way you get a `202` with a `job_id`. Poll `GET /v1/extract/{job_id}` until it returns the same success shape as above. ``` { "status": "processing", "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ``` Full walkthrough, SDK helpers, and webhook delivery instead of polling: [Async & webhooks →](https://txtfetch.com/docs/async) ## Idempotency Pass an `Idempotency-Key` header on `POST /v1/extract` so a retried request is never double-run. A repeat under the same key replays the original response instead of re-extracting. Details, TTL, and the SDKs' automatic retry behavior: [Idempotency →](https://txtfetch.com/docs/idempotency) ## Machine-readable spec The full contract covers every field, error code, and status. It's published as [OpenAPI 3.1](https://txtfetch.com/openapi.json). Generate a typed client for whatever language you're in. guides-and-sdks ## Past the first curl. ### [Quickstarts](https://txtfetch.com/docs/quickstarts) Per-framework install + extract: SDK JS, SDK Python, LangChain (Python + JS), LlamaIndex. install → extract ### [RAG recipe](https://txtfetch.com/docs/recipe) Chunk → embed → index a document end-to-end with a loader, then query it. chunk → embed → index ### [Async & webhooks](https://txtfetch.com/docs/async) Job lifecycle, polling, webhook\_url delivery, and verifying the signature. 202 → poll or push ### [Idempotency](https://txtfetch.com/docs/idempotency) Idempotency-Key semantics: replay, mismatch handling, and SDK auto-retry. retry-safe by default ### [Error reference](https://txtfetch.com/docs/errors) Every error code, its HTTP status, cause, and remediation, all in one table. code → status → fix ### [Changelog](https://txtfetch.com/changelog) A dated record of what shipped since launch, each entry linked to the page that proves it. what shipped, when ## Watch it run, then take a key. The playground replays a real recorded response for every sample document. [Open the playground →](https://txtfetch.com/playground) [Get an API key →](https://app.txtfetch.com/signup)