Classifying Item-Level Expenses
Learn why Sail classifies each product on its own, and how item-level data avoids the outcomes that block a decision.
Sail classifies every expense it holds for HSA/FSA eligibility. With item-level data, that classification happens per product rather than per basket, which is what makes the result usable without any extra work from your end user.
How Sail classifies each product
Classification, called "adjudication" in Sail's API and webhooks, is the process of checking an expense against a defined set of eligibility rules to determine whether it qualifies as a medical expense that a benefit account can pay for.
Sail classifies against Section 213(d) of the US tax code and IRS Publication 502, the same rules HSA, FSA, and HRA administrators use to decide what counts as a qualified medical expense. Other rule sets can be set up for your account on request. The outcome lives on the Expense object's section_213d_classification field.
Because a store connection returns one expense per product, each product gets its own outcome. A prescription and a birthday card bought in the same visit are two expenses with two separate results, not one shared verdict.
Item-level expense classification outcomes
section_213d_classification.status surfaces one of four outcomes:
eligible: the expense qualifies as a Section 213(d) medical expense.ineligible: the expense doesn't qualify.itemization_required: Sail can't classify the expense yet because it needs itemization.lmn_required: Sail can't classify the expense yet because it needs a Letter of Medical Necessity (LMN).
Item-level data changes which of these you see in practice.
Once an expense is
eligible, it can be reimbursed from the user's HSA/FSA. See Reimbursement.
Item-level data avoids itemization_required
itemization_requireditemization_required applies to transaction-origin expenses, meaning origin.type: transaction. It means the purchase came from a mixed-basket store, so the transaction alone doesn't say what was bought.
This is the outcome item-level data from the Itemize Transaction API avoids. An expense that arrives as a product, with origin.type: product, already carries the detail that itemization_required is asking for, so it goes straight to eligible or ineligible instead of waiting on a receipt from your end user.
Where you still see itemization_required is on transaction-level expenses from a card or bank connection whose store hasn't been connected yet. In that case, a non-null merchant.supported_merchant_id is the signal that Sail can connect to that store. Prompting the user to log into that store resolves those expenses, since the sync supplies the item-level detail they were waiting on, and it covers their future purchases there too. See Transaction-Level and Item-Level Expenses for how the two levels relate.
Your end user can also upload an itemized receipt through Upload Receipt or the Upload Receipt Widget, the upload_receipt flow of a hosted session. Sail reclassifies the expense and sends the result as a receipt.processed webhook. See Upload a Receipt for both paths.
lmn_required still applies to products
lmn_required still applies to productslmn_required applies to product-origin expenses, meaning origin.type: product. It means the item is dual-purpose, something with both a medical and a non-medical use such as a general-purpose humidifier, and it becomes eligible only with a Letter of Medical Necessity from the user's provider.
Item-level detail doesn't resolve this one. Knowing exactly which humidifier was bought is what surfaces the question in the first place.
What triggers classification
Classification runs automatically as soon as expense data lands in Sail, whatever its source. That covers expenses synced from a card, bank, or store connection, and expenses pushed through Push Transactions on an external connection. There's no separate "classify this" endpoint, since classification is part of processing the expense.
How classification results reach you
Results arrive by webhook rather than polling. For a store connection, the product expenses are classified and ready when you receive connection.synced with expenses_updated: true. See Webhook Events for the full payload shape.
Once notified, the outcome is available from Get Expense or List Expenses. See Receiving New Data for how to verify and deduplicate any delivery.
Next steps
- To pull your first item-level expenses, see the Get Item-Level Expenses quickstart.
- To see the classification rules that apply to every expense, see Expense Classification.
- To supply the items behind a mixed-basket expense, see Upload a Receipt.
Updated 14 days ago