Extract Invoice Data into Google Sheets with n8n
n8n workflow: Google Drive → HTTP Request Cryvis Invoice API → IF success → Google Sheets. Map invoice_number, seller, totals.
What we're building
A Drive folder receives invoice PDFs or images. n8n downloads each file into $binary.data, posts it to Cryvis, and appends one Sheets row with header fields — number, dates, vendor, totals. One document → one row (no line-item expansion).
Google Drive Trigger (folder)
|
v
Google Drive: Download file → $binary.data
|
v
HTTP Request
POST https://api.cryvis.com/v1/documents/invoice
Form-Data: file = data
|
v
IF {{ $json.success }} equals true
|
v
Google Sheets: Append row
invoice_number | invoice_date | due_date | currency
seller.name | buyer.name | subtotal | tax_total
total_amount | amount_due | file nameHTTP mechanics: HTTP Request OCR setup. Hub: /blog/n8n.
What you need
- n8n Cloud or self-hosted
- Google Drive folder for inbound invoices
- Google Sheet with a header row (columns in step “Sheets”)
- Cryvis API key — Invoice API, docs extractInvoice
- Sample invoice PDF or JPEG/PNG/WebP
Credits: 1 per PDF page or image. See Credits.
Create the workflow
Nodes in order:
- Google Drive Trigger — file created in folder (or Schedule + Drive Search if you prefer polling)
- Google Drive — Download file
- HTTP Request — Cryvis invoice
- IF —
{{ $json.success }}is true - Google Sheets — Append or Update row
- Optional: Error Trigger workflow for failed runs; Google Drive Move file after success
Configure source
Drive Trigger / Search
- Folder: inbound invoices only — do not watch the whole Drive
- Filter by MIME when the node allows:
application/pdf,image/jpeg,image/png,image/webp
Download file
- File ID from the trigger
- Confirm output binary property name (default
data). That name is what HTTP Request must reference.
Edge case: Google Docs native files are not invoice binaries. Stick to uploaded PDFs/images.
Configure Cryvis HTTP Request
| Setting | Value |
|---|---|
| Method | POST |
| URL | https://api.cryvis.com/v1/documents/invoice |
| Authentication | Header Auth / Bearer — Authorization: Bearer <API_KEY> |
| Body Content Type | Form-Data |
| Body parameter | Name file, Type File, Input Data Field Name data |
Do not set Content-Type manually. Timeout: raise to 60–120s for multi-page scans if the default cuts off.
Accepted MIME: pdf, jpeg, png, webp. This workflow sends one file per execution.
Process response
Cryvis returns roughly:
{
"success": true,
"data": {
"invoice_number": "INV-2024-00142",
"invoice_date": "2024-03-15",
"due_date": "2024-04-14",
"currency": "USD",
"seller": { "name": "Acme Cloud Services", "tax_id": "US12-3456789" },
"buyer": { "name": "Globex Corporation" },
"line_items": [],
"subtotal": 1148.98,
"tax_total": 94.79,
"total_amount": 1193.77,
"amount_due": 1193.77
},
"meta": {}
}Expressions for Sheets (and any Set node):
{{ $json.data.invoice_number }}
{{ $json.data.invoice_date }}
{{ $json.data.due_date }}
{{ $json.data.currency }}
{{ $json.data.seller.name }}
{{ $json.data.buyer.name }}
{{ $json.data.subtotal }}
{{ $json.data.tax_total }}
{{ $json.data.total_amount }}
{{ $json.data.amount_due }}Nested objects need dotted paths. Do not dump the whole seller object into one cell unless you want raw JSON.
Configure Sheets
Header row:
Invoice Number | Invoice Date | Due Date | Currency | Vendor | Buyer | Subtotal | Tax | Total | Amount Due | Source File | Processed At
| Column | Expression / source |
|---|---|
| Invoice Number | {{ $json.data.invoice_number }} |
| Invoice Date | {{ $json.data.invoice_date }} |
| Due Date | {{ $json.data.due_date }} |
| Currency | {{ $json.data.currency }} |
| Vendor | {{ $json.data.seller.name }} |
| Buyer | {{ $json.data.buyer.name }} |
| Subtotal | {{ $json.data.subtotal }} |
| Tax | {{ $json.data.tax_total }} |
| Total | {{ $json.data.total_amount }} |
| Amount Due | {{ $json.data.amount_due }} |
| Source File | Drive file name from earlier node ({{ $('Google Drive').item.json.name }} or equivalent) |
| Processed At | {{ $now }} |
Leave line_items for a dedicated flow with a Split Out / Loop later.
IF, filters, errors
IF after HTTP Request: true branch → Sheets; false branch → Set error fields or Slack.
MIME filter before HTTP (IF or Switch on mimeType / extension): only .pdf, .jpg, .jpeg, .png, .webp.
Retry on Fail on HTTP Request: useful for 5xx. Skip aggressive retries on 401/400.
Error Trigger workflow: notify with file name + status code; do not paste full invoice JSON into public channels.
Test
- Drop one known invoice into the Drive folder.
- Execute the workflow once.
- Confirm HTTP status 200 and
success: true. - Verify Sheets columns — especially currency and
amount_duevstotal_amount. - Avoid re-processing the same file: Move to
/Invoices/Processedafter success, or storeinvoice_number+ vendor in a static data / DB check.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 / 403 | Bad Bearer token | Recreate key; Authorization: Bearer … |
| 400 / missing file | Wrong Form-Data name or binary property | Name file, Input Data Field Name matches $binary.* |
| Empty Sheets cells | Mapped without data. | Use {{ $json.data.invoice_number }} |
| Duplicate rows | Trigger re-fires | Move file after success |
| Credits spike | Multi-page PDF | Each page = 1 credit — Credits |
Execute the HTTP Request node alone and inspect JSON before wiring Sheets.
Production notes
- Archive after success so the Drive trigger does not re-pick the same PDF.
- Self-hosted: pin execution data retention if binaries accumulate.
- Idempotency: IF or Code node that skips when
invoice_numberalready exists in Sheets/DB.
Next steps
Gmail intake + Slack: Automate invoice processing. Postgres INSERT: Invoice to database.
Sign up · Pricing · extractInvoice · Invoice · Hub /blog/n8n.