API Docs

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

GET
/v0/inventory/products/breakdown

Get 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"
}
]
FieldMeaning
quantityTotal units across all warehouses (computed from cartons)
warehousesArray with per-warehouse breakdown — each entry has id, name, and quantity
archivedWhether the product has been soft-deleted

Polling Strategy

Use polling to detect inventory changes. Here's the recommended approach:

Recommended Sync Strategy

1

Full sync on startup

Fetch the full product breakdown with pagination to establish your baseline. Store the updatedAt timestamp for each product.

2

Incremental polling every 5 minutes

Use the updatedSince parameter to only fetch records that changed since your last sync.

3

Page through all results

Always paginate through the full result set. Each row is one product with its warehouses array containing per-warehouse quantities.

Sync Lifecycle
Full Sync
Fetch all on startup
Store Timestamps
Track updatedAt
Poll Delta
updatedSince param
Upsert Changes
Update local DB
Repeat
Every 5 minutes

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_sync
params = {"take": 50}
if last_sync:
params["updatedSince"] = last_sync
res = 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 products
const 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 database
for (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, os
BASE = "https://api.3plguys.com/v0"
TOKEN = os.environ["API_TOKEN"]
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
def fetch_all_pages(path: str) -> list:
results = []
skip = 0
take = 50
while 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:
break
skip += take
return results
def 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 here
for 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:

GET
/v0/inventory/cartons/breakdown

Get 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