← Back to blog

Extract Invoice Line Items into Google Sheets with Make.com

Drive → Cryvis Invoice API → Iterator on line_items → one Sheets row per item. Includes Array Aggregator notes for header+lines.

What we're building

Header-only Sheets rows hide SKU-level detail. This scenario extracts the full invoice, then Iterates data.line_items so each description/qty/price becomes its own row — keyed by invoice_number and vendor.

Google Drive: Watch Files
        |
        v
Google Drive: Download a File
        |
        v
HTTP: POST /v1/documents/invoice
        |
        v
JSON: Parse JSON
        |
        +-----> (optional) Sheets: Add a Row  [HEADER sheet]
        |
        v
Iterator: data.line_items[]
        |
        v
Google Sheets: Add a Row  [LINE ITEMS sheet]
  invoice_number | seller.name | description
  quantity | unit_price | amount | currency

When you need the original array again after the Iterator (e.g. write raw JSON to a Data Store), put an Array Aggregator after the Iterator targeting the Iterator module.

What you need

  • Make.com, Google Drive, Google Sheets
  • Two sheets (or tabs): Headers (optional) and Line Items
  • Cryvis API key — Invoice
  • Invoice PDF that actually has multiple line items

Credits: 1 per page/image, regardless of how many line items you write to Sheets.

Create the scenario

  1. Google Drive — Watch Files
  2. Google Drive — Download a File
  3. HTTP — Make a Request
  4. JSON — Parse JSON
  5. Optional: Google Sheets — Add a Row (header tab)
  6. Flow control — Iterator
  7. Google Sheets — Add a Row (line items tab)
  8. Optional: Array Aggregator → Data Store / archive JSON

Configure source

Same Drive Watch + Download pattern as header extraction. Filter to pdf/jpeg/png/webp.

Prefer invoices with clear tables. Scanned photos of crumpled paper still work as images but line boundaries are messier — validate quantity × unit_price ≈ amount in a post-Filter if you care about AP accuracy.

Configure Cryvis HTTP

SettingValue
URLhttps://api.cryvis.com/v1/documents/invoice
MethodPOST
Body typeMultipart/form-data
Field fileDrive binary
Auth headerAuthorization: Bearer <API_KEY>

Response includes data.line_items as an array of objects, typically:

{
  "description": "Cloud hosting - Pro plan",
  "quantity": 2,
  "unit_price": 499.99,
  "amount": 999.98
}

Exact keys can include extras depending on the document; map what exists and leave unused columns blank.

Parent vs item bundles: After the Iterator starts, Make’s mapping UI shows the current line item at the top level of that branch. Invoice-level fields come from earlier modules — expand the Parse JSON / HTTP bundle in the mapper and pick data.invoice_number from there. If you only map Iterator outputs, every row loses the invoice number and your sheet becomes unusable for joins.

Process response

Without Iterator, Sheets would get one cell of JSON for all lines — useless for pivots.

Iterator setup:

  • Array: map data.line_items from Parse JSON
  • Each bundle exposes one line item’s fields as the current item

Inside the Iterator branch, you still have access to parent bundles (invoice-level fields) in Make’s mapper — map both:

Line Items columnSource
Invoice Numberparent data.invoice_number
Vendorparent data.seller.name
Invoice Dateparent data.invoice_date
Currencyparent data.currency
Descriptionitem description
Quantityitem quantity
Unit Priceitem unit_price
Amountitem amount
Line IndexIterator Bundle order position
Source FileDrive name

Empty line_items: Iterator produces zero bundles — no rows. Optionally Filter before Iterator: length(line_items) > 0, else write a Headers-only row with a “No lines” flag.

Array Aggregator notes

Use Array Aggregator when a later module needs the full array again:

Iterator (line_items)
   |
   v
... per-item work (Sheets Add a Row) ...
   |
   v
Array Aggregator
  Source module: Iterator
  Aggregated fields: description, quantity, unit_price, amount
   |
   v
Data Store / HTTP / email summary

Common mistake: Aggregator pointed at HTTP instead of Iterator — you get one element, not the rebuilt array.

You do not need Aggregator just to write Sheets rows; Iterator → Add a Row is enough.

Configure destination

Line Items tab headers:

Invoice Number | Vendor | Invoice Date | Currency | Line # | Description | Quantity | Unit Price | Amount | Source File | Extracted At

Optional Headers tab (one row per invoice):

Invoice Number | Vendor | Buyer | Subtotal | Tax | Total | Amount Due | Line Count

Compute Line Count with length(data.line_items) before the Iterator, or count Aggregator output after.

Filters and error handling

  • Filter HTTP success before Iterator.
  • Per-line Filter (optional): skip items where description is empty and amount is empty (noise rows).
  • Error Handler on Sheets: if quota hits mid-invoice, you get partial line inserts — log invoice_number and re-process from archive carefully (delete partial rows first).
  • Idempotency: Data Store key invoice_number|seller.name. If seen, skip entire Iterator branch.

Test

  1. Use an invoice with 3+ lines and known amounts.
  2. Confirm Sheets has 3 rows, shared invoice_number, correct quantities.
  3. Check sum(Amount)data.subtotal (tax may sit only on header).
  4. Run a header-only invoice (no table) — expect zero line rows; verify your empty-array handling.
  5. Confirm credits charged by page count, not by line count.

Validation formulas (optional)

After extraction, a Filter or Sheets formula can catch bad lines:

  • quantity * unit_price should be within a small tolerance of amount (floating point / rounding).
  • Negative amount may be valid (credits / returns) — do not drop them unless AP policy says so.
  • Blank description with a non-zero amount still deserves a row; use Line # and Amount as the human fallback.

If you also write a Headers tab, store subtotal, tax_total, and total_amount there and treat Line Items as the detail grain. Reconciliation reports should compare SUM(line.amount) to headers.subtotal, not to total_amount (which includes tax).

When to use Aggregator vs Iterator alone

GoalPattern
One Sheets row per lineIterator → Add a Row
Rebuild array for another APIIterator → (optional work) → Array Aggregator
Email a bullet list of descriptionsIterator → Text Aggregator (or Array Aggregator + join)
Stop after first bad lineIterator → Filter/error break

Production considerations

  • Large line counts (100+) = 100 Sheets API writes per invoice. Prefer bulk append modules if available, or batch with Aggregator + a custom script; otherwise expect slow runs.
  • Currency: keep currency on every line row so multi-currency workbooks stay filterable.
  • Do not double-count: dashboards should sum Line Items or use Headers.total_amount, not both.
  • Multi-file HTTP (up to 20 files): this tutorial stays 1:1 file→invoice; batching changes Iterator nesting — keep it simple until single-file works.

Next steps

Header-only flow: Extract invoice data. AP routing by amount: Automate accounts payable. Multipart deep dive: Send multipart/form-data.

extractInvoice · Invoice API · Make hub.