Bulk Create Customizations
Create multiple customizations in a single request.
Overview
Important This endpoint requires HMAC authentication.
Create multiple customizations (pin scopes) in a single API call. This endpoint accepts Newline Delimited JSON (NDJSON).
Each line in the request body must be a valid JSON object representing a pin scope. Up to 5000 pin scopes can be provided in a single call.
Authentication
This endpoint requires HMAC authentication. You must include the Date and Authorization headers. See Authentication for details.
https://live.luigisbox.tech/v1/recommender/pin/{TRACKER_ID}/scopes/_bulk
Request Parameters
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
TRACKER_ID |
String | ✓ | Your unique tracker identifier. |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
conflict_strategy |
String | raise |
Strategy for handling conflicting pins. Options: raise (error), ignore (keep existing), override (replace existing). |
Request Headers
| Header | Value | Description |
|---|---|---|
Content-Type |
application/x-ndjson |
Required. |
Date |
HTTP Date | Required for HMAC. |
Authorization |
Signature | Required for HMAC. |
Example Request
tracker_id="1234-5678"
private_key="your_private_api_key"
host="https://live.luigisbox.tech"
base_path="/v1/recommender/pin/${tracker_id}/scopes/_bulk"
full_path="${base_path}?conflict_strategy=override"
method="POST"
content_type="application/x-ndjson"
date=$(date -u "+%a, %d %b %Y %H:%M:%S GMT")
string_to_sign="$(printf "${method}\n${content_type}\n${date}\n${base_path}")"
signature=$(echo -n "${string_to_sign}" | openssl dgst -sha256 -hmac "${private_key}" -binary | base64)
curl -X ${method} "${host}${full_path}" \
-H "Content-Type: ${content_type}" \
-H "Date: ${date}" \
-H "Authorization: ApiAuth ${tracker_id}:${signature}" \
--data-binary @pins.ndjson
Request Body Format
The request body uses NDJSON format. Each line must be a valid JSON object.
Tip Schema Reference
Each line in the NDJSON body follows the exact same schema as the Create Customization endpoint. Please refer to that page for detailed validation rules on target_criteria and pin_definitions.
Request Body Example
{"target_type": "all", "model": "basket", "pin_definitions": [{"pin_type": "item", "pin_identity": "123"}]}
{"target_type": "item", "target_identity": "456", "model": "basket", "pin_definitions": [{"pin_type": "item", "pin_identity": "789"}]}
How to make a request
All requests to this endpoint must be authenticated using HMAC SHA-256. The examples on the right demonstrate the implementation in various languages, but the core logic follows these steps:
1. Construct the String to Sign
Create a string using the following components, separated by newlines (\n):
- HTTP Method:
POST - Content-Type:
application/x-ndjson - Date: The current timestamp in RFC 1123 format (e.g.,
Mon, 20 Jan 2025 12:00:00 GMT). - Request Path: The resource path excluding query parameters.
2. Generate the HMAC Signature
- Create an HMAC SHA-256 hash of the string above using your Private API Key.
- Encode the result in Base64 and trim whitespace.
3. Send the Headers
Include Date, Content-Type, and Authorization: ApiAuth {TRACKER_ID}:{SIGNATURE} in your request.
Example Code
require 'faraday'
require 'base64'
require 'openssl'
require 'json'
require 'time'
def generate_luigisbox_digest(private_key, http_method, endpoint_path, date_header, content_type_header)
data = "#{http_method}\n#{content_type_header}\n#{date_header}\n#{endpoint_path}"
hash = OpenSSL::HMAC.digest('sha256', private_key, data)
Base64.strict_encode64(hash).strip
end
tracker_id = 'YOUR_TRACKER_ID'
private_key = 'your_private_api_key'
host = 'https://live.luigisbox.tech'
base_path = "/v1/recommender/pin/#{tracker_id}/scopes/_bulk"
full_path = "/v1/recommender/pin/#{tracker_id}/scopes/_bulk?conflict_strategy=override"
ndjson_data = [
{target_type: "all", model: "basket", pin_definitions: [{pin_type: "item", pin_identity: "123"}]},
{target_type: "item", target_identity: "456", model: "basket", pin_definitions: [{pin_type: "item", pin_identity: "789"}]}
].map(&:to_json).join("\n")
conn = Faraday.new(url: host)
http_method = 'POST'
content_type = 'application/x-ndjson'
date_header = Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S GMT")
signature = generate_luigisbox_digest(private_key, http_method, base_path, date_header, content_type)
authorization_header = "ApiAuth #{tracker_id}:#{signature}"
response = conn.post(full_path) do |req|
req.headers['Date'] = date_header
req.headers['Content-Type'] = content_type
req.headers['Authorization'] = authorization_header
req.body = ndjson_data
end
puts response.status
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
function generateLuigisboxDigest($privateKey, $httpMethod, $endpointPath, $dateHeader, $contentTypeHeader) {
$data = "{$httpMethod}\n{$contentTypeHeader}\n{$dateHeader}\n{$endpointPath}";
$hash = hash_hmac('sha256', $data, $privateKey, true);
return trim(base64_encode($hash));
}
$TRACKER_ID = "YOUR_TRACKER_ID";
$YOUR_PRIVATE_KEY = "your_private_api_key";
$LUIGISBOX_HOST = 'https://live.luigisbox.tech';
$BASE_PATH = "/v1/recommender/pin/{$TRACKER_ID}/scopes/_bulk";
$REQUEST_PATH = "/v1/recommender/pin/{$TRACKER_ID}/scopes/_bulk?conflict_strategy=override";
$data_rows = [
["target_type" => "all", "model" => "basket", "pin_definitions" => [["pin_type" => "item", "pin_identity" => "123"]]],
["target_type" => "item", "target_identity" => "456", "model" => "basket", "pin_definitions" => [["pin_type" => "item", "pin_identity" => "789"]]]
];
$ndjson_data = "";
foreach ($data_rows as $row) {
$ndjson_data .= json_encode($row) . "\n";
}
$http_method = 'POST';
$content_type = 'application/x-ndjson';
$current_date = gmdate('D, d M Y H:i:s') . ' GMT';
$signature = generateLuigisboxDigest($YOUR_PRIVATE_KEY, $http_method, $BASE_PATH, $current_date, $content_type);
$authorization_header = "ApiAuth {$TRACKER_ID}:{$signature}";
$client = new GuzzleHttp\Client();
$response = $client->request($http_method, "{$LUIGISBOX_HOST}{$REQUEST_PATH}", [
'headers' => [
'Content-Type' => $content_type,
'Date' => $current_date,
'Authorization' => $authorization_header,
],
'body' => $ndjson_data
]);
echo $response->getStatusCode();
?>
const axios = require('axios');
const crypto = require('crypto');
function generateLuigisBoxDigest(privateKey, httpMethod, endpointPath, dateHeader, contentTypeHeader) {
const data = </span><span class="p">${</span><span class="nx">httpMethod</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="nx">contentTypeHeader</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="nx">dateHeader</span><span class="p">}</span><span class="s2">\n</span><span class="p">${</span><span class="nx">endpointPath</span><span class="p">}</span><span class="s2">;
const hmac = crypto.createHmac('sha256', privateKey);
hmac.update(data);
return hmac.digest('base64').trim();
}
const TRACKER_ID = "YOUR_TRACKER_ID";
const YOUR_PRIVATE_KEY = "your_private_api_key";
const LUIGISBOX_HOST = 'https://live.luigisbox.tech';
const BASE_PATH = /v1/recommender/pin/</span><span class="p">${</span><span class="nx">TRACKER_ID</span><span class="p">}</span><span class="s2">/scopes/_bulk;
const REQUEST_PATH = /v1/recommender/pin/</span><span class="p">${</span><span class="nx">TRACKER_ID</span><span class="p">}</span><span class="s2">/scopes/_bulk?conflict_strategy=override;
const dataRows = [
{target_type: "all", model: "basket", pin_definitions: [{pin_type: "item", pin_identity: "123"}]},
{target_type: "item", target_identity: "456", model: "basket", pin_definitions: [{pin_type: "item", pin_identity: "789"}]}
];
const ndjsonData = dataRows.map(JSON.stringify).join("\n");
const httpMethod = 'POST';
const contentType = 'application/x-ndjson';
const dateHeader = new Date().toUTCString();
const signature = generateLuigisBoxDigest(YOUR_PRIVATE_KEY, httpMethod, BASE_PATH, dateHeader, contentType);
const authorizationHeader = ApiAuth </span><span class="p">${</span><span class="nx">TRACKER_ID</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">signature</span><span class="p">}</span><span class="s2">;
axios({
method: httpMethod,
url: </span><span class="p">${</span><span class="nx">LUIGISBOX_HOST</span><span class="p">}${</span><span class="nx">REQUEST_PATH</span><span class="p">}</span><span class="s2">,
headers: {
'Content-Type': contentType,
'Date': dateHeader,
'Authorization': authorizationHeader
},
data: ndjsonData
})
.then(response => {
console.log(response.status);
})
.catch(error => {
console.error(error);
});
Response Structure
Returns 200 OK on success with an empty JSON object.
The Response Object
{}
Error Handling
| HTTP Status | Description |
|---|---|
| 200 OK | Success. |
| 401 Unauthorized | Missing or invalid authentication. |
| 404 Not Found | Tracker not found. |
| 413 Content Too Large | Too many pins provided in a single request (Limit: 5000). |
| 422 Unprocessable Entity | Invalid pin definition provided. |
| 409 Conflict | If conflict_strategy is raise and a conflict occurs. |
Error Responses
{
"reason": "Invalid pin definition provided",
"exception_details": {
"pin_definitions": ["Missing required field: position"],
"target_type": ["Must be one of: item, criteria, all"]
}
}