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, or Windows-1252, where a stray byte turns a curly quote into mojibake. It might also be UTF-16 with a byte-order-mark, which 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. It detects the character encoding, UTF-8, Windows-1252, or UTF-16 with or without a byte-order-mark, before decoding it. That is where the mojibake bugs live, and it's 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, with 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. It works whether the file arrived as an upload, behind a ?url= fetch, or as an entry inside a .zip.

curl
curl -X POST https://api.txtfetch.com/v1/extract \
  -H "Authorization: Bearer $TXTFETCH_KEY" \
  -F file=@customer-export.csv
Python
import 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"])
JavaScript
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);
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("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
curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/data/transactions-2024.csv" \
  -H "Authorization: Bearer $TXTFETCH_KEY"
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/data/transactions-2024.csv"},
)

print(r.json()["extracted_text"])
JavaScript
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);
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/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": "..."
}

what-comes-back

The corpus behind /diff has no recorded .csv document yet, so we have nothing honest to show you here. Run one of your own instead. The free converter reads the file in your browser, and nothing is uploaded.

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. 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 and 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. It also 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. It still parses as a single field downstream, because 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. txtfetch recognises the file as text from the bytes, detects the encoding, and returns the content. The extension isn't what decides.

go-further

Send a real .csv through it.

One HTTP call returns the text. Read one in your browser first, for free.

Get an API key →

Open the free .csv reader →