fulfillmenttools
API documentationIncident ManagementFeedback
Developer Docs
Developer Docs
  • Developer docs
  • Getting Started
    • Quickstart
    • Integration tutorial
      • Adding facilities
      • Adding listings to facilities
      • Configuring stocks
      • Carrier configuration
      • Placing orders
      • Checkout options
      • Distributed Order Management System (Routing)
      • Local fulfillment configuration
    • Free trial
  • Technical Basics
    • Access to fulfillmenttools
    • Feature status
    • Available regions
    • Backup policies
  • Connecting to fulfillmenttools
    • Client SDKs
    • commercetools connect
    • OpenID connect
      • Configure Microsoft Entra ID / Azure Active Directory
      • Configure Keycloak
  • API
    • Core concepts
      • Authentication & authorization
      • API Versioning & lifecycle
      • Assign user to jobs
      • Localization
      • Resource timestamps
      • Custom attributes
      • Article attributes
      • Recordable attributes
      • Data update guarantees
      • Rate limits & scaling
      • Retries
      • Performance on test vs. production systems
      • Load testing
    • API calls
      • Postman
      • cURL
      • GraphQL Explorer
    • GraphQL API
    • RESTful API
      • Pagination interface
      • RapiDoc
      • OpenAPI 3.0 Spec
    • Eventing
      • Structure of an event
      • Available events
        • Event flows
      • Eventing example
      • Event export
  • Integration Guides
    • Basics
      • Article categories
      • Audits
      • Facilities
      • Facility groups
      • GDPR configuration
      • Listings
      • Remote configuration
      • Receipts
      • Search
      • Subscribe to events
      • Sticker
      • Stocks
      • Storage locations
      • Tags
      • Users
    • Channel inventory
    • Inbound process
    • Outbound stocks
    • Purchase order
    • Receipt
    • Routing strategy (context-based multi-config DOMS)
    • Show sticker to clients
    • Stow jobs
  • More Integration Guides
    • Carrier management
      • Introduction to carrier configuration
      • Required data when operating carriers
      • Adding & connecting carriers to facilities
      • Custom carrier
    • Configurations for order fulfillment
      • Picking configuration
      • Packing configuration
      • Handover configuration
      • Printing and document configuration
      • Packing container types
      • Parcel tag configuration
      • Headless order fulfillment
      • Short-pick reasons
      • External documents in order fulfillment
      • Service jobs
      • Load units
      • Running sequence
    • DOMS - distributed order management system (routing)
    • External actions
    • Interfacility transfer
    • Notifications
    • Orders
      • Place your first order
      • Ship-from-store orders
      • Click-and-collect orders
      • Locked orders
      • Order with custom services
      • Bundled items in an order
      • Order process status
    • Availability & promising
    • Returns
Powered by GitBook
On this page
  • Picking
  • Packing
  • Shipping
  • HandoverJob
  • Finished
Edit on GitHub
  1. More Integration Guides
  2. Configurations for order fulfillment

Headless order fulfillment

Last updated 4 months ago

This article shows you how an order can be fulfilled without using the Operations App.

After an order was created and routed to a facility, jobs are created. Using Most customers pick them using our mobile or web client, but let's imagine there are reasons not to use it. For example you use our app in your stores and a chaotic storage system in your warehouse. In the stores you'd like to pick using our picking app, in the warehouse you want to keep the old WMS picking solution. This is supported by our API, let's have a look how you can do that.

Picking

As you might know, orders are never picked, what is picked is called PickJob. After an order was routed, a routing plan is created which creates PickJobs in 1-n facilities. For each created PickJob, a PICK_JOB_CREATED event occurs, which you could subscribe to. The pick job contains information on the orders to pick and the locations where to pick them from:

Pick job
{
  "routingPlanRef": "f328b21d-8e59-4f2d-acdc-7207c89edecc",
  "facilityRef": "bef04548-dacf-402e-a692-48aecd008d08",
  "orderRef": "3ee8e01c-9af0-4af1-82fc-b106c2fe09a1",
  "orderDate": "2024-06-13T13:28:48.000Z",
  "tenantOrderId": "SOME-ORDER-ID",
  "status": "OPEN",
  "pickLineItems": [
    {
      "quantity": 1,
      "status": "OPEN",
      "scannableCodes": [],
      "article": {
        "tenantArticleId": "SHIRT-W-1234",
        "title": "White T-Shirt",
        "titleLocalized": null,
        "attributes": [
          {
            "category": "descriptive",
            "priority": 100,
            "key": "size",
            "value": "L"
          }
        ],
        "prices": [
          {
            "pricePerUnit": 39.9,
            "currency": "EUR"
          }
        ]
      },
      "secondaryPicked": 0,
      "picked": 0,
      "id": "82ff449c-56b5-4053-a93f-bc7559a13655",
      "partialStockLocations": [
        {
          "quantity": 1,
          "tenantPartialStockId": "a9bdece5-a684-486e-893f-24a68af9ffd8",
          "location": null,
          "available": 1,
          "ratingScore": 0,
          "sequenceScore": null,
          "stockProperties": {}
        },
        {
          "quantity": 0,
          "tenantPartialStockId": "a274a87e-58c4-45d1-bb7e-4c0b70bf9b8b",
          "location": null,
          "available": 0,
          "ratingScore": 0,
          "sequenceScore": null,
          "stockProperties": {}
        }
      ],
      "tags": [],
      "scanningRules": {
        "scanningType": [
          {
            "priority": 0,
            "scanningRuleType": "ARTICLE"
          }
        ],
        "scanningMode": "SCAN_NOT_REQUIRED"
      },
      "scanningRule": {
        "values": [
          {
            "priority": 0,
            "scanningRuleType": "ARTICLE"
          }
        ]
      }
    }
  ],
  "deliveryinformation": "left out for privacy",
  "processId": "d9200d3f-e322-4674-8747-ace7f82a164e",
  "shortId": "CX69",
  "paymentInformation": {
    "currency": "IDR"
  },
  "preferredPickingMethods": [
    "MULTI_ORDER"
  ],
  "customAttributes": null,
  "id": "7b71ac26-401b-478c-a297-3d7eda45b804",
  "tags": [
    {
      "id": "sales_channel",
      "value": "MP"
    }
  ],
  "documentHandling": {
    "sendLabel": {
      "enabled": true
    }
  },
  "documentsRef": "5459b5d5-8935-45c1-9da7-9c0ee9fa1e8c",
  "stickers": [],
  "operativeProcessRef": "17a5ea92-7c03-4f6e-b550-3533abc3130b",
  "created": "2024-06-13T13:28:52.532Z",
  "lastModified": "2024-06-13T13:28:52.532Z",
  "version": 1
}

After receiving the event containing that PickJob you can pick the goods. When sending the result of that picking it is crucial to tell the platform which items were collected, this information is stored in the partialStockLocations. If you are unsure which location to take from the list, we recommend using the first item in the list where the quantity is higher than 0, in the example above it is the first item in the list. After you picked with your solution and want report the result to the platform, you can use the .

PATCH pick job for giving information on picked items
curl -sSL -X PATCH 'https://your.api.fulfillmenttools.com/api/pickjobs/<PickJobId>' \
--header 'Authorization: Bearer <TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{
  "version": 1,
  "actions": [
    {
      "action": "ModifyPickJob",
      "status": "IN_PROGRESS"
    },
    {
      "action": "ModifyPickLineItem",
      "id": "82ff449c-56b5-4053-a93f-bc7559a13655",
      "picked": 1,
      "scannedCodes": [
        {
          "code": "2021-0018",
          "quantity": 1
        }
      ],
      "status": "CLOSED",
      "partialStockLocations": [
        {
          "tenantPartialStockId": "a9bdece5-a684-486e-893f-24a68af9ffd8",
          "picked": 1
        }
      ]
    }
  ]
}
'
PATCH pick job for closing the pick job
curl -sSL -X PATCH 'https://your.api.fulfillmenttools.com/api/pickjobs/<PickJobId>' \
--header 'Authorization: Bearer <TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "version": 2,
    "actions": [
        {
            "action": "ModifyPickJob",
            "status": "CLOSED"
        }
    ]
}'

Assuming you have either picked everything or order split after short pick is enabled, a PackJob will be created and a PICK_JOB_PICKING_FINISHED event is thrown as well as a PACK_JOB_CREATED event. If an order is rerouted after a short-pick, you will receive a PICK_JOB_REROUTED event.

Packing

PATCH for reporting packed items
curl -sSL -X PATCH 'https://your.api.fulfillmenttools.com/api/packjobs/<PackJobId>' \
--header 'Authorization: Bearer <TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "version": 2,
    "actions" :[
        {
            "action": "ModifyPackLineItem",
            "id": "5a07f221-c7ca-4748-a59b-8d705c8989db",
            "packed": 6
        },
        {
            "action": "ModifyPackLineItem",
            "id": "020c9dce-58a6-40eb-8950-98de0933f5d0",
            "packed": 2
        }
    ]
}'

After that the PackJob should also be closed:

PATCH for closing pack job
curl -sSL -X PATCH 'https://your.api.fulfillmenttools.com/api/packjobs/<PackJobId>' \
--header 'Authorization: Bearer <TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "version": 3,
    "actions": [
        {
            "action": "ModifyPackJob",
            "status": "CLOSED"
        }
    ]
}'

Shipping

Assuming you have configured the carrier DHL and use the same parcel classification every time, you don't need to specify it when trying to get a label. In our platform, a label is something that belongs to a parcel and a parcel belongs to a shipment. Each shipment can have multiple parcels, but each parcel has its own and unique label.

That said, you need to add a parcel to a shipment. In each response of the API calls mentioned above, you get an object containing a processId:

Add a parcel to a shipment
{
    "routingPlanRef": "710fa76d-5699-4653-8cc7-954e61baf2f1",
    "facilityRef": "3a091284-e434-467b-aa3b-da72c28f50f7",
    "orderRef": "710fa76d-5699-4653-8cc7-954e61baf2f1",
    "orderDate": "2023-08-02T14:53:36.918Z",
    "tenantOrderId": null,
    "status": "CLOSED",
    "pickLineItems": ["shortened for documentation!"],
    "deliveryinformation": {
      "targetTime": "2024-06-15T05:00:00.000Z",
      "targetTimeBaseDate": null,
      "channel": "SHIPPING"
    },
    "processId": "e43ff765-88d2-452f-85d6-57adab58a832",
    "shortId": "SG2",
    "paymentInformation": null,
    "preferredPickingMethods": [
        "SINGLE_ORDER"
    ],
    "id": "3411701b-5c05-4220-97d1-c3eb5846a0ae",
    "customAttributes": null,
    "documentHandling": {
        "sendLabel": {
            "enabled": true
        }
    },
    "documentsRef": "0c014957-e925-49de-812e-0a7f333256df",
    "stickers": [],
    "created": "2023-08-07T08:37:44.070Z",
    "lastModified": "2023-11-07T13:47:57.909Z",
    "version": 5,
    "editor": {
        "userId": "anonymized",
        "username": "anonymized"
    }
}

curl -sSL -X PATCH 'https://your.api.fulfillmenttools.com/api/parcels/<ParcelId>' \
--header 'Authorization: Bearer <TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "version": 1,
    "actions": [
        {
            "action": "ModifyParcel",
            "status": "DONE",
            "result": {
              "carrierTrackingNumber": "123456789",
              "returnLabelId": "987654321",
              "summary": "",
              "trackingStatus": "picked_up",
              "trackingUrl": "https://nolp.dhl.de/nextt-online-public/set_identcodes.do?lang=de&idc=123456789"
          }
        }
    ]
}'

More information about that can also be found on the page.

HandoverJob

curl -sSL -X PATCH 'https://your.api.fulfillmenttools.com/api/handoverjobs/<HandoverJobId>' \
--header 'Authorization: Bearer <TOKEN>' \
--header 'Content-Type: application/json' \
--data-raw '{
  "actions": [
    {
      "action": "ModifyHandoverjob",
      "status": "HANDED_OVER"
    }
  ],
  "version": 1
}'

Finished

Congratulations: You have successfully fulfilled an order headless.

When the PackJob was created, the PACK_JOB_CREATED event will occur. Subscribing to that, packing with a solution which is not provided by fulfillmenttools, won't be an issue. Using the , you can report the packed items to the platform:

Using the we can get the process:
curl -sSL 'https://your.api.fulfillmenttools.com/api/processes/<ProcessId>' \
  --header 'Authorization: Bearer <TOKEN>'

200 OK
{
    "gdprCleanupDate": "2023-09-06T08:37:40.541Z",
    "deletionDate": "2023-10-06T08:37:40.541Z",
    "isAnonymized": false,
    "id": "e43ff765-88d2-452f-85d6-57adab58a832",
    "version": 20,
    "orderRef": "710fa76d-5699-4653-8cc7-954e61baf2f1",
    "flatRefs": [
        "710fa76d-5699-4653-8cc7-954e61baf2f1",
        "3411701b-5c05-4220-97d1-c3eb5846a0ae",
        "3a091284-e434-467b-aa3b-da72c28f50f7",
        "24cc693f-29e2-4493-9221-8518f5b9c8fe"
    ],
    "pickJobRefs": [
        "3411701b-5c05-4220-97d1-c3eb5846a0ae"
    ],
    "shipmentRefs": [
        "24cc693f-29e2-4493-9221-8518f5b9c8fe"
    ],
    "handoverJobRefs": [],
    "returnRefs": [],
    "packJobRefs": [
        "27c83af2-f789-4375-987a-a92193acf937"
    ],
    "facilityRefs": [
        "3a091284-e434-467b-aa3b-da72c28f50f7"
    ],
    "created": "2023-08-07T08:37:40.542Z",
    "lastModified": "2023-11-07T13:47:59.765Z",
    "domainStatusHistory": ["shortened for documentation"],
    "domainStatuses": {
        "ORDER": "FINISHED",
        "ROUTING_PLAN": "FINISHED",
        "SHIPMENT": "CREATED",
        "PICKJOB": "FINISHED",
        "PACKJOB": "CREATED"
    },
    "status": "IN_PROGRESS",
    "routingPlanRefs": [
        "710fa76d-5699-4653-8cc7-954e61baf2f1"
    ],
    "schemaVersion": 3,
    "operativeStatus": "IN_PROGRESS",
    "domsStatus": "FINISHED",
    "lastDomainEntityStatuses": ["shortened for documentation"]
}

Here you can find the shipmentRefs, which you need for adding parcels to that shipment. An alternative approach would be to use the with pickJobRef as a query parameter.

Assuming you have configured the carrier as mentioned above, you can just call the without any payload. However, this might depend on the used carrier and the product you want.
curl -sSL -X POST 'https://your.api.fulfillmenttools.com/api/shipments/<ShipmentId>/parcels' \
  --header 'Authorization: Bearer <TOKEN>'

201 Created
{
    "loadUnitRefs": [],
    "version": 1,
    "id": "713b9cd8-6d47-45ff-a12a-ef3acb270180",
    "status": "OPEN",
    "shipmentRef": "3e62db41-af9e-4ce1-8c44-1ef340457057",
    "processRef": "3af2e1a7-d104-4ef9-a43b-13890ae318cf",
    "carrierRef": "488bd1a6-6c76-4407-8dab-ece716dac14d",
    "recipient": {
        "firstName": "Luke",
        "lastName": "Skywalker",
        "street": "Planetenstraße",
        "houseNumber": "13",
        "postalCode": "40223",
        "city": "Düsseldorf",
        "country": "DE"
    },
    "sender": {
        "street": "Carlsplatz",
        "houseNumber": "3",
        "postalCode": "40213",
        "city": "Düsseldorf",
        "country": "DE",
        "companyName": "ptdus",
        "resolvedCoordinates": {
            "lon": 6.77285093516085,
            "lat": 51.2263665950437
        },
        "emailAddresses": null,
        "additionalAddressInfo": null,
        "phoneNumbers": null,
        "resolvedTimeZone": {
            "offsetInSeconds": 3600,
            "timeZoneId": "Europe/Berlin",
            "timeZoneName": "W. Europe Standard Time"
        }
    },
    "dimensions": {
        "weight": 2000
    },
    "created": "2023-04-14T15:03:07.982Z",
    "lastModified": "2023-04-14T15:03:07.982Z"
}

The response contains the Id of the newly created parcel which you can later update, e.g. with tracking information you got from the carrier using the endpoint:

After you successfully followed the steps above, it might be useful in some cases (for example when using a custom carrier) to mark the HandoverJob as handed over manually. To do this, you will use the and a request looking like this:

PATCH PickJob endpoint
PATCH PackJob Endpoint
GET process by ID endpoint
GET Shipments endpoint
create parcel endpoint
PATCH parcel by ID
PATCH HandoverJob endpoint