Docs/JavaScript
Language Guides

JavaScript / Node.js

Parse receipts from Node.js, edge functions, or the browser using the native fetch API. Requires Node 18+ or any modern browser.

No installation needed

The ReceiptConverter API uses standard HTTP. In Node 18+ or modern browsers you already have fetch, FormData, and File built in — no polyfills required.

Uploading a file (Node.js)

import { readFileSync } from "node:fs";

const API_KEY = process.env.RECEIPTCONVERTER_API_KEY;

async function parseReceipt(filePath) {
  const bytes = readFileSync(filePath);
  const file  = new File([bytes], "receipt.jpg", { type: "image/jpeg" });

  const form = new FormData();
  form.append("file", file);

  const res = await fetch("https://receiptconverter.com/api/v1/convert", {
    method:  "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body:    form,
  });

  if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);
  return res.json();
}

const receipt = await parseReceipt("./receipt.jpg");
console.log(receipt.data.vendor, receipt.data.total);

TypeScript version

import { readFileSync } from "node:fs";

const API_KEY = process.env.RECEIPTCONVERTER_API_KEY!;

interface TaxLine  { label: string; amount: number; }
interface LineItem { name: string; quantity: number; unit_price: number; total_price: number; }
interface ReceiptData {
  vendor:         string | null;
  date:           string | null;
  total:          number | null;
  subtotal:       number | null;
  currency:       string | null;
  payment_method: string | null;
  category:       string | null;
  taxes:          TaxLine[];
  tip:            number | null;
  items:          LineItem[];
}
interface ConvertResponse {
  success:           boolean;
  processing_ms:     number;
  conversions_used:  number;
  conversions_limit: number;
  data:              ReceiptData;
}

async function parseReceipt(filePath: string): Promise<ConvertResponse> {
  const bytes = readFileSync(filePath);
  const file  = new File([bytes], "receipt.jpg", { type: "image/jpeg" });

  const form = new FormData();
  form.append("file", file);

  const res = await fetch("https://receiptconverter.com/api/v1/convert", {
    method:  "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body:    form,
  });

  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(`${res.status} ${(err as { error?: string }).error ?? res.statusText}`);
  }

  return res.json() as Promise<ConvertResponse>;
}

Parsing from a URL

const API_KEY = process.env.RECEIPTCONVERTER_API_KEY;

async function parseReceiptUrl(url, fileName) {
  const res = await fetch("https://receiptconverter.com/api/v1/convert", {
    method:  "POST",
    headers: {
      Authorization:  `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ url, file_name: fileName }),
  });

  if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
  return res.json();
}

const receipt = await parseReceiptUrl(
  "https://example.com/receipts/lunch.pdf",
  "lunch_march_2024.pdf"
);

Browser / client-side

Works with a file <input> or drag-and-drop. Do NOT expose your API key in client-side code — proxy through your own backend.

// Your backend proxy — keeps the API key server-side
// POST /api/parse-receipt  →  forwards to ReceiptConverter

// Frontend (React example)
async function handleFile(file) {
  const form = new FormData();
  form.append("file", file);

  const res = await fetch("/api/parse-receipt", {
    method: "POST",
    body:   form,
  });

  const receipt = await res.json();
  console.log(receipt.data.vendor, receipt.data.total);
}

Error handling

async function parseReceiptSafe(filePath) {
  try {
    const bytes = readFileSync(filePath);
    const file  = new File([bytes], "receipt.jpg", { type: "image/jpeg" });
    const form  = new FormData();
    form.append("file", file);

    const res = await fetch("https://receiptconverter.com/api/v1/convert", {
      method:  "POST",
      headers: { Authorization: `Bearer ${API_KEY}` },
      body:    form,
      signal:  AbortSignal.timeout(30_000),
    });

    if (res.status === 429) {
      const { upgrade_url } = await res.json();
      console.warn("Rate limit hit. Upgrade at:", upgrade_url);
      return null;
    }

    if (res.status === 401) {
      console.error("Invalid API key.");
      return null;
    }

    if (!res.ok) throw new Error(`Unexpected ${res.status}`);
    return res.json();

  } catch (err) {
    if (err.name === "TimeoutError") console.error("Request timed out");
    else console.error("Request failed:", err.message);
    return null;
  }
}

Batch processing

import { readFileSync } from "node:fs";
import { readdir } from "node:fs/promises";
import path from "node:path";

const API_KEY = process.env.RECEIPTCONVERTER_API_KEY;
const CONCURRENCY = 5; // Stay within rate limits

async function parseOne(filePath) {
  const bytes = readFileSync(filePath);
  const file  = new File([bytes], path.basename(filePath));
  const form  = new FormData();
  form.append("file", file);

  const res = await fetch("https://receiptconverter.com/api/v1/convert", {
    method:  "POST",
    headers: { Authorization: `Bearer ${API_KEY}` },
    body:    form,
    signal:  AbortSignal.timeout(30_000),
  });

  if (!res.ok) return null;
  return res.json();
}

// Process in chunks of CONCURRENCY
const files = await readdir("./receipts");
const results = [];

for (let i = 0; i < files.length; i += CONCURRENCY) {
  const chunk = files.slice(i, i + CONCURRENCY);
  const settled = await Promise.allSettled(
    chunk.map((f) => parseOne(`./receipts/${f}`))
  );
  results.push(...settled.map((s) => s.status === "fulfilled" ? s.value : null));
}

console.log(`Processed ${results.filter(Boolean).length}/${files.length} receipts`);