How to build a receipt scanner with the Genius Scan SDK

As part of its structured data capabilities, the Genius Scan SDK is able to extract information from receipts. This enables you to get both a clean image of the receipt as well as structured data representing the information present on the receipt, extracted and ready to be processed by your system.

We recommend using the Genius Scan SDK if you are building a receipt scanning feature as part of an expense reporting app. This will let you extract and validate the data in the client before forwarding it to your backend for further processing. The receipt scanning feature also works well for extracting data from invoices.

The receipt or invoice digitization can extract:

  • Total amount
  • Currency
  • Merchant
  • Date of purchase
  • Expense category
  • VAT (including multiple rates)
  • Locale

In this guide, we will write a very simple receipt scanning app in a UIKit project. Writing this app should take about 10 minutes.

Getting started

Add the Genius Scan SDK as a dependency to your project by following the section Integrating the framework from the Getting Started guide.

Implement the receipt scanning scan flow

You will instantiate a scan flow with a specific configuration:

  • set configuration.structuredData to .receipt to specify you are interested in scanning receipts.
  • set configuration.multiPage to false as generally you want the user to scan a single receipt and process it.
  • set skipPostProcessingScreen to true if you don’t want the user to review the receipt before processing it.
let configuration = GSKScanFlowConfiguration()
configuration.structuredData = .receipt
configuration.multiPage = false
configuration.skipPostProcessingScreen =  true

Start the scan flow with the above configuration

You can now start the scan flow.

Note: The Genius Scan SDK offers multiple convenient ways of starting the scan flow, in particular from UIKit view controllers or from SwiftUI views.

// Keep a strong reference on ScanFlow
let scanFlow = GSKScanFlow(configuration: configuration)

do {
    let result = try await scanFlow.resultByStarting(fromViewController: viewController)
    handleResult(result)
} catch {
    handleError()
}

Handle the success

We can now write the handleResult method. Since this is a demo, we will just output the extracted data but in your real application you will likely display the data to the user for validation, and then serialize it to send it to your backend.

func handleResult(_ result: GSKScanFlowResult) {
    // We only consider the first scan since we are in single-page mode
    guard let scan = result.scans.first else {
        print("Unexpected error")
        return
    }

    // Check if there is structured data
    guard let structuredResult = scan.structuredDataResult else {
        print("No structured data result")
        return
    }

    // Check if a receipt has been detected
    guard let receipt = structuredResult.receipt else {
        print("No receipt detected")
        return
    }

    // Use the detected data. Here we just print it, but we could
    // make an API call.
    print("Locale: \(receipt.locale?.identifier ?? "N/A")")
    print("Merchant: \(receipt.merchant ?? "N/A")")
    print("Amount: \(receipt.amount ?? 0.0)")
    print("Currency: \(receipt.currency ?? "N/A")")
    print("Date: \(receipt.date ?? Date())")
    print("Category: \(receipt.category?.rawValue ?? "N/A")")
    print("VAT Values: \(receipt.vatValues.map { "\($0)" }.joined(separator: ", "))")
}

Error handling

It’s important to handle the error as well, there are two important cases:

  • The scan flow is cancelled by the user. A special error is raised so that you can choose how to handle such situations. Maybe you don’t want to perform any action, but you can also show a message to the user.
  • The Genius Scan SDK license is expired. This could happen if your license isn’t renewed or if you don’t activate auto-refreshing of the license key. In this case, you may want to show a message or fall back to the regular camera depending on your needs.
func handleError(_ error: Error) {
    if (error as NSError).domain == GSKScanFlowErrorDomain && (error as NSError).code == GSKScanFlowError.userCancellation.rawValue {
        // Do nothing
    } else {
        // Show a UIAlertController with error.localizedDescription
    }
}

Launching the app

You can now build and run the application on an actual device (the iOS simulator doesn’t support the camera) to test the receipt scanning.

© 2025 The Grizzly Labs. All rights reserved.