Skip to content
Back to Blog
1 min read

Receipt Processing with AI Builder: Automating Expense Management

I wrote “Receipt Processing with AI Builder: Automating Expense Management” to share practical, production-minded guidance on this topic.

Receipt Processing Capabilities

AI Builder extracts the following fields from receipts:

extracted_fields:
  merchant:
    - MerchantName
    - MerchantAddress
    - MerchantPhoneNumber

  transaction:
    - TransactionDate
    - TransactionTime

  amounts:
    - Subtotal
    - Tax
    - Tip
    - Total

  items:
    - Description
    - Quantity
    - Price
    - TotalPrice

  payment:
    - PaymentMethod (if visible)

Building an Expense App

Power Apps Receipt Capture

// Main screen with camera capture
CameraButton.OnSelect = Set(
    CapturedReceipt,
    Camera1.Photo
);

// Process receipt when captured
ProcessButton.OnSelect = Set(
    ProcessingStatus,
    "Processing..."
);
Set(
    ReceiptData,
    AIBuilder.ReceiptProcessor(CapturedReceipt)
);
Set(
    ProcessingStatus,
    "Complete"
);

// Populate form with extracted data
// After successful extraction
If(
    !IsBlank(ReceiptData),

    // Set form fields
    Set(MerchantName, ReceiptData.MerchantName);
    Set(TransactionDate, ReceiptData.TransactionDate);
    Set(Subtotal, ReceiptData.Subtotal);
    Set(Tax, ReceiptData.Tax);
    Set(Total, ReceiptData.Total);

    // Show items if extracted
    ClearCollect(
        LineItems,
        ReceiptData.Items
    );

    Navigate(ReviewScreen)
)

Review Screen

// Allow editing of extracted values
Form: ExpenseReviewForm
DataSource: Expenses

// Merchant field with extraction
MerchantInput.Default = MerchantName
MerchantInput.BorderColor = If(
    ReceiptData.MerchantNameConfidence > 0.8,
    Color.Green,
    Color.Orange
)

// Amount field with validation
AmountInput.Default = Total
AmountInput.OnChange = If(
    Value(AmountInput.Text) <> Total,
    Set(ManuallyEdited, true)
)

// Category selection
CategoryDropdown.Items = ["Travel", "Meals", "Office Supplies", "Transportation", "Entertainment", "Other"]
CategoryDropdown.DefaultSelectedItems = If(
    // Auto-categorize based on merchant
    "restaurant" in Lower(MerchantName) Or "cafe" in Lower(MerchantName),
    ["Meals"],
    "uber" in Lower(MerchantName) Or "lyft" in Lower(MerchantName),
    ["Transportation"],
    ["Other"]
)

// Submit button
SubmitButton.OnSelect =
    Patch(
        Expenses,
        Defaults(Expenses),
        {
            Merchant: MerchantInput.Text,
            TransactionDate: DatePicker.SelectedDate,
            Amount: Value(AmountInput.Text),
            Category: CategoryDropdown.Selected.Value,
            ReceiptImage: CapturedReceipt,
            Submitter: User().Email,
            Status: "Pending",
            SubmittedDate: Now()
        }
    );
    Navigate(ConfirmationScreen)

Power Automate Workflow

Email Receipt Processing

{
    "trigger": {
        "type": "When_a_new_email_arrives",
        "inputs": {
            "folderPath": "Inbox",
            "importance": "Any",
            "hasAttachment": true,
            "subjectFilter": "Receipt"
        }
    },
    "actions": {
        "Filter_Image_Attachments": {
            "type": "Filter",
            "inputs": {
                "from": "@triggerBody()?['attachments']",
                "where": "@or(endsWith(item()?['name'], '.jpg'), endsWith(item()?['name'], '.png'), endsWith(item()?['name'], '.pdf'))"
            }
        },
        "Process_Each_Receipt": {
            "type": "ForEach",
            "foreach": "@body('Filter_Image_Attachments')",
            "actions": {
                "Extract_Receipt_Data": {
                    "type": "AIBuilder",
                    "inputs": {
                        "model": "prebuilt-receiptProcessing",
                        "document": "@{items('Process_Each_Receipt')?['contentBytes']}"
                    }
                },
                "Create_Expense_Record": {
                    "type": "CreateRecord",
                    "inputs": {
                        "table": "expenses",
                        "item": {
                            "merchant_name": "@{body('Extract_Receipt_Data')?['results']?['MerchantName']?['value']}",
                            "transaction_date": "@{body('Extract_Receipt_Data')?['results']?['TransactionDate']?['value']}",
                            "subtotal": "@{body('Extract_Receipt_Data')?['results']?['Subtotal']?['value']}",
                            "tax": "@{body('Extract_Receipt_Data')?['results']?['Tax']?['value']}",
                            "total": "@{body('Extract_Receipt_Data')?['results']?['Total']?['value']}",
                            "submitter_email": "@{triggerBody()?['from']}",
                            "receipt_image": "@{items('Process_Each_Receipt')?['contentBytes']}",
                            "confidence_score": "@{body('Extract_Receipt_Data')?['confidence']}",
                            "status": "Pending Review"
                        }
                    }
                },
                "Check_Requires_Review": {
                    "type": "Condition",
                    "expression": {
                        "less": ["@body('Extract_Receipt_Data')?['confidence']", 0.85]
                    },
                    "actions": {
                        "Create_Review_Task": {
                            "type": "CreateRecord",
                            "inputs": {
                                "table": "tasks",
                                "item": {
                                    "subject": "Review expense from @{triggerBody()?['from']}",
                                    "regarding": "@{body('Create_Expense_Record')?['id']}",
                                    "priority": "Normal"
                                }
                            }
                        }
                    }
                }
            }
        },
        "Send_Confirmation": {
            "type": "SendEmail",
            "inputs": {
                "to": "@{triggerBody()?['from']}",
                "subject": "Receipt processed",
                "body": "Your receipt has been processed and submitted for approval."
            }
        }
    }
}

Batch Processing

{
    "trigger": {
        "type": "Recurrence",
        "recurrence": {
            "frequency": "Hour",
            "interval": 1
        }
    },
    "actions": {
        "Get_Unprocessed_Receipts": {
            "type": "ListRecords",
            "inputs": {
                "table": "receipt_uploads",
                "filter": "status eq 'Pending'"
            }
        },
        "Process_Batch": {
            "type": "ForEach",
            "foreach": "@body('Get_Unprocessed_Receipts')?['value']",
            "runtimeConfiguration": {
                "concurrency": {
                    "repetitions": 5
                }
            },
            "actions": {
                "Process_Receipt": {
                    "type": "AIBuilder",
                    "inputs": {
                        "model": "prebuilt-receiptProcessing",
                        "document": "@{items('Process_Batch')?['image']}"
                    }
                },
                "Update_Record": {
                    "type": "UpdateRecord",
                    "inputs": {
                        "table": "receipt_uploads",
                        "id": "@{items('Process_Batch')?['id']}",
                        "item": {
                            "extracted_data": "@{body('Process_Receipt')}",
                            "status": "Processed",
                            "processed_at": "@{utcNow()}"
                        }
                    }
                }
            }
        }
    }
}

Handling Multiple Receipt Types

// Different receipt formats require different handling
ProcessReceipt(receiptImage: Image): Record =
    With(
        {
            result: AIBuilder.ReceiptProcessor(receiptImage)
        },
        {
            // Standard fields
            merchant: result.MerchantName,
            date: result.TransactionDate,
            total: result.Total,

            // Handle missing tax (some receipts don't show tax separately)
            tax: If(
                IsBlank(result.Tax),
                0,
                result.Tax
            ),

            // Calculate subtotal if not extracted
            subtotal: If(
                IsBlank(result.Subtotal),
                result.Total - Coalesce(result.Tax, 0) - Coalesce(result.Tip, 0),
                result.Subtotal
            ),

            // Handle tips (restaurant receipts)
            tip: Coalesce(result.Tip, 0),

            // Line items (may not be available for all receipts)
            items: If(
                CountRows(result.Items) > 0,
                result.Items,
                Table({Description: "Total", TotalPrice: result.Total})
            ),

            // Confidence for review routing
            needsReview: result.Confidence < 0.8 Or IsBlank(result.Total)
        }
    )

Integration with Approval Workflows

{
    "actions": {
        "Get_Expense_Policy": {
            "type": "Query",
            "inputs": {
                "table": "expense_policies",
                "filter": "category eq '@{body('Create_Expense')?['category']}'"
            }
        },
        "Route_For_Approval": {
            "type": "Condition",
            "expression": {
                "greater": [
                    "@body('Create_Expense')?['total']",
                    "@first(body('Get_Expense_Policy')?['value'])?['auto_approve_limit']"
                ]
            },
            "actions": {
                "Start_Approval": {
                    "type": "StartAndWaitForAnApproval",
                    "inputs": {
                        "approvalType": "Basic",
                        "title": "Expense approval: @{body('Create_Expense')?['merchant_name']} - @{body('Create_Expense')?['total']}",
                        "assignedTo": "@{first(body('Get_Expense_Policy')?['value'])?['approver_email']}",
                        "details": "Please review this expense submission.\n\nMerchant: @{body('Create_Expense')?['merchant_name']}\nAmount: @{body('Create_Expense')?['total']}\nDate: @{body('Create_Expense')?['transaction_date']}\nCategory: @{body('Create_Expense')?['category']}",
                        "itemLink": "@{body('Create_Expense')?['id']}"
                    }
                }
            },
            "else": {
                "actions": {
                    "Auto_Approve": {
                        "type": "UpdateRecord",
                        "inputs": {
                            "table": "expenses",
                            "id": "@{body('Create_Expense')?['id']}",
                            "item": {
                                "status": "Approved",
                                "approved_by": "Auto-approved",
                                "approved_date": "@{utcNow()}"
                            }
                        }
                    }
                }
            }
        }
    }
}

Reporting and Analytics

-- Expense analytics query
SELECT
    MONTH(transaction_date) as month,
    category,
    COUNT(*) as receipt_count,
    SUM(total) as total_amount,
    AVG(total) as avg_amount,
    AVG(confidence_score) as avg_confidence
FROM expenses
WHERE YEAR(transaction_date) = 2022
GROUP BY MONTH(transaction_date), category
ORDER BY month, total_amount DESC

Best Practices

receipt_processing_tips:
  capture_quality:
    - Flatten receipt before photographing
    - Ensure good lighting
    - Capture entire receipt in frame
    - Avoid shadows and glare

  handling_variations:
    - Different thermal paper qualities
    - Faded receipts (older)
    - Handwritten receipts (limited support)
    - Foreign language receipts

  validation:
    - Cross-check total with line items
    - Verify date is reasonable
    - Flag unusually high amounts
    - Check for duplicates

Conclusion

Receipt processing with AI Builder transforms expense management:

  • Instant data extraction from photos
  • Reduced manual entry errors
  • Faster reimbursement cycles
  • Better compliance through consistent processing

Combined with approval workflows, it creates a complete expense automation solution.

Resources

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.