Enrich Transactions with Item-Level Data
Connect a user's merchant accounts and retrieve item-level purchase data instead of flat transaction totals.
Sail lets users connect their store accounts and retrieves their order history with product-level details. Instead of a flat transaction such as $49.00 at Walgreens, you can retrieve individual line items, such as bandages ($8.99), shampoo ($12.99), and a thermometer ($27.02).
In this guide, you'll connect a store account through Sail's Connect Account Widget and pull back item-level expenses.
Requirements
Before you start, make sure you have:
- A Sail user created for your end user.
- An API key with the
expensesscope. - A webhook endpoint configured to receive events.
Step 1: Connect a store account
To get item-level data, your end user needs to log into their store account. Create a Connect Widget session with connection_type set to store. Sail returns a single-use URL that you embed in your app as an iframe or redirect. Your end user enters their store credentials inside Sail's Connect Account Widget, and those credentials never touch your servers.
The best moment to ask your end user to connect a store is when one of their expenses carries a non-null merchant.supported_merchant_id, for example mer_walgreens. It means Sail can connect to that store, and connecting it adds item-level detail to purchases your end user already made there. See Transaction-Level and Item-Level Expenses for how the two levels link.
curl -X POST https://live.savewithsail.com/api/v1/hosted-sessions \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"user_id": "usr_abc123",
"flow": "connect_account",
"options": {
"connection_type": "store"
}
}'{
"url": "https://<HOSTED_SESSION_URL>",
"expires_at": "2026-08-10T15:31:07Z",
"flow": "connect_account"
}Open the url in an iframe or redirect your end user to it. The session is single-use and the expires_at field tells you when it stops working.
To find a store yourself, search List Merchants by name, for example with ?search=walgreens. Each store's capabilities.item_level_receipts tells you whether it supplies item-level data through a store connection. To open that store's login directly, pass its id as merchant_id in options. If your end user already has a connection for that store, active or still syncing, the request returns 409 conflict.
Step 2: Listen for webhooks
After your end user completes the login, Sail syncs the store's order history. Results arrive by webhook, so there's nothing to poll. When you receive connection.synced with expenses_updated: true, the data is ready to fetch.
The first sync pulls up to 15 months of order history from the store, not only new orders, so expect more data on this call than on later ones.
Check the
Sail-Signatureheader before you process any event. Delivery is at-least-once, so deduplicate on the eventid. See Receiving New Data for both.
Step 3: Pull item-level expenses
Once the sync completes, fetch the user's expenses. Each item-level expense from a store connection represents a single product from an order.
curl "https://live.savewithsail.com/api/v1/users/usr_abc123/expenses?source_type=store" \
-H "Authorization: Bearer sk_live_..."{
"data": [
{
"id": "exp_9f2c",
"connection_id": "conn_8e3a",
"name": "Digital Thermometer",
"description": "Digital Thermometer - Forehead",
"date": "2026-08-05",
"amount": 27.02,
"source_type": "store",
"origin": {
"type": "product",
"detail": "Digital Thermometer - Forehead"
},
"merchant": {
"name": "Walgreens",
"supported_merchant_id": "mer_walgreens"
},
"enrichment": {
"category": "Pharmacies",
"subcategory": ["Drug Stores"]
},
"parent_expense_id": null,
"section_213d_classification": {
"status": "eligible",
"category": "Health Monitoring Devices",
"reasoning": "Digital thermometers are eligible medical devices under Section 213(d)."
}
}
// ... 2 more items (Bandages $8.99, Shampoo $12.99)
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 3
}
}Amounts are decimal numbers in USD (for example, 27.02 means $27.02).
Each item-level expense includes:
source_type: "store", because it came from a store connection.origin.type: "product", because it's an individual product, not a flat transaction.enrichment, where Sail adds a category and subcategory for each product.section_213d_classification, which says whether the expense qualifies as a Section 213(d) medical expense. To learn more about how classification works, see Classifying Item-Level Expenses.
Next steps
- To understand what each classification outcome means, see Classifying Item-Level Expenses.
- To see how transaction-level and item-level expenses relate, see Transaction-Level and Item-Level Expenses.
- To connect a store and pull expenses in the shortest path, see the Get Item-Level Expenses quickstart.
Updated 13 days ago