Spreadsheets, resolved not raw.
Cell values are indices into a shared string table, formulas store their formula, not their answer — Tika resolves both before you ever see the response.
the-problem
A spreadsheet's cells don't hold their own text. In .xlsx, string cell values are indices into xl/sharedStrings.xml — a dedup table shared across the whole workbook — not inline text, so reading cells in isolation gets you numbers pointing at other numbers. Formula cells store the formula itself (=SUM(A1:A10)); the number a human sees is a separately cached result value, and plenty of naive readers grab the wrong one or the formula string. Number formats add another layer of indirection — a cell can be stored as 0.42 and displayed as 42%, or stored as a serial integer and displayed as a date — so 'the value' and 'what's on screen' genuinely differ. Hidden sheets, hidden rows, and merged cells still carry meaning that a flat text dump can drop entirely.
one-request-solution
txtfetch resolves the indirection for you: shared-string references become their actual text, and formula cells return Tika's cached computed value rather than the =SUM() source, so the numbers in your response match what a person looking at the sheet would see. For table structure — rows and columns kept intact instead of flattened into a wall of numbers — request ?format=markdown.
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@q1-revenue-workbook.xlsximport os
import requests
with open("q1-revenue-workbook.xlsx", "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("q1-revenue-workbook.xlsx")]);
const form = new FormData();
form.append("file", file, "q1-revenue-workbook.xlsx");
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("q1-revenue-workbook.xlsx")
if err != nil {
panic(err)
}
defer f.Close()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, err := writer.CreateFormFile("file", "q1-revenue-workbook.xlsx")
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/reports/regional-sales.xlsx" \
-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/reports/regional-sales.xlsx"},
)
print(r.json()["extracted_text"])const endpoint = new URL("https://api.txtfetch.com/v1/extract");
endpoint.searchParams.set("url", "https://example.com/reports/regional-sales.xlsx");
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/reports/regional-sales.xlsx")
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": "..."
}formats-covered
.xlsx.xls.xlsm.csv.ods
response-options
Need rows and columns, not a flat wall of numbers? ?format=markdown returns each sheet as a proper markdown table.
faq
- Does extraction return the formula or the calculated value?
- The calculated value — Tika reads the cached formula result that Excel stores alongside the formula, so you get 18200 instead of the literal string =SUM(A1:A10).
- Do percentages and dates come back as raw numbers?
- No — the cell's number format is applied before the text is returned, so a cell stored as 0.42 with a percent format comes back as 42%, and a date stored as the serial number 46229 comes back as 2026-07-26. A cell with no format applied returns its stored value unchanged, which is what you want for a plain number.
- Are hidden sheets and hidden rows included?
- Yes — txtfetch extracts hidden sheets and rows along with visible ones, since hiding a sheet in Excel doesn't remove its data.
- Can I get real table structure instead of a flat text dump?
- Yes — pass ?format=markdown and rows/columns come back as a markdown table per sheet instead of space-separated cell values.
go-further
Stop parsing. Start shipping.
Create an account and get an API key in minutes — the free Hobby plan needs no card.
Get started →