Skip to content
Shipping

Fulfillment and delivery

Assign fulfillment orders, record shipments with tracking, and mark orders as delivered.

Andrea Ramos bought two T-shirts. Your system assigns the location that ships them, records the shipment with its tracking, and marks the delivery with the Admin API.

Requirements

  • An Admin token with the api_admin scope. See ERP integration.
  • An application with the shipment:read, shipment:update, and order:update permissions. To list locations, also settings:read.
  • A confirmed order with products that require shipping. Receive it with Incoming orders.
  • Node.js 18 or later and Python 3 with requests, for the examples.
Terminal
export ECOMIQ_TOKEN="<token>"

Read the fulfillment orders

Every order starts with its fulfillment orders: one per shipping location, with only the products that require shipping.

curl https://api.ecomiq.pe/api/v1/orders/7204558912004325301/fulfillment-orders \
  -H "Authorization: Bearer $ECOMIQ_TOKEN"
Response
[
  {
    "id": "7204558912004325310",
    "orderId": "7204558912004325301",
    "orderNumber": "ORD-2026-0001",
    "locationId": null,
    "locationName": null,
    "status": "pendingAssignment",
    "items": [
      {
        "orderItemId": "7204558912004325302",
        "title": "Polo clásico Azul / M",
        "sku": "POL-CL-AZ-M",
        "quantity": 2,
        "shippedQuantity": 0,
        "remainingQuantity": 2,
        "committedQuantity": 0
      }
    ],
    "shipments": [],
    "version": 4821
  }
]

The following examples reuse API and the headers. If no location can ship the items, the fulfillment order stays in pendingAssignment.

StatusMeaning
pendingAssignmentNo location. It cannot be prepared or shipped.
pendingAssigned, with stock reserved.
inProgressBeing prepared.
readyToPickUpReady for pickup.
shippedHas at least one shipment.
deliveredDelivered.
cancelledCancelled. Reserved stock is released.

All fields in List fulfillment orders for an order.

Assign a location

Send the id of the location that will ship it. It must be active and enabled for fulfillment (isActive and useForFulfillment). Find it in List locations.

curl -X POST https://api.ecomiq.pe/api/v1/fulfillment/orders/7204558912004325310/assign \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "locationId": "7204558912004325378" }'
Response
{
  "id": "7204558912004325310",
  "orderNumber": "ORD-2026-0001",
  "locationId": "7204558912004325378",
  "locationName": "Almacén Lima",
  "status": "pending",
  "items": [
    {
      "orderItemId": "7204558912004325302",
      "sku": "POL-CL-AZ-M",
      "quantity": 2,
      "remainingQuantity": 2,
      "committedQuantity": 2
    }
  ],
  "version": 4822
}

The fulfillment order moves to pending with the stock reserved at Almacén Lima. If that location lacks stock, the API rejects the assignment.

You can reassign a fulfillment order in pendingAssignment or pending; the stock at the previous location is released. Once preparation starts, it can no longer be reassigned.

Prepare the package

Mark the fulfillment order in progress and print the packing slip. Marking it is optional and only applies to fulfillment orders in pending.

curl -X POST https://api.ecomiq.pe/api/v1/fulfillment/orders/7204558912004325310/in-progress \
  -H "Authorization: Bearer $ECOMIQ_TOKEN"

curl https://api.ecomiq.pe/api/v1/fulfillment/orders/7204558912004325310/packing-slip \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -o packing-slip.html

When you mark it, the order moves to inProgress and the customer gets a status email.

The packing slip is HTML. It shows the recipient, the origin location, and the items. For ORD-2026-0001 it shows Andrea Ramos, Av. Larco 1234, Miraflores, Lima.

Before shipping it lists every item; after shipping, there is one slip per shipment. For several fulfillment orders, use Get packing slips batch.

Record the shipment

When the package leaves, record the shipment with your carrier's data. With origin: "manual", only that data is stored, without calling a carrier.

curl -X POST https://api.ecomiq.pe/api/v1/fulfillment/orders/7204558912004325310/ship \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "manual",
    "itemSelection": "all",
    "trackingNumber": "PE1234567890",
    "trackingLink": "https://courierlima.pe/seguimiento/PE1234567890",
    "carrierName": "Courier Lima"
  }'
Response
{
  "id": "7204558912004325310",
  "orderNumber": "ORD-2026-0001",
  "locationName": "Almacén Lima",
  "status": "shipped",
  "trackingNumber": "PE1234567890",
  "shipments": [
    {
      "id": "7204558912004325320",
      "originKind": "manual",
      "trackingNumber": "PE1234567890",
      "trackingLink": "https://courierlima.pe/seguimiento/PE1234567890",
      "carrierName": "Courier Lima",
      "items": [
        {
          "orderItemId": "7204558912004325302",
          "sku": "POL-CL-AZ-M",
          "quantity": 2
        }
      ]
    }
  ],
  "version": 4824
}

The reserved stock is consumed and the fulfillment order moves to shipped. The order moves to inTransit, the customer gets an email with the tracking, and the order.in_transit webhook is sent.

For a partial shipment, use itemSelection: "selected" and list items with orderItemId and quantity. Each quantity is a whole number no greater than remainingQuantity.

Also send shipmentId with your carrier's shipment ID. If you retry with the same value, the API returns the existing shipment. All fields in Ship fulfillment order.

Create the shipment with a carrier

If the store has a connected carrier, use this step instead of Record the shipment: the shipment is created with the carrier, with its tracking and label.

curl -X POST https://api.ecomiq.pe/api/v1/fulfillment/orders/7204558912004325310/shipment \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "itemSelection": "all",
    "packageSelection": "automatic",
    "connectionSelection": "orderDefault"
  }'

curl -L https://api.ecomiq.pe/api/v1/fulfillment/orders/7204558912004325310/shipments/7204558912004325320/label \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -o label.pdf
Response
{
  "id": "7204558912004325320",
  "fulfillmentOrderId": "7204558912004325310",
  "orderId": "7204558912004325301",
  "originKind": "connection",
  "orderNumber": "ORD-2026-0001",
  "trackingNumber": "PE1234567890",
  "carrierName": "Courier Lima"
}

orderDefault uses the order's carrier. automatic uses the items' package or the store's default package. The shipping address must include a country code.

In the label path, {shipmentId} is the id from the response. Labels and tracking events only exist for carrier shipments. For a manual shipment, the API responds 400.

If the order has a single fulfillment order, you can also use Create shipment for an order.

Correct the tracking

If the number or link changes, update the shipment. In expectedVersion, send the fulfillment order's version, from Record the shipment or from Get fulfillment order.

curl -X PATCH https://api.ecomiq.pe/api/v1/fulfillment/orders/7204558912004325310/shipments/7204558912004325320/tracking \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "expectedVersion": 4824,
    "trackingNumber": "PE1234567891",
    "trackingLink": "https://courierlima.pe/seguimiento/PE1234567891",
    "carrierName": "Courier Lima"
  }'

Always send all three tracking fields. Use the shipment id from shipments. The response returns the fulfillment order with its new version.

If the fulfillment order changed, the API responds 409: read it again and retry. Only manual shipments on fulfillment orders that are not delivered or cancelled can be corrected. The change is added to the order history and does not resend the shipping email.

Mark the delivery

When your carrier confirms the delivery, mark the fulfillment order as delivered. It applies to fulfillment orders in inProgress, readyToPickUp, or shipped.

curl -X POST https://api.ecomiq.pe/api/v1/fulfillment/orders/7204558912004325310/deliver \
  -H "Authorization: Bearer $ECOMIQ_TOKEN"
Response
{
  "id": "7204558912004325310",
  "orderNumber": "ORD-2026-0001",
  "status": "delivered",
  "trackingNumber": "PE1234567891"
}

When every fulfillment order of the order is delivered, the order moves to delivered, the customer gets the delivery email, and the order.delivered webhook is sent. If only some are delivered, the order stays in partialDelivered.

Choose the customer notice

The order status is derived from its fulfillment orders, and the customer gets a notice only when that status changes.

The order moves toCustomer noticeWebhook
inProgressStatus emailorder.in_progress
inTransitShipping email with the trackingorder.in_transit
deliveredDelivery emailorder.delivered

Emails go to the order's email; without one, there is no notice. To notify by WhatsApp or not at all, use POST /v1/orders/{orderId}/in-transit instead of Record the shipment.

curl -X POST https://api.ecomiq.pe/api/v1/orders/7204558912004325301/in-transit \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "trackingNumber": "PE1234567890",
    "trackingLink": "https://courierlima.pe/seguimiento/PE1234567890",
    "carrierName": "Courier Lima",
    "notifyCustomer": true,
    "notificationChannels": ["email", "whatsApp"]
  }'
Response
{
  "id": "7204558912004325301",
  "orderNumber": "ORD-2026-0001",
  "firstName": "Andrea",
  "lastName": "Ramos",
  "status": "inTransit"
}

The call records a manual shipment with every item of each assigned fulfillment order that has not shipped yet. WhatsApp uses the order's phone. With notifyCustomer: false, the customer gets no notice.

To deliver everything at once, use Deliver order. It marks the assigned fulfillment orders as delivered and sends the delivery email. More fields in Set order in transit.

Next steps

Last updated:

On this page