Best API
Десятки поставщиков описывают один товар по-разному — мы приводим их прайсы к единому канону, чтобы вы получали один чистый каталог, а не зоопарк написаний. Публичный REST API: бренды, коллекции, товары и типизированные характеристики. Авторизация по ключу, cursor-пагинация, ошибки в формате RFC 9457.
Песочница — без регистрации
Запросы уходят к тем же ручкам, что обслуживают дилеров: те же ответы, картинки вариантами, фасеты со счётчиками. Ключ не нужен — его подставляет наш сервер, в браузер он не попадает.
Песочница на этом стенде пока не настроена — запросы вернут ошибку.
Счётчики каталога (товары, бренды, коллекции, разделы, поставщики), момент последнего обновления и крупнейшие бренды/коллекции по именам. Товаров не отдаёт — показывает масштаб.
curl -H "Authorization: Bearer ВАШ_КЛЮЧ" "http://localhost/api/public/v1/demo/stats"Качество данных
Боль номер один рынка — десятки поставщиков описывают один и тот же товар по-разному: регистр, сокращения, порядок слов, формат размера. Руками это не свести (два-три прайса ещё терпимо, десятки — нет), а агентства и штатные интеграторы за эту работу обычно не берутся. Наш слой нормализации делает это на каждом импорте — ниже иллюстративный пример того, во что схлопываются такие написания.
- Керамогранит ITALON Genesis Bianco Perla 60x60 полированный
- Плитка керамогран. ITALON GENESIS BIANCO PERLA 600х600 полир.
- Керамогранит Italon Genesis Bianco Perla пол. 60х60см, 1 сорт
Дальше — тот же принцип на характеристиках и остатках: цена и наличие видны по каждому поставщику отдельно (offers[] ), а агрегаты на карточке (price_min , offers_count , suppliers_count) считаются из них же, а не берутся с последнего импорта поверх предыдущих.
Базовый URL
/api/public/v1 Авторизация — заголовок Authorization: Bearer <ключ>.
Быстрый старт
curl -H "Authorization: Bearer <ваш-ключ>" \
"/api/public/v1/products?limit=2"Ответ:
{
"data": [
{
"public_id": "clx0pub0001",
"slug": "cifre-alchimia-decor-9x9",
"name": "Cifre Alchimia Decor 9x9",
"price_from": 1490,
"in_stock": true,
"offers_count": 3,
"suppliers_count": 2
},
{
"public_id": "clx0pub0002",
"slug": "italon-genesis-bianco-perla-60x60",
"name": "Italon Genesis Bianco Perla 60x60",
"price_from": 2140,
"in_stock": true,
"offers_count": 5,
"suppliers_count": 3
}
],
"page": { "next_cursor": "Y2x4MGJyYW5kMDAwMQ", "has_more": true }
}Ключ для запроса — на странице регистрации : пробный выпускается сразу, без карты и без разговора с продажником.
TS-клиент (SDK)
Не хотите вручную собирать запросы — в репозитории есть типобезопасный dependency-free fetch-клиент @best-api/public-api-sdk (пакет packages/sdk): типы ручек генерируются из этого же OpenAPI-контракта и всегда синхронны с API, курсорная пагинация спрятана в асинхронный генератор, ошибки — единый PublicApiError с машинным кодом вместо разбора текста.
Пакет скоро появится в npm — до публикации запросите dist-архив у нас напрямую.
import { createPublicApiClient } from '@best-api/public-api-sdk';
const client = createPublicApiClient({
baseUrl: 'https://<ваш-домен>/api/public/v1',
apiKey: process.env.API_KEY!,
});
const { data } = await client.listProducts({ limit: 50 });
for await (const product of client.iterateProducts({ limit: 100 })) {
// upsert product в свой каталог…
}
// дифф-лента: new / updated / removed одним проходом, since двигать по as_of
for await (const page of client.iterateChanges({ since: lastSyncIso })) {
// page.new / page.updated / page.removed…
}Ручки
- GET
/api/public/v1/profileДанные клиента и его доступы - GET
/api/public/v1/policyДействующая политика контракта - GET
/api/public/v1/propertiesХарактеристики (типизированный реестр) - GET
/api/public/v1/countriesСправочник стран (ISO 3166-1) - GET
/api/public/v1/measuresСправочник единиц измерения (ОКЕИ) - GET
/api/public/v1/productsТовары (фильтры updated_after, created_after, id; сорт new/цена) - GET
/api/public/v1/products/exportBulk-export товаров (NDJSON) - GET
/api/public/v1/products/facetsДоступные значения фильтров и счётчики под текущий выбор — для сайдбара - GET
/api/public/v1/products/:idOrSlugКарточка товара - GET
/api/public/v1/products/:id/offersПредложения поставщиков по товару - GET
/api/public/v1/product-idsПолный список public_id товаров вертикали - GET
/api/public/v1/brandsБренды каталога - GET
/api/public/v1/brands/facetsФасеты справочника брендов - GET
/api/public/v1/brands/categories/:slugКанонический slug категории брендов - GET
/api/public/v1/brands/:idOrSlugКарточка бренда - GET
/api/public/v1/collectionsКоллекции каталога - GET
/api/public/v1/collections/facetsФасеты режима витрины «Коллекции» - GET
/api/public/v1/collections/:idOrSlugКарточка коллекции - GET
/api/public/v1/collections/:brandSlug/:slug/resolveКанонический slug коллекции (brand-scoped) - GET
/api/public/v1/storesСклады и магазины каталога - GET
/api/public/v1/store-itemsОстатки товаров по складам - GET
/api/public/v1/changesДиф-лента: что изменилось в срезе клиента с момента since - GET
/api/public/v1/webhooksМои подписки на вебхуки - POST
/api/public/v1/webhooksЗарегистрировать приёмник — секрет подписи приходит в ответе один раз - PATCH
/api/public/v1/webhooks/:idИзменить подписку (адрес, события, пауза) - DELETE
/api/public/v1/webhooks/:idУдалить подписку - POST
/api/public/v1/webhooks/:id/rotate-secretСменить секрет подписи - POST
/api/public/v1/webhooks/:id/pingТестовая доставка на приёмник - GET
/api/public/v1/webhooks/:id/deliveriesПоследние доставки подписки - GET
/api/public/v1/menuМеню каталога: разделы вместе с подкатегориями-лендингами - GET
/api/public/v1/categoriesДерево разделов каталога - GET
/api/public/v1/categories/:slug/resolveКанонический slug раздела - GET
/api/public/v1/categories/:categorySlug/facet-groupsФасетные группы раздела (состав панели фильтров) - GET
/api/public/v1/categories/:categorySlug/presetsПресеты фасетов раздела (подкатегории-лендинги) - GET
/api/public/v1/categories/:categorySlug/presets/:slug/resolveКанонический slug пресета раздела
Всё, кроме вебхуков, — только чтение. Ручки /webhooks меняют состояние: тело — JSON, нужен scope webhooks и ключ дилера. Контракт доставки — в секции «Вебхуки».
Вебхуки
Push вместо опроса по расписанию: как только у поставщика меняются цены или остатки, мы сами шлём POST на ваш адрес. Тело события — окно изменений со ссылкой на /changes: сами позиции забираете этой ручкой, поэтому пропущенная доставка не означает потерянных данных.
Приёмник регистрирует ваша интеграция своим же ключом — POST /api/public/v1/webhooks (нужен scope webhooks), события price_changed и stock_changed. Секрет подписи приходит в ответе один раз.
Требования к приёмнику
- Только https и только публичный адрес. Приёмник во внутренней сети (loopback, приватные диапазоны, адреса метаданных облака) не регистрируется, а имя, резолвящееся туда же, отбивается в момент соединения.
- Принято — это ответ 2xx. Тело ответа мы не читаем и не храним: нужен только факт приёма.
- Редиректы мы НЕ следуем: любой 3xx (301, 302, 307, 308) считается отказом приёмника и уходит в ретраи. Адрес подписки должен быть конечным — не тем, который переадресует на «правильный» URL.
- Ответ должен уложиться в 10 секунд, иначе попытка считается неудачной.
- Неудачная попытка повторяется: до 6 попыток с растущей паузой — минута, 5 минут, 15 минут, час, 6 часов. Доставка идёт фоновой задачей, порядок событий не гарантируется.
Подпись и заголовки
Проверяйте подпись до обработки тела: метка времени входит в подписываемую строку, поэтому перехваченную доставку нельзя переиграть позже.
X-Webhook-Signature: sha256=HMAC-SHA256(секрет, "<timestamp>.<тело>")
X-Webhook-Event: price_changed | stock_changed | ping
X-Webhook-Delivery: идентификатор доставки (одинаков у всех попыток)
X-Webhook-Timestamp: время отправки, оно же входит в подпись
X-Webhook-Attempt: номер попытки, начиная с 1 Ротация секрета не обрывает старый секрет мгновенно: 24 часа после ротации доставка подписана ОБЕИМИ подписями сразу — новым секретом и ещё не истёкшим старым, через запятую в X-Webhook-Signature (совпадения любой из них достаточно). За это время переставьте секрет на приёмнике; по истечении окна старый секрет перестаёт приниматься.
Здоровье приёмников, история доставок, тестовая доставка ( ping ) и ротация секрета — в личном кабинете .
Версии и совместимость
Версия — сегмент пути: /api/public/v1. Пока адрес начинается с /public/v1, контракт по этому адресу не ломается: ломающее изменение выходит новым сегментом версии (/public/v2), а не правкой текущей.
Меняем внутри версии без предупреждения
- новые ручки и новые необязательные параметры запроса
- новые поля в ответе
- новые значения перечислений (kind, статусы, коды ошибок)
- изменение порядка ключей в JSON и текстов описаний
Отсюда единственное требование к вашему клиенту: игнорировать незнакомое — новые поля в ответе и новые значения перечислений не должны ронять разбор.
Считаем ломающим (только через новую версию)
- удаление или переименование ручки, поля ответа, параметра
- смена типа или формата поля (строка → число, другой формат даты)
- новый обязательный параметр запроса или ужесточение валидации
- смена смысла значения при том же имени поля
- смена структуры ошибки или HTTP-статуса штатного сценария
Как узнаете о депрекации
Из ответа, а не из письма. Устаревшая ручка (или отдельное поле её ответа) продолжает работать как обычно, но каждый её ответ несёт заголовки Deprecation (RFC 9745) и Sunset (RFC 8594) с датой отключения, а Link ведёт на эту политику и на замену:
Deprecation: @1788220800
Sunset: Thu, 01 Apr 2027 00:00:00 GMT
Link: <https://<домен>/developers#api-policy>; rel="deprecation"; type="text/html",
</api/public/v2/products>; rel="successor-version" То же самое — в OpenAPI-спеке: операция помечена deprecated, детали (что именно устарело, до какой даты и чем заменено) — в расширении x-deprecation. Актуальный список депрекаций всегда виден в спеке, отдельной рассылки для этого не нужно.
Между объявлением и отключением — срок, заданный политикой контракта. Срок проверяется кодом: объявление с более коротким сроком не проходит сборку API, поэтому «выключили завтра» технически невозможно. Машинное значение — в ответе GET /policy (поле deprecation.min_notice_days).
Лимиты и SLA
Лимиты и перегрузка
- Фактический лимит ключа — в каждом ответе:
RateLimit-Limit,RateLimit-Remaining,RateLimit-Reset. Считайте по ним, а не по цифрам на этой странице. - Те же цифры машиночитаемо —
GET /policy: лимит вашего ключа, потолок страницы, окна кэша и срок предупреждения о депрекации. - Превышение —
429сRetry-Afterв секундах. Корректный клиент ждёт указанное время, а не повторяет запрос сразу. - Потолок страницы и состав полей зависят от поверхности ключа: витринный отдаёт меньше позиций за раз и без оптовых агрегатов.
Свежесть данных
- Каталог обновляется по факту импорта прайсов поставщиков, а не по расписанию ответа: заголовок
X-Dataset-Versionпоказывает, на каком срезе данных получен ответ. - Сколько ответ разрешено переиспользовать в клиенте и в общем кэше, говорит
Cache-Controlкаждого ответа: в пределах этого окна ответ может отставать от базы.
Что гарантируем
- Совместимость внутри версии и минимум 180 дней между объявлением депрекации и отключением — см. раздел выше.
- Ошибки в одном формате (RFC 9457): машинный код и человеческое пояснение, без разбора текста.
- ETag / If-None-Match на читающих ручках: повторный запрос без изменений отвечает 304 без тела.
- Инкрементальная синхронизация без полной перевыкачки: /changes и updated_after.
- Текущая доступность видна публично и обновляется автоматически — страница статуса.
- Заблаговременное объявление плановых работ баннером на той же странице статуса — отдельно от фактического здоровья.
Чего не гарантируем на текущем этапе
- Числового SLA доступности (99,9 % и подобное) с компенсациями и кредитами сейчас нет — сервис работает на одной инсталляции, без резервирования и мультирегиона.
- Круглосуточного дежурства нет: на инциденты реагируем в рабочие часы (Москва).
- Нулевых окон обслуживания: короткие перерывы на обновление возможны — ретрай на стороне клиента всё равно обязателен.
- Восстановление после аварии — из суточного дампа (снимается и проверяется восстановлением ежедневно), то есть в худшем случае теряются изменения каталога за срок до суток.
Текущее состояние сервиса и время ответа API — страница статуса . Нужны договорные гарантии доступности под вашу интеграцию — обсуждаем отдельно, до подписания их здесь не обещаем.