Invoice processing is one of those problems every finance team has and nobody wants to solve manually. A junior accountant spending 20 minutes per invoice on data entry is $800/month in time for a 50-invoice business. An OCR-powered processor that extracts the same data in 3 seconds and pushes it to a spreadsheet or accounting system is a $50/month SaaS product people actually pay for.
This tutorial walks through building exactly that — a Next.js app that accepts an invoice image (photo or scan), extracts structured data with the APlicious /tools/ocr endpoint, and returns a clean JSON payload ready to push to QuickBooks, Xero, or a Google Sheet.
What the /tools/ocr endpoint does
The OCR endpoint accepts either a multipart file upload or a JSON body with an image_url. It runs the image through GPT-4o vision and returns extracted text. For invoice processing, you pair OCR extraction with a structured prompt — either in a thin server layer you write, or by using the raw text output and parsing it yourself.
# Basic OCR call
curl -X POST https://aplicious.com/api/v1/tools/ocr \
-H "X-API-Key: YOUR_KEY" \
-F "image=@invoice.jpg"
# Response
{
"success": true,
"data": {
"text": "INVOICE\nVendor: Acme Corp\nDate: 2026-06-15\nInvoice #: INV-0042\n...",
"confidence": 0.97,
"word_count": 312
}
}Project setup
Create a Next.js app and install one dependency — we only need the APlicious API, no OCR libraries:
npx create-next-app@latest invoice-processor --typescript --app
cd invoice-processor
# Add your APlicious key to .env.local
echo "APLICIOUS_KEY=lapi_live_..." >> .env.localThe upload route
Create src/app/api/process/route.ts. This receives the invoice image from the frontend, forwards it to APlicious OCR, then parses the extracted text into structured fields:
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const formData = await req.formData();
const file = formData.get("invoice") as File;
if (!file) return NextResponse.json({ error: "No file" }, { status: 400 });
// Forward to APlicious OCR
const ocrForm = new FormData();
ocrForm.append("image", file);
const ocrRes = await fetch("https://aplicious.com/api/v1/tools/ocr", {
method: "POST",
headers: { "X-API-Key": process.env.APLICIOUS_KEY! },
body: ocrForm,
});
const ocr = await ocrRes.json();
if (!ocr.success) return NextResponse.json({ error: "OCR failed" }, { status: 500 });
// Parse the raw text into invoice fields
const text: string = ocr.data.text;
const invoice = parseInvoiceText(text);
return NextResponse.json({ success: true, invoice, raw_text: text });
}
function parseInvoiceText(text: string) {
// Regex-based extraction — works for most standard invoice formats
const lines = text.split("\n").map(l => l.trim()).filter(Boolean);
const get = (pattern: RegExp) => (text.match(pattern)?.[1] ?? "").trim();
return {
vendor: get(/(?:from|vendor|bill from|company)[:\s]+([^\n]+)/i),
invoice_no: get(/(?:invoice\s*#?|inv[\s-]*no\.?)[:\s]+([A-Z0-9-]+)/i),
date: get(/(?:invoice date|date)[:\s]+([\d\/\-]+)/i),
due_date: get(/(?:due date|payment due)[:\s]+([\d\/\-]+)/i),
total: get(/(?:total|amount due)[:\s]+\$?([\d,\.]+)/i),
currency: text.match(/\b(USD|EUR|GBP|INR|AUD|CAD)\b/)?.[1] ?? "USD",
line_items: extractLineItems(lines),
};
}
function extractLineItems(lines: string[]) {
// Look for lines matching: Description ... $amount
return lines
.filter(l => /\$[\d,\.]+/.test(l) && l.length > 8)
.map(l => {
const amountMatch = l.match(/\$([\d,\.]+)/);
const amount = amountMatch ? parseFloat(amountMatch[1].replace(",", "")) : 0;
const description = l.replace(/\$[\d,\.]+/, "").trim();
return { description, amount };
})
.filter(item => item.description.length > 2 && item.amount > 0);
}The frontend
A minimal upload form in src/app/page.tsx:
"use client";
import { useState } from "react";
export default function Home() {
const [result, setResult] = useState<any>(null);
const [loading, setLoading] = useState(false);
async function handleUpload(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
setLoading(true);
const form = new FormData();
form.append("invoice", file);
const res = await fetch("/api/process", { method: "POST", body: form });
const data = await res.json();
setResult(data.invoice);
setLoading(false);
}
return (
<main style={{ maxWidth: 640, margin: "60px auto", padding: "0 24px" }}>
<h1>Invoice Processor</h1>
<input type="file" accept="image/*,.pdf" onChange={handleUpload} />
{loading && <p>Processing...</p>}
{result && (
<pre style={{ background: "#111", color: "#a9b1d6", padding: 20, borderRadius: 10, marginTop: 20 }}>
{JSON.stringify(result, null, 2)}
</pre>
)}
</main>
);
}What the output looks like
{
"vendor": "Acme Corp",
"invoice_no": "INV-0042",
"date": "2026-06-15",
"due_date": "2026-07-15",
"total": 4750.00,
"currency": "USD",
"line_items": [
{ "description": "Software consulting (40 hrs)", "amount": 4000.00 },
{ "description": "Cloud infrastructure setup", "amount": 750.00 }
]
}Turning this into a $50/month SaaS
The extraction logic above handles the majority of standard invoice formats. From here, the product grows in two directions: integrations (push extracted data to QuickBooks, Xero, Google Sheets) and reliability (handle edge cases, add manual correction UI, support multi-page PDFs via /tools/pdf/split before OCR).
Pricing model: charge per invoice processed, or a flat monthly fee for up to N invoices. At 5 credits per OCR call and the APlicious Starter plan at $19/month (10,000 credits), you can process 2,000 invoices for $19 in upstream costs. Charge customers $0.10/invoice or $49/month for 500 — the margin is real from day one.
Your API key from the dashboard is all you need to run it locally in under 5 minutes.
The OCR endpoint page has the full curl reference, parameter docs, and revenue math for three invoice-processor business ideas.