Sync ShipMonk Inventory Levels
Products for Inventory Sync - API Guide
Overview
The Products for Inventory Sync API uses a two-step cursor-based pattern:
- Create a search (
POST /v1/products/search) - Submit your search criteria and receive a cursor token. - Fetch results (
GET /v1/products/search/paginate) - Use the cursor to paginate through matching products and their inventory data.
This design lets you query large product catalogs efficiently without repeated filtering on every page request. Additionally, this lets you effectively sync a large catalog of products by only retrieving the subset of products where a change in inventory levels has occurred.
Authentication
All requests require an API key passed via the api_key_only firewall. Accepted key types:
- Account Integrations API Key
- Public API Key
- Personal API Key
Include your API key in the request headers as required by your ShipMonk account configuration.
Step 1: Create a Search
POST /v1/products/search
POST /v1/products/searchCreates a search cursor that snapshots matching product IDs. The cursor is valid for 24 hours.
Request Body (JSON)
All fields are optional. An empty body {} returns all products.
| Field | Type | Description |
|---|---|---|
inventory_on_hand_updated_at_start | datetime | Filter: inventory last updated after this time |
inventory_on_hand_updated_at_end | datetime | Filter: inventory last updated before this time |
date_updated_start | datetime | Filter: product updated after this time |
date_updated_end | datetime | Filter: product updated before this time |
date_created_start | datetime | Filter: product created after this time |
date_created_end | datetime | Filter: product created before this time |
search | string | Text search (1-255 characters) against product name/SKU |
status | enum | active, disabled, or blacklisted |
sort_by | enum | id (default), created_at, updated_at, or inventory_on_hand_updated_at |
sort_order | enum | ASC (default) or DESC |
Response
{
"cursor": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"total": 1250
}| Field | Type | Description |
|---|---|---|
cursor | string (36 chars) | UUID cursor token - use this in Step 2 |
total | int | Total number of matching products |
Example Request Body
{
"status": "active",
"inventory_on_hand_updated_at_start": "2025-01-01T00:00:00",
"sort_by": "updated_at",
"sort_order": "DESC"
}Step 2: Fetch Results
GET /v1/products/search/paginate
GET /v1/products/search/paginateRetrieves paginated product data using a cursor from Step 1.
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cursor | string (36 chars) | Yes | - | Cursor from /v1/products/search |
page | int | No | 1 | Page number (minimum: 1) |
pageSize | int | No | 50 | Results per page (1-500) |
Response
{
"data": [ /* array of product objects */ ],
"total": 1250,
"pages": 25,
"page": 1
}Product Object Fields
Each item in data contains:
| Field | Type | Description |
|---|---|---|
id | int | Product ID |
sku | string | Product SKU |
name | string | Product name |
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
inventory_on_hand_updated_at | datetime or null | Last inventory change timestamp |
is_active | bool or null | Whether the product is active |
replacement_cost | float or null | Product replacement cost |
replacement_cost_currency | string or null | Currency code |
barcodes | string[] | List of barcodes |
country_code | string or null | Country of origin |
stock_out_days | int | Days out of stock |
weight | object or null | { value, unit } - units: kg, g, lb, oz |
dimensions | object or null | { length, width, height, unit } - units: m, cm, mm, ft, in |
hs_codes | object[] | [{ hs_code, region }] - regions: us, uk, eu |
custom_data | object[] | [{ name, value }] - custom integration data |
product_bundles | object[] | Bundle associations with SKUs and quantities |
product_handling_units | object[] or null | Handling units (inner_carton, master_carton, pallet) with weight/dimensions |
Inventory Breakdown (inventory)
inventory)Each product includes an inventory object with totals and per-warehouse detail:
"inventory": {
"quantity_total_final": 500,
"quantity_total_available": 480,
"quantity_total_on_hand": 500,
"quantity_total_quarantined": 10,
"quantity_total_unavailable": 10,
"locations": [
{
"quantity_final": 500,
"quantity_available": 480,
"quantity_on_hand": 500,
"quarantined_quantity": 10,
"quantity_unavailable": 10,
"quantity_by_lot": [
{
"lot": { "lot_number": "LOT-001", "expiration_date": "2026-06-01" },
"quantity_on_hand": 500
}
],
"warehouse": {
"id": 1,
"name": "Los Angeles",
"identifier": "LAX"
}
}
]
}Typical Integration Flow
1. POST /v1/products/search --> get cursor + total count
2. GET /v1/products/search/paginate?cursor=...&page=1&pageSize=100
3. GET /v1/products/search/paginate?cursor=...&page=2&pageSize=100
4. ...repeat until all pages fetched
Key things to know
- Cursor expires after 24 hours. Create a new search if the cursor becomes stale.
- The cursor is a snapshot. Products added or updated after cursor creation won't appear in results. Create a fresh search to pick up changes.
- Max page size is 500. Default is 50.
- Datetime fields default to America/New_York timezone for
inventory_on_hand_updated_at_startandinventory_on_hand_updated_at_endif no timezone offset is specified. - An empty search body returns all products in your account, sorted by
idascending.
Updated 12 days ago