CSV, decoded from the bytes.
Comma, semicolon, tab or pipe; UTF-8, Windows-1252, or UTF-16 with a BOM — txtfetch settles the encoding question from the file itself and hands back the rows exactly as written.
the-problem
A .csv extension promises very little about what's actually inside. The delimiter might be a comma, a semicolon (common in locales where comma is the decimal separator), a tab, or a pipe. The character encoding might be UTF-8, Windows-1252 (where a stray byte turns a curly quote into mojibake), or UTF-16 with a byte-order-mark most parsers don't expect on a 'text' file. And quoted fields can legally contain embedded newlines and the delimiter character itself — a naive line-by-line reader splits those rows in the wrong place and shifts every column after it.
one-request-solution
txtfetch identifies the file as text from the actual bytes, not the extension, and detects the character encoding — UTF-8, Windows-1252, UTF-16 with or without a byte-order-mark — before decoding it. That is where the mojibake bugs live, and it is the part you can't do reliably yourself without reading the file first. What comes back is the delimited content, decoded and complete: every row, every quoted field, embedded newlines and separator characters intact. txtfetch deliberately doesn't guess at a delimiter and re-shape your data — your own CSV reader already knows the separator, and it works whether the file arrived as an upload, behind a ?url= fetch, or as an entry inside a .zip.
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@customer-export.csvimport os
import requests
with open("customer-export.csv", "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("customer-export.csv")]);
const form = new FormData();
form.append("file", file, "customer-export.csv");
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("customer-export.csv")
if err != nil {
panic(err)
}
defer f.Close()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, err := writer.CreateFormFile("file", "customer-export.csv")
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/data/transactions-2024.csv" \
-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/data/transactions-2024.csv"},
)
print(r.json()["extracted_text"])const endpoint = new URL("https://api.txtfetch.com/v1/extract");
endpoint.searchParams.set("url", "https://example.com/data/transactions-2024.csv");
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/data/transactions-2024.csv")
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
.csv.tsv.psv.txt
response-options
Want real rows and columns rather than delimited text? That structure has to exist in the file to begin with: ?format=markdown renders a markdown table for spreadsheet formats, where there are actual cells to work from — see the .xlsx page. A raw .csv has no cell model to recover, so it comes back as text.
faq
- Does it handle semicolon- or tab-delimited files, not just commas?
- Yes, because the delimiter isn't something txtfetch has to get right. The file is decoded and returned as text with its separators intact, so a semicolon-, tab- or pipe-delimited export comes back complete and your own CSV reader splits it on the separator it already expects.
- Why do my exported CSVs show garbled characters (mojibake)?
- That's a charset mismatch — a file saved as Windows-1252 read as UTF-8, or a UTF-16 export read one byte at a time. txtfetch detects the encoding from the byte patterns rather than assuming UTF-8, and strips the byte-order-mark that trips up readers treating .csv as plain ASCII.
- What happens to quoted fields containing commas or line breaks?
- Nothing is reflowed. A quoted value containing the separator character or an embedded newline comes back exactly as it appears in the file, so it still parses as a single field downstream — txtfetch doesn't split rows or columns on your behalf.
- Is TSV (tab-separated) or PSV (pipe-separated) treated differently from CSV?
- No — .tsv, .psv and .txt take the same path as .csv: recognise it as text from the bytes, detect the encoding, return the content. The extension isn't what decides.
go-further
Web & data
Stop parsing. Start shipping.
Create an account and get an API key in minutes — the free Hobby plan needs no card.
Get started →