Sync ShipMonk Inventory Levels

Products for Inventory Sync - API Guide

Overview

The Products for Inventory Sync API uses a two-step cursor-based pattern:

  1. Create a search (POST /v1/products/search) - Submit your search criteria and receive a cursor token.
  2. 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

Creates 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.

FieldTypeDescription
inventory_on_hand_updated_at_startdatetimeFilter: inventory last updated after this time
inventory_on_hand_updated_at_enddatetimeFilter: inventory last updated before this time
date_updated_startdatetimeFilter: product updated after this time
date_updated_enddatetimeFilter: product updated before this time
date_created_startdatetimeFilter: product created after this time
date_created_enddatetimeFilter: product created before this time
searchstringText search (1-255 characters) against product name/SKU
statusenumactive, disabled, or blacklisted
sort_byenumid (default), created_at, updated_at, or inventory_on_hand_updated_at
sort_orderenumASC (default) or DESC

Response

{
  "cursor": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "total": 1250
}
FieldTypeDescription
cursorstring (36 chars)UUID cursor token - use this in Step 2
totalintTotal 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

Retrieves paginated product data using a cursor from Step 1.

Query Parameters

ParameterTypeRequiredDefaultDescription
cursorstring (36 chars)Yes-Cursor from /v1/products/search
pageintNo1Page number (minimum: 1)
pageSizeintNo50Results per page (1-500)

Response

{
  "data": [ /* array of product objects */ ],
  "total": 1250,
  "pages": 25,
  "page": 1
}

Product Object Fields

Each item in data contains:

FieldTypeDescription
idintProduct ID
skustringProduct SKU
namestringProduct name
created_atdatetimeCreation timestamp
updated_atdatetimeLast update timestamp
inventory_on_hand_updated_atdatetime or nullLast inventory change timestamp
is_activebool or nullWhether the product is active
replacement_costfloat or nullProduct replacement cost
replacement_cost_currencystring or nullCurrency code
barcodesstring[]List of barcodes
country_codestring or nullCountry of origin
stock_out_daysintDays out of stock
weightobject or null{ value, unit } - units: kg, g, lb, oz
dimensionsobject or null{ length, width, height, unit } - units: m, cm, mm, ft, in
hs_codesobject[][{ hs_code, region }] - regions: us, uk, eu
custom_dataobject[][{ name, value }] - custom integration data
product_bundlesobject[]Bundle associations with SKUs and quantities
product_handling_unitsobject[] or nullHandling units (inner_carton, master_carton, pallet) with weight/dimensions

Inventory Breakdown (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_start and inventory_on_hand_updated_at_end if no timezone offset is specified.
  • An empty search body returns all products in your account, sorted by id ascending.