← Back to blog

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 name

HTTP 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:

  1. Google Drive Trigger — file created in folder (or Schedule + Drive Search if you prefer polling)
  2. Google Drive — Download file
  3. HTTP Request — Cryvis invoice
  4. IF{{ $json.success }} is true
  5. Google Sheets — Append or Update row
  6. 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

SettingValue
MethodPOST
URLhttps://api.cryvis.com/v1/documents/invoice
AuthenticationHeader Auth / Bearer — Authorization: Bearer <API_KEY>
Body Content TypeForm-Data
Body parameterName 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

ColumnExpression / 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 FileDrive 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

  1. Drop one known invoice into the Drive folder.
  2. Execute the workflow once.
  3. Confirm HTTP status 200 and success: true.
  4. Verify Sheets columns — especially currency and amount_due vs total_amount.
  5. Avoid re-processing the same file: Move to /Invoices/Processed after success, or store invoice_number + vendor in a static data / DB check.

Troubleshooting

SymptomLikely causeFix
401 / 403Bad Bearer tokenRecreate key; Authorization: Bearer …
400 / missing fileWrong Form-Data name or binary propertyName file, Input Data Field Name matches $binary.*
Empty Sheets cellsMapped without data.Use {{ $json.data.invoice_number }}
Duplicate rowsTrigger re-firesMove file after success
Credits spikeMulti-page PDFEach 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_number already 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.