Back to Blog
5 min read

Receipt Processing with AI Builder: Automating Expense Management

Receipt processing is one of AI Builder’s most practical applications. Transform photos of receipts into structured expense data automatically, streamlining expense reporting and reimbursement workflows.

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.