Syncing Inventory
Keep your e-commerce platform, ERP, or internal systems in sync with real-time warehouse stock levels.
Required Scope
Your OAuth application needs the inventory scope to access product and stock endpoints.
Architecture Overview
The 3PLGuys API provides three levels of inventory data:
Products
Your product catalog with SKUs, names, and descriptions.
Cartons
Physical containers in the warehouse. Each carton holds quantities of a product.
Product Breakdown
Aggregated quantity per product across warehouses, computed from cartons.
For most integrations, Product Breakdown is the endpoint you want. It gives you the pre-computed total quantity per product with a per-warehouse breakdown, without needing to sum cartons yourself.
Fetching Product Breakdown
/v0/inventory/products/breakdownGet aggregated stock quantities per product with per-warehouse breakdown
curl "https://api.3plguys.com/v0/inventory/products/breakdown?take=50" \-H "Authorization: Bearer YOUR_TOKEN"
const res = await fetch("https://api.3plguys.com/v0/inventory/products/breakdown?take=50",{ headers: { Authorization: `Bearer ${token}` } });const levels = await res.json();console.log(`Got ${levels.length} stock records`);
res = httpx.get("https://api.3plguys.com/v0/inventory/products/breakdown",params={"take": 50},headers={"Authorization": f"Bearer {token}"},)levels = res.json()print(f"Got {len(levels)} stock records")
[{"id": "101","sku": "WDG-2024-BLK","name": "Widget Black","quantity": 360,"warehouses": [{"id": "1","name": "Main Warehouse","quantity": 360}],"archived": false,"createdAt": "2024-06-15T08:00:00Z","updatedAt": "2026-03-07T12:00:00Z"}]
| Field | Meaning |
|---|---|
| quantity | Total units across all warehouses (computed from cartons) |
| warehouses | Array with per-warehouse breakdown — each entry has id, name, and quantity |
| archived | Whether the product has been soft-deleted |
Polling Strategy
Use polling to detect inventory changes. Here's the recommended approach:
Recommended Sync Strategy
Full sync on startup
Fetch the full product breakdown with pagination to establish your baseline. Store the updatedAt timestamp for each product.
Incremental polling every 5 minutes
Use the updatedSince parameter to only fetch records that changed since your last sync.
Page through all results
Always paginate through the full result set. Each row is one product with its warehouses array containing per-warehouse quantities.
Incremental Sync with updatedSince
The most efficient approach — only fetch what changed since your last sync:
curl "https://api.3plguys.com/v0/inventory/products/breakdown?updatedSince=2026-03-07T12:00:00Z" \-H "Authorization: Bearer YOUR_TOKEN"
let lastSync = null; // Persist this (database, file, etc.)async function syncDelta(token) {const url = new URL("https://api.3plguys.com/v0/inventory/products/breakdown");url.searchParams.set("take", "50");if (lastSync) url.searchParams.set("updatedSince", lastSync);const res = await fetch(url, {headers: { Authorization: `Bearer ${token}` },});const updates = await res.json();for (const item of updates) {await db.upsert("stock_levels", {productId: item.id,quantity: item.quantity,warehouses: item.warehouses,});}lastSync = new Date().toISOString();console.log(`Synced ${updates.length} changes`);}
last_sync = None # Persist this (database, file, etc.)def sync_delta(token: str):global last_syncparams = {"take": 50}if last_sync:params["updatedSince"] = last_syncres = httpx.get("https://api.3plguys.com/v0/inventory/products/breakdown",params=params,headers={"Authorization": f"Bearer {token}"},)updates = res.json()for item in updates:db.upsert("stock_levels", {"product_id": item["id"],"quantity": item["quantity"],"warehouses": item["warehouses"],})last_sync = datetime.utcnow().isoformat() + "Z"print(f"Synced {len(updates)} changes")
Rate Limits
Sandbox allows 1,000 requests per hour. A full sync of 1,000 products takes 5 paginated requests (at take=50), well within limits. See Error Handling for details.
Syncing Products
If you also need product metadata (names, SKUs, descriptions), sync the product catalog:
curl "https://api.3plguys.com/v0/inventory/products?take=50" \-H "Authorization: Bearer YOUR_TOKEN"
const res = await fetch("https://api.3plguys.com/v0/inventory/products?take=50",{ headers: { Authorization: `Bearer ${token}` } });const products = await res.json();
[{"id": "101","sku": "WDG-2024-BLK","name": "Widget Black","description": "Premium black widget","createdAt": "2024-06-15T08:00:00Z","updatedAt": "2026-03-07T12:00:00Z","deletedAt": null}]
Products change less frequently than stock quantities. Sync products once daily or when you detect a new id in a product breakdown response.
Complete Example: Full Sync Script
const BASE = "https://api.3plguys.com/v0";async function fetchAllPages(path, token) {const results = [];let skip = 0;const take = 50;while (true) {const res = await fetch(`${BASE}${path}?take=${take}&skip=${skip}`,{ headers: { Authorization: `Bearer ${token}` } });if (!res.ok) throw new Error(`API error: ${res.status}`);const page = await res.json();results.push(...page);if (page.length < take) break;skip += take;}return results;}async function fullSync(token) {console.log("Starting full inventory sync...");// 1. Sync productsconst products = await fetchAllPages("/inventory/products", token);console.log(`Fetched ${products.length} products`);// 2. Sync product breakdown (stock quantities)const levels = await fetchAllPages("/inventory/products/breakdown", token);console.log(`Fetched ${levels.length} product breakdowns`);// 3. Upsert into your databasefor (const p of products) {await db.upsert("products", { id: p.id, sku: p.sku, name: p.name });}for (const l of levels) {await db.upsert("stock", {productId: l.id,quantity: l.quantity,warehouses: l.warehouses,});}console.log("Full sync complete");}fullSync(process.env.API_TOKEN);
import httpx, osBASE = "https://api.3plguys.com/v0"TOKEN = os.environ["API_TOKEN"]HEADERS = {"Authorization": f"Bearer {TOKEN}"}def fetch_all_pages(path: str) -> list:results = []skip = 0take = 50while True:res = httpx.get(f"{BASE}{path}",params={"take": take, "skip": skip},headers=HEADERS,)res.raise_for_status()page = res.json()results.extend(page)if len(page) < take:breakskip += takereturn resultsdef full_sync():print("Starting full inventory sync...")products = fetch_all_pages("/inventory/products")print(f"Fetched {len(products)} products")levels = fetch_all_pages("/inventory/products/breakdown")print(f"Fetched {len(levels)} product breakdowns")# Upsert into your database herefor p in products:db.upsert("products", {"id": p["id"], "sku": p["sku"]})for l in levels:db.upsert("stock", {"product_id": l["id"],"quantity": l["quantity"],"warehouses": l["warehouses"],})print("Full sync complete")full_sync()
Working with Cartons
For advanced integrations that need carton-level detail (e.g., packaging breakdown, per-warehouse carton counts), use the carton breakdown endpoint:
/v0/inventory/cartons/breakdownGet carton types with contents and per-warehouse quantities
curl "https://api.3plguys.com/v0/inventory/cartons/breakdown?take=50" \-H "Authorization: Bearer YOUR_TOKEN"
[{"id": "1","name": "Widget Black [10-Pack Case]","dimensions": { "length": 762, "width": 508, "height": 381 },"weight": 2041,"contents": [{"id": "101","sku": "WDG-2024-BLK","name": "Widget Black","quantity": 10,"createdAt": "2024-06-15T08:00:00Z","updatedAt": "2026-03-07T12:00:00Z"}],"quantity": 36,"warehouses": [{"id": "1","name": "Main Warehouse","quantity": 36}],"createdAt": "2024-06-15T08:00:00Z","updatedAt": "2026-03-07T12:00:00Z","archived": false}]
Dimensions are in millimeters and weight is in grams (converted from the original units stored on the carton type).
When to use carton breakdown vs product breakdown
Use product breakdown for simple quantity sync (e.g., updating your Shopify inventory count). Use carton breakdown when you need to understand packaging details, or when creating SPD shipments that reference specific carton types.
Next Steps
- Connecting Shopify to 3PLGuys — Push inventory to Shopify and automate fulfillment
- Connecting WooCommerce to 3PLGuys — Sync stock to WooCommerce with batch updates
- Multi-Channel Fulfillment — Keep one inventory pool accurate across all channels
- Inbound Receiving — Track arriving shipments that increase your stock