Extract Receipt Data with n8n and Cryvis
n8n workflow: Google Drive → HTTP Request POST /v1/documents/receipt → IF success → Google Sheets with merchant, totals, payment.
Finance dumps receipts into a shared Drive folder. The gap is structured data: merchant, date, and total still get typed by hand. This workflow closes that gap — Drive source, Cryvis Receipt API, Google Sheets ledger.
Invoice PDFs use a different endpoint: Extract invoice data. HTTP building block: HTTP Request OCR. Hub: /blog/n8n.
Workflow
Google Drive Trigger (Finance/Receipts/Inbox)
|
v
IF: MIME pdf|jpeg|png|webp
|
v
Google Drive: Download → $binary.data
|
v
HTTP Request
POST https://api.cryvis.com/v1/documents/receipt
Form-Data: file = data
Authorization: Bearer <API_KEY>
|
v
IF {{ $json.success }} equals true
|
v
Google Sheets: Append row
merchant | date | total | currency | receipt #Nodes
| Step | Node | Role |
|---|---|---|
| 1 | Google Drive Trigger | New file in receipts inbox |
| 2 | IF | Supported MIME / extension |
| 3 | Google Drive Download | Binary for multipart |
| 4 | HTTP Request | Cryvis Receipt API |
| 5 | IF | {{ $json.success }} |
| 6 | Google Sheets Append | Persist merchant and totals |
| 7 | Optional Error Trigger | Slack on hard failures |
Watch a dedicated folder. Do not watch the whole Drive — spreadsheet exports will fire the workflow.
Cryvis HTTP Request
| Setting | Value |
|---|---|
| Method | POST |
| URL | https://api.cryvis.com/v1/documents/receipt |
| Auth | Header Authorization: Bearer <API_KEY> |
| Body Content Type | Form-Data |
| Body | Name file, Type File, Input Data Field Name data |
Store the key in an n8n credential. Self-hosted: rotate via env-backed credentials.
Supported MIME: application/pdf, image/jpeg, image/png, image/webp. 1 credit per PDF page or image — Credits.
Docs: extractReceipt · Product: Receipt API.
Single-file response
{
"success": true,
"data": {
"receipt_number": "RCT-88421",
"transaction_date": "2026-09-10",
"transaction_time": "12:41:03",
"merchant": { "name": "North Cafe", "tax_id": null },
"customer": null,
"location": { "city": "Austin", "country": "US" },
"payment": { "method": "card", "last4": "4242" },
"currency": "USD",
"line_items": [],
"subtotal": 24.00,
"tax_total": 1.98,
"total_amount": 28.40,
"amount_paid": 28.40,
"receipt_type": "restaurant",
"notes": null
},
"meta": {}
}Send one file per execution so you map data directly. Batch multipart (multiple file parts) returns a results[] envelope — skip that shape for this Drive flow.
Sheets mapping
| Column | Expression |
|---|---|
| Receipt Number | {{ $json.data.receipt_number }} |
| Merchant | {{ $json.data.merchant.name }} |
| Date | {{ $json.data.transaction_date }} |
| Time | {{ $json.data.transaction_time }} |
| Currency | {{ $json.data.currency }} |
| Subtotal | {{ $json.data.subtotal }} |
| Tax | {{ $json.data.tax_total }} |
| Total | {{ $json.data.total_amount }} |
| Amount Paid | {{ $json.data.amount_paid }} |
| Payment Method | {{ $json.data.payment.method }} |
| City | {{ $json.data.location.city }} |
| Source File | Drive file name |
| Processed At | {{ $now }} |
Create headers before the first run. Type totals as numbers in Sheets so SUM/pivots work.
Filters and errors
- IF before HTTP: extension/MIME allowlist.
- IF after HTTP: only Append when
{{ $json.success }}is true. - Retry on Fail on HTTP Request for 5xx.
- Error Trigger: file name + status — not full card
last4in public Slack if policy forbids it. - After success: Move Drive file to
Finance/Receipts/Processedso the trigger does not re-run.
Test
- Drop one cafe JPEG into the inbox folder.
- Execute once — confirm
success: trueand merchant name. - Check Sheets Total vs receipt total.
- Re-drop the same file — Move-after-success should prevent a duplicate, or add a dedupe IF on
receipt_number+ merchant.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 401 | Bad Bearer | Fix Header Auth credential |
| 400 missing file | Wrong field name / binary prop | Form-Data name file, field data |
| Empty merchant | Mapped $json.merchant | Use $json.data.merchant.name |
| Credits × N | Multi-page PDF | Expected — Credits |
Production notes
- Phone photos: jpeg/png/webp are fine; blurry scans yield null fields — route null
total_amountto a Review sheet via IF. - Line items: leave for a later Split Out on
{{ $json.data.line_items }}if expense policy needs SKU-level rows. - Self-hosted retention: receipt images in execution history — shorten prune interval for finance workflows.
Next steps
Invoice AP from Gmail: Automate invoice processing. Invoice → Postgres: Invoice to database.
Sign up · Pricing · Receipt · extractReceipt · /blog/n8n.