API Docs

Automating Pick & Pack Shipments

Pick & Pack shipments are customer orders where the warehouse opens cartons, picks individual product units, repacks them, and ships to a customer address. This guide walks through the full lifecycle via the API.

Required Scope

Your OAuth application needs the shipments scope to access these endpoints.

Pick & Pack Lifecycle

Every Pick & Pack shipment follows this status progression:

Happy Path
Draft
Create & configure
Pending
Submitted, awaiting warehouse
Processing
Being picked & packed
Forwarded
Shipped to customer
Cancellation Path
Pending
Submitted
Pending Cancel
Cancel requested
Cancelled
Order cancelled

All configuration (shipping address, items, notes) happens during the draft phase. Once submitted, address and items are locked.

Pick & Pack vs SPD

Pick & Pack opens cartons to pick individual product units for customer orders — you specify products and quantities, provide a shipping address, and the warehouse handles packing and shipping. Per-unit labor fees apply. Inventory is deducted when the warehouse marks the order as forwarded. SPD ships full cartons as-is without opening — you provide carton types, quantities, and shipping labels. Use the SPD guide for that workflow.


Step 1: Create a Draft

POST
/v0/shipments/pnp

Create a new Pick & Pack draft

curl -X POST https://api.3plguys.com/v0/shipments/pnp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "warehouseId": "1" }'
const res = await fetch("https://api.3plguys.com/v0/shipments/pnp", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ warehouseId: "1" }),
});
const draft = await res.json();
console.log(`Draft created: ${draft.id}`);
res = httpx.post(
"https://api.3plguys.com/v0/shipments/pnp",
json={"warehouseId": "1"},
headers={"Authorization": f"Bearer {token}"},
)
draft = res.json()
print(f"Draft created: {draft['id']}")
{
"id": "15",
"status": "draft",
"type": "outbound-pickpack",
"warehouse": { "id": "1", "name": "Main Warehouse" },
"notes": "",
"createdAt": "2026-03-04T10:00:00.000Z",
"updatedAt": "2026-03-04T10:00:00.000Z"
}

Step 2: Set Shipping Address

The customer's delivery address is required before submitting. It can only be set while the shipment is in draft status.

PUT
/v0/shipments/pnp/:id/shipping-address

Set or update the shipping address

curl -X PUT https://api.3plguys.com/v0/shipments/pnp/15/shipping-address \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"company": "Acme Corp",
"address1": "123 Main St",
"address2": "Suite 100",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"phone": "555-1234"
}'
await fetch(`https://api.3plguys.com/v0/shipments/pnp/${draftId}/shipping-address`, {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "John Doe",
company: "Acme Corp",
address1: "123 Main St",
address2: "Suite 100",
city: "Los Angeles",
state: "CA",
zip: "90001",
phone: "555-1234",
}),
});
res = httpx.put(
f"https://api.3plguys.com/v0/shipments/pnp/{draft_id}/shipping-address",
json={
"name": "John Doe",
"company": "Acme Corp",
"address1": "123 Main St",
"address2": "Suite 100",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"phone": "555-1234",
},
headers={"Authorization": f"Bearer {token}"},
)

All fields are optional per-request (for partial updates), but name, address1, city, state, and zip must be set before submitting. Optional: company, address2, phone.


Step 3: Set Order Items

Order items specify which products and how many units to ship. This replaces all existing items on the draft.

PUT
/v0/shipments/pnp/:id/items

Set order items — replaces all existing items

curl -X PUT https://api.3plguys.com/v0/shipments/pnp/15/items \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "productId": "1", "quantity": 50 },
{ "productId": "2", "quantity": 100 }
]
}'
const items = await fetch(
`https://api.3plguys.com/v0/shipments/pnp/${draftId}/items`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
items: [
{ productId: "1", quantity: 50 },
{ productId: "2", quantity: 100 },
],
}),
}
).then(r => r.json());
[
{
"productId": "1",
"sku": "ABC123",
"productName": "Egg Shells",
"quantity": 50
},
{
"productId": "2",
"sku": "DEF456",
"productName": "Bubble Wrap",
"quantity": 100
}
]

Products, not cartons

Pick & Pack items are products with unit quantities — the warehouse decides which cartons to open. This is different from SPD, where you specify carton types and counts.


Step 4: Add Notes (Optional)

Free-text notes visible to the warehouse team. Notes can be updated at any time, even after submission.

PUT
/v0/shipments/pnp/:id/notes

Set or update shipment notes

curl -X PUT https://api.3plguys.com/v0/shipments/pnp/15/notes \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "notes": "Handle with care — fragile items inside" }'

Step 5: Submit

Submitting validates the shipping address and items, then creates a new shipment in pending status. No request body needed.

POST
/v0/shipments/pnp/:id/submit

Validate address + items, create pending shipment

What happens on submit:

  1. Validates a complete shipping address (name, address1, city, state, zip)
  2. Validates at least one order item exists
  3. Creates a new shipment in pending status
  4. Deletes the draft
  5. Generates a packing plan
curl -X POST https://api.3plguys.com/v0/shipments/pnp/15/submit \
-H "Authorization: Bearer YOUR_TOKEN"
const res = await fetch(
`https://api.3plguys.com/v0/shipments/pnp/${draftId}/submit`,
{
method: "POST",
headers: { Authorization: `Bearer ${token}` },
}
);
const submitted = await res.json();
// IMPORTANT: Use the NEW ID from the response, not the draft ID
console.log(`Submitted! New shipment ID: ${submitted.id}`);
{
"id": "16",
"status": "pending",
"type": "outbound-pickpack",
"warehouse": { "id": "1", "name": "Main Warehouse" },
"notes": "Handle with care — fragile items inside",
"createdAt": "2026-03-04T10:05:00.000Z",
"updatedAt": "2026-03-04T10:05:00.000Z"
}

New ID after submit

Submitting creates a brand new shipment and deletes the draft. The response contains the new shipment ID. Always use the returned ID for tracking and cancellation.


Step 6: Track Status

Poll the shipment to monitor progress through the warehouse:

async function waitForShipment(shipmentId, token) {
const terminal = ["forwarded", "cancelled"];
while (true) {
const res = await fetch(
`https://api.3plguys.com/v0/shipments/${shipmentId}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const data = await res.json();
console.log(`Status: ${data.status}`);
if (terminal.includes(data.status)) return data;
await new Promise(r => setTimeout(r, 60_000)); // Check every minute
}
}

Cancel a Shipment

If the warehouse hasn't started processing, you can request cancellation. Only available for shipments in pending status.

POST
/v0/shipments/pnp/:id/cancel

Request cancellation (pending → pending_cancel)

curl -X POST https://api.3plguys.com/v0/shipments/pnp/16/cancel \
-H "Authorization: Bearer YOUR_TOKEN"

The shipment moves to pending_cancel. The warehouse reviews the request and either confirms cancellation (cancelled) or continues processing.


Complete Example

const BASE = "https://api.3plguys.com/v0";
const TOKEN = process.env.API_TOKEN;
const auth = { Authorization: `Bearer ${TOKEN}` };
async function createPickPackOrder({ warehouseId, address, items, notes }) {
// 1. Create draft
const draft = await fetch(`${BASE}/shipments/pnp`, {
method: "POST",
headers: { ...auth, "Content-Type": "application/json" },
body: JSON.stringify({ warehouseId }),
}).then(r => r.json());
// 2. Set shipping address
await fetch(`${BASE}/shipments/pnp/${draft.id}/shipping-address`, {
method: "PUT",
headers: { ...auth, "Content-Type": "application/json" },
body: JSON.stringify(address),
});
// 3. Set order items
await fetch(`${BASE}/shipments/pnp/${draft.id}/items`, {
method: "PUT",
headers: { ...auth, "Content-Type": "application/json" },
body: JSON.stringify({ items }),
});
// 4. Set notes (optional)
if (notes) {
await fetch(`${BASE}/shipments/pnp/${draft.id}/notes`, {
method: "PUT",
headers: { ...auth, "Content-Type": "application/json" },
body: JSON.stringify({ notes }),
});
}
// 5. Submit
const submitted = await fetch(`${BASE}/shipments/pnp/${draft.id}/submit`, {
method: "POST",
headers: auth,
}).then(r => r.json());
return submitted; // Use submitted.id for tracking
}
// Usage
const shipment = await createPickPackOrder({
warehouseId: "1",
address: {
name: "Jane Smith",
address1: "456 Oak Ave",
city: "San Francisco",
state: "CA",
zip: "94102",
phone: "555-9876",
},
items: [
{ productId: "1", quantity: 2 },
{ productId: "2", quantity: 5 },
],
notes: "Gift order — include tissue paper",
});
console.log(`Order ${shipment.id} submitted!`);

Error Handling

Common errors during Pick & Pack shipment creation:

StatusMessageCause
400Only draft shipments can be editedTried to modify items/address on a non-draft
400Only draft shipments can be submittedTried to submit a non-draft shipment
400Please provide a complete shipping address before submittingSubmitted without name, address1, city, state, or zip
400Please add at least one item before submittingSubmitted with no order items
400Only pending shipments can request cancellationTried to cancel a non-pending shipment
400Only draft shipments can be deletedTried to delete a non-draft shipment
404Shipment not foundInvalid shipment ID or wrong organization
404Warehouse not foundInvalid or archived warehouse ID
404Product not found: Invalid, archived, or wrong-org product ID

Key Differences from SPD

  • Pick & Pack requires a shipping address — SPD requires shipping labels
  • Pick & Pack items are products + quantities — SPD items are carton types + counts
  • Pick & Pack inventory is deducted on forwarded — SPD deducts on submit
  • Pick & Pack incurs per-unit labor fees for picking and repacking

Next Steps