Invoice OCR to Postgres (or MySQL) with n8n
n8n strength vs spreadsheet zaps: Drive → Cryvis Invoice API → IF success → Postgres/MySQL INSERT with typed columns.
What we're building
Spreadsheets are fine for pilots. Finance systems want typed rows in Postgres or MySQL. n8n ships native Postgres and MySQL nodes — you extract with Cryvis, then INSERT without bouncing through Airtable as a middle database.
Google Drive Trigger
|
v
Download → $binary.data
|
v
HTTP Request
POST https://api.cryvis.com/v1/documents/invoice
Form-Data: file = data
|
v
IF {{ $json.success }} equals true
|
v
Postgres (or MySQL): Insert
invoice_number, dates, seller, totals, source_fileCompared to a Zapier → Airtable path: one less SaaS copy of vendor tax IDs, SQL constraints, and joins to your ERP staging tables. Sheets version of the same extract: Extract invoice data. Hub: /blog/n8n.
What you need
- n8n with Postgres or MySQL credentials (Cloud or self-hosted; self-hosted often sits on the same VPC as the DB)
- Drive folder for inbound invoices
- Cryvis API key — Invoice API · extractInvoice
- Table DDL below (or adapt)
Credits: 1 per page/image.
Suggested schema (Postgres)
CREATE TABLE invoices (
id BIGSERIAL PRIMARY KEY,
invoice_number TEXT,
invoice_date DATE,
due_date DATE,
currency TEXT,
seller_name TEXT,
seller_tax_id TEXT,
buyer_name TEXT,
subtotal NUMERIC(14, 2),
tax_total NUMERIC(14, 2),
total_amount NUMERIC(14, 2),
amount_due NUMERIC(14, 2),
source_file TEXT,
drive_file_id TEXT,
extracted_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE (seller_name, invoice_number)
);MySQL: same columns; use AUTO_INCREMENT and DATETIME(6) as you prefer. The unique key stops vendor resends from double-inserting.
Workflow nodes
- Google Drive Trigger (or Schedule + list new files)
- Google Drive — Download →
$binary.data - IF — MIME / extension allowlist (optional but recommended)
- HTTP Request — Cryvis
- IF —
{{ $json.success }}equals true - Postgres / MySQL — Insert (or Execute Query with parameterized INSERT)
- Optional: Google Drive — Move to Processed; false branch → error log table
Cryvis HTTP Request
| Setting | Value |
|---|---|
| Method | POST |
| URL | https://api.cryvis.com/v1/documents/invoice |
| Auth | Authorization: Bearer <API_KEY> (Header Auth credential) |
| Body Content Type | Form-Data |
| Parameter | Name file, Type File, Input Data Field Name data |
Full multipart notes: HTTP Request OCR setup.
Map expressions into INSERT
| Column | Expression |
|---|---|
invoice_number | {{ $json.data.invoice_number }} |
invoice_date | {{ $json.data.invoice_date }} |
due_date | {{ $json.data.due_date }} |
currency | {{ $json.data.currency }} |
seller_name | {{ $json.data.seller.name }} |
seller_tax_id | {{ $json.data.seller.tax_id }} |
buyer_name | {{ $json.data.buyer.name }} |
subtotal | {{ $json.data.subtotal }} |
tax_total | {{ $json.data.tax_total }} |
total_amount | {{ $json.data.total_amount }} |
amount_due | {{ $json.data.amount_due }} |
source_file | Drive file name from download node |
drive_file_id | Drive file id from trigger |
Use the Postgres node’s column mapping UI, or Execute Query:
INSERT INTO invoices (
invoice_number, invoice_date, due_date, currency,
seller_name, seller_tax_id, buyer_name,
subtotal, tax_total, total_amount, amount_due,
source_file, drive_file_id
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13
)
ON CONFLICT (seller_name, invoice_number) DO NOTHING;Wire query parameters from the same {{ $json.data.* }} expressions. Prefer ON CONFLICT DO NOTHING (or MySQL INSERT IGNORE / ON DUPLICATE KEY) over silent duplicate finance rows.
Null and type pitfalls
- Dates may be
nullif OCR cannot read them — allow NULL columns or route to aninvoices_reviewtable via IF{{ $json.data.invoice_number }}is empty. - Numbers from Cryvis are JSON numbers — map to
NUMERIC, not text with currency symbols. seller.tax_idis often null; that is valid.
Error handling
HTTP Request
retry on fail: 2–3 (5xx only in practice)
|
+-- IF success false → INSERT into invoice_errors (file, status, body snippet)
|
Error Trigger workflow → Slack: workflow + node + message (no full PII dump)Self-hosted: put the DB credential on a restricted network path; do not expose Postgres to the public internet just for n8n Cloud if policy forbids it — run n8n in the same private network instead.
Test
- Insert one fixture PDF into Drive.
- Run workflow; confirm one row in
invoices. - Re-drop the same file (or re-run) — unique constraint /
ON CONFLICTshould block a second row. - Upload a PNG logo — MIME IF should stop before HTTP (no wasted credits).
- Spot-check
amount_dueandcurrencyagainst the PDF.
Why not Sheets here
Sheets is fine for ops review. Databases win when you need:
- Unique constraints on
(seller, invoice_number) - Joins to purchase orders / vendors
- Downstream dbt or ERP loaders reading SQL
- Retention and access control that match your security review
Keep a parallel Sheets append only if humans need a live queue — or query Postgres from a BI tool.
Production checklist
- Unique key on seller + invoice number
- Move Drive file after successful INSERT
- Error table + Error Trigger alert
- Credit budget for multi-page statements — Credits
- Credential rotation for Cryvis Bearer and DB user
Next steps
Gmail → Sheets + Slack triage: Automate invoice processing. Building-block HTTP: HTTP Request OCR.
Sign up · Pricing · Invoice · extractInvoice · /blog/n8n.