fulfillmenttools HTTP API documentation

OC fulfillment GmbH, api@fulfillmenttools.com, 0.1-Beta, 23.06.2020

This document gives an overview of the characteristics and features of the fulfillmenttools RESTful API.

WARNING Please understand, that the API is currently in a Pre-Released state and is subject to frequent changes until further notice!

General topics

Host

The fulfillmenttools API is served over HTTPS. A tenant will get his own host once the corresponding environment has been created. The naming format of this host is similar to https://TENANT-SLUG.api.fulfillmenttools.com while TENANT-SLUG is a unique identifier to each environment. For documentation purposes the URLs referenced in the documentation are abbreviated by: https://{host}

Region

Our service is hosted with Google Cloud Platform. We leverage products that are located either in Region europe-west1 (St.Ghislain, Belgium) or europe-west3 (Frankfurt, Germany).

API Versioning

We will release a new version of the API when we make backwards-incompatible changes. The following changes however are considered to be backwards-compatible:

  • Adding new API resources.
  • Adding new optional request parameters to existing API methods.
  • Adding new properties to existing API responses.
  • Changing the order of properties in existing API responses.
  • Adding values to enum fields (this is unusual – please also refer to BETA functionality for more details.)

BETA functionality

Due to the fact that the fulfillmenttools API is not strictly versioned (it does not have a version number assigned to it & it changes (see API Versioning) Beta Flags are introduced. Those flags look like this in the OpenAPI-Specification under https://fulfillmenttools.github.io/api-reference-ui/:

You will find this flag on ressources, types (or part of types) and a few other places which are under development. If you encounter such a flag it means the following:

  • this endpoint, type, etc. might be subject to breaking changes in the future
  • it might not be available at all times
  • it could disapear without specific warning
  • it currently does not fall under our SLA regulations

Important

There are a lot enumerations in the OpenAPI Spec to ease the use of specific values for statuses, etc., for example:

OrderStatus:
   type: "string"
   enum:
   - "OPEN"
   - "CANCELLED"
   - "LOCKED"

Please be aware that adding a value to it is considered a non breaking change (as described API Versioning). It happens very rarely, but it could happen that another value is added to an enumeration.

However, the tooling around this kind of "exceptional" use of enumerations is not very good: A code generator will most likely produce enumerations which cannot contain different values than the one described. This is why most of the enumerations also carry a BETA-Flag: To make this behaviour more apparent. If you are handling enumerations best practice is to anticipate this kind of behaviour and map unknown enum values to one that you recognize or yield an error.

Authorization

Authorization against our API works by using a registered user and using the OAuth-Protocol. If the authentication was successfully, a signed JWT Token is issued to the caller which can be used to authenticate against the API or to get a fresh token from the authentication provider using the refresh_token. Currently we are using the Google Identity Provider as an authentication provider.

Logging in and obtaining a valid token can for example be done by issuing the following request:

curl --request POST 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=<AUTHKEY>' \
        --header 'Content-Type: application/json' \
        --data-raw '{"email": username,  "password": password, "returnSecureToken": true}'

A valid username and password as well as the customer specific AUTH_KEY can be obtained from fulfillmenttools when the tenant is entitled to use the API.

As a result, an answer looking similar to this is responded:

{
  "kind": "identitytoolkit#VerifyPasswordResponse",
  "localId": "jdwBuqqYWdYoqWTH1Xv85EJJMpm2",
  "email": "yourlogin@yourdomain.com",
  "displayName": "",
  "idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImMzZjI3NjU0MmJmZmUN0WU5OGMyMGQ2MDNlYmUyYmExMTc2ZWRhMzMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vb2NmZi1kZXYtYm94ZmlzaC12MS1ibHViIiwiYXVkIjoib2NmZi1kZXYtYm94ZmlzaC12MS1ibHViIiwiYXV0aF90aW1lIjoxNTkyODI0MDg2LCJ1c2VyX2lkIjoiamR3QnVxcVlXZFlvcVdUSDFYdjg1RUpKTXBtMiIsInN1YiI6Impkd0J1cXFZV2RZb3FXVEgxWHY4NUVKSk1wbTIiLCJpYXQiOjE1OTI4MjQwODYsImV4cCI6MTU5MjgyNzY4NiwiZW1haWwiOiJ0ZXN0QHRlc3QuZGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsidGVzdEB0ZXN0LmRlIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.nTlNZTj5B3-lOToCuzEFIqTQSz4cPY1lOhdj12-RG1wOTlOoS_u5JGi4Zw4S684o07381g3ooC_B-KM2UhqaQMAkWfA_OA1cJgD_rrAdIUov_cuYdCYXHmvI-8kAajsy6R08Uh3lQYHx9tmyzbweqDFluGXEc9huH-QoWfoiwZ9Y1sjguAgC1ZCbQi3AkgKcKOqHVn8bGFxYK6KxoyXMZxaiFrfwjMQ-lov0554akQDBU0gAqLCszXtmQP7rNI5boeMIA1vo0myTXwvmYLMHIVJVn6Ej-I2SSAY1OCdafgF6k492lxJN8lJhsTsJfCynLgbiNgHJJxtBXSTSFnp2fA",
  "registered": true,
  "refreshToken": "AE0u-NeGDdHWPB0RjOYOL5AlfSO6r8CvMO6eLSXxdjMG9xiXQmBxZKJgu9OSwZ2JJc2jgSTgiNFYTFYmd1DAlfcCzpunAOF6JC8ZofkrkM75lTjMyQgxWlGWIP24dk2qaMvtAPt2oK8RtsjKx4TRQosFagokGTukQKxSWxSpTEDZl0QbWM9zmbBjzBqSK5yCWMwK2qHJpYgfJvoyAyReo76mRDC36NUpRMKoncagBq30OFCJkEgpvyI",
  "expiresIn": "3600"
}

The value of idToken can be used in a HTTP call within the Authorization Header in a request towards our API like this:

Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6I…​

Refreshing the access token (which expires by default after 60 minutes) is done by using the value of the field refreshToken. The following snippet shows how a call is issued in order to get a new access token by using a refresh token:

curl --request POST 'https://securetoken.googleapis.com/v1/token?grant_type=refresh_token&refresh_token=<REFRESH_TOKEN>&key=<AUTHKEY>'

Representations

The fulfillmenttools API currently offers application/json as the standard representation of request- and response-bodies.

Timeouts & Response Times

Currently each endpoint can have a response time of up to 60 seconds.

Update Guarantees

The API provides different update guarantees for different kinds of requests.

Strong Consistency

Each resource provides read-after-write consistency. Read-after-write consistency means that when an entity is updated, The changes that were made are instantly availabe after waiting for the successful response and reading the same entity.

Eventual Consistency

Some updates are not visible immediately in the used clients (especially mobile and web clients) after the update. For example, if a shipment was created which is by design an asynchronous process, updating the shipment itself provides read-after-write consistency, but the actual visibility in said clients is updated with a delay by the platform.

These delayed updates provide an Eventual Consistency guarantee. It means that after a delay you will be able to see the changes that you or a follow up process performed.

These delays can vary depending on the amount of data to update. We try to keep these delays as small as possible.

Optimistic Concurrency Control (Optimistic Locking)

Many API resources use optimistic concurrency control to prevent lost updates when making changes to existing data concurrently. These resources have a version attribute. When sending (partial) updates to these resources, the expected (and to the client last known version) of a resource is sent as a part of the request.

After making an update, the HTTP response body contains the new version of the resource. An API client must wait for the new version before sending follow-up requests.

Background processes and other events can also change a resource’s version number without any requests sent from an API client directly to the resource. Do not rely on a consistently incremented version number.

The API does not use ETag and If-Match HTTP headers for the purpose of optimistic concurrency control.

If a version mismatch occurs, the API returns a 409 HTTP status (Conflict) error.

Resource Timestamps

Timestamps as created- or lastModified or other fields that represent a date are always UTC Timestamps corresponding to Coordinated Universal Time (UTC), for example 2020-06-22T12:10:31.000Z.

Pagination Interface

The fulfillmenttools platform offers a pagination interface for some endpoints. Pagination is done based on size and startAfterId provided in the request by using query parameters. This way you will get the portion of data according to the requested parameters:

GET /api/orders?size=10&startAfterId=11bkLXXLQBYaQcqfDzMcqj

This call would return 10 orders starting after (not including) the order with the specified id.

Users

In the fulfillmenttools platform there is a simple yet effective rights & permission system in place. It hides data from users, that are authenticated but not authorized to view data or it disables to use features in API, backoffice or mobile clients.

Roles and permissions

There is a fixed set of roles a user can possess within the platform. Currently the system knows the following roles:

Table 1. Available Roles in fulfillmenttools
Role Facility affiliation

FULFILLER

yes

SUPERVISOR

yes

ADMINISTRATOR

no

A user always has to possess a known role in order to interact with the clients or via the API. This information is issued to the Identity-Provider and will be reflected in the next JWT, that is issued to the user.

TIP

Only information that comes within the signed JWT token is considered for authorization in the platform. That also means, that for example a role change can take up to 60 minutes for a user to become visible, because already issued JWT Tokens still remain valid. You can force acquiring a new role for example by asking the user to logout and login again.

Currently there is a 1:1 relationship between a user and a role: Any given user in the system can have exactly one role. The existing roles are sharing permissions and implement a concept of inheritance which is shown in the following diagram:

Facility affiliation

In order to prevents user of specific roles to read out data that they are not allowed to see (for GDPR reasons for instance) some roles are only assignable together with a mandatory reference to a facility (see table).

IMPORTANT
Currently the list of available roles is fixed and cannot be extended by the client.

Use Cases

The following calls are allowed to the following roles: SUPERVISOR, ADMINISTRATOR

NOTE

SUPERVISORS can only manage users within their assigned facility while ADMINISTRATORS can manage users for any existing facility.

Creation of new users

curl --location --request POST 'https://{host}/api/users' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Valid idToken>' \
--data-raw '{
  "firstname": "Donna",
  "lastname": "Sheridan-Carmichael",
  "username": "dsheridan",
  "password": "fsdf6556",
  "roles": [
    {
      "name": "FULFILLER",
      "facilities": [
    "0AMSoRCbm7kSM3LJPoeH"
      ]
    }
  ]
}'

Response:
201 OK
{
    "customClaims": {
        "roles": [
            {
                "facilities": [
                    "0T1vKaEar0nuG58CxzA5"
                ],
                "name": "FULFILLER"
            }
        ]
    },
    "version": 1,
    "lastname": "Sheridan-Carmichael",
    "username": "dsheridan",
    "firstname": "Donna",
    "created": "2020-09-25T10:06:35.009Z",
    "lastModified": "2020-09-25T10:06:35.009Z",
    "id": "x5jrZrDHvYYs6HpaDICKYG4QuIk2"
}
IMPORTANT
Operation is allowed by either a SUPERVISOR of facility 0AMSoRCbm7kSM3LJPoeH or an ADMINISTRATOR.

Modifying role of a user

curl --request PATCH 'https://{host}}/api/users/x5jrZrDHvYYs6HpaDICKYG4QuIk2' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Valid idToken>' \
--data-raw '{
  "version": 1,
  "actions": [
    {
        "action": 	"ModifyUser",
          "roles": [
              {
                "name": "SUPERVISOR",
                "facilities": [
              "0T1vKaEar0nuG58CxzA5"
                ]
              }
            ]
    }
  ]
}'

Response:
200 OK
{
    "lastname": "Sheridan-Carmichael",
    "customClaims": {
        "roles": [
            {
                "facilities": [
                    "0T1vKaEar0nuG58CxzA5"
                ],
                "name": "SUPERVISOR"
            }
        ]
    },
    "version": 2,
    "lastModified": "2020-09-25T10:13:27.236Z",
    "username": "dsheridan",
    "created": "2020-09-25T10:06:35.009Z",
    "firstname": "Donna",
    "id": "x5jrZrDHvYYs6HpaDICKYG4QuIk2"
}
IMPORTANT
Role changes to users can only be done by users that have enough rights. In other words: A SUPERVISOR can not make another user an ADMINISTRATOR

Deleting a user

curl --request DELETE 'https://{host}}/api/users/x5jrZrDHvYYs6HpaDICKYG4QuIk2' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Valid idToken>'

Response:
200 OK
IMPORTANT
When a user is deleted all currently active JWT Tokens of said user are invalidated. The user will not be able to use an issued token anymore.

Tenant

In the fulfillmenttools platform a tenant is another word for our customer - and that happens to be you (once you decided to work with us). You will be issued with your own independent managed SaaS platform in regards of data segregation, call routing and configuration for your business without having to worry about scalability and security.

The tenant system can be configured and in this section we will present some configurations on a tenant level.

Tenant based configurations

The configurations mentioned here are applicable for the whole tenant. In other words: to all your facilities and for all your processes. More detailed configuration on facility level is mentioned in the Facilities section.

Name of feature Description Default Value How to activate?

Infinite Stock

The platform or, to be more precise, the Distributing Order Management System, can be configured in a way that it assumes infinite stock for the product listings you provide to fulfillmenttools. However, you can still disable single listings for not to be taken into consideration in your Backoffice-Application.

Disabled

Please get in contact with us.

Facilities

This resource describes the stores, warehouses, popup stores, etc. of a company. In general, a facility is a place where fulfillment processes take place or should be considered.

The facilities in our platform have some basic data to them: an address, provided services, etc.. They are also an important component of the Distributing Order Management System: an Order and a facility are combined into a Pickjob which is then picked to fulfill the consumers‘ order.

The api offers a delete endpoint. Handle with care as the call will delete every data related to the facility such as listings and configurations.

Creating a facility

curl --request POST 'https://{host}}/api/facilities' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Valid idToken>' \
--data-raw '{
    "name": "Otternasen Deluxe-Store",
    "locationType": "STORE",
    "address": {
        "street": "Allende-Platz",
        "houseNumber": "3",
        "postalCode": "20146",
        "city": "Hamburg",
        "country": "DE",
        "companyName": "Deluxe-Store",
        "phoneNumbers": [
            {
                "value": "040 - 25 45 78 41",
                "type": "PHONE",
                "label": "Anschluss im Laden"
            }
        ],
        "emailAddresses": [
            {
                "value": "tim@otternasen.com",
                "recipient": "Tim Otter"
            }
        ]
    },
    "contact": {
        "firstName": "Tim",
        "lastName": "Otter",
        "roleDescription": "Head of Everything"
    },
    "status": "ONLINE",
    "services": [
        {
            "type": "SHIP_FROM_STORE"
        }
    ]
}'

Response:
201 Facility is created.

{
    "locationType": "STORE",
    "name": "Otternasen Deluxe-Store",
    "version": 1,
    "status": "ONLINE",
    "address": {
        "phoneNumbers": [
            {
                "type": "PHONE",
                "label": "Anschluss im Laden",
                "value": "040 - 25 45 78 41"
            }
        ],
        "city": "Hamburg",
        "emailAddresses": [
            {
                "value": "tim@otternasen.com",
                "recipient": "Tim Otter"
            }
        ],
        "country": "DE",
        "houseNumber": "3",
        "postalCode": "20146",
        "companyName": "Deluxe-Store",
        "street": "Allende-Platz"
    },
    "services": [
        {
            "type": "SHIP_FROM_STORE"
        }
    ],
    "contact": {
        "roleDescription": "Head of Everything",
        "firstName": "Tim",
        "lastName": "Otter"
    },
    "created": "2020-10-13T15:03:39.011Z",
    "lastModified": "2020-10-13T15:03:39.011Z",
    "id": "AeFgjHMHHBBX4H4gwydZ"
}

Updating state of a facility

curl --request PATCH 'https://{host}}/api/facilities/AeFgjHMHHBBX4H4gwydZ' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Valid idToken>' \
--data-raw '{
  "version": 2,
    "actions": [{
        "action": "ModifyFacility",
        "status": "OFFLINE"
    }
  ]
}'

Response:
200 OK
{
    "services": [
        {
            "type": "SHIP_FROM_STORE"
        }
    ],
    "id": "AeFgjHMHHBBX4H4gwydZ",
    "created": "2020-10-13T15:03:39.011Z",
    "version": 2,
    "status": "OFFLINE",
    "address": {
        "street": "Allende-Platz",
        "companyName": "Deluxe-Store",
        "country": "DE",
        "city": "Hamburg",
        "houseNumber": "3",
        "phoneNumbers": [
            {
                "label": "Anschluss im Laden",
                "type": "PHONE",
                "value": "040 - 25 45 78 41"
            }
        ],
        "emailAddresses": [
            {
                "value": "tim@otternasen.com",
                "recipient": "Tim Otter"
            }
        ],
        "postalCode": "20146"
    },
    "contact": {
        "roleDescription": "Head of Everything",
        "lastName": "Otter",
        "firstName": "Roshanak"
    },
    "name": "Otternasen Deluxe-Store",
    "lastModified": "2020-10-13T15:17:58.238Z",
    "locationType": "STORE"
}

Configurations

A facility is, similar to a couple of other entities in the platform, configurable. The configurations for a facility are accessible via Get configurations for a facility. The reponse to a request to this resource will contain a list of set / available configurations to the depicted facility.

A facility can have some specific restrictions regarding stock. In a first version the available parameters for stock in a facility can be found within the next section.

Stock Configuration

Since a tenant might have ongoing offline sales (caused by offline consumers who walk into the facility and buy certain articles), we have to deal with the uncertainty that there will be stock which we cannot use for online orders. Furthermore we have to deal with the fact that the reported stock is not 100% correct for each given moment in time.

Therefore we “split” the stock of each article into offline and online stock and reserve a certain stock level for offline sales. The Distributing Order Management System will not include any of this offline stock into its calculations.

The reserved offline stock for a facility can be obtained, enabled or disabled by using the Get Facility Stock Configuration endpoint.

This configuration can be enabled for facilties of type STORE while facilities of type WAREHOUSE do not have visiting offline consumers and thus do not need this configuration. Once the reserved offline stock is set, it is taken into consideration for every new order that is supplied to the platform – existing pickjobs that were routed beforehand are not reprocessed.

NOTE
In the first iteration stage the retained offline stock needs to be set manually by defining a relative quantity ( % ) for offline stock with a decimal value between 0 and 100. The calculated offline stock is rounded off. Since the level for offline stock could vary for each facility (e.g. facilities in pedestrian zones should have a higher offline stock level than a facility outside the city) it is configurable by facility.

Listings

A listing describes the general availability of an article in Facilities or, to be more precise, within one facility. It also contains facility-dependend information like the article image, title and its price. This information is essential for the Distributing Order Management System as an order should be ideally routed to a facility which has all requested articles in stock.

A tenant can also be configured to have infiniteStockEnabled which means that when distributing an order only the state of a listing and not the actual amount of stock is considered. if a short pick is detected and the infiniteStockEnabled toggle is active the short picked article’s listing is deactivated. By default every deactivated listing is reactivated again after 24 hours. This behaviour can be configured on a facility base by setting the configuration listingReactivationAfter (in hours) using the Get Facility Stock Configuration endpoint.

Create/Update a listing

curl --request PUT 'https://{host}}/api/facilities/x5jrZrDHvYYs6HpaDICKYG4QuIk2/listings' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Valid idToken>' \
--data-raw '{
    "listings": [
        {
            "title": "SUPERSTAR SCHUH",
            "subtitle": "42-43",
            "tenantArticleId": "116d0qb-a12",
            "imageUrl": "https://yourshopshost.com/images/pictureUrlWithThePossibilityOfAutoscaleIfPossible.jpg",
            "price": 1000,
            "stockinformation":
             {
                    "stock": 8,
                    "reserved": 1
             }
        },
        {
            "title": "SUPERSTAR SCHUH",
            "subtitle": "42-43",
            "tenantArticleId": "116d0qb-a13",
            "imageUrl": "https://yourshopshost.com/images/pictureUrlWithThePossibilityOfAutoscaleIfPossible.jpg",
            "price": 2000,
            "stockinformation":
             {
                    "stock": 9,
                    "reserved": 2
             }
        },
        {
            "title": "SUPERSTAR SCHUH",
            "subtitle": "42-43",
            "tenantArticleId": "116d0qb-a14",
            "imageUrl": "https://yourshopshost.com/images/pictureUrlWithThePossibilityOfAutoscaleIfPossible.jpg",
            "price": 3000,
            "stockinformation":
             {
                    "stock": 12,
                    "reserved": 3
             }
        }
    ]
}'

Response:
200 OK

Introducing Picking Sequence

It is possible to sort the pick line items and show them in an order in the app. This enhances the picking speed of the picker since the items are presented to them in the correct running order.

This can be done by providing a PickingSequence attribute of a listing. If more than one pick line item has the same PickingSequencetenantArticleId will be used for the sorting.

Note: Listing’s PickingSequence attribute will override Order’s PickingSequence attribute.

Stock

An even further refined way to distribute an order is to consider the actual available stock of an article within a facility, namely a listing. This is why listings have attributes that in turn have impact on the distribution of orders and transforming them to pickjobs. Currently the following attributes are available:

  • Stock: This value depicts the amount of articles that is currently on stock. It is an absolute number and ist not taking reserved or offline retained stock into account.
  • Reserved: Reserved stock is the amount of articles, that is already planned to be used in OPEN Pick Line Items of Pickjobs. Whenever a new order arrives in the system the reserved value for the listing of the corresponding facility and article is being raised, whenever a Pickjob is finished the amount is substracted again.
  • Retained Offline Stock: See Stock Configuration
{
    "listings": [
        {
            "tenantArticleId": "2020249",
            "price": 19.95,
            "stockinformation": {
                "stock": 50,
                "reserved": 10
            },
            "title": "T-Shirt",
            "imageUrl": "https://example.com/image.jpg",
            "attributes": [
                {
                "category": "pickingSequence", // Category must be set to `PickingSequence`
                "priority": 1, // This property will be taken into account if there are multiple `PickingSequence` attributes defined for one listing, otherwise you can leave it
                "key": "pickingSequence",
                "value": "1" // This is the value that sorts the article.  The sorting is being done in an ascending way, so the value 1 will be on top of others.
                }
            ]
        }
    ]
}

Orders

The distributed order management system (DOMS) is a backend component that orchestrates incoming orders from your eCommerce shop and decides based on routing rules where and how the order can be fulfilled best to meet your consumers needs.

An order is a collection of the orderlines and some extra information, which should be fulfilled. It is also a typical entry point to the operational processes within the fulfillmenttools platform: An order is created after the consumer went through the checkout in the online shop. The following paragraph tells about the abilities and features that are enclosed within this entity.

Default behaviour

An order is typically mapped to one or more Pickjobs and dispatched to Facilities for fulfillment after it has been supplied to the platform. There are Routing rules present in the system, that could be configured in order to meet certain expectations towards for example stock or geolocation based distribution. These rules apply by default to every new order, that is entering the system. This default behaviour can be bypassed by certain information in the order message,

namely the deliverypreferences field. If you provide an order not having this field the following default will be added:

{
    ...
    "deliverypreferences" : [
        "shipping": {
             "servicetype": "BEST_EFFORT"
            }
    ]
    ...
}

The order will be shipped by a carrier using a best effort servicetype.

Providing preferred delivery types per order

With fulfillmenttools platform you can currently provide a certain set of parameters that are used within the process after the picking. The order can carry the following information about the desired delivery approaches: * the picked goods are handed over in person at the store where the pickjob was fulfilled (Click & Collect) * the picked items are packed and sent via a parcel carrier to the consumer (Deliverydefault)

"deliveryPreferences": {
        "collect": [
            {
             "facilityRef": "28MB9nc4njINUx9rDrCU"  // The facility which the consumer has chosen to hand over the order
            }
        ]
}

Introducing Picking Sequence

It is possible to sort the pick line items and show them in an order in the app. This enhances the picking speed of the picker since the items are presented to them in the correct running order.

This can be done by providing a PickingSequence attribute of an pick line item. If more than one pick line item has the same PickingSequencetenantArticleId will be used for the sorting.

Note: Listing’s PickingSequence attribute will override Order’s PickingSequence attribute.

{
    "article": {
        "tenantArticleId": "your-tenantArticleId",
        "title": "title",
        "imageUrl": "imageUrl",
        "attributes": [
            {
                "category": "pickingSequence", // Category must be set to `PickingSequence`
                "priority": 1, // This property will be taken into account if there are multiple `PickingSequence` attributes defined for one article, otherwise you can leave it
                "key": "pickingSequence",
                "value": "1" // This is the value that sorts the article.  The sorting is being done in an ascending way, so the value 1 will be on top of others.
            }
        ]
    },
    "quantity": 2,
    "scannableCodes": [
        "2020249"
    ]
}

Pickjobs

PickJobs are collections of Pick Line Items wrapped in a meta data envelope. It is one of the central resources in the API and is used in a process to collect goods that were ordered by the consumer before packing and shipping them.

Modifying a PickJob

The following code snippet shows how to pick an existing pickjob using HTTP PATCH Operations.

Let’s imagine we have the following pickjob present:

curl --location --request GET 'https://{host}/api/pickjobs/91e343cb-08aa-4367-8806-a2c0d732fb92-0' \
--header 'Authorization: Bearer <secret>'

Response:
{
    "id": "91e343cb-08aa-4367-8806-a2c0d732fb92-0"
    "status": "OPEN",
    "version": 1,
    "pickLineItems": [
        {
            "status": "OPEN",
            "quantity": 9,
            "article": {
                "tenantArticleId": "116d0qb-a12",
                "imageUrl": "https://cdn.pixabay.com/photo/2014/11/30/14/11/cat-551554_960_720.jpg",
                "title": "SUPERSTAR SCHUH",
                "attributes": []
            },
            "scannableCodes": [
                "0815"
            ],
            "id": "f7c1d31c-f6d4-4418-8228-c9e88d8bc37b",
            "picked": 0
        },
        {
            "quantity": 6,
            "picked": 0,
            "status": "OPEN",
            "id": "7bfc1500-adcf-482b-a279-8ca9bc5229f2",
            "article": {
                "imageUrl": "https://cdn.pixabay.com/photo/2014/04/13/20/49/cat-323262_960_720.jpg",
                "title": "Birkenstock Classic",
                "tenantArticleId": "116d0qb-a13",
                "attributes": []
            },
            "scannableCodes": [
                "0815-1"
            ]
        },
        {
            "quantity": 3,
            "picked": 0,
            "scannableCodes": [
                "0815-2"
            ],
            "id": "c859373c-b055-459a-bf90-6126dd4ca48b",
            "status": "OPEN",
            "article": {
                "attributes": [],
                "imageUrl": "https://cdn.pixabay.com/photo/2016/03/28/12/35/cat-1285634_960_720.png",
                "tenantArticleId": "116d0qb-a14",
                "title": "FILA Halbschuh"
            }
        }
    ],
    ...
}

For this pickjob we want to

  • Set the pickjob IN_PROGRESS (this is an indicator for other clients that somebody is already picking this pickjob)
  • Perfect Pick all the lines. That means we pick exactly the requested amounts for each line.
  • Set the pickjob DONE which finalizes the picking process

We split the above steps into two calls: The first sets the pickjob IN_PROGRESS, the second picks all the lines and closes the pickjob:

curl --location --request PATCH 'https://{host}/api/pickjobs/91e343cb-08aa-4367-8806-a2c0d732fb92-0' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <secret>' \
--data-raw '{
      "version": 1,
      "actions": [
        {
          "action": "ModifyPickJob",
          "status": "IN_PROGRESS"
        }
      ]'


Response:
200 OK
{
    "status": "IN_PROGRESS",
    "version": 2,
    "facilityRef": "vipiMKWlogORdL4QOLXG",
    "id": "91e343cb-08aa-4367-8806-a2c0d732fb92-0",
    "pickLineItems": [
        {
            "status": "OPEN",
            "picked": 0,
            "quantity": 9,
            "id": "f7c1d31c-f6d4-4418-8228-c9e88d8bc37b",
            ...
        },
        {
            "picked": 0,
            "quantity": 6,
            "status": "OPEN",
            "id": "7bfc1500-adcf-482b-a279-8ca9bc5229f2",
            ...
        },
        {
            "id": "c859373c-b055-459a-bf90-6126dd4ca48b",
            "picked": 0,
            "status": "OPEN",
            "quantity": 3,
            ...
        }
    ],
    ...
}

Please note the increment in the pickjob version and its changed state. Please also note that the picklines themselves are still in state OPEN.

The second PATCH call to close the Pickjob and perfect pick all the lines looks similar to the following:

curl --location --request PATCH 'https://{host}/api/pickjobs/91e343cb-08aa-4367-8806-a2c0d732fb92-0' \
--header 'Authorization: Bearer <secret>' \
--header 'Content-Type: application/json' \
--data-raw '{
      "version": 2,
      "actions": [
        {
            "action": "ModifyPickJob",
            "status": "CLOSED"
        },
        {
            "id": "f7c1d31c-f6d4-4418-8228-c9e88d8bc37b",
            "action": "ModifyPickLineItem",
            "status": "CLOSED",
            "picked": 9
        },
        {
            "id": "7bfc1500-adcf-482b-a279-8ca9bc5229f2",
            "action": "ModifyPickLineItem",
            "status": "CLOSED",
            "picked": 6
        },
        {
            "id": "c859373c-b055-459a-bf90-6126dd4ca48b",
            "action": "ModifyPickLineItem",
            "status": "CLOSED",
            "picked": 3
        }
      ]
    }'

Response:
200 OK
{
    "id": "91e343cb-08aa-4367-8806-a2c0d732fb92-0",
    "status": "CLOSED",
    "pickLineItems": [
        {
            "quantity": 9,
            "picked": 9,
            "status": "CLOSED",
            "id": "f7c1d31c-f6d4-4418-8228-c9e88d8bc37b"
            ...
        },
        {
            "quantity": 6,
            "id": "7bfc1500-adcf-482b-a279-8ca9bc5229f2",
            "status": "CLOSED",
            "picked": 6,
            ...
        },
        {
            "picked": 3,
            "id": "c859373c-b055-459a-bf90-6126dd4ca48b",
            "quantity": 3,
            "status": "CLOSED"
            ...
        }
    ],
    "version": 3
    ...
}

The last call concludes the example on how to perform changes on pickjobs. Details about which fields are patchable and some other useful information can be found in the OpenAPI reference under the models of ModifyPickJobAction and ModifyPickLineItemAction respectively.

Information about delivery

Complete set of delivery information is conveyed from order to pick job, so, type of order (Click & Collect or default Delivery) could be fetched from deliveryinformation field in pick job.

    "deliveryinformation": {
        "channel": "COLLECT",
        "details": {
            "collect": {
                 "identifier": "Stefan Bauer"  // Identity of the consumer
            },
            "shipping": {
              // Contains address of the consumer
            }
        }
    }

By Default channel field of the delivery information is set to „SHIPPING“ but in Click & Collect option, it is „COLLECT“.

Shipments

Shipments are being created in order to place a request for the advise of possibly multiple Parcels via a carrier. When creating a shipment the fulfillmenttools platform takes care of creating the amount of packages the shipment requires, advising those against the selected carrier and serves as a resource that bundles the information of the corresponding packages.

Provided information to a Shipment

When creating a shipment you have to provide a reference to an existing pickjob for a facility the user is also assigned to (see Roles and permissions).

Parcels

Parcels are (in contrast to Shipments) the representation of physical packages that are handed over to carriers. A parcel resource has its own lifecycle, which is decoupled from the lifecycle of the (possibly) connected shipment.

After a successful advise to a carrier, which will be done asynchronously by the platform, it will bear the information about status, tracking number and the link to the downloadable parcel label that has to be attached to the package

Handoverjobs

A Handoverjob is the entity that supports the process around the handover of picked goods towards

  • a carrier (in case of deliveries / ship from store)

Handoverjobs must be distinguished from Parcels as the latter are the physical packages, that are sent, receive tracking information and are delivered to the consumer in the end. Handoverjobs on the other side are entities, that aid in the handover process of the physical picking result to the next process step, namely the pickup by a carrier.

Lifecycle of Handoverjobs

For now a handoverjob is automatically created whenever a label of a parcel is successfully being created. The current state transitions are

State Description

OPEN

The parcel label was successfully created (carrier acknowledged the transportation of the connected parcel) & the parcel should be ready to be picked up by the carrier.

HANDED_OVER

The parcel was handed over to the carrier / put to the place where the carrier will find it for the actual pickup at a later point in time. The connected parcel will receive further tracking status updates.

Eventing

One can subscribe to events to receive updates on business events.

List of supported events

Event Payload

ORDER_CREATED

The created order.

FACILITY_CREATED

The created facility.

PICK_JOB_CREATED

The created pickjob.

PICK_JOB_PICKING_COMMENCED

A pickjob that is currently picked.

PICK_JOB_PICKING_PAUSED

A paused pickjob.

PICK_JOB_PICKING_FINISHED

A finished pickjob.

PICK_JOB_PICK_LINE_PICKED

A picked line of a pickjob.

PARCEL_CARRIER_ACKNOWLEDGED

A parcel that has been confirmed by the cap provider.

HANDOVERJOB_CREATED

The created handoverjob.

HANDOVERJOB_HANDED_OVER

A handover job that just has been handed over.

HANDOVERJOB_REVERTED

This event is published, when a handover job is set back to OPEN due to an error.

Processes

A Process is the entity that serves as a container around the tenant specific fulfillment process.

The process entity could be used to solve at least two challenges:

  • Anonymization of entities: GDPR
  • Provide a “signpost” to give a good answer to the question “Where do we stand in the Fulfillment process over routing, picking …​” by doing a single call.

A process entity is created by the creation of an order, pickjob, shipment and handover. A process is retrievable via REST- and GraphQL-API. It cannot be created via API as it is created indirectly by the creation of an entity that is part of the fulfillment process of the tenant.

A process has a property called gdprCleanupDate. When this date is reached, all entities belonging to this Process will be anonymized.

References

Please use the file api.swagger.yaml within this project to get a comprehensive overview about the endpoints and the way to interact with them.

Also you can use the SwaggerUI to better understand the capabilities of the API. It can be found here: https://fulfillmenttools.github.io/api-reference-ui/

Endpoints / Resources

Please refer to api.swagger.yaml to learn about the provided endpoints and their methods.

Used models

Please refer to api.swagger.yaml to learn about the used models.