API Docs

Multi-Channel Fulfillment with 3PLGuys

Selling across multiple channels — Shopify, WooCommerce, Amazon, wholesale, your own website — creates fulfillment complexity. With the 3PLGuys API, you can route orders from every channel to a single warehouse and keep inventory accurate across all of them.

Why Multi-Channel Fulfillment?

Without a unified system:

  • Inventory is fragmented across platforms
  • Overselling happens when stock updates lag
  • Each channel has its own fulfillment workflow
  • Scaling means more manual work

With 3PLGuys as your central fulfillment layer:

  • One inventory pool — all channels draw from the same stock
  • One API — every order becomes a Pick & Pack shipment
  • Real-time sync — push stock levels to all channels from one source
  • Scale effortlessly — adding a channel is just another webhook handler

Architecture

Multi-Channel Architecture
Sales Channels
Shopify, WooCommerce, Amazon, DTC
Order Router
Your middleware
3PLGuys API
Pick & Pack fulfillment
Ship & Sync
Fulfill + update channels

The order router is your middleware — a server that:

  1. Receives orders from all sales channels
  2. Normalizes them into a common format
  3. Creates 3PLGuys Pick & Pack shipments
  4. Tracks fulfillment and updates each channel

Unified Order Format

Normalize orders from any channel into a standard shape:

interface UnifiedOrder {
channel: "shopify" | "woocommerce" | "amazon" | "direct";
channelOrderId: string;
customer: {
name: string;
company?: string;
address1: string;
address2?: string;
city: string;
state: string;
zip: string;
phone?: string;
};
items: Array<{
sku: string;
quantity: number;
}>;
notes?: string;
}
function fromShopify(order) {
const addr = order.shipping_address;
return {
channel: "shopify",
channelOrderId: String(order.id),
customer: {
name: `${addr.first_name} ${addr.last_name}`,
company: addr.company,
address1: addr.address1,
address2: addr.address2,
city: addr.city,
state: addr.province_code,
zip: addr.zip,
phone: addr.phone,
},
items: order.line_items.map(i => ({ sku: i.sku, quantity: i.quantity })),
notes: `Shopify #${order.order_number}`,
};
}
function fromWooCommerce(order) {
const addr = order.shipping;
return {
channel: "woocommerce",
channelOrderId: String(order.id),
customer: {
name: `${addr.first_name} ${addr.last_name}`,
company: addr.company,
address1: addr.address_1,
address2: addr.address_2,
city: addr.city,
state: addr.state,
zip: addr.postcode,
phone: order.billing.phone,
},
items: order.line_items.map(i => ({ sku: i.sku, quantity: i.quantity })),
notes: `WooCommerce #${order.number}`,
};
}
function fromAmazon(order, items) {
return {
channel: "amazon",
channelOrderId: order.AmazonOrderId,
customer: {
name: order.ShippingAddress.Name,
address1: order.ShippingAddress.AddressLine1,
address2: order.ShippingAddress.AddressLine2,
city: order.ShippingAddress.City,
state: order.ShippingAddress.StateOrRegion,
zip: order.ShippingAddress.PostalCode,
phone: order.ShippingAddress.Phone,
},
items: items.map(i => ({ sku: i.SellerSKU, quantity: i.QuantityOrdered })),
notes: `Amazon ${order.AmazonOrderId}`,
};
}

Fulfillment Engine

A single function handles fulfillment for all channels:

const TPL_BASE = "https://api.3plguys.com/v0";
const tplAuth = { Authorization: `Bearer ${tplToken}` };
async function fulfillOrder(order) {
// 1. Resolve SKUs to 3PLGuys product IDs
const items = order.items.map(item => {
const productId = skuMap[item.sku];
if (!productId) throw new Error(`Unknown SKU: ${item.sku}`);
return { productId, quantity: item.quantity };
});
// 2. Create Pick & Pack draft
const draft = await fetch(`${TPL_BASE}/shipments/pnp`, {
method: "POST",
headers: { ...tplAuth, "Content-Type": "application/json" },
body: JSON.stringify({ warehouseId: "1" }),
}).then(r => r.json());
// 3. Set shipping address
await fetch(`${TPL_BASE}/shipments/pnp/${draft.id}/shipping-address`, {
method: "PUT",
headers: { ...tplAuth, "Content-Type": "application/json" },
body: JSON.stringify(order.customer),
});
// 4. Set items
await fetch(`${TPL_BASE}/shipments/pnp/${draft.id}/items`, {
method: "PUT",
headers: { ...tplAuth, "Content-Type": "application/json" },
body: JSON.stringify({ items }),
});
// 5. Set notes with channel reference
await fetch(`${TPL_BASE}/shipments/pnp/${draft.id}/notes`, {
method: "PUT",
headers: { ...tplAuth, "Content-Type": "application/json" },
body: JSON.stringify({ notes: order.notes }),
});
// 6. Submit
const submitted = await fetch(
`${TPL_BASE}/shipments/pnp/${draft.id}/submit`,
{ method: "POST", headers: tplAuth }
).then(r => r.json());
// 7. Store mapping
await db.orders.create({
channel: order.channel,
channelOrderId: order.channelOrderId,
tplShipmentId: submitted.id,
status: "submitted",
});
return submitted;
}

Unified Inventory Sync

Push a single source of truth to all channels simultaneously:

async function syncInventoryToAllChannels() {
// Fetch 3PLGuys stock levels (single source of truth)
const levels = await fetch(
`${TPL_BASE}/inventory/products/breakdown?take=50`,
{ headers: tplAuth }
).then(r => r.json());
const updates = levels.map(p => ({
sku: p.sku,
productId: p.id,
available: p.quantity,
}));
// Sync to all channels in parallel
await Promise.all([
syncToShopify(updates),
syncToWooCommerce(updates),
syncToAmazon(updates),
]);
console.log(`Synced ${updates.length} products to all channels`);
}
async function syncToShopify(updates) {
for (const item of updates) {
const inventoryItemId = shopifyMap[item.sku];
if (!inventoryItemId) continue;
await fetch(
`https://${SHOP}.myshopify.com/admin/api/2024-01/inventory_levels/set.json`,
{
method: "POST",
headers: {
"X-Shopify-Access-Token": SHOPIFY_TOKEN,
"Content-Type": "application/json",
},
body: JSON.stringify({
location_id: SHOPIFY_LOCATION,
inventory_item_id: inventoryItemId,
available: item.available,
}),
}
);
}
}
async function syncToWooCommerce(updates) {
const batch = updates
.filter(p => wooMap[p.sku])
.map(p => ({
id: wooMap[p.sku],
stock_quantity: p.available,
manage_stock: true,
}));
for (let i = 0; i < batch.length; i += 100) {
await fetch(`https://store.com/wp-json/wc/v3/products/batch`, {
method: "POST",
headers: {
Authorization: `Basic ${btoa(`${WC_KEY}:${WC_SECRET}`)}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ update: batch.slice(i, i + 100) }),
});
}
}
// Run every 10 minutes
setInterval(syncInventoryToAllChannels, 10 * 60 * 1000);

Fulfillment Status Tracker

Poll 3PLGuys and update each channel when orders are shipped:

async function trackAllOrders() {
const pending = await db.orders.findAll({
where: { status: "submitted" },
});
for (const order of pending) {
const shipment = await fetch(
`${TPL_BASE}/shipments/${order.tplShipmentId}`,
{ headers: tplAuth }
).then(r => r.json());
if (shipment.status === "forwarded") {
// Update the originating channel
switch (order.channel) {
case "shopify":
await markShopifyFulfilled(order.channelOrderId);
break;
case "woocommerce":
await markWooCompleted(order.channelOrderId);
break;
case "amazon":
await confirmAmazonShipment(order.channelOrderId);
break;
}
await db.orders.update(
{ status: "fulfilled" },
{ where: { id: order.id } }
);
}
}
}
setInterval(trackAllOrders, 5 * 60 * 1000);

Key Benefits

BenefitWithout 3PLGuysWith 3PLGuys
InventoryFragmented per channelSingle pool, synced everywhere
OversellingCommon — stock updates lagPrevented — real-time sync from one source
New channelNew fulfillment workflowJust add a webhook adapter
VisibilityCheck each platformOne API, one dashboard
ScalingMore manual work per channelSame automation handles all volume

Getting started

Start with your highest-volume channel first. Get the webhook → fulfillment → status update loop working end-to-end, then add channels one at a time. See the detailed guides for Shopify and WooCommerce.


Next Steps