Reads each receipt or invoice you link, pulls out supplier, date, total and tax, and adds a row to your expenses sheet.
Uses
Google Sheets
OpenAI
Turn receipts and invoices into expense rows
Paste the links to a batch of receipts and invoices and run it. Each file is read, its supplier, date, invoice number, total, tax and currency are pulled out, and a row is added to your 'Expenses' tab.
Values are written exactly as they were read, so nothing becomes a formula or a reinterpreted date. A wrong figure costs more than a missing row, so it refuses to guess: if no supplier, date or total could be read, or the reader was not confident, the row goes to a 'Needs review' tab with a last column saying what is wrong. A figure not found is left blank rather than written as zero. Links already on either tab are skipped, so you can add receipts to the list and run it again. A file that cannot be opened is named in the run's result and tried again next time, and the rest of the batch still goes through.
You need an OpenAI connection (it reads the receipts) and a Google Sheets connection, a spreadsheet with an 'Expenses' tab and a 'Needs review' tab headed for the columns the rows fill, its link, and the receipt links - each must open the file itself without signing in.
receiptLinks to blank.Set receiptLinks to blank.spreadsheetUrl to blank.Set spreadsheetUrl to blank.expensesTab to Expenses.Set expensesTab to Expenses.reviewTab to Needs review.Set reviewTab to Needs review.maxDocuments to 25.Set maxDocuments to 25.recordedOn.Get today's date and save it as recordedOn.recordedLinks to an empty list.Set recordedLinks to an empty list.recordedLinksErrorIf a step below fails, save the error as recordedLinksErrorspreadsheetUrl. Save it as expenseTabRows.Read the rows of spreadsheetUrl. Save it as expenseTabRows.spreadsheetUrl. Save it as reviewTabRows.Read the rows of spreadsheetUrl. Save it as reviewTabRows.column_8 from every item in expenseTabRows.rows and save it as expenseLinks.Get column_8 from every item in expenseTabRows.rows and save it as expenseLinks.column_8 from every item in reviewTabRows.rows and save it as reviewLinks.Get column_8 from every item in reviewTabRows.rows and save it as reviewLinks.expenseLinks and reviewLinks into one list and save it as recordedLinks.Combine expenseLinks and reviewLinks into one list and save it as recordedLinks.recordedLinksError.message), so nothing was read or added: without them, receipts recorded earlier could be added twice. Check the address in 'spreadsheetUrl', that both tabs exist with exactly those names, and that the Google account connected to Lodol can edit the sheet, then run it again.Stop and report a problem: Could not read the links already recorded on the '@expensesTab' and '@reviewTab' tabs (recordedLinksError.message), so nothing was read or added: without them, receipts recorded earlier could be added twice. Check the address in 'spreadsheetUrl', that both tabs exist with exactly those names, and that the Google account connected to Lodol can edit the sheet, then run it again.documentsRead to 0.Set documentsRead to 0.expensesAdded to 0.Set expensesAdded to 0.sentForReview to 0.Set sentForReview to 0.alreadyRecorded to 0.Set alreadyRecorded to 0.linksLeft to 0.Set linksLeft to 0.unreadLinks to an empty list.Set unreadLinks to an empty list.lastReadError to blank.Set lastReadError to blank.rawLink in receiptLinks split by a new line:For each rawLink in receiptLinks split by a new line:rawLink and save it as link.Trim rawLink and save it as link.link is emptyIf link is emptyrecordedLinks contains link is yesIf recordedLinks contains link is yesalreadyRecorded.Add 1 to alreadyRecorded.documentsRead is greater than or equal to maxDocumentsIf documentsRead is greater than or equal to maxDocumentslinksLeft.Add 1 to linksLeft.documentsRead.Add 1 to documentsRead.link doesn't start with http (ignoring case)If link doesn't start with http (ignoring case)badLinkRow to [recordedOn, , , , , , , link, This is not a web address. Use a link that opens the PDF or image directly.].Set badLinkRow to [recordedOn, , , , , , , link, This is not a web address. Use a link that opens the PDF or image directly.].reviewTab in spreadsheetUrl.Add a row to reviewTab in spreadsheetUrl.link to the end of recordedLinks and save it as recordedLinks.Add link to the end of recordedLinks and save it as recordedLinks.sentForReview.Add 1 to sentForReview.invoice to nothing.Set invoice to nothing.readErrorIf a step below fails, save the error as readErrorreceiptReading.Run an AI agent: Read the attached receipt or invoice and pull out who issued it, its date, its number, it… and save it as receiptReading.invoice to receiptReading.output.Change invoice to receiptReading.output.link to the end of unreadLinks and save it as unreadLinks.Add link to the end of unreadLinks and save it as unreadLinks.lastReadError to readError.message.Change lastReadError to readError.message.supplier to blank.Set supplier to blank.missingSupplierIf a step below fails, save the error as missingSupplierinvoice.vendor_name and save it as supplier.Trim invoice.vendor_name and save it as supplier.supplier to blank.Change supplier to blank.invoiceDate to blank.Set invoiceDate to blank.missingInvoiceDateIf a step below fails, save the error as missingInvoiceDateinvoice.invoice_date and save it as invoiceDate.Trim invoice.invoice_date and save it as invoiceDate.invoiceDate to blank.Change invoiceDate to blank.invoiceNumber to blank.Set invoiceNumber to blank.missingInvoiceNumberIf a step below fails, save the error as missingInvoiceNumberinvoice.invoice_number and save it as invoiceNumber.Trim invoice.invoice_number and save it as invoiceNumber.invoiceNumber to blank.Change invoiceNumber to blank.currency to blank.Set currency to blank.missingCurrencyIf a step below fails, save the error as missingCurrencyinvoice.currency and save it as currency.Trim invoice.currency and save it as currency.currency to blank.Change currency to blank.total to invoice.total.Set total to invoice.total.tax to invoice.tax.Set tax to invoice.tax.readerIsConfident to invoice.requires_review is false.Set readerIsConfident to invoice.requires_review is false.reviewReason to blank.Set reviewReason to blank.supplier is emptyIf supplier is emptyreviewReason to reviewReasonNo supplier name was read.Change reviewReason to reviewReasonNo supplier name was read.invoiceDate is emptyIf invoiceDate is emptyreviewReason to reviewReasonNo date was read.Change reviewReason to reviewReasonNo date was read.total is emptyIf total is emptyreviewReason to reviewReasonNo total was read.Change reviewReason to reviewReasonNo total was read.readerIsConfident is noIf readerIsConfident is noreviewReason to reviewReasonThe reader was not confident about this document.Change reviewReason to reviewReasonThe reader was not confident about this document.problem in invoice.validation_errors:For each problem in invoice.validation_errors:problemText to blank.Set problemText to blank.problemErrorIf a step below fails, save the error as problemErrorproblem.description and save it as problemText.Trim problem.description and save it as problemText.problemText to blank.Change problemText to blank.problemText isn't emptyIf problemText isn't emptyreviewReason to reviewReason@problemText .Change reviewReason to reviewReason@problemText .totalCell to blank.Set totalCell to blank.total isn't emptyIf total isn't emptytotalCell to total.Change totalCell to total.taxCell to blank.Set taxCell to blank.tax isn't emptyIf tax isn't emptytaxCell to tax.Change taxCell to tax.expenseRow to [recordedOn, supplier, invoiceDate, invoiceNumber, totalCell, taxCell, currency, link].Set expenseRow to [recordedOn, supplier, invoiceDate, invoiceNumber, totalCell, taxCell, currency, link].reviewReason is emptyIf reviewReason is emptyexpensesTab in spreadsheetUrl.Add a row to expensesTab in spreadsheetUrl.link to the end of recordedLinks and save it as recordedLinks.Add link to the end of recordedLinks and save it as recordedLinks.expensesAdded.Add 1 to expensesAdded.reviewReason to the end of expenseRow and save it as reviewRow.Add reviewReason to the end of expenseRow and save it as reviewRow.reviewTab in spreadsheetUrl.Add a row to reviewTab in spreadsheetUrl.link to the end of recordedLinks and save it as recordedLinks.Add link to the end of recordedLinks and save it as recordedLinks.sentForReview.Add 1 to sentForReview.Added to '@expensesTab': @expensesAdded. Sent to '@reviewTab' for a person to check: @sentForReview. Already recorded, so skipped: @alreadyRecorded. and save it as runSummary.Build the text Added to '@expensesTab': @expensesAdded. Sent to '@reviewTab' for a person to check: @sentForReview. Already recorded, so skipped: @alreadyRecorded. and save it as runSummary.unreadLinks isn't emptyIf unreadLinks isn't emptyunreadLinks with a new line and save it as unreadList.Join unreadLinks with a new line and save it as unreadList.runSummary These links could not be opened or read, so nothing was recorded for them and they are tried again on the next run:Stop and report a problem: runSummary These links could not be opened or read, so nothing was recorded for them and they are tried again on the next run:
unreadList
The error was: lastReadError
Each link must open the receipt itself (a PDF, PNG, JPG or WEBP file) without signing in. Fix or remove these links in 'receiptLinks'.linksLeft is greater than 0If linksLeft is greater than 0runSummary Links left for the next run: linksLeft (at most maxDocuments are read per run). Run it again to carry on; links already recorded are skipped.Finish the run: runSummary Links left for the next run: linksLeft (at most maxDocuments are read per run). Run it again to carry on; links already recorded are skipped.documentsRead is 0If documentsRead is 0runSummary.Finish the run: runSummary.