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 | currencyWhen 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) andLine 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
- Google Drive — Watch Files
- Google Drive — Download a File
- HTTP — Make a Request
- JSON — Parse JSON
- Optional: Google Sheets — Add a Row (header tab)
- Flow control — Iterator
- Google Sheets — Add a Row (line items tab)
- 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
| Setting | Value |
|---|---|
| URL | https://api.cryvis.com/v1/documents/invoice |
| Method | POST |
| Body type | Multipart/form-data |
Field file | Drive binary |
| Auth header | Authorization: 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_itemsfrom 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 column | Source |
|---|---|
| Invoice Number | parent data.invoice_number |
| Vendor | parent data.seller.name |
| Invoice Date | parent data.invoice_date |
| Currency | parent data.currency |
| Description | item description |
| Quantity | item quantity |
| Unit Price | item unit_price |
| Amount | item amount |
| Line Index | Iterator Bundle order position |
| Source File | Drive 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 summaryCommon 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
descriptionis empty andamountis empty (noise rows). - Error Handler on Sheets: if quota hits mid-invoice, you get partial line inserts — log
invoice_numberand re-process from archive carefully (delete partial rows first). - Idempotency: Data Store key
invoice_number|seller.name. If seen, skip entire Iterator branch.
Test
- Use an invoice with 3+ lines and known amounts.
- Confirm Sheets has 3 rows, shared
invoice_number, correct quantities. - Check
sum(Amount)≈data.subtotal(tax may sit only on header). - Run a header-only invoice (no table) — expect zero line rows; verify your empty-array handling.
- 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_priceshould be within a small tolerance ofamount(floating point / rounding).- Negative
amountmay be valid (credits / returns) — do not drop them unless AP policy says so. - Blank
descriptionwith a non-zeroamountstill deserves a row; useLine #andAmountas 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
| Goal | Pattern |
|---|---|
| One Sheets row per line | Iterator → Add a Row |
| Rebuild array for another API | Iterator → (optional work) → Array Aggregator |
| Email a bullet list of descriptions | Iterator → Text Aggregator (or Array Aggregator + join) |
| Stop after first bad line | Iterator → 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.