Skip to main content

Overview

Two resources — packages and returns — cover the post-order lifecycle. Both follow the atomic-create pattern: a single POST 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 refund
  • POST /v1/packages/:id/fulfill — mark shipped, post GL fulfillment entries
  • GET /v1/tracking-events?package_id=:id — live tracking events

Shippo not configured

If the entity has no Shippo credentials, buy_label: true returns:
The package is created and returned in the error body so you can retry 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:
  1. Item-on-order — the product must be on the parent order. Off-order adds return 422 product_not_on_order.
  2. Quantity cap — requested qty must not exceed ordered_qty - already_packed_qty across ALL packages on the order. Over-cap adds return 422 package_quantity_exceeds_ordered.
Either 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
To preview what’s still packable on an order, call 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 to fulfillment.pack_leftovers and surfaced to the warehouse operator on the Pack Order page and the Order Detail > Fulfillment tab.

Listing leftovers for an order

Returns { data: [...rows], total, unresolved_count }. Each row includes:

Rejection reason codes

Dim comparison (packpilot_item_too_large)

When the rejection is packpilot_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

Four resolution actions are supported:
  • packed_manually — add the units to an existing package. Requires package_id. Optional partial quantity.
  • dismissed — operator override with a mandatory notes field (audit trail).
  • backordered — promote to a customer backorder. Optional quantity (partial) and expected_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

All three formats store the computed flat amount in returns.restocking_fee and return it as a JS number.

RMA lifecycle

  • POST /v1/returns/:id/receive — record inbound goods, restore inventory
  • POST /v1/returns/:id/inspect — flag for QC inspection (if entity settings require)
  • POST /v1/returns/:id/disposition — restock / write off / send to vendor
  • POST /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

If entities.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

Call POST /v1/serials/validate-for-return to check a serial before submitting the receive. The endpoint returns a verdict without blocking the caller:
Response shapes:

Error codes (hard mode)

When serial_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

Pass raw_barcodes[] in the capture request. The backend runs barcode parsing (prefix stripping, format normalization, lot extraction) automatically.
curl
Response: { data: { captured_count, total_required, remaining, serials[], partial } }.

Partial capture (N of total)

Use POST /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
Call the endpoint again as each additional unit is scanned. When 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:
This lets you recover without duplicating the package — just retry buy-label using the package.id from the error body.