API для продавцов
API кабинета продавца позволяет подключить 1С, МойСклад или свою учётную систему: выгружать список товаров, менять цены и остатки, снимать товары с продажи и возвращать их, отмечать продажи, загружать товары пачкой, отвечать на отзывы и сообщения покупателей, забирать статистику. Через API можно ровно то же, что в кабинете, — владение объявлениями, модерация и лимиты проверяются так же; кое-что по ключу недоступно вовсе (см. ниже).
Как получить ключ
- Кабинет продавца → Настройки → API-ключи для интеграций. Раздел есть только у бизнес-аккаунта.
- Ключу даётся имя (например, «1С склад»), чтобы потом понимать, какой отзывать. Действующих ключей — не больше 10.
- Секрет вида
plk_…показывается один раз, сразу после выпуска. Потеряли — отзовите ключ и выпустите новый. - Отозвать ключ можно там же. Отзыв начинает действовать в течение 5 минут.
- «Выйти со всех устройств» и смена пароля ключ не отзывают, но прерывают
доступ по нему: до 5 минут запросы получают
401 «Требуется вход», потом ключ снова работает. Чтобы закрыть доступ интеграции, ключ нужно отозвать.
Авторизация
Ключ передаётся в каждом запросе заголовком:
Authorization: Bearer plk_…
Ключ работает только с API продавца (адреса, начинающиеся с
https://opolka.ru/api/seller) и не заменяет вход на сайте. Храните его как
пароль: не кладите в код, который видят покупатели, и не пересылайте в
переписке.
Базовый адрес и формат
- Базовый адрес:
https://opolka.ru/api/seller. Все пути ниже — относительно него. - Только HTTPS. Тела запросов и ответы — JSON в UTF-8, заголовок
Content-Type: application/jsonдля запросов с телом. - Ошибки —
{"detail": "…"}с текстом по-русски; у ошибок проверки формы (422)detail— список полей. - Цены — целые рубли. Даты и время — ISO 8601 (UTC).
idтовара — число из списка товаров; оно подставляется в путь остальных запросов.
В примерах:
KEY="plk_…"
API="https://opolka.ru/api/seller"
Товары
GET /catalog — список товаров
curl -H "Authorization: Bearer $KEY" \
"$API/catalog?status=active&per_page=200&page=1"
Параметры (все необязательные):
status—all(по умолчанию),active(в продаже),waiting(черновики, на проверке, истёкшие),archive(снятые, проданные, снятые модератором);q— подстрока названия (до 200 символов);sort—created(новые выше, по умолчанию),views,contacts(больше просмотров/контактов за 30 дней выше),price(дешёвые выше);page— с 1;per_page— от 1 до 200, по умолчанию 50.
Ответ:
{
"items": [
{
"id": 123, "title": "Стул дубовый", "price": 4900, "currency": "RUB",
"stock": 12, "status": "active", "bucket": "active",
"is_expired": false, "expires_at": "2026-10-25T09:00:00",
"created_at": "2026-09-25T09:00:00", "city": "Москва",
"cover_url": "https://…", "seo_path": "/moskva/…/stul-dubovyy_123",
"views": 340, "favorites": 5, "chats": 3,
"barcode": null, "moderation_reason": null, "reject_reason": null,
"metrics_30d": {"views": 120, "phone_reveals": 8, "chats_started": 2}
}
],
"total": 1, "page": 1, "per_page": 200, "metrics_days": 30,
"counts": {"all": 40, "active": 31, "waiting": 6, "archive": 3}
}
status— состояние объявления:draft,moderation,active,archived,sold,blocked(снято модератором).bucket— вкладка кабинета, по ней работает фильтрstatus.stock— остаток;null, если остаток не ведётся.variants— варианты товара ([{"label", "price", "stock"}],nullбез вариантов);price_by_variants— цена товара = минимум цен вариантов,stock_by_variants— остаток = сумма остатков вариантов. Такие цену и остаток быстрой правкой не изменить, а продажа указывает вариант (см. «Цены и остатки» и «Продажи»).total— сколько товаров подходит под фильтр;counts— по вкладкам по всему каталогу, без учёта фильтров.- Цена и остаток в списке — актуальные, сразу после правки.
metrics_30d(просмотры, показы телефона, начатые переписки за 30 дней) обновляются с задержкой до часа.
GET /catalog/by-barcode — найти товар по штрихкоду
curl -H "Authorization: Bearer $KEY" \
"$API/catalog/by-barcode?code=4600000000003"
code— штрихкод: EAN-8, UPC-A (12 цифр), EAN-13 или GTIN-14; пробелы и дефисы отбрасываются. GTIN-14 с ведущим нулём ищется как EAN-13.- Ищутся только ваши товары — по штрихкоду товара и его вариантов, во всех статусах.
- Ответ:
{"code": "4600000000003", "items": […], "metrics_days": 30}—code— код, по которому искали (без пробелов и дефисов),items— товары в том же виде, что вGET /catalog. Не нашлось — пустойitems, не404. - Не штрихкод или не сходится контрольная цифра —
400 «Неверный штрихкод».
Цены и остатки
PATCH /catalog/{id}
curl -X PATCH -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"price": 1490, "stock": 12}' "$API/catalog/123"
- Оба поля необязательны: отсутствующее не меняется. Цена — целое число
больше нуля (иначе
422), каждое изменение цены попадает в историю цен. - Остаток — целое число; отрицательное сохраняется как 0. Объявление с
нулевым остатком с витрины само не снимается — снимите его действием
archive, если нужно. - Если остаток задан у вариантов товара (
stock_by_variants: trueв карточке), общий остаток — их сумма и здесь не меняется: запрос сstockцеликом получит422, цена из него тоже не сохранится. Так же с ценой у товара с вариантами (price_by_variants: true): она — минимум цен вариантов, запрос с другойprice—422. Совпадающее с текущим значение — не правка, ошибки нет. Цены и остатки вариантов правятся в карточке товара на сайте; варианты коротко (label,price,stock) есть в карточке списка — полеvariants. - Ответ — карточка товара в том же виде, что в списке (без
bucketиmetrics_30d). У снятого модератором —403.
Действия с товаром
curl -X POST -H "Authorization: Bearer $KEY" "$API/catalog/123/archive"
curl -X POST -H "Authorization: Bearer $KEY" "$API/catalog/123/publish"
curl -X POST -H "Authorization: Bearer $KEY" "$API/catalog/123/sold"
curl -X POST -H "Authorization: Bearer $KEY" "$API/catalog/123/duplicate"
curl -X DELETE -H "Authorization: Bearer $KEY" "$API/catalog/123"
archive— снять с продажи (в архив, без удаления).publish— опубликовать черновик или вернуть в продажу из архива, продлевает срок размещения. Нужен подтверждённый email аккаунта; незаполненные обязательные поля —422 «Заполните обязательные поля: …». Текст проходит автопроверку: объявление может уйти на модерацию (status: moderation).sold— отметить проданным целиком: объявление уходит с витрины в архив, записывается продажа одной единицы по текущей цене. Только для активного, иначе400. Чтобы продать часть остатка и оставить товар в продаже — см. «Продажи».duplicate— копия черновиком (фото, характеристики и остаток переносятся, штрихкод — нет); ответ201с карточкой копии.DELETE— удалить навсегда вместе с фото, чатами и продажами по нему; ответ204без тела.- Действия отвечают карточкой товара. Снятое модератором объявление не
публикуется и не архивируется (
403).
Массовые действия
POST /catalog/bulk
curl -X POST -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"ids": [123, 124, 125], "action": "archive"}' "$API/catalog/bulk"
ids— до 100 id за запрос;action—archive,publish,renew(продлить срок активным),delete.- Ответ — счётчики:
{"done": 2, "skipped": 1}. Пропускаются чужие id и неподходящие по состоянию (архивировать можно только активное, публиковать — черновик или архив с заполненными обязательными полями, продлевать — активное). - Какие именно пропущены, ответ не сообщает — сверьте статусы повторным
GET /catalog.
Продажи
Продажа фиксируется продавцом — оплаты через площадку нет. Продажа без снятия с витрины уменьшает остаток и попадает в отчёт о выручке.
POST /sales/{id товара} — отметить продажу
curl -X POST -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"quantity": 2, "price": 1400}' "$API/sales/123"
quantity— от 1 до 10 000, по умолчанию 1;price— цена за единицу, целое ≥ 0, по умолчанию цена варианта (если указан) или текущая цена объявления.variant_label— вариант товара (егоlabelизvariantsкарточки, без учёта регистра, до 40 символов). Если остаток ведётся по вариантам (stock_by_variants: true), вариант обязателен —422 «Укажите вариант: остаток ведётся по вариантам», и списывается остаток именно этого варианта. Неизвестный вариант —422 «Варианта «…» нет в объявлении».- Только для активного объявления, иначе
400. Если остаток ведётся, он уменьшается наquantity; продать больше остатка (варианта) нельзя —400 «Продано больше, чем есть в наличии». Объявление остаётся в продаже и при нулевом остатке. - Ответ
201:{"id", "listing_id", "price", "quantity", "sold_at", "listing_published_at", "variant_label"}. Сохранитеid— он нужен для отмены.
DELETE /sales/{id товара}/{id продажи} — отменить продажу
Ответ 204. Количество возвращается в остаток, если он ведётся; у продажи
варианта — в остаток того же варианта. Если вариант с тех пор переименовали
или удалили, отмена проходит, а количество не возвращается никуда —
поправьте остаток в карточке товара. Отмена продажи, которой товар отмечен
«Продано», возвращает его в продажу (как publish); остаток при этом не
меняется. Продажа не от этого объявления — 404.
GET /sales?period=30 — отчёт о продажах
period — 7, 30 (по умолчанию) или 90 дней, включая сегодня. Ответ:
{
"period": 30,
"revenue": 58800, "sales_count": 14, "units": 21, "avg_check": 4200,
"prev": {"revenue": 40100, "sales_count": 11, "units": 15, "avg_check": 3645},
"turnover_days_median": 6,
"buyout_rate": 0.1167, "contacts": 120,
"bookings": {"total": 0, "confirmed": 0, "cancelled": 0, "buyout_rate": null},
"by_day": [{"date": "2026-08-27", "revenue": 0, "sales": 0}, …],
"items": [{"id": 77, "listing_id": 123, "title": "Стул дубовый",
"price": 1400, "quantity": 2, "sold_at": "2026-09-25T10:15:00",
"variant_label": null}]
}
revenue— сумма цена × количество;avg_check— выручка на продажу;prev— те же четыре показателя за предыдущий период такой же длины.turnover_days_median— медиана дней от публикации до продажи,nullбез данных;buyout_rate— продаж на один контакт (показ телефона или переписка),nullбез контактов.bookings— брони с датой заезда в периоде;null, если данные о бронях временно недоступны.by_day— каждый день периода, дни без продаж — нули;items— до 50 последних продаж периода. Продажи в отчёте видны сразу, отменённые — сразу исчезают.
Импорт товаров из JSON
Импорт создаёт черновики: сначала строки проверяются и сопоставляются со
справочниками, потом по готовым строкам создаются объявления. Публикация
черновиков — отдельно, через publish или массовое действие.
POST /import/json — разобрать строки
curl -X POST -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d @goods.json "$API/import/json"
{
"filename": "1c-2026-09-25.json",
"default_city": "Москва",
"rows": [
{
"title": "Стул дубовый",
"price": 4900,
"category": "Мебель / Стулья",
"city": "Москва",
"description": "Массив дуба",
"stock": 12,
"phone": "+79990000000",
"photos": ["https://example.com/stul-1.jpg", "https://example.com/stul-2.jpg"],
"attributes": {"Цвет": "Коричневый"}
}
]
}
rows— от 1 до 1000 объектов, иначе422.filename(до 200 символов) — подпись задания в кабинете, необязательна.default_city— город для строк без своего;default_city_id— он же числом из справочника городов сайта (точнее имени: сёл-тёзок в стране сотни).- Обязательные поля строки:
title(до 200 символов),price(больше нуля; можно строкой «4 900»),category. Остальные — по желанию:city,description(до 5000),stock,phone(пустой — при публикации подставится телефон компании),photos— до 10 ссылок http/https списком или строкой через «|». - Ключи можно писать и по-русски, как колонки CSV-шаблона: «Название», «Цена», «Категория», «Город», «Описание», «Остаток», «Телефон», «Фото», «Характеристики». Регистр не важен.
category— название («Стулья»), путь через «/», если название встречается в нескольких ветках («Мебель / Стулья»), или адрес категории (slug).city— название города или села, как на сайте. Из одноимённых выбирается город (от 5000 жителей): «Москва» — столица, а не деревня Москва Тверской области. Если город среди тёзок не нашёлся, а сёл с таким именем несколько, строка получает ошибку — уточните регион через запятую: «Ивановка, Оренбургская область».attributes— объект «характеристика → значение». Ключ — название характеристики, как в форме объявления, или её код. Значение из списка должно совпадать с вариантом формы (без учёта регистра; несколько — через «|»), число — числом, да/нет — словом. Неизвестные характеристики пропускаются, неверное значение — ошибка строки.
Ответ 201 — задание со строками (ничего ещё не создано):
{
"id": 42, "filename": "1c-2026-09-25.json", "status": "parsed",
"total": 2, "ready": 1, "errors": 1, "created": 0, "failed": 0,
"created_at": "2026-09-25T10:00:00", "applied_at": null,
"apply_total": 0, "apply_processed": 0, "apply_error": null,
"rows": [
{"id": 1, "line_no": 1, "title": "Стул дубовый", "price": 4900,
"category_id": 15, "category_name": "Мебель / Стулья",
"city_id": 1, "city_name": "Москва", "stock": 12,
"status": "ready", "listing_id": null, "error": null, "warning": null,
"photos": ["https://example.com/stul-1.jpg"],
"attributes": [{"name": "Цвет", "value": "Коричневый"}]},
{"id": 2, "line_no": 2, "title": "Лампа", "price": null, …,
"status": "error", "error": "Непонятная цена «бесплатно»"}
]
}
- Статус строки:
ready— готова,error— ошибка в данных (текст вerror),created— черновик создан (listing_id),failed— сайт не принял объявление (текст вerror, можно повторить). - Статус задания:
parsed— есть что применять,applying— черновики создаются,done— всё готовое создано.
POST /import/{id}/apply — создать черновики
curl -X POST -H "Authorization: Bearer $KEY" "$API/import/42/apply"
- Создаёт черновики по строкам
readyиfailed. Обычно ответ —200и задание в том же виде, уже с результатом по строкам. - Большой файл (много строк или медленные фото) может не уложиться в
~25 секунд — тогда ответ
202: задание соstatus: "applying"иapply_total(сколько строк взято), создание продолжается. Ход и итог — опросомGET /import/{id}раз в несколько секунд:apply_processed— сколько строк уже обработано, статусы строк и счётчики меняются по ходу; когдаstatusперестал бытьapplying, проход закончен. Не ждать вовсе —POST /import/{id}/apply?wait=0: сразу202, итог — тем же опросом. - Пока проход идёт, повторный
applyотвечает409«Импорт уже применяется — дождитесь окончания». Проход оборвался (например, перезапуск сервиса) — причина вapply_error, необработанные строки остаются готовыми,applyможно вызвать снова. - Повторный вызов не создаёт дублей: трогаются только строки без объявления.
- Фото скачиваются по ссылкам в этот момент (только публичные адреса,
картинка до 10 МБ, ожидание до 10 секунд). Недоступное фото строку не
отменяет: черновик создаётся без него, причина — в
warning. - Создавать можно не больше 10 объявлений в час. Упёрлись в лимит — проход
останавливается, остальные строки остаются готовыми; повторите
applyчерез час.
GET /import и GET /import/{id}
Последние 20 заданий (без строк) и одно задание со строками. Чужое задание —
404. Тот же импорт принимает и файлы: POST /import с телом
{"filename", "text"} (CSV до 2 МБ) или {"filename", "xlsx_base64"};
шаблон CSV — GET /import/template.
Отзывы
GET /reviews
{
"avg": 4.8, "count": 25,
"items": [
{"id": 501, "rating": 5, "text": "Всё отлично", "created_at": "2026-09-20T12:00:00",
"author_id": 9, "author_name": "Анна", "target_id": 3, "target_name": "ООО Ромашка",
"listing_id": 123, "listing_title": "Стул дубовый", "listing_seo_path": "/moskva/…",
"reply": null, "reply_at": null}
]
}
Отзывы о вашей компании, новые сверху. Без ответа — reply: null.
POST /reviews/{id}/reply
curl -X POST -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"text": "Спасибо за покупку!"}' "$API/reviews/501/reply"
- Ответ на отзыв один; сохраняются первые 2000 символов (текст длиннее
4000 —
422). Пустойtextилиnull— снять ответ. - Изменить или снять ответ можно в течение суток после первой публикации,
дальше —
403. Текст проходит автопроверку (400с причиной). - Ответ — отзыв целиком в том же виде, что в списке.
Брони
Брони есть у объявлений посуточного жилья.
GET /bookings
[
{"id": 31, "date_from": "2026-10-01", "date_to": "2026-10-05",
"status": "pending", "past": false,
"listing_id": 123, "listing_title": "Дом у моря",
"guest_id": 9, "guest_name": "Анна"}
]
status — pending (ждёт решения), confirmed, cancelled; past — дата
выезда прошла. Ожидающие — сверху, прошедшие и отменённые — в конце.
POST /bookings/{id}/confirm · POST /bookings/{id}/cancel
Подтвердить или отменить бронь; гость получает сообщение в чате. Ответ —
{"id": 31, "status": "confirmed"}. Повтор того же действия безопасен;
подтвердить отменённую — 409.
Чаты с покупателями
GET /chats
Переписки по вашим объявлениям (где вы — продавец), свежие сверху:
[
{"id": 88, "listing_id": 123, "listing_title": "Стул дубовый",
"listing_price": 4900, "listing_cover": "https://…", "listing_seo_path": "/moskva/…",
"role": "seller", "is_support": false,
"peer_id": 9, "peer_name": "Анна", "peer_avatar": null,
"last_message": "Здравствуйте, ещё продаёте?", "last_at": "2026-09-25T09:40:00",
"unread": 1}
]
GET /chats/{id}
Переписка — {"chat": {…как в списке}, "messages": [...]}, последние 200
сообщений по порядку: {"id", "sender_id", "body", "deleted", "created_at", "read_at"}.
Ваши сообщения — те, где sender_id не равен peer_id. Запрос помечает
входящие прочитанными, как открытие переписки на сайте.
POST /chats/{id}/messages
curl -X POST -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"body": "Да, в наличии"}' "$API/chats/88/messages"
body — до 2000 символов, пустое — 400. Ответ — отправленное сообщение. Не
больше 30 сообщений в минуту; если собеседник вас заблокировал — 403.
Шаблоны быстрых ответов — GET/POST /chats/templates,
DELETE /chats/templates/{id}.
Статистика
Данные обновляются раз в час. period — строго 7, 30 (по умолчанию) или
90, иначе 422; период заканчивается сегодняшним днём (UTC).
GET /summary?period=30— сводка:views,unique_visitors,phone_reveals,chats_started,favorites(сколько раз товары сейчас в избранном),prev— то же за прошлый период,updated_at— время последнего обновления,spark— ряды за 7 дней,catalog(активные, черновики, истекают за 7 дней, без фото, всего) иchat(переписок, доля отвеченных, медиана первого ответа в минутах) — последние дваnullдо первого обновления.GET /funnel?period=30— воронка{"steps": [{"key", "title", "value"}], "conversions": {"views_to_phone", "phone_to_chat"}}.GET /top?period=30&limit=10—by_views,by_contacts({listing_id, views, phone_reveals}) иsilent— просмотры есть, контактов нет;limit1–50.GET /timeseries?period=30&metric=views—{"points": [{"date", "value"}]}по дням, дни без данных — нули;metric—views,phone_reveals,chats_started.GET /export?period=30— CSV (разделитель «;», UTF-8 с BOM): дата, id товара, источник, просмотры, уникальные посетители, показы телефона, переписки, избранное.GET /rating— рейтинг продавца 0–100 по скорости ответов, отменам броней, модерации и отзывам, с советами, что улучшить.GET /me— компания и счётчики: неотвеченные отзывы, непрочитанные чаты, брони на решении, черновики, объявления без фото и истекающие. Счётчик, который не удалось получить, приходитnull.
Лимиты
- 600 запросов в минуту на ключ по всем адресам вместе. Сверх —
429 «Слишком много запросов по ключу — не больше 600 в минуту»; дождитесь конца минуты. - Не больше 20 запросов в секунду с одного IP-адреса — сверх приходит
429, иногда без JSON-тела. - Правила сайта действуют как в кабинете: создание объявлений (импорт,
копии) — 10 в час, публикация по одному — 20 в час, отметки продаж — 60 в
минуту, сообщения — 30 в минуту. Их
429приходит с текстом «Слишком много запросов — попробуйте позже». - Размеры: до 200 товаров на странице списка, до 100 id в массовом действии, до 1000 строк и до 10 фото на строку в импорте.
Коды ошибок
| Код | Когда |
|---|---|
| 401 «Требуется вход» | Нет заголовка Authorization или он не вида Bearer plk_…; либо в аккаунте только что вышли со всех устройств или сменили пароль — ключ снова заработает в течение 5 минут |
| 401 «Ключ недействителен» | Ключ неверный или отозван, аккаунт заблокирован или перестал быть бизнес-аккаунтом. Причина намеренно не уточняется |
| 403 «Недоступно по API-ключу» | Раздел настроек и безопасности — см. «Что недоступно по ключу» |
| 403 «Раздел доступен бизнес-аккаунтам» | Аккаунт больше не компания |
| 403 «Это не ваше объявление» и подобные | Объект принадлежит другому пользователю или действие запрещено правилами (например, объявление снято модератором) |
| 404 | Объект не найден: объявление, отзыв, бронь, продажа, задание импорта |
| 400 / 409 | Нарушено правило площадки: «Продано больше, чем есть в наличии», «Бронь уже отменена» и т. п. — текст в detail |
| 422 | Ошибка в теле или параметрах. У проверки формы detail — список полей с описанием, у правил площадки — строка |
| 429 | Превышен лимит (см. «Лимиты») — подождите и повторите |
| 502 «Основной сервис недоступен…» / 503 «Кабинет временно недоступен…» | Временный сбой на нашей стороне — повторите позже с паузой |
Что недоступно по ключу
Настройки компании и безопасность: профиль компании и логотип, смена пароля,
«выйти со всех устройств», список входов и их завершение, возврат на личный
аккаунт, удаление аккаунта, повтор письма подтверждения, принятие оферты и
проверка ИНН — и сами API-ключи: выпускать и отзывать их можно только в
кабинете. Так утёкший ключ не даёт захватить аккаунт или выпустить себе новые
ключи. На всё это API отвечает 403 «Недоступно по API-ключу».
Советы по синхронизации
- Сопоставьте товары учётной системы с
idодин раз (по названию или штрихкоду из списка) и храните связку у себя — дальше работайте поid. Штрихкод уникален в пределах города: если товар продаётся в нескольких городах, у него по объявлению на город с одним и тем же кодом. - Остатки и цены синхронизируйте раз в несколько минут, а не в непрерывном цикле, и отправляйте только изменившиеся позиции.
- Весь каталог читайте страницами по 200 (
per_page=200), пока не наберётсяtotal. - Продажи из 1С отмечайте через
POST /sales/{id}, а не правкой остатка: так они попадут в отчёт о выручке, а остаток уменьшится сам. У товара с остатком по вариантам передавайтеvariant_label. - На
429,502и503повторяйте с растущей паузой (например, 5, 15, 60 секунд).401 «Ключ недействителен»повтором не лечится — выпустите новый ключ;401 «Требуется вход»при верном заголовке проходит сам за несколько минут. - Новые товары загружайте импортом: разберите строки, исправьте ошибки,
примените, затем опубликуйте черновики массовым
publish; еслиskippedбольше нуля — найдите непубликованные вGET /catalog?status=waiting.
Вопросы по интеграции — support@opolka.ru.