Order Search

The Order Search feature is intended for merchants to perform search requests on a high number of Orders in a Project. It is not intended for searching through the customer's order history in a storefront application.

The Order Search API does not return the resource data of the matching Orders. Instead, it returns a list of Order IDs, which can then be used to fetch the Orders by their ID.

Since the response also contains the version of the matching Order, it allows you to:

  • keep a cache of Orders
  • compare version numbers to detect outdated Orders
  • detect if the search index for an Order is outdated and react on that (for example by showing a warning, or trigger a rebuild of the search index).

Activation of the feature The Order Search feature is not active for the Project by default. Before first use, indexing has to be activated either

Once the Project setting has been changed, the indexing of all Orders existing in the Project will start and the Search Orders endpoint will become fully functional soon after.

Automatic deactivation The Order Search index for a Project will be deactivated automatically if there have been no calls against the Search Orders endpoint within the last 30 days. Indexing can be reactivated again with one of the options listed above.

Representations

OrderPagedSearchResponse

total
Int

Total number of results matching the query.

offset
Int

Number of elements skipped.

limit
Int

Number of results requested.

Maximum: 100
hits
Array of Hit

Actual results.

Hit

id
String

Unique identifier of the Order.

version
Int

Current version of the Order.

relevance
Float

The higher the value is, the more relevant the hit is for the search request.

Maximum: 1

Search Orders

In case this endpoint responds with Error 404 "no index found for project" the feature has been deactivated due to inactivity and needs to be reactivated again before API requests will succeed.
POST
https://api.{region}.commercetools.com/{projectKey}/orders/search
OAuth 2.0 Scopes:
view_orders:{projectKey}
Path parameters:
region
String

Region in which the Project is hosted.

projectKey
String

key of the Project.

Request Body:
application/json
query

The Order search query.

sort
Array of OrderSearchSorting

Controls how results to your query are sorted. If not provided, the results are sorted by relevance in descending order.

limit
Int

The maximum number of search results to be returned.

Default: 10Maximum: 100
offset
Int

The number of search results to be skipped in the response for pagination.

Default: 0Maximum: 10 000
Response:
200OrderPagedSearchResponseasapplication/json
200 Response Example: OrderPagedSearchResponsejson
{
"total": 3,
"offset": 0,
"limit": 50,
"hits": [
{
"id": "c2d430e8-1ccf-44c8-8976-36763b572f3e",
"version": 12,
"relevance": 0.9871
},
{
"id": "ee8e3533-00d2-43c2-a2f4-57b1eee0f5b4",
"version": 2,
"relevance": 0.735654
},
{
"id": "9a9e129c-2ed1-436d-90d9-af60d83490ed",
"version": 44,
"relevance": 0.42415
}
]
}

Query section

This section describes the query language to be used on the Search Orders endpoint as well as for the Advanced Order Search in the Merchant Center.

A query consists of:

  • A compound expression wrapper, like and, or, not, filter
  • A query expression, like exact, fullText. You can use more than one query expression per query.
  • A set of query fields. The field and value fields are required, but there are other optional fields.

Example query:

{
"query": {
"and": [
{
"fullText": {
"field": "customLineItems.name",
"language": "en",
"value": "banana"
}
},
{
"filter": [
{
"exact": {
"field": "store.name",
"language": "en",
"value": "fruit_store"
}
}
]
}
]
},
"sort": [
{
"field": "customLineItems.name",
"language": "en",
"order": "desc"
}
],
"limit": 50,
"offset": 0
}

This query is searching for all orders which have banana in their lineItems.name and are in the fruit_store. This can be seen as:

(lineItems.name.en=banana AND (filter (store.name=fruit_store)))

The result will be all documents where lineItems.name.en contains 'banana' and the store name is 'fruit_store', sorted descending (desc) by lineItems.name.en. The result will be limited to the first 50 hits.

Compound expressions

The outermost layer of the query is a compound expression. This expression specifies how the composition of the sub-expressions is evaluated. Sub-expressions can be query expressions or compound expressions.

Following compound expressions are currently supported:

ExpressionBehavior
andonly matches documents that match all sub-expressions.
oronly matches documents where at least one of the sub-expressions is matched.
notonly matches documents that do not match any of its sub-expressions.
filterMatching documents of a query are checked for their relevancy to the search. The relevancy is expressed by an internal score. All expressions except filter expressions contribute to that score. All sub-expressions of a filter are implicitly connected with an and expression.

A compound expression can only be used one time on the same level of composition.
For example, this composition of two and expressions is not supported: Z = (A and B and C),
using only one and expression, like: X = (A and B), is allowed.
Using a different compound expression is allowed too, like for: Y = (A and B or C).
Z can still be specified by (X and C) since X is a compound expression on a different level then.

Query expressions

A query expression contains:

  • a field
  • a value
  • for localized texts: a language field. The language value is an IETF language tag.

The index is calculated with languages defined in the Project. It is not possible to find documents with a non-defined language.

Query fields

The field can have any of the searchable Order fields, like orderNumber, createdAt, country including fields of nested objects like LineItems (lineItems.productId) or ShippingMethods (shippingAddress.firstName).

Example of a query finding orders with a specific price:

{
"query": {
"and": [
{
"exact": {
"field": "totalPrice.currencyCode",
"value": "EUR"
}
},
{
"exact": {
"field": "totalPrice.centAmount",
"value": 2222
}
}
]
}
}

Searchable Order fields

These are the Order fields that are currently supported by the Order Search feature. Next to each field you will find a type that determines what query expression type can be used on that field:

  • all - textField
  • billingAddress.city - textField
  • billingAddress.company - textField
  • billingAddress.country - textField
  • billingAddress.firstName - textField
  • billingAddress.lastName - textField
  • billingAddress.mobile - phoneField
  • billingAddress.phone - phoneField
  • billingAddress.postalCode - textField
  • completedAt - dateTimeField
  • country - textField
  • createdAt - dateTimeField
  • createdBy.anonymousId - keywordField
  • createdBy.clientId - textField
  • createdBy.externalUserId - keywordField
  • custom.<field-name>
  • customLineItems.name - localizedTextField
  • customLineItems.state.quantity - longField
  • customLineItems.state.state.key - keywordField
  • customLineItems.state.state.name - localizedTextField
  • customerEmail - textField
  • customerGroup.id - keywordField
  • customerGroup.key - textField
  • customerGroup.name - textField
  • id - keywordField
  • itemShippingAddresses.city - textField
  • itemShippingAddresses.company - textField
  • itemShippingAddresses.country - textField
  • itemShippingAddresses.firstName - textField
  • itemShippingAddresses.lastName - textField
  • itemShippingAddresses.mobile - phoneField
  • itemShippingAddresses.phone - phoneField
  • itemShippingAddresses.postalCode - textField
  • lastModifiedAt - dateTimeField
  • lastModifiedBy.anonymousId - keywordField
  • lastModifiedBy.clientId - textField
  • lastModifiedBy.externalUserId - keywordField
  • lineItems.name - localizedTextField
  • lineItems.productId - textField
  • lineItems.state.quantity - longField
  • lineItems.state.state.key - keywordField
  • lineItems.state.state.name - localizedTextField
  • lineItems.variant.sku - textField
  • orderNumber - keywordField
  • orderState - textField
  • origin - keywordField
  • paymentInfo.payments.interfaceId - keywordField
  • paymentInfo.payments.paymentMethodInfo.method - textField
  • paymentInfo.payments.transactions.id - keywordField
  • paymentInfo.payments.transactions.interactionId - keywordField
  • paymentState - textField
  • returnInfo.returnDate - dateTimeField
  • returnInfo.returnTrackingId - keywordField
  • shipmentState - textField
  • shippingAddress.city - textField
  • shippingAddress.company - textField
  • shippingAddress.country - textField
  • shippingAddress.firstName - textField
  • shippingAddress.lastName - textField
  • shippingAddress.mobile - phoneField
  • shippingAddress.phone - phoneField
  • shippingAddress.postalCode - textField
  • shippingInfo.deliveries.parcels.trackingData.trackingId - keywordField
  • shippingInfo.shippingMethodName - textField
  • state.key - keywordField
  • state.name - localizedTextField
  • store.key - keywordField
  • store.name - localizedTextField
  • totalPrice.centAmount - longField
  • totalPrice.currencyCode - keywordField
  • version - longField

Expanded fields

Following fields in the Order model are Reference Expansions that are not updated regularly. They are kept in the initial order state, even if the referred resource is changed. A manual re-indexing is required to get the most recent values of the following fields

  • state - key, name
  • lineItems.state.state - key, name, quantity
  • customLineItems.state.state - key, name
  • customerGroup - key, name
  • store- key, name
  • paymentInfo.payments
    • interfaceId,
    • paymentMethodInfo.method,
    • transactions.id,
    • transactions.interactionId

Custom Fields

It is possible to search in the order's custom fields. Therefore, the customType needs to be specified in the query. Currently, the following types are supported.

  • BooleanType
  • StringType
  • LocalizedStringType
  • EnumType
  • LocalizedEnumType
  • NumberType
  • DateType
  • TimeType
  • DateTimeType
  • SetType.StringType
  • SetType.LocalizedStringType
  • SetType.EnumType
  • SetType.LocalizedEnumType
  • SetType.NumberType
  • SetType.DateType
  • SetType.TimeType
  • SetType.DateTimeType
{
"query": {
"exact": {
"field": "custom.myOrderField",
"value": "special order",
"customType": "StringType"
}
}
}

If you want to search for a date in a custom field SetType with many dates, a query might look like this:

{
"query": {
"prefix": {
"field": "custom.myDateField",
"value": "2021-05-10",
"customType": "SetType.DateType"
}
}
}

The customTypes support the following query expressions, where SetType support the same expressions as the type inside the Set:

customTypefullTextexactprefixrangewildcardexists
NumberType
StringType, EnumType, DateType, TimeType, DateTimeType
LocalizedStringType, LocalizedEnumType
BooleanType

Query expression types

fullText

Performs a full text search of the field.

fullText queries have the option to pass mustMatch on how multiple terms should be combined. mustMatch can either be any or all (the default is all). If you search for 'yellow car' the search will find documents that contain both 'yellow' and 'car' by default. When you now pass any as mustMatch the search will find documents that contain either 'yellow' or 'car'.

Note that fullText search will only return Orders for which the terms match completely. If you need partial matches, the prefix search would be the better approach.

A fullText query performs a full-text search:

{
"query": {
"fullText": {
"field": "customLineItems.name",
"language": "en",
"value": "yellow car",
"mustMatch": "any"
}
}
}

exact

Searches for exact values only. If you provide a value of yellow car, a field with the best yellow car will not match. This means the value you provide for searching must exactly match the value in the order search.

If you want to have a match searching yellow car even if the value in the order is the best yellow car, a fullText search would be the right choice.

If you want to have a match search for yellow car in yellow cars, a prefix search would be the right choice.

exact queries have the option to treat the search term caseInsensitive.

An exact query only matches exact values:

{
"query": {
"exact": {
"field": "lineItems.variant.sku",
"value": "chiquita_yellow_123",
"caseInsensitive": true
}
}
}

prefix

Searches the field specified for ones that begin with the prefix specified.

prefix queries have the option to treat the search term caseInsensitive.

A prefix query only matches values that start with the given one:

{
"query": {
"prefix": {
"field": "customerEmail",
"value": "commerceto",
"caseInsensitive": true
}
}
}

Note that searching for yell ca in the value yellow car will not lead to a match, all terms need to form one prefix, you would need to search for yellow c to match yellow car.

range

A range query only matches values between the specified boundaries and is useful for restricting the search query to certain time frames or ranges of numerical values. The interval may also be open by omitting the upper or lower boundary value.

Example query for Orders last modified between two specific dates:

{
"query": {
"range": {
"field": "lastModifiedAt",
"gte": "2018-08-25T12:00:00.000Z",
"lte": "2018-08-26T12:00:00.000Z"
}
}
}

wildcard

A wildcard query matches documents that have fields matching the specified wildcard expression.

In wildcard expression, the following characters have a special meaning:

  • * for one or more characters
  • ? for exactly one character.

wildcard queries have the option to treat the search term caseInsensitive.

{
"query": {
"wildcard": {
"field": "customerEmail",
"value": "ab*@commercetools.com"
}
}
}
{
"query": {
"wildcard": {
"field": "customerEmail",
"value": "ab?@commercetools.com",
"caseInsensitive": true
}
}
}

It is even possible to use more than one wildcard in one term.

{
"query": {
"wildcard": {
"field": "customerEmail",
"value": "ab?@*.com",
"caseInsensitive": true
}
}
}

Like the prefix and exact, wildcard queries as well have the option to treat the search term caseInsensitive.

Wildcard expression are quite expensive to calculate. Prefer a prefix expression if this is suitable for your use-case.

exists

An exists query matches documents that have a field with a non-null value:

{
"query": {
"exists": {
"field": "customerEmail"
}
}
}

boost (optional)

When you provide more than one query expression to a query, the boost field makes the results that match one particular query more relevant than the results that match the others.

Example query: Documents that have butter in the lineItems.name field are scored as more relevant than those that have butter in their customLineItems.name field.

{
"query": {
"or": [
{
"fullText": {
"field": "lineItems.name",
"language": "en",
"value": "butter",
"boost": 2
}
},
{
"fullText": {
"field": "customLineItems.name",
"language": "en",
"value": "butter"
}
}
]
}
}

Types of fields

  • booleanField: for Boolean fields.
  • longField: for Number fields.
  • dateTimeField: for DateTime fields.
  • keywordField: for fields holding unique identifiers.
  • textField: for String fields.
  • localizedTextField: for LocalizedString fields.
  • phoneField: for phone number-formatted fields.

What query expression type can be used on what type of field?

The different query expression types (fullText, exact, prefix, range, wildcard and exists) can't all be used on all order fields, depending on the type of the order field.

TypesfullTextexactprefixrangewildcardexists
booleanField
longField
dateTimeField
keywordField
textField
localizedTextField
phoneField

Specifics for phoneField

When searching on a phoneField type of field all non numeric characters are ignored. For example, a query of type fullText for (783) 627-3740 will also match (783) 6273740. The same applies to prefix queries. Special characters are only taken into account when performing queries of type exact.

Sorting

Sorting allows you to control how results to your query are sorted. If no sorting is specified, the results are sorted by relevance in descending (desc) order.

Example query (sorts the results ascending by createdAt):

{
"sort": [
{
"field": "createdAt",
"order": "asc"
}
]
}

Sort mode

If you sort by a field in an array (like customLineItems.name) you can optionally pass a sort mode. This is relevant because a single order can have multiple lineItems and thus multiple name fields. That means that there might not be a single value to sort on, but multiple. Using the sort mode we can choose which of the values in the array to use for sorting or how to aggregate them. The default sorting mode is min

Following four sort modes are provided:

  • min - Use the minimum of all available values
  • max - Use the maximum of all available values
  • avg - Use the average of all available values
  • sum - Use the sum of all available values.

Example query (Using the min sort mode, will sort using the min customLineItems.name in descending order):

{
"sort": [
{
"field": "customLineItems.name",
"language": "en",
"order": "desc",
"mode": "min"
}
]
}

Sort filter

In case the above sort modes are not enough to specify exactly which document or aggregation you want to sort on, you can use a sort filter.

{
"sort": [
{
"field": "lineItems.name",
"language": "en",
"order": "asc",
"filter": {
"exact": {
"field": "lineItems.productId",
"value": "4054a159-7f3e-4fe9-a30c-8db80ca7d665"
}
}
}
]
}

Note that you can only filter on the same parent field you sort by. In this case on the root of the order.

Reindex Orders

If you realize the Order Search API does not contain the latest Orders, you can trigger a rebuild of the search index. This service is provided on a GraphQL endpoint by submitting a specific reindex mutation.

Reindex mutation

OAuth 2.0 Scopes: manage_project_settings:{projectKey}, view_orders:{projectKey}

mutation ReindexAllOrders {
reIndexAllOrders {
indexingJobId
existingIndexingJobId
}
}

This mutation returns following indexing job IDs:

  • indexingJobId is the ID of a new indexing job.
  • existingIndexingJobId is the ID of an already running job making progress.

Check indexing progress

For each indexing job ID you can check the status of the respective jobs like so:

OAuth 2.0 Scopes: manage_project_settings:{projectKey}, view_orders:{projectKey}

query GetReindexingStatus ($jobId: String!) {
getReindexingStatus(id: $jobId) {
nbrOfIndexedDocuments
nbrOfFailedDocuments
totalNbrOfDocuments
percentCompleted
completed
}
}

Stop indexing jobs

You can stop the indexing of all Orders with following mutation:

OAuth 2.0 Scopes: manage_project_settings:{projectKey}

mutation StopIndexingOrders {
stopOrdersIndexing {
status
}
}

The response contains the status of the indexing job.

GraphQL endpoint

You can access the GraphQL endpoint with following URL:

https://api.{region}.commercetools.com/{projectKey}/orders/indexer/graphql

The endpoint accepts HTTP POST requests with following fields in a JSON body:

  • query - String - GraphQL query as a string
  • variables - Object - Optional - containing JSON object that defines variables for your query
  • operationName - String - Optional - the name of the operation, in case you defined several of them in the query

GraphiQL

To explore the GraphQL endpoint, you can use an interactive GraphiQL environment with a web browser, accessible through the following URL:

https://api.{region}.commercetools.com/{projectKey}/orders/indexer/graphiql?token={access_token}

Check search index

To check whether a search index exists for the Project's Orders the following endpoint can be used.

Endpoint: /{projectKey}/orders/search
Method: HEAD
OAuth 2.0 Scopes: view_project_settings:{projectKey}
Response status codes:

  • 200 - the index exists and the Search Orders endpoint can be used.
  • 404 - the index does not exist and the Search Orders endpoint returns Error 404 only.

On the GraphQL endpoint you can use following query:

query FetchIndicesExists {
ordersIndicesExist {
searchableIndexExists
newInProgress
}
}

The query response contains the following fields indicating the status of the search index:

  • searchableIndexExists - the index exists and Orders can be searched.
  • newInProgress - the index is being created by a job. Use the reIndexAllOrders mutation to get the job ID.

Find below an example for a FetchIndicesExists query to be executed as cURL command:

$ curl -X POST https://api.{region}.commercetools.com/{projectKey}/orders/indexer/graphql \
-H "Content-Type:application/json" \
-H "Authorization:Bearer ..." \
-d '{"query": "{ ordersIndicesExist { searchableIndexExists } }" }'