Skip to content

Developing with ECS Material

This material is for the Developing with ECS course. Please ensure you have attended the Using ECS course and/or read the ECS Concept Article and the Developer Overview Article.

SDK

ECS offers two types of packages: NPM (TypeScript) and NuGet (.NET). The NuGet packages are automated Swagger clients that include a factory client for easy authentication. The NPM packages contain a TypeScript client for the Search Host API.

C#: Getting started

You can easily start with the NuGet packages by using the Getting-Started Console Application. It contains example code on how to create clients.

Try It Yourself

Ensure you have a working C# application.

TypeScript: Getting started

To begin using the TypeScript client, you need the NPM package @bizzkit/ecommerce-search - see more about the package here. To try out the TypeScript client, you can set up a simple Vite project.

  • Add .npmrc to root
1
2
3
@bizzkit:registry=https://pkgs.dev.azure.com/bizzkit-platform/7dad82b4-f2ae-4a3a-ab87-3fc791e4ea62/_packaging/bizzkit-partner-feed/npm/registry/

always-auth=true
  • Add package.json to root and packages
1
2
3
4
npm init -f
npm i typescript --save
npm i @bizzkit/ecommerce-search --save
npm i vite --save
  • add vite.config.js to root
1
2
3
4
5
6
7
8
9
export default {
    build: {
        target: 'esnext',    
        base: "."
    },
    server:{
        origin: "/"
    }
};
  • add tsconfig.json to root
1
2
3
4
5
6
7
8
9
{
    "compilerOptions": {
        "target": "ESNext",
        "strictNullChecks": false,
        "module": "ESNext",
        "moduleResolution": "Node",
    },
    "include": ["src"]
}
  • add index.ts to root/src
import { UnifiedSearchRequestModel } from '@bizzkit/ecommerce-search/dist/client';
import { Client } from "@bizzkit/ecommerce-search";

const baseUrl = "https://localhost:8021";
const request : UnifiedSearchRequestModel  = {
    segmentId: "merch-b2c-en",
    scopeId: "browse",
    phrase: "hat"
};

const searchClient = new Client({ baseUrl: baseUrl });
const response = await searchClient.search.search(request);
console.log(response.data);        
  • add index.html to root
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Demo</title>
</head>
<body>
    <script type="module" src="/src/index.ts"></script> 
</body>
</html>
  • Run and check browser console
npx vite   

Try It Yourself

Ensure you have a working TS/JS application.

Academy playground

We have a created a simple playground for the SearchHost API - contact your instructor for access.

Academy Playground

Swagger

You can use the Swagger UI on all APIs to test endpoints.

APIs

As outlined in the Developer Overview Article, ECS offers three APIs.

Admin API

The Admin API manages various aspects of Ecommerce Search. It handles everything from curating dictionaries and parameters to managing products and SKUs. This API equips you with the necessary tools to configure and maintain your search system, ensuring it meets your specific requirements.

Info

Note that the APIs are available in preview versions, which may include endpoints that do not adhere to Bizzkit's release policy and might change between versions.

Most endpoints, such as those related to contexts, dictionaries, facets, parameters, etc., are self-explanatory. Please refer to the Swagger and ECS Documentation.

Try It Yourself

Experiment with some of the endpoints using Swagger or C#. - Get all sections - Add a synonym - Etc.

However, some endpoints require additional information:

Ingestion

To search for a product in ECS, you need to add data, keeping in mind that:

  • A searchable product must contain at least one SKU.
  • Products comprise various fields - please review Products.
  • A SKU is a specific variant of a product and inherits fields from its product/master. Refer to SKUs.
  • Products and SKUs can be ingested through:
    • /api/ingestion/segments/segmentId/products/bulk
    • /api/ingestion/segments/segmentId/skus/bulk

Here are examples to understand the data that can be ingested into Search. If you'd like to try them, start by creating a new segment and then use Swagger. Trigger all jobs in the job runner to ensure Search is ready to receive data.

Example of 'real' products/SKUs

Demo data for 'real' products

This a simple example of a "real" product for test/debug, and the summary table (some columns missing):

ProductId Name AlternativeIds AlternativeSearchWords ShortDescription Categories
bizzkit-beanie-gray-v2 Bizzkit Beanie Gray bizzkit-beanie-gray, 123, abc WinterBeanie, Comfort, Unisex, Cozy, Elegant Nice beanie for when the... Clothing, Outerwear, Hats

Demo data for 'real' SKUs

This is a simple example of "real" SKUs for test/debug, and the summary table (some columns missing):

SkuId Name ProductId StringAttribute NumberAttributes IntervalAttributes Prices
bizzkit-beanie-gray-l-v2 Bizzkit Beanie Gray bizzkit-beanie-gray-v2
Color:
Gray
Size:
L
Rating:
4
TemperatureRangeCelcius:
Min:
-10
Max:
5
default:
Price: 18
List:16
From:00010101
default:
Price: 17
List:16
From:20240101
BlackFriday:
Price: 15
List:10
From:00010101
bizzkit-beanie-gray-m-v2 Bizzkit Beanie Gray bizzkit-beanie-gray-v2
Color:
Gray
Size:
M
Rating:
5
TemperatureRangeCelcius:
Min:
-10
Max:
5
default:
Price: 18
List:16
From:00010101
default:
Price: 17
List:16
From:20240101
BlackFriday:
Price: 16
List:10
From:00010101
Example of 'mock' products/SKUs

Demo data for 'mock' products

This is a simple example of a "mock" products for test/debug (use the /api/ingestion/segments/{segmentId}/products/bulk endpoint for this purpose), and the summary table (some columns missing):

ProductId Name AlternativeIds AlternativeSearchWords ShortDescription Categories
product-1 Product 1 product-1-extra, 123 product-1-alternative-1, product-1-alternative-2 Lorem ipsum dolor sit amet,... Category 0, Category 1, Category 2
product-2 Product 2 product-2-extra, 456 product-2-alternative-1, product-2-alternative-2 Etiam porta sem malesuada magna... Category 0, Category 1, Category 3
product-3 Product 3 product-3-extra, 789 product-3-alternative-1, product-3-alternative-2 Curabitur blandit tempus porttitor consectetur... Category 0, Category 4

Demo data for 'mock' SKUs

This is a simple example of a "mock" SKUs for test/debug (use the /api/ingestion/segments/{segmentId}/skus/bulk endpoint for this purpose), and the summary table (some columns missing):

SkuId Name ProductId StringAttribute NumberAttributes IntervalAttributes Prices
product-1-sku-1 SKU 1 for Product 1 product-1
StringAttr1:
aliqua
StringAttr2:
incididunt
SecretAttr1:
tempor
NumberAttr1:
1, 2
IntervalAttr1:
Min:
0
Max:
15
default:
Price: 18
List:16
From:00010101
default:
Price: 14
List:16
From:20240301
PriceGroup1:
Price: 1
List:1
From:00010101
PriceGroup2:
Price: 2
List:2
From:00010101
product-1-sku-2 SKU 2 for Product 1 product-1
StringAttr1:
nostrud
StringAttr2:
dolor
SecretAttr1:
magna
NumberAttr1:
3, 4
IntervalAttr1:
Min:
10
Max:
20
default:
Price: 20
List:18
From:00010101
PriceGroup1:
Price: 1
List:1
From:00010101
PriceGroup2:
Price: 2
List:2
From:00010101
product-2-sku-1 SKU 1 for Product 2 product-2
StringAttr1:
dolore
StringAttr2:
adipiscing
SecretAttr1:
veniam
NumberAttr1:
5, 6
IntervalAttr1:
Min:
20
Max:
30
default:
Price: 22
List:20
From:00010101
default:
Price: 21
List:20
From:20240301
PriceGroup1:
Price: 1
List:1
From:00010101
PriceGroup2:
Price: 2
List:2
From:00010101
product-2-sku-2 SKU 2 for Product 2 product-2
StringAttr1:
minim
StringAttr2:
labore
SecretAttr1:
consectetur
NumberAttr1:
7, 8
IntervalAttr1:
Min:
30
Max:
40
default:
Price: 24
List:22
From:00010101
default:
Price: 23
List:22
From:20240301
PriceGroup1:
Price: 1
List:1
From:00010101
PriceGroup2:
Price: 2
List:2
From:00010101
product-2-sku-3 SKU 3 for Product 2 product-2
StringAttr1:
consequat
StringAttr2:
ipsum
SecretAttr1:
commodo
NumberAttr1:
9, 10
IntervalAttr1:
Min:
40
Max:
50
default:
Price: 26
List:24
From:00010101
default:
Price: 24
List:22
From:20240202
PriceGroup1:
Price: 1
List:1
From:00010101
PriceGroup2:
Price: 2
List:2
From:00010101
product-3-sku-1 SKU 1 for Product 3 product-3
StringAttr1:
exercitation
StringAttr2:
ullamco
SecretAttr1:
eiusmod
NumberAttr1:
11, 12
IntervalAttr1:
Min:
50
Max:
60
default:
Price: 28
List:26
From:00010101
default:
Price: 20
List:20
From:20240301
PriceGroup1:
Price: 1
List:1
From:00010101
PriceGroup2:
Price: 2
List:2
From:00010101
Example of 'mock' parameters

Demo data for 'mock' parameters

This the 'mock' parameters used. There is not a bulk endpoint so you have to go one parameter a time. You can use the /api/ingestion/segments/{segmentId}/parameters/bulk endpoint for this purpose.

Data shown as a table:

ParameterId Name ParameterType
cparameter1 cparameter1 Rank
cparameter2 cparameter2 Rank
Example of 'mock' user affinities

Demo data for 'mock' user affinities

If you would like to experiment with user affinities, here is some template data. You can use the /api/ingestion/segments/{segmentId}/user-affinities/bulk endpoint for this purpose. Please note that this endpoint might only be available in the preview API.

Data shown as a table:

UserId Affinities
User1
caffinity1:
0,75
caffinity2:
0,10
User2
caffinity1:
0,25
caffinity2:
0,50
User3
caffinity1:
0,10
caffinity2:
0,90

Try It Yourself

  • Create a new segment (perhaps with a naming convention like {initials}_mysegment).
  • Inject the "mock" products and SKUs.
  • Run the job runners (simply select and trigger all jobs).
  • Open the UI and view contexts, fields, etc.

Search Host

Searches are conducted through the Search Host API, and it's recommended that all search requests are directed at the UnifiedSearch endpoint.

Request model

Below is a summary table of the Request Model used for searching:

Name Type Description
segmentId string The segment to search in. An id must have a maximal length of 255 characters, can include only digits, English lowercase and uppercase letters and these characters: -_
scopeId string A scope defines a configuration that is applied to the search request. Scopes define the fields that can be returned and the fields that can be filtered on. An id must have a maximal length of 255 characters, can include only digits, English lowercase and uppercase letters and these characters: -_
filters object Field name and filter models
authenticationToken string JWT Token for authenticated search.
phrase string The phrase to search for
offsetProducts integer The number of products to skip.
numberOfProducts integer Indicates the number of records to be fetched. For example, if set to 3, then up to 3 products are returned. Default value is defined on the scope.
offsetContent integer The number of content to skip.
numberOfContent integer Indicates the number of records to be fetched. For example, if set to 3, then up to 3 content are returned. Default value is defined on the scope.
sort array Used to specify the ordering of the product result. Currently, only the first element will have an effect on the sorting of products. Will sort by score in descending order by default.
userId string UserId used to enable personalized boost. An id must have a maximal length of 255 characters, can include only digits, English lowercase and uppercase letters and these characters: -_
forceSearch boolean When enabled any search optimization will be omitted. E.g. did-you-mean will not be followed.
splitTestId string SplitTestId is among other things used to select a context. An id must have a maximal length of 255 characters, can include only digits, English lowercase and uppercase letters and these characters: -_
pinnedIds array Pinned ids can be used to pin products to the top of a search response. The ids can be sku id or product ids. In order for a product to be pinned the product must match the search request.
sessionId string The session id of the user making the search. It should be unique and consistent for each event in a session. The session id is optional, but will not contribute to some features in ecommerce search, if not included.
UnifiedSearchRequestModel in JSON
/api/parameters
{
    "UnifiedSearchRequestModel": {
        "required": [
            "scopeId",
            "segmentId"
        ],
        "type": "object",
        "properties": {
            "segmentId": {
                "minLength": 1,
                "pattern": "^[_\\-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]{0,255}$",
                "type": "string",
                "description": "The segment to search in An id must have a maximal length of 255 characters, can include only digits, English lowercase and uppercase letters and these characters: -_ ",
                "example": "b2b-dk-en"
            },
            "scopeId": {
                "minLength": 1,
                "pattern": "^[_\\-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]{0,255}$",
                "type": "string",
                "description": "A scope defines a configuration that is applied to the search request.\r\nScopes define the fields that can be returned and the fields that can be filtered on. An id must have a maximal length of 255 characters, can include only digits, English lowercase and uppercase letters and these characters: -_ ",
                "example": "browse"
            },
            "filters": {
                "type": "object",
                "additionalProperties": {
                    "$ref": "#/components/schemas/FilterModel"
                },
                "description": "Field name and filter models",
                "nullable": true
            },
            "authenticationToken": {
                "type": "string",
                "description": "JWT Token for authenticated search.",
                "nullable": true
            },
            "phrase": {
                "maxLength": 255,
                "minLength": 0,
                "type": "string",
                "description": "The phrase to search for",
                "nullable": true,
                "example": "pants"
            },
            "offsetProducts": {
                "maximum": 2147483647,
                "minimum": 0,
                "type": "integer",
                "description": "The number of products to skip.",
                "format": "int32",
                "example": 0
            },
            "numberOfProducts": {
                "maximum": 1000,
                "minimum": 0,
                "type": "integer",
                "description": "Indicates the number of records to be fetched.\r\nFor example, if set to 3, then up to 3 products are returned.\r\nDefault value is defined on the scope.",
                "format": "int32",
                "nullable": true,
                "example": 12
            },
            "offsetContent": {
                "maximum": 2147483647,
                "minimum": 0,
                "type": "integer",
                "description": "The number of content to skip.",
                "format": "int32",
                "example": 0
            },
            "numberOfContent": {
                "maximum": 1000,
                "minimum": 0,
                "type": "integer",
                "description": "Indicates the number of records to be fetched.\r\nFor example, if set to 3, then up to 3 content are returned.\r\n            \r\nDefault value is defined on the scope.",
                "format": "int32",
                "nullable": true,
                "example": 12
            },
            "sort": {
                "maxItems": 1000,
                "type": "array",
                "items": {
                    "$ref": "#/components/schemas/SortModel"
                },
                "description": "Used to specify the ordering of the product result.\r\nCurrently, only the first element will have an effect on the sorting of products.\r\nWill sort by score in descending order by default.",
                "nullable": true
            },
            "userId": {
                "pattern": "^[_\\-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]{0,255}$",
                "type": "string",
                "description": "UserId used to enable personalized boost An id must have a maximal length of 255 characters, can include only digits, English lowercase and uppercase letters and these characters: -_ ",
                "nullable": true,
                "example": "external-user-1"
            },
            "forceSearch": {
                "type": "boolean",
                "description": "When enabled any search optimization will be omitted. E.g. did-you-mean will not be followed"
            },
            "splitTestId": {
                "pattern": "^[_\\-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]{0,255}$",
                "type": "string",
                "description": "SplitTestId is among other things used to select a context. An id must have a maximal length of 255 characters, can include only digits, English lowercase and uppercase letters and these characters: -_ ",
                "nullable": true,
                "example": "control-makeup"
            },
            "pinnedIds": {
                "maxItems": 200,
                "type": "array",
                "items": {
                    "type": "string"
                },
                "description": "Pinned ids can be used to pin products to the top of a search response.\r\nThe ids can be sku id or product ids. In order for a product to be pinned the product\r\nmust match the search request.",
                "nullable": true
            },
            "sessionId": {
                "type": "string",
                "description": "The session id of the user making the search. It should be unique and consistent for each event in a session.\r\nThe session id is optional, but will not contribute to some features in ecommerce search, if not included.",
                "nullable": true,
                "example": "12345"
            }
        },
        "additionalProperties": false,
        "description": "Base class of product and sku search requests."
    }
}

Try it yourself

  • Use the Unified search endpoint though C# or TS
    • Set segment and scope id
    • Try a simple search phrase and view results
    • Try a search and note the different suggestions
      • Make sure scope is set up correctly
    • Try different facets and view request/response
  • Use the Academy Playground to "play around"

Most arguments are straightforward; however, the authentication token is crucial for authenticated searches, which are used to limit publicly available data, such as price groups.

The process from a developer's perspective involves:

  • Ingesting a price group into SKUs.
  • Creating an authenticated scope.
  • Generating an authentication token:
    • Adding a filter to access the price group
  • Performing an authenticated search filtered by the price group.

Please refer to the documentation for more information.

Create a token

You can use this C# code to create a token. It's based on the Getting Started Console App, and you can replace Program.cs with:

using Bizzkit.Sdk.EcommerceSearch;
using Bizzkit.Sdk.Search;
using Bizzkit.Sdk.Search.Authenticated;
using Bizzkit.Sdk.Search.Authenticated.Services;
using Microsoft.Extensions.DependencyInjection;
using Bizzkit.Academy;

string segmentId = "merch-b2c-en";  // REPLACE
string[] allowedScopes = { "vip" }; // REPLACE
var config = DomainConfig.GetDefaultDockerDomainConfig();
var provider = SetupServiceProvider(config);
await CreateSigningKey(provider, config, segmentId);
var filters = new Dictionary<string, Bizzkit.Sdk.Search.FilterModel>();

filters.Add("PriceGroup", new Bizzkit.Sdk.Search.FilterModel
{
    Values = new List<string> { "vip" } // REPLACE
});

var token = await GetToken(provider, segmentId, allowedScopes, filters, TimeSpan.FromHours(1));
System.Console.WriteLine(token.Token);

async Task<AuthenticatedSearchToken> GetToken(IServiceProvider provider, string segmentId, string[] scope, Dictionary<string, Bizzkit.Sdk.Search.FilterModel> filters, TimeSpan tokenTtl)
{

    var allowedScopeIds = new HashSet<string>(scope);    
    var authSearchModel = new AuthenticatedSearchModel(segmentId)
    {
        AllowedScopeIds = allowedScopeIds,
        Filters = filters
    };
    var tokenOpts = new AuthenticatedSearchOptions { TokenTtl = tokenTtl };
    var signingService = provider.GetRequiredService<IAuthenticatedSearchSigningService>();
    var token = await signingService.CreateTokenAsync(authSearchModel, tokenOpts);
    return token;
}

async Task CreateSigningKey(IServiceProvider provider, DomainConfig config, string segmentId, string issuerUrl ="https://mywebshop.example.com/")
{ 
    var administrationClientFactory = provider.GetRequiredService<ISearchAdministrationClientFactory>();
    var administrationClient = await administrationClientFactory.CreateAuthenticatedClientAsync();
    var signingKeySettings = new SearchSigningKeySettingsModel
    {
        AudienceUrl = new Uri(config.EcsAdminApiUrl!),
        IssuerUrl = new Uri(issuerUrl)
    };    
    await administrationClient.UpsertSigningKeySettingsAsync(segmentId, signingKeySettings);
}

IServiceProvider SetupServiceProvider(DomainConfig config)
{

    var ecommerceSearchOptions = new SearchAdministrationConnectionOptions
    {
        Authority = config.Authority,
        BaseUrl = config.EcsAdminApiUrl,
        ClientId = config.ClientId,
        ClientSecret = config.ClientSecret
    };

    var searchOptions = new SearchConnectionOptions
    {
        BaseUrl = config.EcsHostApiUrl
    };

    var services = new ServiceCollection();
    services.AddLogging();
    services.AddSingleton<ISearchAdministrationClientFactory>(new SearchAdministrationClientFactory(ecommerceSearchOptions));
    services.AddSingleton<ISearchClientFactory>(new SearchClientFactory(searchOptions, new()));
    services.AddBizzkitAuthenticatedSearch();
    return services.BuildServiceProvider();

}

Use https://jwt.io/ to view the decoded values.

Try It Yourself: Price group

  • Review the price for a specific SKU.
  • Create a token with a filter for the VIP price group, and add this to the request
  • Review the price for a specific SKU again - it should now display a different price.

Try It Yourself: Authenticated scope

  • Create an authenticated scope called vip make include a "hidden" field like "VIP level"
  • See if the scope is accessible without a token
  • Create a token and add it to the request
  • See if the scope is accessible and the field is included in the response.

Response model

Below is a summary table of the Result Model:

Name Type Description
products array Products as matched the search.
facets array Collection of facets.
suggestions array Suggestions that matched search. Example: "Shorts for women".
didYouMean array Did you mean, returns a number of examples for search phrases. This can be used to help with spelling errors. Example: "Shorts for women".
relatedTags array The related tags that matched search.
numberOfRequestedProductResults integer The number of products that was requested.
totalProducts integer The total number of products that matched the search.
totalSuggestions integer The total number of suggestions that matched the search.
totalRelatedTags integer The total number of related tags that matched the search.
originalPhrase string The original search phrase.
usedPhrase string The search phrase that eventually was used. This may be updated if there was not matching products, suggestions and related tags.
productOffset integer The number of products to skip. Example: 0.
numberOfRequestedContentResults integer The number of content that was requested.
totalContent integer The total number of content that matched the search.
content array Content as matched the search.
UnifiedSearchResultModel in JSON
/api/parameters
{
    "UnifiedSearchResultModel": {
        "type": "object",
        "properties": {
            "products": {
                "maxItems": 1000,
                "type": "array",
                "items": {
                    "$ref": "#/components/schemas/SearchProductWithSkusModel"
                },
                "description": "Products as matched the search",
                "nullable": true
            },
            "facets": {
                "maxItems": 1000,
                "type": "array",
                "items": {
                    "$ref": "#/components/schemas/FacetModel"
                },
                "description": "Collection of facets",
                "nullable": true
            },
            "suggestions": {
                "maxItems": 1000,
                "type": "array",
                "items": {
                    "type": "string"
                },
                "description": "Suggestions that matched search",
                "nullable": true,
                "example": [
                    "Shorts for women"
                ]
            },
            "didYouMean": {
                "maxItems": 1000,
                "type": "array",
                "items": {
                    "type": "string"
                },
                "description": "Did you mean, returns a number of examples for search phrases.\r\nThis can be used to help with spelling errors.",
                "nullable": true,
                "example": [
                    "Shorts for women"
                ]
            },
            "relatedTags": {
                "maxItems": 1000,
                "type": "array",
                "items": {
                    "$ref": "#/components/schemas/RelatedTagModel"
                },
                "description": "The related tags that matched search",
                "nullable": true
            },
            "numberOfRequestedProductResults": {
                "maximum": 2147483647,
                "minimum": 0,
                "type": "integer",
                "description": "The number of products that was requested",
                "format": "int32"
            },
            "totalProducts": {
                "minimum": 0,
                "type": "integer",
                "description": "The total number of products that matched the search",
                "format": "int64"
            },
            "totalSuggestions": {
                "minimum": 0,
                "type": "integer",
                "description": "The total number of suggestions that matched the search",
                "format": "int64"
            },
            "totalRelatedTags": {
                "minimum": 0,
                "type": "integer",
                "description": "The total number of related tags that matched the search",
                "format": "int64"
            },
            "originalPhrase": {
                "type": "string",
                "description": "The original search phrase",
                "nullable": true
            },
            "usedPhrase": {
                "type": "string",
                "description": "The search phrase that eventually was used.\r\nThis may be updated if there was not matching products, suggestions and related tags.",
                "nullable": true
            },
            "productOffset": {
                "type": "integer",
                "description": "The number of products to skip.",
                "format": "int32",
                "example": 0
            },
            "numberOfRequestedContentResults": {
                "maximum": 2147483647,
                "minimum": 0,
                "type": "integer",
                "description": "The number of content that was requested",
                "format": "int32"
            },
            "totalContent": {
                "minimum": 0,
                "type": "integer",
                "description": "The total number of content that matched the search",
                "format": "int64"
            },
            "content": {
                "maxItems": 1000,
                "type": "array",
                "items": {
                    "$ref": "#/components/schemas/ContentModel"
                },
                "description": "Content as matched the search",
                "nullable": true
            }
        },
        "additionalProperties": false,
        "description": "Used when returning unified search response"
    }
}

Courier API

The Courier API enables configuration setups for integration with various platforms, including Google Analytics, Raptor, and Bizzkit CMS. For detailed guidance on integration processes, please refer to our documentation.

Info

In the Bizzkit Docker Environment, the Courier API is accessible on port 8023.