Интеграция 1С-Битрикс с PIM-системой Salsify
Salsify — американская SaaS PIM-платформа, ориентированная на крупный e-commerce и мультиканальные продажи. Используется преимущественно брендами и дистрибьюторами, которые управляют тысячами SKU с синдикацией на маркетплейсы (Amazon, Walmart, Target). Если на Битрикс перенесён западный бренд или партнёрство предполагает использование Salsify как источника данных — интеграция строится через Salsify REST API.
Salsify API: основные эндпоинты
Salsify предоставляет REST API с токен-аутентификацией:
Base URL: https://app.salsify.com/api/v1/orgs/{org_id}/
Authorization: Bearer {api_key}
Ключевые эндпоинты:
GET products — список продуктов с атрибутами
GET products/{product_id} — конкретный продукт
GET products?filter[updated_at][gte]={timestamp} — изменённые с даты
GET property_groups — группы атрибутов (аналог разделов свойств)
GET assets?filter[product_id]={id} — медиафайлы продукта
GET digital_assets/{id}/download — скачать файл
Salsify использует плоскую структуру атрибутов: каждый атрибут — пара «имя» : «значение», где имя может быть произвольным UUID или строкой. Перед маппингом нужно получить справочник атрибутов:
GET properties — возвращает все атрибуты организации с ID, name, data_type
Клиент Salsify
class SalsifyClient
{
private string $baseUrl;
private string $apiKey;
public function __construct(string $orgId, string $apiKey)
{
$this->baseUrl = "https://app.salsify.com/api/v1/orgs/{$orgId}/";
$this->apiKey = $apiKey;
}
private function request(string $endpoint, array $params = []): array
{
$http = new \Bitrix\Main\Web\HttpClient();
$http->setHeader('Authorization', 'Bearer ' . $this->apiKey);
$http->setHeader('Content-Type', 'application/json');
$url = $this->baseUrl . $endpoint;
if ($params) {
$url .= '?' . http_build_query($params);
}
$response = $http->get($url);
return json_decode($response, true) ?? [];
}
public function getProducts(int $page = 1, int $perPage = 100, ?string $updatedAfter = null): array
{
$params = ['page' => $page, 'per_page' => $perPage];
if ($updatedAfter) {
$params['filter[updated_at][gte]'] = $updatedAfter;
}
return $this->request('products', $params);
}
public function getProperties(): array
{
return $this->request('properties');
}
public function downloadAsset(string $assetId): string
{
$http = new \Bitrix\Main\Web\HttpClient();
$http->setHeader('Authorization', 'Bearer ' . $this->apiKey);
return $http->get($this->baseUrl . 'digital_assets/' . $assetId . '/download');
}
}
Маппинг атрибутов Salsify
Атрибуты Salsify идентифицируются по salsify:id (UUID) или по name. Конфигурация маппинга:
// /local/config/salsify-mapping.php
// 'salsify_property_id_or_name' => ['target' => 'bitrix_field', 'type' => 'field|prop']
return [
'Product Name' => ['target' => 'NAME', 'type' => 'field'],
'Long Description' => ['target' => 'DETAIL_TEXT', 'type' => 'field'],
'Short Description' => ['target' => 'PREVIEW_TEXT', 'type' => 'field'],
'Brand' => ['target' => 'BRAND', 'type' => 'prop'],
'Net Weight (kg)' => ['target' => 'WEIGHT', 'type' => 'prop'],
'Color' => ['target' => 'COLOR', 'type' => 'prop'],
'Country of Origin' => ['target' => 'COUNTRY_ORIGIN','type' => 'prop'],
'GTIN' => ['target' => 'CML2_BAR_CODE', 'type' => 'prop'],
'Manufacturer SKU' => ['target' => 'CML2_ARTICLE', 'type' => 'prop'],
];
Синхронизация
function syncSalsifyAgent(): string
{
$lastSync = \Bitrix\Main\Config\Option::get('salsify_sync', 'last_run', '');
$client = new SalsifyClient(SALSIFY_ORG_ID, SALSIFY_API_KEY);
$mapping = include '/local/config/salsify-mapping.php';
$page = 1;
$newSync = date('c');
do {
$response = $client->getProducts($page, 100, $lastSync ?: null);
$products = $response['products'] ?? [];
foreach ($products as $product) {
importSalsifyProduct($product, $mapping, $client);
}
$page++;
$meta = $response['meta'] ?? [];
} while (($meta['current_page'] ?? 1) < ($meta['total_pages'] ?? 1));
\Bitrix\Main\Config\Option::set('salsify_sync', 'last_run', $newSync);
return __FUNCTION__ . '();';
}
function importSalsifyProduct(array $product, array $mapping, SalsifyClient $client): void
{
$attributes = $product['attributes'] ?? [];
$sku = $product['salsify:id'];
// Получаем значения атрибутов
$getValue = static function (array $attrs, string $key): mixed {
return $attrs[$key] ?? null;
};
$fields = ['IBLOCK_ID' => CATALOG_IBLOCK_ID, 'ACTIVE' => 'Y'];
$props = [];
foreach ($mapping as $salsifyKey => $config) {
$value = $getValue($attributes, $salsifyKey);
if ($value === null) continue;
// Массивы преобразуем в строку через разделитель
if (is_array($value)) {
$value = implode(', ', $value);
}
if ($config['type'] === 'field') {
$fields[$config['target']] = $value;
} else {
$props[$config['target']] = $value;
}
}
$existing = CIBlockElement::GetList(
[], ['IBLOCK_ID' => CATALOG_IBLOCK_ID, 'PROPERTY_CML2_ARTICLE' => $sku]
)->Fetch();
$el = new CIBlockElement();
if ($existing) {
$productId = $existing['ID'];
$el->Update($productId, $fields);
} else {
$productId = $el->Add($fields);
}
if ($productId) {
CIBlockElement::SetPropertyValuesEx($productId, CATALOG_IBLOCK_ID, $props);
// Медиафайлы
$primaryImage = $product['salsify:primary_image'] ?? null;
if ($primaryImage) {
importSalsifyAsset($productId, $primaryImage, $client);
}
}
}
Особенности Salsify
Сложные значения атрибутов. Salsify возвращает атрибуты как массив, где каждый элемент может быть скалярным значением, объектом или списком. Enum-атрибуты возвращают {"salsify:id": "...", "salsify:name": "Red"} — нужно извлекать salsify:name.
Цены. Salsify не является системой ценообразования. Цены, как правило, не синхронизируются из Salsify — они поступают из 1С. Проверьте с заказчиком, является ли Salsify источником цен или только контента.
Медиафайлы. Salsify хранит медиафайлы с CDN-ссылками. Можно не скачивать файлы, а хранить внешние URL в свойстве EXTERNAL_IMAGE_URL и подтягивать на лету через <img src="...">. Это экономит место на сервере Битрикс.
Webhook. Salsify поддерживает вебхуки при изменении продукта. Настраивается в Salsify → Integrations → Webhooks. URL обработчика принимает POST-запрос с product_id, затем запрашивает актуальные данные через API.
Сроки реализации
| Объём | Состав | Срок |
|---|---|---|
| До 5 000 SKU, базовый контент | Клиент + маппинг + агент | 1–2 недели |
| 10 000–100 000 SKU + медиа + вебхуки | + обработка сложных атрибутов + оптимизация | 3–4 недели |
| Мультирегиональный каталог (разные цены, контент по регионам) | + логика каналов Salsify + мультисайт Битрикс | 5–7 недель |







