Enrich Transactions with Item-Level Data
Sail lets users connect their merchant accounts and retrieves their order history with product-level details. Instead of a flat transaction such as 8.99), shampoo (27.02).
In this guide, you’ll create a user, connect a merchant account through Sail’s connect widget, and pull back item-level expenses.
Requirements
Before you start, make sure you have:
- A Sail user created for your internal user
- An API key with the
expensesscope - A webhook endpoint configured to receive events
Step 1: Connect a merchant account
To get item-level data, your user needs to log into their merchant 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 user enters their merchant credentials inside Sail’s Connect Account Widget, and those credentials never touch your servers.
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 user to it. The session is single-use and the expires_at field tells you when it stops working. You can check which merchants Sail supports through the List Merchants endpoint.
Step 2: Listen for webhooks
After your user completes the login, Sail syncs the merchant’s order history. Listen for these webhook events to know when data is ready:
| Event | What it means |
|---|---|
connection.created | Your user finished the connect widget and the connection exists. |
connection.status_changed | The connection moved to a new status (for example, aggregating to active). |
connection.synced | A sync finished. Check the expenses_updated flag to know if new expenses landed. |
When you receive connection.synced with expenses_updated: true, the data is ready to fetch.
The first connection sync pulls up to 15 months of order history from the merchant, not only new orders.
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,
"status": "eligible",
"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"— it came from a merchant connection.origin.type: "product"— it’s an individual product, not a flat transaction.enrichment— Sail adds a category and subcategory for each product.section_213d_classification— whether the expense qualifies as a tax-deductible healthcare expense under IRS Section 213(d). To learn more about how classification works, see the Classify your own transactions guide.