Product Management
The Product Management API provides offline-first access to product catalog data with comprehensive filtering, sorting, and pagination capabilities.
Table of contents
- Get Products (Paginated)
- Get Product Categories (Paginated)
- Get Product Detail
- Product Data Models
- Best Practices
- Common Use Cases
Get Products (Paginated)
Retrieves a paginated list of products with advanced filtering and sorting options.
import zync.api.product.models.GetProductsResult
import zync.api.product.models.ZyncProductSortAndFilter
import zync.api.product.models.ZyncProductSortBy
import zync.api.product.models.ZyncProductType
import zync.api.common.filter.ZyncSortType
val sortAndFilter = ZyncProductSortAndFilter(
sortType = ZyncSortType.Descending,
sortBy = ZyncProductSortBy.PRODUCT_NAME,
keyword = "hammer",
isAvailable = true,
type = listOf(ZyncProductType.PRODUCT, ZyncProductType.PART)
)
when (val result = zync.product.getProducts(
sortAndFilter = sortAndFilter,
page = 1,
pageSize = 20
)) {
is GetProductsResult.Success -> {
val products = result.data
val currentPage = result.currentPage
val totalPages = result.totalPages
val totalRecords = result.totalRecords
val isPartialData = result.isPartialData
println("Fetched ${products.size} products (page $currentPage of $totalPages)")
if (isPartialData) {
println("Data from cache - may be incomplete")
}
}
is GetProductsResult.Failure -> {
println("Error: ${result.error.message}")
}
}
import ZuperSync
let sortAndFilter = ZyncProductSortAndFilter(
sortType: .descending,
sortBy: .productName,
keyword: "hammer",
isAvailable: true,
type: [.product, .part]
)
switch onEnum(of: await zync.product.getProducts(
sortAndFilter: sortAndFilter,
page: 1,
pageSize: 20
)) {
case .success(let success):
let products = success.data
let currentPage = success.currentPage
let totalPages = success.totalPages
let isPartialData = success.isPartialData
print("Fetched \(products.count) products (page \(currentPage) of \(totalPages))")
if isPartialData {
print("Data from cache - may be incomplete")
}
case .failure(let failure):
print("Error: \(failure.error.message)")
}
Parameters
| Parameter | Type | Description |
|---|---|---|
sortAndFilter | ZyncProductSortAndFilter | Comprehensive filter and sort options |
page | Int | Page number (1-based) |
pageSize | Int | Number of products per page |
ZyncProductSortAndFilter Options
| Property | Type | Description |
|---|---|---|
sortType | ZyncSortType | Sort direction (Ascending/Descending/DEFAULT) |
sortBy | ZyncProductSortBy | Field to sort by (PRODUCT_ID, PRODUCT_NAME, CREATED_DATE, PRODUCT_NUMBER, QUANTITY) |
keyword | String? | Search keyword for product name or ID |
category | String? | Filter by product category UID |
location | ZyncFilterModule? | Filter by location UID |
type | List<ZyncProductType>? | Filter by product types (PRODUCT, PART, SERVICE, BUNDLE, LABOR, CUSTOM_PRODUCT) |
isAvailable | Boolean? | Filter by availability status |
brand | String? | Filter by brand name |
specification | String? | Filter by specification |
customField | ZyncFilterByCustomField? | Filter by custom field values |
uid | List<String>? | Filter by specific product UIDs |
productId | String? | Filter by product ID |
priceListUid | String? | Filter by price list UID |
scannedCode | String? | Filter by scanned barcode |
Return Value: GetProductsResult
Success Case:
data: List ofZyncProductobjectscurrentPage: Current page numbertotalPages: Total number of pages availabletotalRecords: Total number of products across all pagesisPartialData:trueif data is from cache (may be incomplete),falseif from API
Failure Case:
error:ZyncErrorwith error details
Get Product Categories (Paginated)
Retrieves a paginated list of product categories.
import zync.api.product.models.GetProductCategoriesResult
when (val result = zync.product.getProductCategories(
page = 1,
pageSize = 50
)) {
is GetProductCategoriesResult.Success -> {
val categories = result.data
val currentPage = result.currentPage
val totalPages = result.totalPages
val isPartialData = result.isPartialData
println("Fetched ${categories.size} categories (page $currentPage of $totalPages)")
categories.forEach { category ->
println("${category.name} (${category.categoryUid})")
}
}
is GetProductCategoriesResult.Failure -> {
println("Error: ${result.error.message}")
}
}
import ZuperSync
switch onEnum(of: await zync.product.getProductCategories(
page: 1,
pageSize: 50
)) {
case .success(let success):
let categories = success.data
let currentPage = success.currentPage
let totalPages = success.totalPages
let isPartialData = success.isPartialData
print("Fetched \(categories.count) categories (page \(currentPage) of \(totalPages))")
categories.forEach { category in
print("\(category.name) (\(category.categoryUid))")
}
case .failure(let failure):
print("Error: \(failure.error.message)")
}
Parameters
| Parameter | Type | Description |
|---|---|---|
page | Int | Page number (1-based) |
pageSize | Int | Number of categories per page |
Return Value: GetProductCategoriesResult
Success Case:
data: List ofZyncProductCategoryobjectscurrentPage: Current page numbertotalPages: Total number of pages availabletotalRecords: Total number of categories across all pagesisPartialData:trueif data is from cache (may be incomplete),falseif from API
Failure Case:
error:ZyncErrorwith error details
Get Product Detail
Retrieves comprehensive details for a specific product, including pricing, inventory locations, bundle items, attachments, and custom fields.
import zync.api.product.models.ProductDetailResult
import zync.api.common.filter.ZyncFetchType
when (val result = zync.product.getProductDetail(
productUid = "550e8400-e29b-41d4-a716-446655440000",
fetchType = ZyncFetchType.NONE
)) {
is ProductDetailResult.Success -> {
val product = result.data
val syncStatus = result.syncStatus
val lastSynced = result.lastSyncedAt
println("Product: ${product.productName}")
println("ID: ${product.productId}")
println("Type: ${product.productType}")
println("Price: ${product.price} ${product.currency}")
println("Available: ${product.isAvailable}")
println("Quantity: ${product.quantity}")
println("Category: ${product.category?.name}")
println("Bundle Items: ${product.bundleItems.size}")
println("Locations: ${product.locationItems.size}")
println("Attachments: ${product.attachments.size}")
if (syncStatus == ZyncDataSyncStatus.AGED) {
println("Note: Data is older than 5 minutes. Last synced: $lastSynced")
}
}
is ProductDetailResult.Failure -> {
println("Error: ${result.error.message}")
}
}
import ZuperSync
switch onEnum(of: await zync.product.getProductDetail(
productUid: "550e8400-e29b-41d4-a716-446655440000",
fetchType: .none
)) {
case .success(let success):
let product = success.data
let syncStatus = success.syncStatus
let lastSynced = success.lastSyncedAt
print("Product: \(product.productName)")
print("ID: \(product.productId ?? "N/A")")
print("Type: \(product.productType.rawValue)")
print("Price: \(product.price ?? 0) \(product.currency ?? "")")
print("Available: \(product.isAvailable)")
print("Quantity: \(product.quantity)")
print("Category: \(product.category?.name ?? "N/A")")
print("Bundle Items: \(product.bundleItems.count)")
print("Locations: \(product.locationItems.count)")
print("Attachments: \(product.attachments.count)")
if syncStatus == .aged {
print("Note: Data is older than 5 minutes. Last synced: \(lastSynced)")
}
case .failure(let failure):
print("Error: \(failure.error.message)")
}
Parameters
| Parameter | Type | Description |
|---|---|---|
productUid | String | Unique identifier of the product |
fetchType | ZyncFetchType | Data freshness strategy (default: NONE) |
ZyncFetchType Options
| Value | Description |
|---|---|
NONE | Use cached data with background sync (default) |
MARK_STALE | Mark current cache as stale and fetch with 3s timeout |
FORCE_REFRESH | Force immediate API call without timeout, fallback to cache on failure |
Return Value: ProductDetailResult
Success Case:
data:ZyncProductDetailobject with comprehensive product informationsyncStatus:ZyncDataSyncStatus- Indicates the data freshness (NONE,AGED- older than 5 minutes,PARTIAL_RECORD,OUTDATED_RECORD)lastSyncedAt:String- ISO-8601 formatted timestamp of when this record was last synced from the server
Failure Case:
error:ZyncErrorwith error details
Product Data Models
ZyncProduct (List Item)
Basic product information returned in paginated lists:
| Property | Type | Description |
|---|---|---|
productUid | String | Unique identifier |
productName | String | Product name |
productId | String? | Product ID/code |
prefix | String? | Product prefix |
productDescription | String? | Product description |
productImage | String? | Product image URL |
productType | ZyncProductType | Product type enum |
brand | String? | Brand name |
specification | String? | Product specification |
locations | List<ZyncProductLocationAvailability>? | Inventory locations |
quantity | Double | Available quantity |
minQuantity | Double | Minimum quantity threshold |
price | Double? | Selling price |
purchasePrice | Double? | Purchase price |
uom | String? | Unit of measure |
currency | String? | Currency code |
isAvailable | Boolean | Availability status |
bundlePricingLevel | ZyncBundlePricingLevel | Bundle pricing level |
bundleItems | List<ZyncProductBundleItem> | Bundle component items |
tax | ZyncLineItemCustomTax? | Tax information |
markup | ZyncProductMarkup? | Markup configuration |
productCategory | ZyncProductCategory? | Product category |
isBillable | Boolean | Billable status |
ZyncProductDetail (Full Detail)
Comprehensive product information including all related data:
| Property | Type | Description |
|---|---|---|
productUid | String | Unique identifier |
productName | String | Product name |
productId | String? | Product ID/code |
prefix | String? | Product prefix |
productDescription | String? | Product description |
productImage | String? | Product image URL |
productBarCode | String? | Barcode/SKU |
productType | ZyncProductType | Product type enum |
brand | String? | Brand name |
specification | String? | Product specification |
price | Double? | Selling price |
purchasePrice | Double? | Purchase price |
currency | String? | Currency code |
hasCustomTax | Boolean | Custom tax enabled |
tax | ZyncLineItemCustomTax? | Tax information |
isBillable | Boolean | Billable status |
quantity | Double | Available quantity |
minQuantity | Double | Minimum quantity threshold |
uom | String? | Unit of measure |
isAvailable | Boolean | Availability status |
pricingLevel | String? | Pricing level |
markup | ZyncProductMarkup? | Markup configuration |
attachments | List<ZyncAttachment> | Product attachments |
bundleItems | List<ZyncProductBundleItem> | Bundle component items |
locationItems | List<ZyncProductLocationAvailability> | Inventory by location |
category | ZyncProductCategory? | Product category |
createdAt | String? | Creation timestamp |
updatedAt | String? | Last update timestamp |
isDeleted | Boolean | Deletion status |
customFields | List<ZyncFormField> | Custom field values |
ZyncProductCategory
Product category information:
| Property | Type | Description |
|---|---|---|
categoryUid | String | Unique identifier |
name | String | Category name |
icon | String? | Category icon URL |
ZyncProductType Enum
Product type values:
| Value | Description |
|---|---|
PRODUCT | Standard product |
PART | Product part/component |
SERVICE | Service item |
CUSTOM_PRODUCT | Custom product |
BUNDLE | Product bundle |
LABOR | Labor item |
Best Practices
Pagination
Start with page 1 and use consistent page sizes across requests. Monitor the isPartialData flag to determine if data is from cache.
suspend fun loadAllProducts() {
val filter = ZyncProductSortAndFilter(isAvailable = true)
var currentPage = 1
val pageSize = 50
do {
when (val result = zync.product.getProducts(filter, currentPage, pageSize)) {
is GetProductsResult.Success -> {
processProducts(result.data)
currentPage++
if (result.isPartialData) {
println("Warning: Showing cached data")
}
if (currentPage > result.totalPages) break
}
is GetProductsResult.Failure -> {
println("Error loading page $currentPage: ${result.error.message}")
break
}
}
} while (true)
}
func loadAllProducts() async {
let filter = ZyncProductSortAndFilter(isAvailable: true)
var currentPage = 1
let pageSize = 50
while true {
switch onEnum(of: await zync.product.getProducts(
sortAndFilter: filter,
page: currentPage,
pageSize: pageSize
)) {
case .success(let success):
processProducts(success.data)
currentPage += 1
if success.isPartialData {
print("Warning: Showing cached data")
}
if currentPage > success.totalPages { break }
case .failure(let failure):
print("Error loading page \(currentPage): \(failure.error.message)")
break
}
}
}
Filtering & Sorting
Combine filters to find products by category, type, brand, or location. Use keyword search for name or ID.
val filter = ZyncProductSortAndFilter(
sortType = ZyncSortType.Ascending,
sortBy = ZyncProductSortBy.PRODUCT_NAME,
keyword = "wrench",
category = "tool-category-uid",
type = listOf(ZyncProductType.PRODUCT),
isAvailable = true,
brand = "Craftsman"
)
val result = zync.product.getProducts(filter, 1, 50)
let filter = ZyncProductSortAndFilter(
sortType: .ascending,
sortBy: .productName,
keyword: "wrench",
category: "tool-category-uid",
type: [.product],
isAvailable: true,
brand: "Craftsman"
)
let result = await zync.product.getProducts(
sortAndFilter: filter,
page: 1,
pageSize: 50
)
Offline-First Behavior
The Product Management API follows an offline-first approach. Data is immediately available from cache, with background synchronization when online. Always check the
isPartialDataflag to determine data freshness.
The SDK returns cached data when offline and syncs in the background when online.
Error Handling
Always handle both Success and Failure cases. Use error.message for user-friendly messages and error.httpStatusCode for specific error handling.
when (val result = zync.product.getProductDetail(productUid)) {
is ProductDetailResult.Success -> {
// Handle success
}
is ProductDetailResult.Failure -> {
when (result.error.httpStatusCode) {
404 -> println("Product not found")
401 -> println("Authentication required")
else -> println("Error: ${result.error.message}")
}
}
}
switch onEnum(of: await zync.product.getProductDetail(productUid: productUid)) {
case .success(let success):
// Handle success
case .failure(let failure):
switch failure.error.httpStatusCode {
case 404:
print("Product not found")
case 401:
print("Authentication required")
default:
print("Error: \(failure.error.message)")
}
}
Common Use Cases
Search Products by Keyword
val searchFilter = ZyncProductSortAndFilter(
keyword = "hammer",
isAvailable = true
)
when (val result = zync.product.getProducts(searchFilter, 1, 20)) {
is GetProductsResult.Success -> {
println("Found ${result.totalRecords} products matching 'hammer'")
result.data.forEach { product ->
println("${product.productName} - ${product.price} ${product.currency}")
}
}
is GetProductsResult.Failure -> {
println("Search failed: ${result.error.message}")
}
}
let searchFilter = ZyncProductSortAndFilter(
keyword: "hammer",
isAvailable: true
)
switch onEnum(of: await zync.product.getProducts(
sortAndFilter: searchFilter,
page: 1,
pageSize: 20
)) {
case .success(let success):
print("Found \(success.totalRecords) products matching 'hammer'")
success.data.forEach { product in
print("\(product.productName) - \(product.price ?? 0) \(product.currency ?? "")")
}
case .failure(let failure):
print("Search failed: \(failure.error.message)")
}
Filter by Category and Type
val categoryFilter = ZyncProductSortAndFilter(
category = "tools-category-uid",
type = listOf(ZyncProductType.PRODUCT, ZyncProductType.PART),
isAvailable = true
)
val result = zync.product.getProducts(categoryFilter, 1, 50)
let categoryFilter = ZyncProductSortAndFilter(
category: "tools-category-uid",
type: [.product, .part],
isAvailable: true
)
let result = await zync.product.getProducts(
sortAndFilter: categoryFilter,
page: 1,
pageSize: 50
)
Get Bundle Product with Components
when (val result = zync.product.getProductDetail(productUid)) {
is ProductDetailResult.Success -> {
val product = result.data
val syncStatus = result.syncStatus
val lastSynced = result.lastSyncedAt
println("Product: ${product.productName}")
println("Type: ${product.productType}")
println("Price: ${product.price} ${product.currency}")
if (syncStatus == ZyncDataSyncStatus.AGED) {
println("Note: Data is older than 5 minutes. Last synced: $lastSynced")
}
// Access bundle items
if (product.productType == ZyncProductType.BUNDLE) {
println("\nBundle Components (${product.bundleItems.size}):")
product.bundleItems.forEach { item ->
println(" - ${item.productName}")
println(" Quantity: ${item.quantity}")
println(" Price: ${item.price}")
}
}
// Access inventory locations
println("\nInventory Locations (${product.locationItems.size}):")
product.locationItems.forEach { location ->
println(" - ${location.locationName}: ${location.quantity} units")
}
// Access attachments
println("\nAttachments (${product.attachments.size}):")
product.attachments.forEach { attachment ->
println(" - ${attachment.fileName}")
}
// Access custom fields
println("\nCustom Fields (${product.customFields.size}):")
product.customFields.forEach { field ->
println(" ${field.fieldName}: ${field.fieldValue}")
}
}
is ProductDetailResult.Failure -> {
println("Error: ${result.error.message}")
}
}
switch onEnum(of: await zync.product.getProductDetail(productUid: productUid)) {
case .success(let success):
let product = success.data
let syncStatus = success.syncStatus
let lastSynced = success.lastSyncedAt
print("Product: \(product.productName)")
print("Type: \(product.productType.rawValue)")
print("Price: \(product.price ?? 0) \(product.currency ?? "")")
if syncStatus == .aged {
print("Note: Data is older than 5 minutes. Last synced: \(lastSynced)")
}
// Access bundle items
if product.productType == .bundle {
print("\nBundle Components (\(product.bundleItems.count)):")
product.bundleItems.forEach { item in
print(" - \(item.productName)")
print(" Quantity: \(item.quantity)")
print(" Price: \(item.price ?? 0)")
}
}
// Access inventory locations
print("\nInventory Locations (\(product.locationItems.count)):")
product.locationItems.forEach { location in
print(" - \(location.locationName): \(location.quantity) units")
}
// Access attachments
print("\nAttachments (\(product.attachments.count)):")
product.attachments.forEach { attachment in
print(" - \(attachment.fileName)")
}
// Access custom fields
print("\nCustom Fields (\(product.customFields.count)):")
product.customFields.forEach { field in
print(" \(field.fieldName): \(field.fieldValue ?? "N/A")")
}
case .failure(let failure):
print("Error: \(failure.error.message)")
}
Scan Barcode to Find Product
val barcodeFilter = ZyncProductSortAndFilter(
scannedCode = "1234567890123"
)
when (val result = zync.product.getProducts(barcodeFilter, 1, 1)) {
is GetProductsResult.Success -> {
if (result.data.isNotEmpty()) {
val product = result.data.first()
println("Found product: ${product.productName}")
println("Price: ${product.price} ${product.currency}")
println("Available: ${product.quantity} ${product.uom}")
} else {
println("No product found with this barcode")
}
}
is GetProductsResult.Failure -> {
println("Error: ${result.error.message}")
}
}
let barcodeFilter = ZyncProductSortAndFilter(
scannedCode: "1234567890123"
)
switch onEnum(of: await zync.product.getProducts(
sortAndFilter: barcodeFilter,
page: 1,
pageSize: 1
)) {
case .success(let success):
if let product = success.data.first {
print("Found product: \(product.productName)")
print("Price: \(product.price ?? 0) \(product.currency ?? "")")
print("Available: \(product.quantity) \(product.uom ?? "")")
} else {
print("No product found with this barcode")
}
case .failure(let failure):
print("Error: \(failure.error.message)")
}