← Back to blog

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

StepNodeRole
1Google Drive TriggerNew file in receipts inbox
2IFSupported MIME / extension
3Google Drive DownloadBinary for multipart
4HTTP RequestCryvis Receipt API
5IF{{ $json.success }}
6Google Sheets AppendPersist merchant and totals
7Optional Error TriggerSlack on hard failures

Watch a dedicated folder. Do not watch the whole Drive — spreadsheet exports will fire the workflow.

Cryvis HTTP Request

SettingValue
MethodPOST
URLhttps://api.cryvis.com/v1/documents/receipt
AuthHeader Authorization: Bearer <API_KEY>
Body Content TypeForm-Data
BodyName 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 imageCredits.

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

ColumnExpression
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 FileDrive 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 last4 in public Slack if policy forbids it.
  • After success: Move Drive file to Finance/Receipts/Processed so the trigger does not re-run.

Test

  1. Drop one cafe JPEG into the inbox folder.
  2. Execute once — confirm success: true and merchant name.
  3. Check Sheets Total vs receipt total.
  4. Re-drop the same file — Move-after-success should prevent a duplicate, or add a dedupe IF on receipt_number + merchant.

Troubleshooting

SymptomCauseFix
401Bad BearerFix Header Auth credential
400 missing fileWrong field name / binary propForm-Data name file, field data
Empty merchantMapped $json.merchantUse $json.data.merchant.name
Credits × NMulti-page PDFExpected — Credits

Production notes

  • Phone photos: jpeg/png/webp are fine; blurry scans yield null fields — route null total_amount to 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.