Streaming is available in most browsers,
and in the Developer app.
-
Meet FinanceKit
Learn how FinanceKit lets your financial management apps seamlessly and securely share on-device data from Apple Cash, Apple Card, and more, with user consent and control. Find out how to request one-time and ongoing access to accounts, transactions, and balances — and how to build great experiences for iOS and iPadOS.
Chapters
- 0:00 - Introduction
- 1:48 - Overview of data types
- 5:03 - Access financial data
- 21:56 - Best practices
Resources
-
Download
Hi everyone. My name is Antonio, and I'm an iOS engineer on the Apple Wallet team.
Managing finances is a critical part of modern life. And whether you’re looking to track your income against your credit card payments, or simply want to understand how much money you spent on cappuccinos this month, you need timely access to your financial data. And that’s where FinanceKit comes in.
The FinanceKit APIs provide access to a central repository of financial data stored in Apple Wallet.
All financial data it accesses is local, on device, and requires no internet to be accessed. In addition, FinanceKit aggregates financial data from several sources including, Apple Card and Savings, and Apple Cash. And finally, FinanceKit protects privacy by providing the data-owner explicit control over which, and how much, financial data an app may access. FinanceKit provides a rich set of financial information you can use to build compelling financial apps, including: high-level details for each account, available balance information including the time of the latest known change, and access to financial transactions, like money going in or out, or changes in available credit. Alongside with all their history. So, let’s jump right in. Today I'm going to introduce you to FinanceKit and cover what you'll need to know to access and present financial data. I’ll start by walking you through an overview of the core structures surfaced by the FinanceKit API.
Then I’ll cover how to use the API to access the rich financial data stored in Apple wallet. And I’ll wrap up with some of the best practices to keep in mind when integrating FinanceKit into your app.
So, let me begin by walking you through some of the core objects that are at your disposal. FinanceKit exposes 3 main data types to model financial data. At the root is the account. This could represent a checking account, a credit card, or even Savings with Apple Card. Every account has a balance, that, at high level, represents the amount of money in the account at a particular moment in time. In addition to a balance an account contains transactions, which, in short, are a representation of how money moved in or out of the account.
FinanceKit tracks how all these models have changed since the user opened the account, with respect to local laws in financial data handling. Now, the transaction is a struct that contains a number of useful fields. Including an identifier that is not only unique for every transaction, but unique per device as well. This ID is especially useful to track how a given transaction evolves over time. Most transactions will provide a merchant category code, conforming to ISO 18245. This will be useful if your app tracks spending categories, and a merchant name if it’s available in the financial data.
All transactions will contain an original transaction description. This is the one provided by the financial institution and if possible, a display friendly description will be available as well. Transactions always contain a transaction date, and depending on the status and depth of information received from the institutions, FinanceKit will provide the date that the transaction was posted. And finally, the transaction will provide an amount, composed of a currency code and a decimal value. If the transaction occurred abroad, the institution will provide the amount in foreign currency as well. Now, it’s important to note that for these amounts, FinanceKit is not doing the conversion between currencies. FinanceKit data is provided exactly as it was received from the institutions and they’re stored as positive decimal values regardless of whether they’re debit or credit. So you may wonder how you’ll determine whether a given transaction has a credit or a debit amount. And that’s where the credit debit indicator comes in.
Every transaction has a debit or credit indicator property that can be read to indicate to whether the money moved in or out of a given account.
And unsurprisingly the possible values are debit or credit.
But it’s important to note that the interpretation of the value will vary based on the type of the account.
If the transaction is a debit, that means a decrease in the balance of the account when it’s an asset account, for example Apple Cash, or, a decrease in the available credit when the transaction belongs to a liability account, like a credit card for example.
When the transaction credit debit indicator is credit, then the transaction increased the balance of an asset account or increased the available credit on the liability account.
So that was a quick run through the top-level types you’ll use in FinanceKit.
Refer to the documentation for a deeper dive, as it covers every class, struct, and field available but I’ve covered what you need, to move on to the fun part, accessing the financial data represented by these types using the FinanceKit API.
Accessing financial data is predominately comprised of 3 parts: Including determining data availability, using a picker to access a user-selected set of financial data, and using the query APIs to create one-time or long-running access to financial data.
Let’s start by walking through how to determine whether a given device supports FinanceKit, and, if it does, whether any of the financial data is restricted.
To check financial data availability you’ll first need to import FinanceKit.
Then, check for data availability using the isDataAvailable method on the Finance Store class, and passing the financialData enum case. If the framework returns false, then no other financial data related calls should be made. The framework will terminate the app in such a scenario, in order to provide a high strength signal.
If, on the other hand, it returns true you’re good to continue with the confidence that this value will not change between launches of the app, and can be considered constant over iOS versions.
Keep in mind that returning true doesn’t guarantee that any actual data is accessible on the device.
Now, even if financial data is available, its access could be restricted on some devices and device configurations.
While data availability can be treated as a constant, data restriction can be transient, and its value could change between calls, even while your app is in the foreground.
For example, if the Apple Wallet app is not available, or if access to Apple Wallet is restricted by a company’s mobile device management system.
If data becomes restricted the framework will throw an error, without terminating, indicating that data is now restricted.
At which point your app can inform the user that financial data is not currently accessible.
With the knowledge that financial data is available you’re ready to access financial data. A quick and easy way to do this is allowing the person using your app to select transactions they want to share with you. via the FinanceKit transaction picker.
This new view provides your app the ability to decide when to present a selectable list of the available transactions.
The person using your app views a list of their transactions, picking what they wish to grant you access to. By default, the transactions are ordered chronologically, however, there’s also support for filtering based on entering free text into the search field at the top, or by selecting one or more of the available tokens suggested while they type. Keep in mind that while your app will have immediate access to the transactions after they have been shared access to the shared transactions is ephemeral. The transactions are passed directly to your app for immediate use, and the picker will not remember or store any of the transactions shared.
Presenting the picker is really straight-forward, and, if you’ve used the photo picker API, the transaction picker API will seem quite familiar. Using the simplest approach, you’ll import the UI framework associated with FinanceKit, FinanceKitUI. Then declare a variable that will hold the selected transactions. For this example I’ll use a view state property. As I mentioned earlier, your app is responsible to determine if financial data is available. In this example I’ll call isDataAvailable inside the body of the view then pass the selected items state variable to the TransactionPicker view as the first parameter. As a second parameter we pass a ViewBuilder closure that in this example, is simply a label for the button that will trigger the presentation of the picker. And, with just these few lines of code I’ve got a button that when tapped, presents the transaction picker and makes available the selected transactions to my app. So that’s the transaction picker and it’s ideal for apps like those for tracking travel expenses for work, or any use case where the person using an app may want to selectively allow access to specific transactions versus allowing unlimited access to them all even those they may consider private. But, of course, some apps are most useful when they have full access to the financial data in Wallet.
So let me dive into the query APIs you can use for full access to financial data. They’re designed to be long-running, or simply pull a snapshot of data.
The query APIs are asynchronous, and provide access to all of the financial data types available through FinanceKit not just the transactions. User consent is required to ensure no data is shared without explicit permission. But, with that approval, your app is able to have a persistent connection with ongoing access to the available financial data. And, you’ll be pleased to know that these APIs are available on any iPhone running iOS 17.4 or higher. Now, there are 2 ways to query for data in FinanceKit.
The first returns an ordered collection of financial data types based on their current values.
They accept predicate, sort descriptors, limit and offset parameters, as you would expect, and asynchronously return an ordered collection of financial data types for use in your app.
There’s also a changes only API that works in a slightly different way. Instead of returning an array of matching financial data, these queries return the history of the changes that have occurred in the desired data.
And, they can be long running. So your app can get change only updates as soon as new data comes from the institutions.
Now, using these APIs has some additional requirements. So you’ll want to make sure that the transaction picker doesn’t fit your needs before diving into the documentation. First, you’ll need to add a static usage description that will be shown to your customers in your app’s info.plist.
You can do this by looking for Financial Data Usage Description under privacy, or by manually adding it with the NS Financial Data Usage Description key.
With the description entered, you’re ready to request for authorization in your code by calling the requestAuthorization function on the shared instance of the FinanceStore class.
Requesting authorization will display a system alert prompting for permission to access financial data, followed by a screen where the user can select which accounts they want to share.
The result of calling request authorization can either be granted, meaning that you have permission to query financial data, bearing in mind that the user can revoke this access from Settings at any time. The result could be denied if your request to gain access has been declined. And, finally, the result could return unknown, if there there wasn’t an opportunity for the user to make a meaningful choice.
While calling request authorization will return a status the authorizationStatus function will simply return the current status without ever prompting the user. If you’ve decided you want to make your app available on the App Store, you’ll need to request a distribution entitlement. We have created a streamlined process to request it, and you’ll find a link to the information page in the resources associated with this session.
Let me show you how the request authorization process looks in code.
After importing FinanceKit we check if financial data is available, and only proceed if it is. We then call request authorization on the shared FinanceStore instance. This will present the system dialog on top of your app, if applicable. After the user made a choice, the dialog will dismiss, and the asynchronous function will return a new status to your app.
You can check if the user granted access to their financial data and move forward.
As we saw earlier when your app requests for authorization, and the user did not express a preference, a system alert is presented. If the user presses Don’t Allow, you will receive a denied authorization status. If, on the other hand, they press Select Accounts, the operating system will present a list of all eligible accounts. From there, users can select which accounts they would like to share, and indicate for each one the earliest activity that will be available to your app.
If users change their mind, they can just press Cancel and your app will be able to request authorization again at another time.
On the other hand, users can finalize their choices by pressing the Share with button, and control will be returned to your app.
Like many other frameworks when the user made a choice, by either granting or denying access, they will not be prompted again when requesting authorization.
But if they change their mind at a later time, they can modify the access to their financial data from Settings. From there, they can also share more accounts, or even change the earliest sharing date.
Now that your app requested and was granted access to the user's financial data, let’s explore how we can query this data.
But first, we need to know a bit more about accounts, one of the main financial data types that are exposed by FinanceKit.
Let’s explore how they are structured.
Accounts are modelled as enum cases with some properties that are common to all accounts, and others that are specific to a particular account type.
Doing so gives FinanceKit the flexibility to model each account type with their specific set of properties, while still maintaining a common core.
One of these common properties is a local unique identifier. The account identifier is also present in every transaction and balance, making it very easy to associate each one of these objects to their containing account.
Every account has an institution name, which is a string that represents the name of the institution that the account belongs to, a display name, the name of the account to be shown to a customer, an account description, if the institution supports it. The account’s main currency is indicated as a 3 letter currency code.
These are all common properties present in the account model. If the account is a liability account, for example, the one used to model an Apple Card account, then it will have additional properties.
Such as credit limit, indicating the maximum that can be borrowed.
The next payment date, and the minimum amount to be payed. And, if present, the overdue payment amount. Now that we know how an account is modeled, let’s go and query some accounts from the FinanceStore.
We first define a sort descriptor, in this case the account display name. 0 or more sort descriptors can be used in a query, but we suggest to have at least 1.
Then we define a predicate, using the new Swift predicate macros. For this example we just want accounts linked to Apple Card, Cash and Savings.
We then build the query passing the sort descriptors and the predicate as arguments. As we can see here, every financial data type has an associated query. And, finally, we can then use the query to fetch the matching accounts from the store. Now that we explored accounts, let’s see what we can do with balances. As we stated before, every account has at least one balance, and based on when the user upgraded to iOS 17.4, likely more than one.
FinanceKit keeps an historical record of balances that can be used to show the user how their balances evolved over time, and, for example, to show trends.
Let’s first have a look at how a balance is shaped.
Balances are modelled as enums, composed of 3 cases. Available, meaning that pending transactions have been taken into account when producing the balance. Booked, where, on the other hand, only transactions that have been posted are taken into consideration. Or available and booked, that, as the name suggests, have 2 fields with the 2 balances.
Aside from this difference, balances have a set of properties that are shared between all enum cases. Similarly to the other financial data types we got to know today, balances have a local unique identifier. Alongside an account identifier, so you can map the balances to the account they belong to.
FinanceKit also records the date when they were calculated by the institution, in a property called AsOfDate. This property can also be used to check the balance freshness. And, last but not least, balances will contain at least one amount, as a decimal and currency code pair.
Also, with balances, the amounts are provided by the institutions and not calculated by the framework. And they are stored as positive values. So the framework will rely on another creditDebitIndicator property, to differentiate between balance states. If an asset account has a balance with creditDebitIndicator equal to debit, then it means it has a negative balance. On the other hand, if the balance is listed as credit, it is positive. A liability account balance that has a credit indicator, informs you that the account is in credit at that time. However, if the account has spent balance, it will be listed as debit. Now that we know what a balance looks like, let’s see how to query data to create a chart similar to the one shown here, with the latest available balances plotted over dates. Now our get balances function, we first define a reverse date sort descriptor, in order to fetch balances starting from the most recent. Then, we narrow the results to only available balances, and, only to balances that belong to the account we are interested in.
Building the query should not be a surprise by now, but in addition to sort descriptors and predicates, we are also going to limit the number of the results.
We then execute the query and reverse the fetched balances in order to have them ready to display in chronological order.
Now that we explored snapshot queries, let’s get to long running queries.
Long running queries are indicated when your app is interested in consuming live updates as soon as they are received on your device or when you want a query to be resumable between app launches. Long running queries are built over Swift async sequences, allowing a continuous stream of updates to be delivered easily.
Instead of returning an array of models, like snapshot queries, long running queries return changes, and a change is a diff from the last, or the initial state of the database. Changes are composed of an array of inserted models, for example, all new transactions since the last update, an array of updated objects, for example a liability account with an increased credit limit, and deleted object identifiers, so your app can delete financial data objects that are not present in Wallet anymore.
Every change will also provide a history token.
A history token is a proxy for a specific point in time for the financial data store.
It is an opaque struct that can be serialized easily as it conforms to Codable, and it can be used to resume a long running query, or to get the store updates after its point in time.
Historical changes can be compacted by the system for space and performance reasons.
This is generally a fairly rare case, as FinanceKit tries to preserve as is at least some months of history. But, it can result in historical queries throwing an error if the latest token your app saved, now points to a compacted change.
Anyway, it’s not an issue, and the framework is able to let your app get up to speed again without losing any data, while still being able to resume the queries.
Let’s see how we can setup a simple long running query for transactions. After importing FinanceKit, we define the transaction history async sequence with the transactionHistory API, and then we listen for all the changes with a for await on the transaction sequence.
We are then free to process those changes as we like.
This is our example personal finance app that loads all the transactions on opening. Then, as soon as we get a new transaction in the associated account, we can see it appears straight away on screen.
Now that we saw how a simple long running query works, let’s use the resumable feature.
First of all, every time we process a new change from the API, we persist the history token somewhere safe, like on disk.
In order to use the stored history token, we have to load it. Then we can pass the most recent loaded token to the historical query, using the since parameter. With this simple change, our app will be able to resume a long running query between relaunches and eliminate duplicated events.
Financial data async sequences do not normally terminate, so they can produce live updates for the app to consume. If only resumable queries are important to the application, then the best solution is to pass a parameter to the long running queries to not monitor for new changes. Doing so, the async sequence will terminate as soon as the app processed all the existing changes. That is how you determine availability and can access financial data on device, and now let me wrap up with some best practices to keep in mind when integrating FinanceKit into your app.
To reiterate, financial data is very sensitive information for every human, so respect the trust users showed when they gave your app access to their data.
You can do so, for example, by deleting data when it has been deleted, or when the user removes access to your app.
Even if you have access to more data, you should only query what you need. The transaction picker is a win win approach to financial data access: it has fewer requirements for your app while still providing valuable data, and, at the same time, gives our users the power to only share what they are comfortable sharing.
Financial data access has been designed to be performant, and if your app can access all financial data, it may seem convenient to always request a complete snapshot. A more efficient strategy is to use resumable long running queries, only processing the latest updates.
This strategy reduces the amount of data your app needs to process significantly.
And, with that, we explored everything you need, to go and build your first financial app powered by FinanceKit.
FinanceKit gives your app the power to access financial data on customers' devices. Transaction picker and query APIs have different interactions and requirements, and they are able to power a lot of use cases. You can visit the Apple Developer forums, where you can ask questions and get help all year round. And lastly, if you have any feedback, we'd love to hear from you. You can do this by using Feedback Assistant.
And that's it. I hope you've enjoyed this session. Thanks for joining me. Ciao!
-
-
5:38 - Check if financial data is available
// Check if financial data is available import FinanceKit let available = FinanceStore.isDataAvailable( .financialData ) guard available else { // No meaningful action can be performed return }
-
8:08 - Present the transaction picker
// Present the transaction picker import SwiftUI import FinanceKit import FinanceKitUI struct TransactionSelector: View { @State private var selectedItems: [FinanceKit.Transaction] = [] var body: some View { if FinanceStore.isDataAvailable(.financialData) { TransactionPicker(selection: $selectedItems) { Text("Show Transaction Picker") } } }
-
12:16 - Requesting authorization for financial data
// Requesting authorization for financial data import FinanceKit let store = FinanceStore.shared guard store.isDataAvailable(for: .financialData) else { // No meaningful action can be performed return } let authStatus = await store.requestAuthorization() guard authStatus == .authorized else { // User did not grant access to financial data, stop here return }
-
15:24 - Simple query to retrieve all Apple accounts
// Simple query to retrieve all Apple accounts let store = FinanceStore.shared let sortDescriptor = SortDescriptor(\Account.displayName) let predicate = #Predicate<Account> { account in account.institutionName == "Apple" } let query = AccountQuery( sortDescriptors: [sortDescriptor], predicate: predicate ) let accounts : [Account] = try await store.accounts(query: query)
-
18:12 - Get latest 7 available balances for account
// Get latest 7 available balances for account func getBalances(account: Account) async throws -> [AccountBalance] { let sortDescriptor = SortDescriptor(\AccountBalance.asOfDate, order: .reverse) let predicate = #Predicate<AccountBalance> { balance in balance.available != nil && balance.accountId == account.id } let query = AccountBalanceQuery( sortDescriptors: [sortDescriptor], predicate: predicate, limit: 7 ) return try await store.accountBalances(query: query).reversed() }
-
20:27 - Retrieve all the transaction history for an account
// Retrieve all the transaction history for an account import FinanceKit let store = FinanceStore.shared let account: Account = ... let transactionSequence = store.transactionHistory( forAccountID: account.id ) for try await change in transactionSequence { processChanges(change.inserted, change.updated, change.deleted) }
-
21:04 - Use the history token to resume queries
// Use the history token to resume queries import FinanceKit let store = FinanceStore.shared let account: Account = ... let currentToken = loadToken() let transactionSequence = store.transactionHistory( forAccountID: account.id, since: currentToken ) for try await change in transactionSequence { processChanges(change.inserted, change.updated, change.deleted) persist(token: change.newToken) }
-
21:41 - Non monitoring resumable queries
import FinanceKit let store = FinanceStore.shared let account: Account = ... let currentToken = loadToken() let transactionSequence = store.transactionHistory( forAccountID: account.id, since: currentToken, isMonitoring: false ) for try await change in transactionSequence { processChanges(change.inserted, change.updated, change.deleted) persist(token: change.newToken) }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.