Overview
Two resources — packages and returns — cover the post-order lifecycle. Both follow the atomic-create pattern: a singlePOST creates the parent resource plus all inline children in one transactional write, returning the fully hydrated response. You never need follow-up calls to add items or purchase a label.
Packages
Canonical scenario: package with items + label + fulfill in one POST
This request creates the package, adds two line items, purchases a Shippo label, and fulfills the package (posts GL entries, sends shipment notification) in a single atomic call.Response
Inline create options
Separate label purchase (two-step flow)
If you prefer to separate rate shopping from package creation:Package lifecycle
POST /v1/packages/:id/buy-label— purchase label (transitions to packed + label attached)POST /v1/packages/:id/void-label— void active label, request Shippo refundPOST /v1/packages/:id/fulfill— mark shipped, post GL fulfillment entriesGET /v1/tracking-events?package_id=:id— live tracking events
Shippo not configured
If the entity has no Shippo credentials,buy_label: true returns:
buy-label after configuring Shippo.
Webhook delivery: the
package.shipped event is queued when a package is fulfilled. Real-time webhook delivery to your endpoint is available in Q3 2026. In the interim, poll GET /v1/tracking-events to check shipment status.Adding items to an existing package
POST /v1/packages/:id/items enforces two invariants before inserting:
- Item-on-order — the product must be on the parent order. Off-order adds return
422 product_not_on_order. - Quantity cap — requested qty must not exceed
ordered_qty - already_packed_qtyacross ALL packages on the order. Over-cap adds return422 package_quantity_exceeds_ordered.
order_item_id OR product_id may be supplied; the canonical writer resolves the matching order_item when only product_id is provided.
422 product_not_on_order
422 package_quantity_exceeds_ordered
GET /v1/orders/:id/items?packable=true. The response includes one row per (order_id, product_id) with ordered_qty, already_packed_qty, remaining_qty.
Handling pack leftovers
When Pack with Mighty (the Arcus Mighty packing engine) cannot fit one or more items into any available box, those items are returned as leftovers rather than silently dropped. Each leftover is persisted tofulfillment.pack_leftovers and surfaced to the warehouse operator on the Pack Order page and the Order Detail > Fulfillment tab.
Listing leftovers for an order
{ data: [...rows], total, unresolved_count }. Each row includes:
Rejection reason codes
Dim comparison (packpilot_item_too_large)
When the rejection ispackpilot_item_too_large, the reason_detail object provides exact dimensions so you can diagnose without a second lookup:
axis_failure is the index of the first failing axis (0=length, 1=width, 2=height). In the example above, index 2 means the item’s height (9.0 in) does not fit the box’s inner height (4.0 in).
Resolving a leftover
packed_manually— add the units to an existing package. Requirespackage_id. Optional partialquantity.dismissed— operator override with a mandatorynotesfield (audit trail).backordered— promote to a customer backorder. Optionalquantity(partial) andexpected_date.split_to_new_order— create a new sales order for the full leftover quantity, linked to the original.
Returns (RMAs)
Canonical scenario: return with items + restocking fee + auto-refund
This creates the RMA, adds line items, and issues the refund in a single atomic call.Response
Inline create options
Restocking fee formats
returns.restocking_fee and return it as a JS number.
RMA lifecycle
POST /v1/returns/:id/receive— record inbound goods, restore inventoryPOST /v1/returns/:id/inspect— flag for QC inspection (if entity settings require)POST /v1/returns/:id/disposition— restock / write off / send to vendorPOST /v1/returns/:id/refund— issue explicit refund (if not done inline)POST /v1/returns/:id/cancel— cancel the RMA, release holds
Receiving and disposition (separate calls)
After the physical goods arrive:Return-window enforcement
Ifentities.settings.return_window_days is set, returns outside the window are rejected with 422 outside_return_window. Pass override_window: true to bypass (requires returns:write scope).
Validating serials on return
For serialized products, Arcus can verify that the scanned serial was actually sold on the order being returned before persisting it. This prevents swap fraud and inventory drift.Entity setting
Configure the validation mode in Settings > Shipping & Fulfillment > Returns, or via the API:Probing a serial before submit
CallPOST /v1/serials/validate-for-return to check a serial before submitting the receive. The endpoint returns a verdict without blocking the caller:
Error codes (hard mode)
Whenserial_validation_mode is hard and a receive/disposition is submitted with a mismatched serial, the endpoint returns 422 with a structured error:
Scopes required
Serial capture during pack
Serialized products require one serial number per unit before a package can be fulfilled. Arcus supports two capture flows: full capture (all serials at once) and partial capture (progressively scan as you pick).Full capture
Passraw_barcodes[] in the capture request. The backend runs barcode parsing (prefix stripping, format normalization, lot extraction) automatically.
curl
{ data: { captured_count, total_required, remaining, serials[], partial } }.
Partial capture (N of total)
UsePOST /v1/serials/capture-partial when picking units one at a time before all units are in hand. The endpoint accepts the same shape as /v1/serials/capture and returns a partial: true flag when remaining > 0.
curl
remaining reaches 0 and partial: false, the line is ready to fulfill.
Release a serial (before order closes)
DELETE /v1/serials/:id/release removes a serial from its package item so it can be re-scanned or re-assigned. This endpoint is blocked once the order is in a terminal state:
When an order is already fulfilled, use the RMA flow (
POST /v1/returns) to return the serial unit rather than releasing it directly.
Error handling
All errors follow the{ error, code, type, hint } envelope. Partial-success responses (e.g. package created but label failed) include the created resource in the response body alongside the error:
buy-label using the package.id from the error body.
