Яндекс.Маркет API: карточки, поиск и фильтры
Ищите товары по словам, уточняйте выдачу фильтрами и получайте карточку по ссылке. Ниже — метод карточки; параметры поиска и фильтров описаны в отдельных разделах. Для вызова нужен API-токен BHAPI с доступом к парсеру Яндекс.Маркет.
/yamarket/api/v1/item/detail-by-urlЗапрос
Параметры JSON-запроса
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
url | string | да | Полный URL карточки Яндекс.Маркета, 1–8192 символов. Единственное поле JSON; тело не более 16 КиБ. |
Заголовки
| Заголовок | Обязательный | Описание |
|---|---|---|
X-API-Token | да | API-токен BHAPI с доступом к Яндекс.Маркет. Передавайте в заголовке. |
Content-Type | да | application/json; charset допустим. |
Принимается HTTPS-ссылка https://market.yandex.ru/card/<slug>/<osku_id>. Передавайте только url. Подробные ограничения описаны в правилах URL.
Пример запроса
curl --max-time 180 --fail-with-body -i -X POST "https://bhapi.ru/yamarket/api/v1/item/detail-by-url" \
-H "Content-Type: application/json" \
-H "X-API-Token: ВАШ_API_TOKEN" \
--data '{"url":"https://market.yandex.ru/card/example-product/000100000001"}' Проверяйте HTTP status и Retry-After в заголовках. Не считайте null цены нулём.
Ниже показан ответ для этого запроса. Все значения примера синтетические.
Успешный ответ 200 OK
HTTP 200: {"status":"ok","data":ProductData}. Все значения ниже синтетические; служебный envelope источника наружу не передаётся.
{
"status": "ok",
"data": {
"item_id": "000100000001",
"product_url": "https://market.yandex.ru/card/example-product/000100000001",
"title": "Синтетический чайник",
"brand": "Пример",
"desc": "Учебное описание товара",
"currency": "RUB",
"price_info": {
"sale_price": "1200.50",
"origin_price": "1500"
},
"main_imgs": [
"https://cdn.example.org/kettle.jpg"
],
"main_videos": [
"https://cdn.example.org/kettle.mp4"
],
"category_path": [
{
"id": "category-1",
"name": "Чайники",
"url": "https://market.yandex.ru/catalog--kettles/1"
}
],
"product_props": [
{
"group": "Общие",
"name": "Объём",
"value": "1.5 л"
}
],
"shop_info": {
"shop_name": "Учебный магазин",
"shop_url": "https://market.yandex.ru/business--example/1"
},
"delivery_info": {
"options": [
{
"type": "delivery",
"day_from": 1,
"day_to": 3,
"date": null,
"price": "0",
"currency": "RUB",
"partner_type": "market"
}
],
"context": "anonymous_session"
},
"review_info": {
"rating_star": 4.5,
"rating_count": 12,
"review_count": 8
},
"sku_props": [
{
"name": "Цвет",
"values": [
"Синий",
"Белый"
]
}
],
"skus": [
{
"item_id": "000100000002",
"offer_id": "offer-variant",
"product_url": "https://market.yandex.ru/product--example-blue/100000002",
"properties": {
"Цвет": "Синий"
},
"price": "1250",
"currency": "RUB",
"available": true
}
],
"market": {
"schema_version": "1.0",
"ids": {
"osku_id": "000100000001",
"catalog_sku_id": "catalog-1",
"product_id": "product-1",
"offer_id": "offer-main",
"business_id": "business-1",
"shop_id": "shop-1",
"supplier_id": "supplier-1",
"vendor_id": "vendor-1",
"category_id": "category-1",
"navnode_id": "navnode-1"
},
"availability": {
"in_stock": true,
"available_count": 7
},
"price_context": {
"anonymous": true,
"is_personal": true,
"discount_percent": 19.97
},
"seller": {
"business_id": "business-1",
"shop_id": "shop-1",
"rating": 4.8,
"rating_count": 40,
"reviews_count": 30,
"orders_count": 100,
"subscribers_count": 20,
"age_text": "2 года",
"logo_url": "https://cdn.example.org/shop-logo.png",
"legal_info": [
{
"legal_form": "ООО",
"name": "Учебный продавец",
"supplier_name": "Учебный поставщик",
"inn": "synthetic-inn",
"ogrn": "synthetic-ogrn"
}
]
},
"manufacturer": {
"brand_name": "Пример",
"legal_name": "Учебный производитель",
"country": "Россия"
},
"reviews": [
{
"id": "review-1",
"rating": 4,
"date_text": "12 октября 2026",
"pro": "Учебное достоинство",
"contra": null,
"comment": "Синтетический отзыв",
"votes_agree": 2,
"votes_reject": 0,
"media_urls": [
"https://cdn.example.org/review.jpg"
]
}
],
"rating_distribution": [
{
"value": 4,
"count": 2,
"percent": 0.25
}
],
"review_pagination": {
"loaded_count": 1,
"reported_review_count": 8,
"has_more": true,
"complete": false
},
"variants_complete": false,
"sections": {
"description": {
"state": "present",
"source": "productCardMeta.description"
},
"images": {
"state": "present",
"source": "gallery.mediaItems"
},
"specifications": {
"state": "present",
"source": "widgets.AboutFullSpecsListV2"
},
"price": {
"state": "present",
"source": "price.mainPrice"
},
"seller": {
"state": "present",
"source": "shopInfo"
},
"delivery": {
"state": "present",
"source": "offerAnalytics.allDeliveryOptions"
},
"rating": {
"state": "present",
"source": "reviews.ratingStats"
},
"reviews": {
"state": "present",
"source": "reviews.reviews"
},
"variants": {
"state": "unverified",
"source": "options"
}
},
"warnings": [
"unverified_variants",
"price_personal"
],
"fetch": {
"fetched_at": "2026-10-12T09:00:00Z",
"request_id": "11111111-1111-4111-8111-111111111111",
"attempts": 1,
"cache_hit": false
}
}
}
}Частичная карточка
Отсутствующие значения сохраняются как null, пустые списки — []. Неизвестное значение не равно 0 или false. Неполный раздел не делает всю карточку ошибкой: обязательные контейнеры сохраняются.
Состояния секций: present — данные присутствуют; empty — явно пустой раздел; missing — данных нет в источнике; unverified — полнота не проверена. Для variants состояние всегда unverified.
{
"status": "ok",
"data": {
"item_id": "000100000001",
"product_url": "https://market.yandex.ru/card/example-product/000100000001",
"title": "Синтетический чайник",
"brand": null,
"desc": null,
"currency": null,
"price_info": {
"sale_price": null,
"origin_price": null
},
"main_imgs": [
"https://cdn.example.org/kettle.jpg"
],
"main_videos": [],
"category_path": [],
"product_props": [
{
"group": "Общие",
"name": "Объём",
"value": "1.5 л"
}
],
"shop_info": {
"shop_name": null,
"shop_url": null
},
"delivery_info": {
"options": [],
"context": "anonymous_session"
},
"review_info": {
"rating_star": null,
"rating_count": null,
"review_count": null
},
"sku_props": [],
"skus": [],
"market": {
"schema_version": "1.0",
"ids": {
"osku_id": "000100000001",
"catalog_sku_id": null,
"product_id": null,
"offer_id": null,
"business_id": null,
"shop_id": null,
"supplier_id": null,
"vendor_id": null,
"category_id": null,
"navnode_id": null
},
"availability": {
"in_stock": null,
"available_count": null
},
"price_context": {
"anonymous": true,
"is_personal": null,
"discount_percent": null
},
"seller": {
"business_id": null,
"shop_id": null,
"rating": null,
"rating_count": null,
"reviews_count": null,
"orders_count": null,
"subscribers_count": null,
"age_text": null,
"logo_url": null,
"legal_info": []
},
"manufacturer": {
"brand_name": null,
"legal_name": null,
"country": null
},
"reviews": [],
"rating_distribution": [],
"review_pagination": {
"loaded_count": 0,
"reported_review_count": null,
"has_more": null,
"complete": false
},
"variants_complete": false,
"sections": {
"description": {
"state": "missing",
"source": null
},
"images": {
"state": "present",
"source": "gallery.mediaItems"
},
"specifications": {
"state": "missing",
"source": null
},
"price": {
"state": "missing",
"source": null
},
"seller": {
"state": "missing",
"source": null
},
"delivery": {
"state": "missing",
"source": null
},
"rating": {
"state": "missing",
"source": null
},
"reviews": {
"state": "empty",
"source": "reviews.reviews"
},
"variants": {
"state": "unverified",
"source": "options"
}
},
"warnings": [
"section_missing",
"unverified_variants"
],
"fetch": {
"fetched_at": "2026-10-12T09:00:00Z",
"request_id": "11111111-1111-4111-8111-111111111111",
"attempts": 1,
"cache_hit": false
}
}
}
}Справочник всех полей
Пути ниже отсчитываются от data. Идентификаторы — строки, денежные суммы — конечные неотрицательные decimal strings без символа валюты; nullable значения допускают null. Рейтинги — 0..5, счётчики — целые ≥0. URL — безопасные HTTPS-ссылки; BHAPI не загружает возвращённые медиа. Описание и отзывы — недоверенный текст, их нельзя исполнять как HTML.
Основные поля карточки
| Поле | Тип | Смысл |
|---|---|---|
item_id | string | OSKU карточки из пути URL; сохраняет ведущие нули. |
product_url | string | Каноническая ссылка карточки с значимым offer/shop query. |
title | string | Непустое название товара. |
brand | string | null | Бренд. |
desc | string | null | Описание как недоверенный текст. |
currency | string | null | Код валюты. |
main_imgs | array | Изображения карточки. |
main_videos | array | Видео карточки. |
price_info
| Поле | Тип | Смысл |
|---|---|---|
price_info | object | Цена выбранного оффера. |
price_info. | string | null | Текущая цена; строка "0" допустима. |
price_info. | string | null | Исходная цена. |
category_path
| Поле | Тип | Смысл |
|---|---|---|
category_path | array | Путь категорий. |
category_path[]. | string | null | Строковый идентификатор. |
category_path[]. | string | Название. |
category_path[]. | string | null | Безопасный HTTPS URL категории или null, если ссылка неизвестна. |
product_props
| Поле | Тип | Смысл |
|---|---|---|
product_props | array | Характеристики товара. |
product_props[]. | string | Группа характеристики. |
product_props[]. | string | Название. |
product_props[]. | string | Значение. |
shop_info
| Поле | Тип | Смысл |
|---|---|---|
shop_info | object | Публичные данные магазина. |
shop_info. | string | null | Название магазина. |
shop_info. | string | null | Ссылка магазина. |
delivery_info
| Поле | Тип | Смысл |
|---|---|---|
delivery_info | object | Доставка в анонимной сессии. |
delivery_info. | array | Наблюдаемые варианты доставки. |
delivery_info. | string | null | Способ доставки. |
delivery_info. | integer | null | Начало относительного интервала дней; не календарная дата. |
delivery_info. | integer | null | Конец относительного интервала дней. |
delivery_info. | string | null | Текст даты источника. |
delivery_info. | string | null | Стоимость как decimal string. |
delivery_info. | string | null | Код валюты. |
delivery_info. | string | null | Тип партнёра доставки. |
delivery_info. | anonymous_session | Всегда anonymous_session; адрес клиента отсутствует. |
review_info
| Поле | Тип | Смысл |
|---|---|---|
review_info | object | Сводные счётчики оценок и отзывов. |
review_info. | number | null | Рейтинг товара, 0..5. |
review_info. | integer | null | Число оценок, не число текстовых отзывов. |
review_info. | integer | null | Число отзывов по источнику; не длина загруженного списка. |
sku_props
| Поле | Тип | Смысл |
|---|---|---|
sku_props | array | Явно объявленные оси вариантов. |
sku_props[]. | string | Название. |
sku_props[]. | array | Значения оси. |
skus
| Поле | Тип | Смысл |
|---|---|---|
skus | array | Наблюдаемые варианты без построения декартова произведения. |
skus[]. | string | null | OSKU карточки из пути URL; сохраняет ведущие нули. |
skus[]. | string | null | Собственный ID оффера варианта; может отличаться от выбранного оффера основной карточки. |
skus[]. | string | null | Ссылка варианта; допускается /product--..., может отличаться от основного OSKU. |
skus[]. | dict[string,string] | Словарь строковых значений только по объявленным sku_props[].name; значения null запрещены. |
skus[]. | string | Значение объявленной оси варианта. |
skus[]. | string | null | Стоимость как decimal string. |
skus[]. | string | null | Код валюты. |
skus[]. | boolean | null | Наличие варианта; null означает неизвестно. |
market
| Поле | Тип | Смысл |
|---|---|---|
market | object | Расширенный контракт Маркета. |
market. | 1.0 | Всегда "1.0". |
market. | false | Всегда false; полный набор вариантов не гарантируется. |
market. | array | Только закрытые коды предупреждений, перечисленные ниже. |
market.ids
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Независимые идентификаторы; не подменяют OSKU. |
market. | string | Обязательный OSKU; совпадает с item_id. |
market. | string | null | ID каталожного SKU; отличается от OSKU. |
market. | string | null | ID каталожного продукта. |
market. | string | null | ID выбранного оффера; при do-waremd5 обязан совпасть с запросом. |
market. | string | null | ID бизнеса продавца. |
market. | string | null | ID магазина; не подменяет business_id. |
market. | string | null | ID поставщика. |
market. | string | null | ID бренда/производителя. |
market. | string | null | ID категории. |
market. | string | null | ID навигационного узла. |
market.availability
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Наличие по источнику, без вывода из цены. |
market. | boolean | null | Наличие товара; null неизвестно. |
market. | integer | null | Доступное количество; null неизвестно. |
market.price_context
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Условия наблюдаемой цены. |
market. | true | Всегда true. |
market. | boolean | null | Флаг персональной цены источника; может быть true при anonymous=true. |
market. | number | null | Конечное число 0..100; отдельная шкала скидки. |
market.seller
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Публичная статистика продавца. |
market. | string | null | ID бизнеса продавца. |
market. | string | null | ID магазина; не подменяет business_id. |
market. | number | null | Рейтинг 0..5. |
market. | integer | null | Число оценок, не число текстовых отзывов. |
market. | integer | null | Число отзывов продавца. |
market. | integer | null | Заказы продавца; не являются продажами товара. |
market. | integer | null | Число подписчиков продавца. |
market. | string | null | Возраст продавца как исходный текст. |
market. | string | null | Ссылка публичного логотипа. |
market.seller.legal_info
| Поле | Тип | Смысл |
|---|---|---|
market. | array | Только опубликованные реквизиты продавца. |
market. | string | null | Организационная форма. |
market. | string | null | Название. |
market. | string | null | Название поставщика. |
market. | string | null | Опубликованный ИНН. |
market. | string | null | Опубликованный ОГРН. |
market.manufacturer
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Данные производителя. |
market. | string | null | Название бренда. |
market. | string | null | Юридическое название. |
market. | string | null | Страна. |
market.reviews
| Поле | Тип | Смысл |
|---|---|---|
market. | array | Только загруженные отзывы, без профилей авторов. |
market. | string | Строковый идентификатор. |
market. | number | null | Рейтинг 0..5. |
market. | string | null | Дата отзыва как исходный текст. |
market. | string | null | Достоинства как недоверенный текст. |
market. | string | null | Недостатки как недоверенный текст. |
market. | string | null | Комментарий как недоверенный текст. |
market. | integer | null | Число согласных голосов. |
market. | integer | null | Число несогласных голосов. |
market. | array | Публичные HTTPS-ссылки медиа отзыва. |
market.rating_distribution
| Поле | Тип | Смысл |
|---|---|---|
market. | array | Распределение оценок в исходной шкале. |
market. | integer | Целая оценка 1..5. |
market. | integer | null | Число оценок в группе. |
market. | number | null | Конечное неотрицательное число без гарантии шкалы: 0.25 сохраняется, не превращается в 25. |
market.review_pagination
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Полнота наблюдаемых отзывов. |
market. | integer | Ровно len(market.reviews). |
market. | integer | null | Общий счётчик источника; может отличаться от review_info.review_count. |
market. | boolean | null | Признак дополнительных отзывов; null неизвестно. |
market. | false | Всегда false в версии 1.0. |
market.sections
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Девять закрытых разделов с state/source. |
market.sections.description
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Именованный раздел; объект state/source. |
market. | string | present | empty | missing | unverified; variants только unverified. |
market. | string | null | Логическая метка источника или null; допустимые значения перечислены ниже. |
market.sections.images
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Именованный раздел; объект state/source. |
market. | string | present | empty | missing | unverified; variants только unverified. |
market. | string | null | Логическая метка источника или null; допустимые значения перечислены ниже. |
market.sections.specifications
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Именованный раздел; объект state/source. |
market. | string | present | empty | missing | unverified; variants только unverified. |
market. | string | null | Логическая метка источника или null; допустимые значения перечислены ниже. |
market.sections.price
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Стоимость как decimal string. |
market. | string | present | empty | missing | unverified; variants только unverified. |
market. | string | null | Логическая метка источника или null; допустимые значения перечислены ниже. |
market.sections.seller
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Публичная статистика продавца. |
market. | string | present | empty | missing | unverified; variants только unverified. |
market. | string | null | Логическая метка источника или null; допустимые значения перечислены ниже. |
market.sections.delivery
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Именованный раздел; объект state/source. |
market. | string | present | empty | missing | unverified; variants только unverified. |
market. | string | null | Логическая метка источника или null; допустимые значения перечислены ниже. |
market.sections.rating
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Рейтинг 0..5. |
market. | string | present | empty | missing | unverified; variants только unverified. |
market. | string | null | Логическая метка источника или null; допустимые значения перечислены ниже. |
market.sections.reviews
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Только загруженные отзывы, без профилей авторов. |
market. | string | present | empty | missing | unverified; variants только unverified. |
market. | string | null | Логическая метка источника или null; допустимые значения перечислены ниже. |
market.sections.variants
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Именованный раздел; объект state/source. |
market. | unverified | present | empty | missing | unverified; variants только unverified. |
market. | string | null | Логическая метка источника или null; допустимые значения перечислены ниже. |
market.fetch
| Поле | Тип | Смысл |
|---|---|---|
market. | object | Данные исходного получения и корреляции. |
market. | string | UTC timestamp исходного получения. |
market. | string | Серверный идентификатор текущей операции. |
market. | integer | Внутренние попытки источника; не количество списаний BHAPI. |
market. | boolean | Ожидается false; true нарушает принудительное обновление и даёт 502. |
Допустимые source: productCardMeta.description, gallery.mediaItems, widgets.AboutFullSpecsListV2, price.mainPrice, shopInfo, offerAnalytics.allDeliveryOptions, deliveryInfoOption, reviews.ratingStats, reviews.reviews, options или null.
Коды warnings: review_count_mismatch, jsonld_conflict, unverified_variants, price_personal, section_missing. Они не гарантируют полноту или отсутствие конфликтов в источнике.
Поиск товаров по словам
Один запрос возвращает одну страницу поисковой выдачи. Поиск по картинке не поддерживается. Результат содержит данные сниппетов: полная карточка каждого товара отдельно не загружается. Для подробностей передайте возвращённый product_url в метод карточки выше.
/yamarket/api/v1/searchНужны те же заголовки X-API-Token и Content-Type: application/json, что и для карточки. Тело — JSON-объект до 16 КиБ. Неизвестные свойства, в том числе во вложенном фильтре, запрещены. Типы строгие: строки вместо чисел или boolean не принимаются; ID передавайте строками. Публичный параметр no_cache запрещён с любым значением: BHAPI запрашивает свежий результат автоматически.
| Поле | Тип / по умолчанию | Правила |
|---|---|---|
query | string, обязательно | После удаления пробелов по краям 1–512 символов. Поисковые слова, не URL. |
sort | string / popular | popular — популярные; price_asc — подешевле; price_desc — подороже; rating_desc — высокий рейтинг. |
page | integer / 1 | 1–1000, без строк, дробей и boolean. Доступность страницы определяет Маркет. |
include_filters | boolean / true | При false ключ filters отсутствует в ответе. Входные фильтры всё равно применяются и проверяются; selected_filters остаётся. |
category_ids | string[] / [] | До 20 уникальных ID, каждый 1–32 цифры. Это категории hid, не навигационные узлы nid и не ID фильтров. |
price_from | number | null / null | Нижняя граница в рублях, конечное число ≥0, без строки с валютой. |
price_to | number | null / null | Верхняя граница в рублях, конечное число ≥0. Если заданы обе: price_from <= price_to. NaN/Infinity запрещены. |
filters | object[] / [] | Не больше одного enum-фильтра: {"id":"123","value_id":"456"}. Оба поля обязательны, каждое — строка из 1–32 цифр. Несколько значений одного enum не поддерживаются. |
null разрешён во входе только для границ цены. Не копируйте произвольные параметры URL Маркета в JSON. Отсутствие category_ids или [] отдаёт выбор категорий Маркету.
Фильтры: получить, выбрать, применить
/yamarket/api/v1/search/filtersМетод возвращает расширенную форму фильтров первой страницы для текущего поиска. Принимает query, sort, category_ids, price_from, price_to и filters по тем же правилам. Поля page и include_filters здесь запрещены. Ответ не содержит items и pagination.
- Выполните поиск с нужным
query. Сохраните возвращённыеcategory_idsи условия поиска. - Для расширенной формы вызовите метод фильтров с теми же query, sort, категориями, ценой и текущим enum. Не отправляйте page/include_filters.
- Найдите descriptor с
supported=true,type="enum"и цифровымid. Покажите пользователюnameи вариантыvalues[].label. Передавайте выбранныйvalues[].idкакvalue_id; названия не заменяют ID. - Повторите поиск с выбранным enum, сохранив категории и остальные условия. При изменении выбора сбрасывайте
pageв1. - Проверьте HTTP 200,
status="ok"и подтверждённыеselected_filters,price_from,price_to. Если выбор не подтверждён Маркетом, BHAPI вернёт HTTP 422 сreason="filter_not_applied", а не успешную неотфильтрованную выдачу.
ID динамические: берите их из своего актуального ответа для того же контекста. ID категории, фильтра и значения — разные сущности. Примеры ниже синтетические, их ID не являются постоянным справочником.
id="glprice" с supported=true означает поддержку цены через отдельные price_from/price_to. Передавать glprice внутри filters нельзя. Специальные range/boolean/radio и другие неподдерживаемые фильтры могут отображаться с supported=false: наличие на сайте не означает поддержку на входе API. Диапазоны характеристик, фильтры доставки/оплаты и несколько enum пока не принимаются.
Для сброса enum передайте filters: [] или опустите поле. Для сброса цены опустите границу либо задайте null. Начинайте с первой страницы. Сохраняйте категории, чтобы продолжить тот же поиск. Сервер не хранит активный выбор между запросами: для каждой страницы передавайте все условия снова.
Как читать поисковый ответ
Успех: {"status":"ok","data":SearchData}. Данные находятся непосредственно в data, без вложенных code, msg или второго data. Поля ниже указаны относительно data.
| Поле | Тип | Смысл |
|---|---|---|
query | string | Подтверждённый текст текущего поиска после trim. |
sort, sort_options | string | Оба содержат активный код сортировки, например popular. sort_options — строка, не список доступных сортировок. |
category_ids | string[] | Категории текущей выдачи. [] — категории не опубликованы; это не отсутствие категории у всех товаров. |
selected_filters | object[] | Подтверждённый выбор: 0–1 объект с полями id и value_id. |
price_from, price_to | number | null | Подтверждённые выбранные границы, не диапазон фактических цен товаров. |
items | object[] | Одна страница сниппетов в порядке Маркета. Одинаковые канонические URL удаляются; разные офферные контексты могут сохраниться. |
items[].item_id | string | OSKU из /card/…, не каталоговый SKU/model ID. |
items[].title | string | Непустое название текущего сниппета. |
items[].product_url | string | HTTPS-ссылка товара; tracking удалён, допустимый контекст оффера/магазина сохранён. |
items[].price | number | null | Показанная цена текущего сниппета, например 985.45. null — неизвестно, не 0. |
items[].currency | string | null | RUB при распознанном рублёвом обозначении, не выводится из границ цены. |
items[].image_url | string | null | URL картинки сниппета, без скачивания изображения. |
items[].rating | number | null | Рейтинг товара 0–5, не магазина. |
items[].price_type | string | null | yandex_pay — явно указана цена с картой Яндекс Пэй; displayed — без такого обозначения; null — цена не распознана. |
items[].shop_name | string | null | Название магазина, если опубликовано. |
Цена относится к анонимной сессии. Доступность цены с картой Яндекс Пэй конкретному покупателю не гарантируется; цену без Пэй и скидку по промокоду API не рассчитывает. Счётчики отзывов, оценок и покупок, тексты цены и доставки в поисковых товарах не возвращаются. Для подробностей используйте отдельный запрос карточки, который расходует свою единицу квоты.
Пагинация и сортировка
| Поле | Тип | Смысл |
|---|---|---|
pagination.page | integer | Подтверждённая фактическая страница; совпадает с запрошенной. |
pagination.items_per_page | integer | null | Размер страницы по Маркету; не обязан совпадать с len(items). |
pagination.total | integer | null | Текущая оценка количества результатов, не аудит уникальных SKU. |
pagination.page_count | integer | null | Число страниц источника, без собственного пересчёта. |
pagination.has_next_page | boolean | null | true — следующая страница есть; false — нет; null — неизвестно. |
Для следующей страницы меняйте только page. При смене запроса, сортировки, категорий, enum или цены начинайте с page=1. Размер страниц непостоянный; не рассчитывайте offset или page_count из фиксированного размера. Отсутствующие/некорректные необязательные счётчики становятся null, а не нулём. Подтверждённая пустая выдача — успешный ответ с items=[] и total=0.
Сортирует Маркет, API сохраняет порядок сниппетов. Реклама, особые цены и изменения ассортимента влияют на порядок: строгая монотонность price/rating не обещается. Между запросами порядок, товары и totals могут измениться; snapshot/cursor нет. При обходе страниц учитывайте возможные повторы/пропуски и не считайте полученный список полным каталогом.
Дескрипторы фильтров
| Поле | Тип | Смысл |
|---|---|---|
filters[].id | string | ID фильтра. У специальных неподдерживаемых фильтров может быть нецифровым. |
filters[].name | string | Название; пустая строка при отсутствии. |
filters[].type | string | enum/range/boolean/radio и другие; unknown, если тип отсутствует. |
filters[].unit | string | null | Единица измерения, например ₽; null при отсутствии. |
filters[].supported | boolean | Можно применить цифровой enum через filters или glprice через отдельные границы цены. |
filters[].values | object[] | Опубликованные варианты текущего поиска, без гарантии полного списка. |
filters[].values[].id | string | ID варианта; для входного enum только 1–32 цифры. |
filters[].values[].label | string | Подпись варианта; может быть пустой. |
filters[].values[].selected | boolean | Вариант отмечен выбранным в текущем контексте. |
filters[].values[].found | integer | null | Счётчик источника для варианта; не общий total и не гарантия уникальных товаров. |
filters[].selected_value_ids | string[] | Текущий выбор источника; специальные фильтры могут содержать нецифровые ID. |
filters[].lazy | boolean | null | Только /search: признак ленивой загрузки; null — неизвестно. |
filters[].loaded | boolean | null | Только /search: признак загрузки списка; true не доказывает полноту. |
filters[].complete | boolean | Только /search: всегда false, полнота не доказана. |
В /search/filters у дескрипторов отсутствуют lazy, loaded и complete. Расширенная форма и непустой values не означают полный список. В обоих поисковых методах нет служебных warnings/fetch. При include_filters=false в /search ключ filters полностью исключён, а не заменён null или [].
Синтетический успешный ответ с одной позицией; это пример формы, не обещание товара, цены или ID:
{
"status": "ok",
"data": {
"query": "coffee",
"sort": "popular",
"category_ids": [
"123"
],
"selected_filters": [],
"price_from": null,
"price_to": null,
"sort_options": "popular",
"pagination": {
"page": 1,
"items_per_page": 8,
"total": 17,
"page_count": 2,
"has_next_page": true
},
"items": [
{
"item_id": "123",
"title": "Coffee",
"product_url": "https://market.yandex.ru/card/test/123?do-waremd5=offer123",
"price": 1303.0,
"currency": "RUB",
"image_url": "https://avatars.mds.yandex.net/get-mpic/123/example/orig",
"rating": 4.8,
"price_type": "yandex_pay",
"shop_name": "Example shop"
}
],
"filters": [
{
"id": "7893318",
"name": "Brand",
"type": "enum",
"unit": null,
"supported": true,
"values": [
{
"id": "456",
"label": "Example brand",
"selected": false,
"found": null
}
],
"selected_value_ids": [],
"lazy": true,
"loaded": false,
"complete": false
},
{
"id": "glprice",
"name": "Price",
"type": "range",
"unit": "RUB",
"supported": true,
"values": [],
"selected_value_ids": [],
"lazy": null,
"loaded": null,
"complete": false
}
]
}
}Пример ответа метода фильтров для того же контекста:
{
"status": "ok",
"data": {
"query": "coffee",
"sort": "popular",
"category_ids": [
"123"
],
"selected_filters": [],
"price_from": null,
"price_to": null,
"sort_options": "popular",
"filters": [
{
"id": "7893318",
"name": "Brand",
"type": "enum",
"unit": null,
"supported": true,
"values": [
{
"id": "456",
"label": "Example brand",
"selected": false,
"found": null
}
],
"selected_value_ids": []
},
{
"id": "glprice",
"name": "Price",
"type": "range",
"unit": "RUB",
"supported": true,
"values": [],
"selected_value_ids": []
}
]
}
}При ошибке фильтра публичный ответ имеет нейтральную форму; внутренние filter_ids и текст источника не передаются:
{"detail":{"code":422,"reason":"filter_not_applied","message":"Применение выбранного фильтра или цены не подтверждено."}}Примеры: от поиска до применения фильтра
cURL (Bash): первый запрос получает категории, второй — расширенную форму. Третий использует только синтетические ID: замените их ID из своего ответа. В PowerShell используйте curl.exe и UTF-8 JSON-файл через --data-binary @request.json, чтобы сохранить кавычки и кириллицу. Не сохраняйте токен в общем файле.
curl --max-time 180 --fail-with-body -i -X POST "https://bhapi.ru/yamarket/api/v1/search" \
-H "X-API-Token: $BHAPI_TOKEN" -H "Content-Type: application/json" \
--data '{"query":"кофе","sort":"popular","page":1}'
# Замените category_ids категориями из первого ответа.
curl --max-time 180 --fail-with-body -i -X POST "https://bhapi.ru/yamarket/api/v1/search/filters" \
-H "X-API-Token: $BHAPI_TOKEN" -H "Content-Type: application/json" \
--data '{"query":"кофе","sort":"popular","category_ids":["123"]}'
# Замените все ID актуальными; синтетический пример применения enum + цены.
curl --max-time 180 --fail-with-body -i -X POST "https://bhapi.ru/yamarket/api/v1/search" \
-H "X-API-Token: $BHAPI_TOKEN" -H "Content-Type: application/json" \
--data '{"query":"кофе","sort":"price_asc","category_ids":["123"],"page":1,"price_from":100,"price_to":2000,"filters":[{"id":"7893318","value_id":"456"}],"include_filters":false}' Python: выберите нужный вариант осмысленно по name/label. Пример показывает варианты и запрашивает ID у оператора, проверяет их наличие и поддержку. Каждый успешный вызов, включая discovery, расходует единицу квоты; получение второй страницы — ещё один вызов.
import os
import requests
BASE = "https://bhapi.ru/yamarket/api/v1"
HEADERS = {"X-API-Token": os.environ["BHAPI_TOKEN"]}
def call(method, params):
response = requests.post(f"{BASE}/{method}", headers=HEADERS,
json=params, timeout=180)
if response.status_code != 200:
print("HTTP", response.status_code, "Retry-After:",
response.headers.get("Retry-After"), "X-Request-ID:",
response.headers.get("X-Request-ID"))
print(response.json().get("detail"))
response.raise_for_status()
result = response.json()
if result.get("status") != "ok" or not isinstance(result.get("data"), dict):
raise RuntimeError("Неожиданный формат ответа")
return result["data"]
context = {"query": "кофе", "sort": "popular"}
first = call("search", {**context, "page": 1})
context["category_ids"] = first["category_ids"]
form = call("search/filters", context)
choices = {}
for descriptor in form["filters"]:
if (descriptor["supported"] and descriptor["type"] == "enum"
and descriptor["id"].isascii() and descriptor["id"].isdigit()):
for value in descriptor["values"]:
if value["id"].isascii() and value["id"].isdigit():
choices[(descriptor["id"], value["id"])] = value
print(descriptor["id"], descriptor["name"],
value["id"], value["label"])
if not choices:
raise RuntimeError("Подходящий enum в этом контексте не опубликован")
filter_id = input("ID нужного фильтра: ").strip()
value_id = input("ID нужного значения: ").strip()
if (filter_id, value_id) not in choices:
raise ValueError("Выберите опубликованные ID из списка выше")
selection = [{"id": filter_id, "value_id": value_id}]
params = {**context, "filters": selection, "price_from": 100,
"price_to": 2000, "include_filters": False, "page": 1}
result = call("search", params)
if (result["selected_filters"] != selection or result["price_from"] != 100
or result["price_to"] != 2000 or result["pagination"]["page"] != 1):
raise RuntimeError("Условия ответа не совпадают с запросом")
for item in result["items"]:
print(item["title"], item["price"], item["price_type"], item["product_url"])
# Никакого фиксированного размера страниц и автоматического обхода всего каталога.
if result["pagination"]["has_next_page"] is True:
second = call("search", {**params, "page": 2})
print("Вторая страница:", len(second["items"]))Не выполняйте автоматический повтор после сетевого timeout: завершение операции и списание могут быть уже подтверждены. Сверьте кабинет и X-Request-ID. При HTTP 422 filter_not_applied обновите доступные фильтры в том же контексте и пересмотрите выбор; не используйте ответ как отфильтрованную выдачу. При 429 проверьте Retry-After, ограничения и оставшуюся квоту.
Правила URL
Принимается HTTPS URL https://market.yandex.ru/card/<slug>/<osku_id>, порт 443 допустим. OSKU — строка с сохранением ведущих нулей, а не catalog_sku_id. Во входе метода карточки короткие ссылки, URL каталога/поиска, редиректы и пути /product--... не поддерживаются. В ссылках возвращённых SKU путь /product--... допустим: это ссылки вариантов, а не вход основного метода.
Сохраняются параметры выбора оффера/магазина: do-waremd5, resale_goods, businessId, shopId, isShopInShop. Значения непустые, до 512 символов; дубликаты этих ключей запрещены, всего не более 100 query-полей. Tracking и fragment удаляются. Без do-waremd5 возвращается выбранный источником оффер, без гарантии минимальной цены.
Дополнительные поля JSON и неизвестные query-поля метода дают 422.
Ошибки
Таблицы и примеры этого раздела описывают ошибки legacy (формат по умолчанию).
Чтобы получать единый контракт ошибок, передайте заголовок
X-BHAPI-Error-Format: problem-v1.
Статусы некоторых ошибок в этом режиме отличаются; успешные ответы не меняются.
Общий справочник кодов, форматов и действий клиента →
{"detail":{"code":502,"reason":"upstream_invalid_response","message":"Сервис Яндекс.Маркет вернул некорректный ответ."}}Код HTTP — категория ошибки; reason, если присутствует, уточняет причину. В ошибках авторизации, локальных лимитов и общем ответе 500 формат detail может отличаться от примера.
| Код | Причина |
|---|---|
| 400 | query_token_deprecated: токен в параметре URL не принимается; используйте заголовок X-API-Token. |
| 401/403 | Токен отсутствует, отозван или нет доступа к парсеру. |
| 404 | item_not_found: товар не найден; это подтверждённый ответ о товаре. |
| 413 | request_too_large: тело запроса больше 16 КиБ. |
| 415 | unsupported_media_type: нужен Content-Type: application/json. |
| 417 | item_fetch_failed: не удалось подтвердить выбранное предложение товара. |
| 422 | invalid_request: проверьте поля и типы выбранного метода; для карточки — поле url и допустимый путь.filter_not_applied: выбранный enum или цена не подтверждены. Обновите фильтры и проверьте контекст поиска. |
| 429 | Локальный лимит токена или квота тарифа: проверьте кабинет. Ограничение частоты по IP: снизьте общую частоту запросов с этого IP. parser_overloaded: очередь или ограничение источника. Если есть Retry-After, выдержите указанную паузу; не повторяйте автоматически. |
| 500 | Неожиданная ошибка BHAPI; reason может содержать internal_error или отсутствовать. Передайте X-Request-ID и время запроса в поддержку. |
| 502 | upstream_invalid_response: ответ не прошёл проверку формата или соответствия запрошенному товару. |
| 503 | upstream_unavailable: парсер выключен, не настроен либо источник временно недоступен.upstream_unreachable: ошибка соединения с источником.upstream_access_restricted: источник ограничил доступ; это не ошибка API-токена клиента BHAPI.parser_internal_error: внутренняя ошибка парсера. |
| 504 | upstream_timeout: истекло время ожидания операции или чтения ответа. |
Для parser_overloaded заголовок Retry-After — целые секунды 1..3600, по умолчанию 1; Cache-Control: no-store. Для item_not_found также no-store. Локальные лимиты и квоты с кодом 429 сохраняют собственную политику заголовков. Исходные технические сообщения не возвращаются.
Квота и ограничения
Один успешный запрос любого метода расходует одну единицу квоты Яндекс.Маркет: карточка, страница поиска (включая пустую выдачу) или получение фильтров. Отдельного списания за каждый товар в items нет; каждый дополнительный вызов карточки или страницы — новая единица. Ошибка парсинга или отмена до подтверждения результата не расходует успешную квоту. Лимиты и доступ берутся из назначенного тарифа в кабинете.
Если результат уже подтверждён, сбой журнала или отмена после подтверждения может привести к ошибке ответа и сохранённому однократному списанию. Автоматический возврат не гарантируется. Для сверки передайте в поддержку X-Request-ID из заголовков ответа и время запроса; не отправляйте запрос повторно автоматически при неясном исходе.
Поиск возвращает одну страницу за запрос. Batch и метод карточки по ID не поддерживаются. Полный набор отзывов и вариантов не гарантируется: review_pagination.complete и variants_complete всегда false. rating_count — оценки, review_count — отзывы; loaded_count — только загруженные отзывы. seller.orders_count относится к продавцу и не является продажами товара. Округлённые счётчики нельзя преобразовывать в точные числа.
Цена, наличие и доставка относятся к анонимной сессии (anonymous_session). Регион клиента, персональная цена и минимальная цена не гарантируются. fetched_at/cache_hit не гарантируют актуальность цены на стороне самого Маркета. Nullable наличие нельзя выводить из цены.
Пример Python
import requests
response = requests.post(
"https://bhapi.ru/yamarket/api/v1/item/detail-by-url",
headers={"X-API-Token": "ВАШ_API_TOKEN"},
json={"url": "https://market.yandex.ru/card/example-product/000100000001"},
timeout=180,
)
if response.status_code == 429:
print("Пауза, секунд:", response.headers.get("Retry-After"))
response.raise_for_status()
price = response.json()["data"]["price_info"]["sale_price"]
print("Цена неизвестна" if price is None else price)Пример JavaScript
Только для собственного доверенного приложения: токен нельзя размещать в публичном frontend. Общедоступный сайт вызывает API через свой backend.
const response = await fetch("https://bhapi.ru/yamarket/api/v1/item/detail-by-url", {
method: "POST",
headers: {"Content-Type": "application/json", "X-API-Token": "ВАШ_API_TOKEN"},
body: JSON.stringify({url: "https://market.yandex.ru/card/example-product/000100000001"}),
signal: AbortSignal.timeout(180000)
});
if (response.status === 429) console.log("Пауза, секунд:", response.headers.get("Retry-After"));
if (!response.ok) throw new Error("Ошибка API: " + response.status);
const price = (await response.json()).data.price_info.sale_price;
console.log(price === null ? "Цена неизвестна" : price);Пример PHP
<?php
$retryAfter = null;
$ch = curl_init("https://bhapi.ru/yamarket/api/v1/item/detail-by-url");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/json", "X-API-Token: ВАШ_API_TOKEN"],
CURLOPT_POSTFIELDS => json_encode(["url" => "https://market.yandex.ru/card/example-product/000100000001"], JSON_THROW_ON_ERROR),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 180,
CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$retryAfter) {
if (stripos($line, "Retry-After:") === 0) $retryAfter = trim(substr($line, 12));
return strlen($line);
}
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status === 429) echo "Пауза, секунд: " . ($retryAfter ?? "не указана");
if ($body === false || $status !== 200) throw new RuntimeException("Ошибка API: " . $status);
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo $data["data"]["price_info"]["sale_price"] ?? "Цена неизвестна";