Small Parcel Delivery (SPD)
scope: shipmentsCreate outbound SPD shipments for Amazon FBA and similar workflows. SPD ships full cartons as-is — unlike Pick & Pack, no cartons are opened or repacked. Select carton types and quantities, upload shipping labels per item, and submit for fulfillment with automatic stock validation and inventory deduction.
Lifecycle
An SPD shipment moves through a defined set of stages. All configuration happens during the draft phase — once submitted, the shipment is locked and handled by the warehouse.
- 1Create a draft
Choose a warehouse. The shipment starts in
draftstatus. - 2Set items
Add carton types and counts. Each item represents a carton type with a quantity (e.g. 5 units of "10-Pack Case").
- 3Upload shipping labels
Each item must have a shipping label attached before the shipment can be submitted. FNSKU labels and custom attachments are optional.
- 4Submit
Validates that all items have shipping labels and sufficient stock exists at the warehouse. On success, inventory is immediately deducted, a new shipment is created in
pendingstatus, and the draft is deleted. - 5Warehouse processes and ships
The warehouse picks cartons, applies labels, and ships. Status progresses through
processingtoforwarded. Track viaGET /v0/shipments/:id.
Status Flow
Happy path
Cancellation
SPD = full cartons only
Endpoints
Shipment
/v0/shipments/spdCreate a new SPD draft
/v0/shipments/spd/:idDelete a draft shipment (draft only, 204)
/v0/shipments/spd/:id/submitSubmit shipment — validates labels + stock, deducts inventory (draft → pending)
/v0/shipments/spd/:id/cancelRequest cancellation (pending → pending_cancel)
Notes
/v0/shipments/spd/:id/notesGet shipment notes
/v0/shipments/spd/:id/notesUpdate shipment notes (any status)
Items
/v0/shipments/spd/:id/itemsGet items with carton details and attachments
/v0/shipments/spd/:id/itemsSet items — replaces all existing items (draft only)
Shipping Labels
/v0/shipments/spd/:id/items/:itemId/shipping-labelUpload shipping label (binary body, application/octet-stream)
/v0/shipments/spd/:id/items/:itemId/shipping-labelRemove shipping label
FNSKU Labels
/v0/shipments/spd/:id/items/:itemId/fnsku-labelUpload FNSKU label (binary body, optional)
/v0/shipments/spd/:id/items/:itemId/fnsku-labelRemove FNSKU label
Shipment Attachments
/v0/shipments/spd/:id/attachmentsUpload custom attachment (binary body)
/v0/shipments/spd/:id/attachments/:attachmentIdRemove custom attachment
Create Draft
Creates a new SPD shipment in draft status. You must specify which warehouse will fulfill the shipment.
Request Body
| Parameter | Type | Description |
|---|---|---|
warehouseId* | string | ID of the warehouse that will fulfill this shipment |
curl -X POST "https://api.3plguys.com/v0/shipments/spd" \-H "Authorization: Bearer <token>" \-H "Content-Type: application/json" \-d '{ "warehouseId": "1" }'
const res = await fetch("https://api.3plguys.com/v0/shipments/spd", {method: "POST",headers: {Authorization: `Bearer ${token}`,"Content-Type": "application/json",},body: JSON.stringify({ warehouseId: "1" }),});const draft = await res.json();
res = httpx.post("https://api.3plguys.com/v0/shipments/spd",json={"warehouseId": "1"},headers={"Authorization": f"Bearer {token}"},)draft = res.json()
{"id": "21","status": "draft","type": "outbound-spd","warehouse": { "id": "1", "name": "Main Warehouse" },"notes": "","invoiceId": null,"workflowId": null,"createdAt": "2026-03-04T10:00:00.000Z","updatedAt": "2026-03-04T10:00:00.000Z",}
Set Items
Sets the carton types and quantities for the shipment. This replaces all existing items — pass the full desired item list each time. Any previously uploaded labels for removed items are also deleted. Only works while the shipment is in draft status.
Request Body
| Parameter | Type | Description |
|---|---|---|
items* | array | List of carton types and counts to ship |
items[].cartonTypeId* | string | Carton type ID (must belong to your organization) |
items[].cartonCount* | number | Number of cartons of this type to ship (min: 1) |
curl -X PUT "https://api.3plguys.com/v0/shipments/spd/21/items" \-H "Authorization: Bearer <token>" \-H "Content-Type: application/json" \-d '{"items": [{ "cartonTypeId": "1", "cartonCount": 5 },{ "cartonTypeId": "2", "cartonCount": 3 }]}'
const res = await fetch(`https://api.3plguys.com/v0/shipments/spd/${draftId}/items`, {method: "PUT",headers: {Authorization: `Bearer ${token}`,"Content-Type": "application/json",},body: JSON.stringify({items: [{ cartonTypeId: "1", cartonCount: 5 },{ cartonTypeId: "2", cartonCount: 3 },],}),});const items = await res.json();
{"items": [{ "cartonTypeId": "1", "cartonCount": 5 },{ "cartonTypeId": "2", "cartonCount": 3 }]}
[{"id": "16","cartonTypeId": "1","cartonTypeName": "10-Pack Case","productId": "1","sku": "ABC123","productName": "Egg Shells","cartonCount": 5,"unitsPerCarton": 10,"attachments": []},{"id": "17","cartonTypeId": "2","cartonTypeName": "24-Pack Case","productId": "1","sku": "ABC123","productName": "Egg Shells","cartonCount": 3,"unitsPerCarton": 24,"attachments": []}]
Get Items
Retrieve the current items for the shipment, including carton details, product info, and all attached files.
[{"id": "16","cartonTypeId": "1","cartonTypeName": "10-Pack Case","productId": "1","sku": "ABC123","productName": "Egg Shells","cartonCount": 5,"unitsPerCarton": 10,"attachments": [{"id": "1","type": "SHIPPING_LABEL","fileName": "shipping-label-10pack.pdf","fileUrl": "https://storage.3plguys.com/..."}]}]
Item Response Fields
| Parameter | Type | Description |
|---|---|---|
id* | string | Item ID (used for label upload/remove endpoints) |
cartonTypeId* | string | Carton type ID |
cartonTypeName* | string | Carton type name |
productId* | string | Product ID of the contents |
sku* | string | Product SKU |
productName* | string | Product name |
cartonCount* | number | Number of cartons of this type |
unitsPerCarton* | number | Product units per carton |
attachments* | array | Files attached to this item |
attachments[].id* | string | Attachment ID |
attachments[].type* | enum | SHIPPING_LABEL or FNSKU_LABEL |
attachments[].fileName* | string | Original file name |
attachments[].fileUrl* | string | Download URL for the file |
Notes
Free-text notes visible to the warehouse. Notes can be updated at any time regardless of shipment status — unlike items and labels which are locked after submission.
Request Body
| Parameter | Type | Description |
|---|---|---|
notes* | string | Free-text notes for the warehouse |
{"notes": "FBA shipment for Q1 restock — handle with care"}
{"notes": "FBA shipment for Q1 restock — handle with care"}
File Upload
Shipping labels, FNSKU labels, and custom attachments are uploaded as raw binary data. The request body is the file content itself — not multipart form data.
Upload Requirements
| Header / Param | Value |
|---|---|
Content-Type | application/octet-stream |
fileName (query param) | Original file name, e.g. label.pdf |
| Request body | Raw binary file content |
Upload Shipping Label
Uploads a shipping label for a specific item. Each item can have one shipping label — uploading again replaces the existing one. Every item must have a shipping label before the shipment can be submitted.
curl -X PUT \"https://api.3plguys.com/v0/shipments/spd/21/items/16/shipping-label?fileName=label.pdf" \-H "Authorization: Bearer <token>" \-H "Content-Type: application/octet-stream" \--data-binary @label.pdf
import fs from "fs";const labelData = fs.readFileSync("label.pdf");const res = await fetch(`https://api.3plguys.com/v0/shipments/spd/${draftId}/items/${itemId}/shipping-label?fileName=label.pdf`,{method: "PUT",headers: {Authorization: `Bearer ${token}`,"Content-Type": "application/octet-stream",},body: labelData,});const attachment = await res.json();
label_data = open("label.pdf", "rb").read()res = httpx.put(f"https://api.3plguys.com/v0/shipments/spd/{draft_id}/items/{item_id}/shipping-label",params={"fileName": "label.pdf"},headers={"Authorization": f"Bearer {token}","Content-Type": "application/octet-stream",},content=label_data,)
{"id": "1","fileName": "label.pdf","fileUrl": "https://storage.3plguys.com/..."}
Upload Response Fields
| Parameter | Type | Description |
|---|---|---|
id* | string | Attachment ID (use for deletion) |
fileName* | string | File name as provided in the query parameter |
fileUrl* | string | URL to download the uploaded file |
Remove Shipping Label
Removes the shipping label from an item. Only works while the shipment is in draft status. Returns 204 No Content.
curl -X DELETE \"https://api.3plguys.com/v0/shipments/spd/21/items/16/shipping-label" \-H "Authorization: Bearer <token>"
Upload FNSKU Label
Uploads an FNSKU label for a specific item. Same upload pattern as shipping labels — raw binary body with fileName query parameter. FNSKU labels are optional and not required for submission.
curl -X PUT \"https://api.3plguys.com/v0/shipments/spd/21/items/16/fnsku-label?fileName=fnsku.pdf" \-H "Authorization: Bearer <token>" \-H "Content-Type: application/octet-stream" \--data-binary @fnsku.pdf
Remove FNSKU Label
Removes the FNSKU label from an item. Returns 204 No Content. Draft only.
Upload Custom Attachment
Uploads a custom attachment to the shipment itself (not a specific item). You can attach multiple files. Custom attachments are optional.
curl -X PUT \"https://api.3plguys.com/v0/shipments/spd/21/attachments?fileName=packing-instructions.pdf" \-H "Authorization: Bearer <token>" \-H "Content-Type: application/octet-stream" \--data-binary @packing-instructions.pdf
{"id": "5","fileName": "packing-instructions.pdf","fileUrl": "https://storage.3plguys.com/..."}
Remove Custom Attachment
Removes a custom attachment by its ID. Returns 204 No Content.
curl -X DELETE \"https://api.3plguys.com/v0/shipments/spd/21/attachments/5" \-H "Authorization: Bearer <token>"
Submit
Submitting validates the shipment and triggers fulfillment. This is a multi-step operation:
- Validates items exist — the shipment must have at least one item.
- Validates shipping labels — every item must have a
SHIPPING_LABELattachment. - Checks stock — verifies there are enough cartons of each type at the warehouse.
- Deducts inventory — carton counts are immediately decremented at the warehouse.
- Creates new shipment — a new shipment in
pendingstatus is created with all items, labels, and attachments copied over. - Deletes the draft — the original draft shipment is removed.
No request body is needed. The response is the newly created shipment.
{"id": "22","status": "pending","type": "outbound-spd","warehouse": { "id": "1", "name": "Main Warehouse" },"notes": "FBA shipment for Q1 restock","invoiceId": null,"workflowId": null,"createdAt": "2026-03-04T10:05:00.000Z","updatedAt": "2026-03-04T10:05:00.000Z",}
New ID after submit
Cancel
Request cancellation of a submitted shipment. The shipment must be in pending status. This moves the status to pending_cancel. The warehouse will review the request and either confirm the cancellation (restoring inventory) or continue processing.
No request body is needed.
{"id": "22","status": "pending_cancel","type": "outbound-spd","warehouse": { "id": "1", "name": "Main Warehouse" },"notes": "FBA shipment for Q1 restock","invoiceId": null,"workflowId": null,"createdAt": "2026-03-04T10:05:00.000Z","updatedAt": "2026-03-04T10:06:00.000Z",}
Delete Draft
Permanently deletes a draft SPD shipment, including all items, labels, and attachments. Only works while the shipment is in draft status. Returns 204 No Content on success.
204 No Content
Shipment Response Fields
Create, submit, and cancel endpoints return the full shipment object. This follows the same structure as GET /v0/shipments/:id.
| Parameter | Type | Description |
|---|---|---|
id* | string | Unique shipment ID |
status* | enum | Current status: draft, pending, processing, forwarded, pending_cancel, cancelled (see Shipments page for full lifecycle) |
type* | enum | Always "outbound-spd" for SPD shipments |
warehouse* | object | Warehouse handling this shipment |
warehouse.id* | string | Warehouse ID |
warehouse.name* | string | Warehouse name |
notes | string | Free-text notes attached to the shipment |
invoiceId | string | null | Invoice ID if the shipment has been invoiced, null otherwise |
workflowId | string | null | Workflow ID linking grouped shipments, null if not part of a workflow |
createdAt* | ISO 8601 | Timestamp when the shipment was created |
updatedAt* | ISO 8601 | Timestamp of the last update |
Carton details via GET /v0/shipments/:id
cartons array. To retrieve carton details for a submitted shipment, use GET /v0/shipments/:id which includes the full cartons breakdown.Error Responses
400Only draft shipments can be deletedReturned when trying to delete a non-draft shipment.
400Only draft shipments can be editedReturned when trying to modify items, labels, or attachments on a non-draft shipment.
400Only draft shipments can be submittedReturned when trying to submit a shipment that is not in draft status.
400Please add at least one item before submittingReturned when submitting a shipment with no items set.
400Please upload shipping labels for every itemReturned when submitting and one or more items are missing a SHIPPING_LABEL attachment.
400Not enough cartons of '10-Pack Case' in stock to submit this shipmentReturned when the warehouse does not have enough cartons in stock for the requested quantity. The error message includes the specific product name.
400Only pending shipments can request cancellationCancellation is only available for shipments in pending status.
404Carton type not found: <id>A carton type ID in the items list does not exist or does not belong to your organization.
404Shipment not foundThe shipment ID does not exist or does not belong to your organization.
404Item not foundThe item ID does not exist or does not belong to this shipment.
SPD vs Pick & Pack
Both shipment types follow the same lifecycle but have key differences in how items and fulfillment work.
| Pick & Pack | SPD | |
|---|---|---|
| What ships | Individual units — warehouse opens cartons, picks units, repacks | Full cartons — shipped as-is, no opening or repacking |
| Items | Products + unit quantities | Carton types + carton counts |
| Shipping address | Required (ships to customer) | Not needed (ships to FBA or similar) |
| File uploads | None | Shipping labels required, FNSKU + attachments optional |
| Additional fees | Pick & pack labor fees per unit | No additional labor — cartons ship whole |
| Submit validation | Items + shipping address | Items + shipping labels + stock check |
| Inventory deduction | When warehouse forwards | Immediately on submit |
Key points
- Items and labels can only be modified while the shipment is in
draftstatus. - Notes can be updated at any time, regardless of status.
- Every item must have a shipping label before the shipment can be submitted.
- Inventory is deducted immediately upon submission — ensure stock is available.
- After submission, a new shipment ID is returned. The draft is deleted.
- Submit and cancel endpoints do not require a request body.
- FNSKU labels and custom attachments are optional.