# Ключи и доступ (/access)
Каждый запрос содержит ключ в заголовке `X-API-Key`:
```bash
curl "https://api.rewio.ru/v3/reviews?object_id=20" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ"
```
Ключи начинаются с `hrev_`. Запрос без ключа или с неверным получает `401` с кодом `unauthorized`.
## Два уровня доступа [#access-levels]
| Скоуп | Что можно | Для чего |
| ----------- | --------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `read_only` | Только чтение: отзывы, аналитика, списки объектов, ссылок и площадок. | Серверная выдача отзывов и интеграции, которым нужно только читать. |
| `full` | Чтение и запись: создавать и менять объекты, ссылки, вебхуки. | Серверная интеграция с полным доступом. |
Собственные ключи (`full`/`read_only`) рекомендуем хранить только на своём сервере: `read_only`
тоже раскрывает ваши данные на чтение, хотя и запрещает любые изменения.
Чтобы показать отзывы на публичном сайте, обращайтесь к Rewio со своего бэкенда, а браузеру
отдавайте уже готовый ответ.
## Тестовый ключ [#test-key]
Только на чтение и только по демо-данным. Им работают примеры на этих страницах и кнопка
«Отправить запрос»:
```
hrev_FaDGMKJEDDeCR5BCBgEg0aEjMogI7O7hOzE53YY4T1M
```
## Где взять боевой ключ [#live-key]
В личном кабинете [app.rewio.ru](https://app.rewio.ru). После регистрации аккаунт сразу получает
два ключа – **Full** и **Read-only**. Там же можно создать дополнительные, посмотреть значение
выданного и отозвать ненужный.
Остались вопросы по подключению – напишите: .
## Пробный доступ [#trial]
После регистрации аккаунт работает бесплатно **21 день**. В это время действуют два
ограничения:
| Что | Сколько на пробном доступе | После оплаты |
| ---------------- | -------------------------------------- | ---------------- |
| Объектов | 30 | Сколько оплачено |
| Отзывов в выдаче | 20 самых свежих **по каждой площадке** | Все собранные |
Отзывы при этом собираются полностью — ограничена только выдача. Оплата открывает
накопленное сразу, пересобирать ничего не нужно.
Когда окно действует, ответ методов отзывов и аналитики содержит блок `access`:
```json
{
"data": [ /* ... */ ],
"total": 25,
"access": {
"reason": "trial",
"limit_per_link": 20,
"hidden_total": 204,
"message": "Пробный доступ показывает 20 самых свежих отзывов по каждой площадке. Ещё 204 отзыва уже собрано — они откроются сразу после оплаты."
}
}
```
`hidden_total` – сколько отзывов уже собрано сверх окна. Блока `access` нет вовсе,
если ограничения нет: его отсутствие и означает полный доступ.
Окно считается **до** фильтров и сортировки. `sort_order=asc` или фильтр по рейтингу
работают внутри двадцати последних, а не выбирают другие двадцать из архива.
## Версии API [#versions]
Актуальная версия — **`/v3`**, она описана на этих страницах. Полная схема:
[`api.rewio.ru/openapi.json`](https://api.rewio.ru/openapi.json).
`/v1` закрыт: он отвечает только интеграциям, написанным до закрытия, и отдаёт им
`Deprecation` в заголовках. Новый ключ на `/v1` получит `410` с кодом
`api_version_gone`. `/v2` остаётся рабочим и описан [отдельно](/v2/) — переходить
на `/v3` можно постепенно, но новые интеграции пишите сразу на `/v3`.
## Проверка доступности [#health]
Два метода работают без ключа:
| Метод | Ответ | Что означает |
| ---------------------------------------- | ---------------------------------- | ---------------------------------------------------- |
| [`GET /health`](/extra/health) | `{"status": "healthy"}` | API на связи. |
| [`GET /health/read`](/extra/health-read) | `{"status": "ready"}`, иначе `503` | Чтение работает, методы отзывов и аналитики ответят. |
```bash
curl https://api.rewio.ru/health
curl https://api.rewio.ru/health/read
```
## Как не опрашивать нас в цикле [#polling]
Отзывы обновляются по расписанию, поэтому тесный цикл запросов ничего не ускоряет. Чтобы узнавать
о новых отзывах и ответах, подпишитесь на [вебхуки](/guides/webhooks) – мы пришлём событие сами –
или используйте [синхронизацию данных](/guides/sync) через курсор.
# Интеграция через AI (/ai)
Если вы разрабатываете через AI-агента, передайте ему наш Skill. В этом файле
содержится вся необходимая информация с нужными ссылками, чтобы написать
качественную интеграцию. При необходимости агент сам найдёт нужные разделы
документации или сам напишет в поддержку.
## Где взять ключ и как его передать [#api-key]
API-ключ выпускается в личном кабинете [app.rewio.ru](https://app.rewio.ru),
раздел «API-ключи».
Спросите у своего агента, как безопасно передать ему ключ.
## Попробовать без своего ключа [#try-without-key]
Демо-ключ (только чтение, только демо-данные):
`hrev_FaDGMKJEDDeCR5BCBgEg0aEjMogI7O7hOzE53YY4T1M`, демо-объект `object_id=20`.
## Машиночитаемые адреса [#machine-readable]
Их можно давать агенту напрямую — он выкачает документацию сам.
| Адрес | Что внутри |
| ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| [`docs.rewio.ru/skill/SKILL.md`](https://docs.rewio.ru/skill/SKILL.md) | Skill целиком: модель данных, методы, ловушки, правила. |
| [`docs.rewio.ru/llms-full.txt`](https://docs.rewio.ru/llms-full.txt) | Вся документация одним файлом (обзор, быстрый старт, руководства, справочник). |
| [`docs.rewio.ru/llms.txt`](https://docs.rewio.ru/llms.txt) | Карта разделов со ссылками — если контекст ограничен, агент выберет нужное. |
| [`api.rewio.ru/v3/openapi.json`](https://api.rewio.ru/v3/openapi.json) | OpenAPI 3.1: все эндпоинты, параметры и схемы. **Источник истины** для любых спорных деталей. |
## Проверьте, что агент понял [#verify-agent]
Прежде чем принимать код, задайте агенту эти вопросы. Ответы должны совпасть —
если нет, он импровизирует: подключите Skill заново или дайте ему `SKILL.md`
целиком.
| Вопрос | Правильный ответ |
| ---------------------------------------------- | --------------------------------------------------------------------------------- |
| Как аутентифицироваться? | Заголовок `X-API-Key`, префикс `/v3`. |
| Как выглядит список? | `{data, total, limit, offset, has_next}`, элементы всегда в `data`. |
| Как узнать, что появилось новое? | Вебхук — не опрос в цикле и не выборка по времени изменения. |
| Чем `review_date` отличается от `created_at`? | Публикация в местном времени площадки против UTC-времени захвата. |
| Пользователь просит «четвёрки» — какой фильтр? | `min_rating=4&max_rating=4.9`: это корзина, а не число. |
| В какой шкале `rating`? | 1–5, нормализованная, дробная; исходная — в `rating_original`. |
| Повторный сбор создаёт дубли? | Нет: отзыв опознаётся по паре «ссылка + его id на площадке» и обновляется. |
| Что делать сразу после создания объекта? | Ждать `scrape_status` = `success` / `partial` / `failed`, потом читать отзывы. |
| Какие ошибки повторять? | `rate_limited`, `5xx`, сетевые. `402` и квотные — никогда. |
| Что означает `202` при публикации ответа? | Приняли в работу; результат — только через `GET /v3/reviews/{id}/reply`. |
| Откуда берётся формат ссылки? | Из [«Где взять ссылку»](/link-formats) — адреса не конструируются самостоятельно. |
Skill описывает API на дату публикации. Если ответ расходится с описанием —
верьте [`openapi.json`](https://api.rewio.ru/v3/openapi.json) и напишите нам:
[info@rewio.ru](mailto:info@rewio.ru) или
[@const\_rewio](https://t.me/const_rewio). Агент со Skill умеет сообщить
об этом сам.
# Модель данных (/data-model)
Всё в Rewio держится на трёх сущностях: объект, ссылка, отзыв.
```
Объект «Гранд Отель» (object_id: 20)
├── Ссылка → Яндекс Карты (link_id: 181, source_id: 1) → отзывы…
├── Ссылка → 2ГИС (link_id: 182, source_id: 3) → отзывы…
└── Ссылка → Otzovik (link_id: 183, source_id: 202) → отзывы…
```
**Объект** – ваша конкретная бизнес-сущность: один отель, одна клиника, один врач, один ресторан.
**Ссылка** – страница этого объекта на конкретной площадке. Обычно у объекта несколько
ссылок, по одной на каждую площадку.
**Отзыв** привязан к ссылке и площадке внутри объекта.
## Папка – необязательный уровень выше [#folders]
Если объектов много, их можно сложить в папки и запрашивать данные сразу по набору:
`GET /v3/reviews?folder_id=…`. Максимальная глубина: два уровня. Один объект может лежать
в нескольких папках.
Отзывы при этом живут в объекте: папка только адресует запрос. Подробности в
[руководстве](/guides/folders).
## Ответ на отзыв [#reply]
У отзыва бывает ответ организации: он приходит в полях `reply_text` и `reply_date` и собирается
с площадки как есть, кем бы он ни был написан.
Ответ, который вы публикуете [через нас](/guides/replies), – это то же самое место на площадке,
только заполненное вами: после публикации он приходит в тех же `reply_text` и `reply_date`.
Ответ у отзыва ровно один, и место под него на площадке одно.
## Оценки приведены к 1–5 [#ratings]
Площадки считают по-разному: пять баллов, десять, сто, «палец вверх».
Rewio приводит всё к шкале 1–5. Это поле `rating`, оно может быть дробным. Исходная оценка
площадки остаётся рядом, в `rating_original`. У площадок без числовой оценки `rating` пустой.
## Площадки [#sources]
[`GET /v3/sources`](/sources/list) отдаёт справочник площадок: числовой `id`, машинное
имя `slug`, название для показа `name` и возможности: поддерживает ли площадка ответы
организации, бывают ли фотографии, какая у неё исходная шкала.
По `id` фильтруют отзывы и аналитику (`source_ids`), `slug` используется в фильтрах
вебхуков (`allowed_sources`).
# Ошибки (/errors)
Любая ошибка приходит одним и тем же телом, с любым статусом `4xx` или `5xx`:
```json
{
"detail": "Понятное человеку описание, что пошло не так",
"code": "machine_readable_code"
}
```
`detail` – текст для логов, его формулировка может меняться.
`code` – стабильный код. Реализуйте логику по нему.
## Коды состояния [#status-codes]
| Статус | `code` | Когда |
| ------ | --------------------- | ---------------------------------------------------------------------------------- |
| `400` | `bad_request` | Битые параметры или невалидное тело. |
| `401` | `unauthorized` | Нет заголовка `X-API-Key` или ключ неверный. |
| `402` | `payment_required` | Доступ к API приостановлен. Что именно произошло, написано в `detail`. |
| `403` | `forbidden` | Не хватает прав: запись ключом на чтение или чужой ресурс. |
| `403` | `session_required` | Операция выполняется только из личного кабинета. Никакой ключ на неё не действует. |
| `404` | `not_found` | Ресурс не найден или принадлежит другому аккаунту. |
| `405` | `method_not_allowed` | Метод не поддерживается этим адресом. |
| `409` | `conflict` | Конфликт состояния, например дубликат. |
| `422` | `validation_error` | Тело или параметры не прошли проверку схемы. |
| `429` | `rate_limited` | Слишком много запросов подряд. Повторите с задержкой. |
| `500` | `internal_error` | Наша ошибка. Стоит повторить с нарастающей задержкой. |
| `503` | `service_unavailable` | Сервис временно недоступен. Повторите с задержкой. |
## Адресация и папки [#addressing-and-folders]
| Статус | `code` | Когда |
| ------ | ---------------------------- | ------------------------------------------------------------------------------------------------- |
| `400` | `object_or_folder_required` | В запросе отзывов или аналитики нет ни `object_id`, ни `folder_id`. |
| `400` | `object_and_folder_conflict` | Переданы оба сразу. Адресовать нужно ровно один. |
| `400` | `folder_depth_exceeded` | Нарушена глубина: родитель сам вложен, у папки есть подпапки, или её делают родителем самой себе. |
| `403` | `folder_limit_reached` | Достигнут потолок в 1000 живых папок на аккаунт. |
| `404` | `folder_not_found` | Папка не найдена или чужая. |
| `409` | `folder_not_empty` | У папки есть живые подпапки. Сначала перенесите или удалите их. |
## Объекты, ссылки и виджет [#objects-links-widget]
| Статус | `code` | Когда |
| ------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `403` | `object_limit_reached` | Достигнут потолок активных объектов на вашем тарифе. |
| `403` | `object_creation_budget_exhausted` | Исчерпан бюджет пересозданий объектов. Это защита от абьюза, а не тарифный лимит: как поднять – написано в `detail`. |
| `402` | `source_requires_upgrade` | Площадка не входит в ваш тариф. |
| `400` | `pinned_limit_exceeded` | Закреплённых отзывов на объект не больше 30. |
| `400` | `duplicate_review_ids` | В `ordered_review_ids` есть повторы. |
| `409` | `idempotency_conflict` | Тот же `Idempotency-Key` пришёл с другим телом запроса. |
| `409` | `idempotency_in_progress` | Запрос с этим `Idempotency-Key` ещё выполняется. Повторите позже. |
Отказы по отдельным ссылкам при создании объекта и добавлении площадок ошибкой не считаются:
они приходят внутри `201`, в поле `error_code` каждой ссылки – `source_not_detected`,
`source_url_invalid`, `source_requires_upgrade`, `source_already_linked`, `link_already_added`.
## Синхронизация отзывов [#sync]
| Статус | `code` | Когда |
| ------ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | `invalid_cursor` | Курсор не читается: испорчен, изменён или выдан другому аккаунту. Начните обход заново, без `cursor` – [как это работает](/reviews/sync). |
| `400` | `cursor_mismatch` | Вместе с курсором передан `object_id`, `folder_id` или `mode`, не совпадающий с тем, для которого курсор выдан. |
## Ответы на отзывы [#replies]
| Статус | `code` | Когда |
| ------ | ------------------------------ | ------------------------------------------------------------------------------------------------------- |
| `402` | `reply_requires_upgrade` | Публикация ответов входит в тариф «Полный». Удалить уже опубликованный ответ можно на любом тарифе. |
| `403` | `reply_access_revoked` | Площадка не даёт нам отвечать по этой ссылке: доступ был и отозван. Что сделать, написано в `detail`. |
| `404` | `reply_not_found` | У этого отзыва нет вашего ответа. Ответ, написанный не через нас, виден как `reply_text` самого отзыва. |
| `409` | `reply_not_supported` | На этой площадке мы пока не публикуем ответы. Смотрите `can_publish_reply` в `GET /v3/sources`. |
| `409` | `reply_owned_by_other_account` | На отзыв уже отвечает другой аккаунт: на площадке место под ответ одно. |
| `409` | `reply_in_progress` | Предыдущий ответ сейчас публикуется, отменить его нечем. Повторите через несколько секунд. |
| `409` | `reply_edit_not_supported` | На этой площадке опубликованный ответ нельзя переписать. |
| `409` | `reply_delete_not_supported` | На этой площадке ответ нельзя удалить. |
| `422` | `validation_error` | Текст пуст, содержит управляющие символы или длиннее допустимого на площадке. |
`202` означает, что задачу мы приняли; опубликованный ответ виден в самом отзыве (`reply_text`)
и приходит событием `replies.new`. Если ответ не появился, причина обычно одна из этих – и почти
все мы отрабатываем сами:
| Причина | Что происходит |
| -------------------------------------- | ------------------------------------------------------------------------ |
| Наш доступ к площадке обновляется | Ничего делать не нужно, ответ уйдёт сам. |
| Площадка просит подождать | Повтор произойдёт автоматически. |
| Сеть или сбой на стороне площадки | Повторим сами, до трёх раз. |
| Из ссылки не выводится объект площадки | Проверьте адрес ссылки в объекте. |
| **Площадка отклонила текст** | **Единственный случай, где нужны вы:** измените текст и отправьте снова. |
Ответ не появился – напишите на .
## Как обрабатывать [#handling]
* Повторяйте `429` и `5xx` с нарастающей задержкой. Остальные `4xx` повторять бессмысленно –
запрос надо чинить.
* Логируйте `code` вместе с `detail`: по коду мы быстрее поймём, что случилось.
* При добавлении ссылок отказ приходит по каждой ссылке отдельно: текст в `error`, машинная
причина в `error_code`. Негодная ссылка не отменяет остальные – они добавятся. Форматы
адресов смотрите в [Форматах ссылок](/link-formats).
Примеры:
```json
// 401 – ключ не передан
{ "detail": "Missing API key", "code": "unauthorized" }
// 403 – запись ключом на чтение
{ "detail": "This key is read-only", "code": "forbidden" }
// 422 – невалидное тело
{ "detail": "url: field required", "code": "validation_error" }
```
# Где взять ссылку (/link-formats)
Для добавления объекта в систему необходимо скопировать ссылку с нужной площадки.
Копируйте адрес страницы целиком, ничего не обрезая: площадку мы определим сами и
приведём ссылку к нужному формату.
Ниже примеры со снимками для каждой площадки. На снимках обведено то, что нужно
скопировать: **А** – адрес из строки браузера, **Б** – короткая ссылка из кнопки
«Поделиться» там, где она есть.
## Карты [#maps]
Откройте карточку организации на Яндекс.Картах.
Подойдёт любой из двух – вставляйте ту, что удобнее.
**Вариант А. адрес из строки браузера.** Откройте карточку организации и скопируйте адрес из строки браузера.
**Вариант Б. короткая ссылка из «Поделиться».** Либо нажмите «Поделиться» – площадка покажет короткую ссылку, её мы развернём сами.
Найдите организацию в Google Картах и откройте её карточку.
Подойдёт любой из двух – вставляйте ту, что удобнее.
**Вариант А. адрес из строки браузера.** Откройте карточку организации и скопируйте адрес из строки браузера.
**Вариант Б. короткая ссылка из «Поделиться».** Либо нажмите «Поделиться» – площадка покажет короткую ссылку, её мы развернём сами.
Откройте карточку организации в 2ГИС – кнопка здесь называется «Отправить».
Подойдёт любой из двух – вставляйте ту, что удобнее.
**Вариант А. адрес из строки браузера.** Откройте карточку организации и скопируйте адрес из строки браузера.
**Вариант Б. короткая ссылка из «Поделиться».** Либо нажмите «Поделиться» – площадка покажет короткую ссылку, её мы развернём сами.
## Отели и бронирование [#hotels]
Откройте страницу отеля.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу отеля. Окно выбора дат можно просто закрыть.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу отеля.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу объекта размещения.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу отеля.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу отеля или санатория.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу отеля.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте карточку отеля.
Откройте карточку организации и скопируйте адрес из строки браузера.
Домен tripadvisor.ru в России не открывается – пользуйтесь tripadvisor.com. Карточка объекта на обоих доменах одна и та же, и ссылку мы примем любую.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу отеля.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу отеля.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу отеля.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу объекта.
Откройте карточку организации и скопируйте адрес из строки браузера.
## Медицина [#medicine]
Откройте страницу клиники или врача – отзывы соберутся по той странице, чей адрес вы вставили.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу клиники или врача.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу клиники или врача.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу клиники или врача.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу клиники или врача.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу специалиста.
Откройте карточку организации и скопируйте адрес из строки браузера.
## Услуги и специалисты [#services]
Откройте профиль специалиста.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте профиль исполнителя.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте профиль продавца или страницу бренда.
Откройте карточку организации и скопируйте адрес из строки браузера.
## Справочники [#directories]
Откройте карточку организации на Zoon.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу компании на Flamp.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте карточку организации на Yell.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу объекта.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу объекта.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте страницу компании в разделе отзывов.
Откройте карточку организации и скопируйте адрес из строки браузера.
Откройте сообщество во ВКонтакте.
Подойдёт любой из двух: адрес страницы сообщества или адрес страницы с отзывами.
**Вариант А. Адрес сообщества.** Скопируйте адрес сообщества из строки браузера.
**Вариант Б. Адрес страницы отзывов.** Либо откройте раздел «Отзывы» и скопируйте адрес уже этой страницы.
Не нашли нужную площадку? на добавление.
## Форматы ссылок [#url-formats]
Шаблон адреса и рабочий пример для каждой площадки. `{…}` – переменная часть.
### Карты [#url-maps]
| `source_id` | Площадка | Формат ссылки | Пример |
| ----------- | -------------- | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `1` | Яндекс.Карты | `yandex.ru/maps/-/{код}`
`yandex.ru/maps/org/{название}/{org_id}`
`yandex.ru/profile/{org_id}` | `https://yandex.ru/maps/org/areal/1262964858`
`https://yandex.ru/profile/7958059620` |
| `2` | Google Reviews | `maps.app.goo.gl/{код}`
`google.com/maps/place/{название}/@{координаты}/data=…` | `https://maps.app.goo.gl/dSiPVi4F8MBLvG4w8` |
| `3` | 2ГИС / Otello | `go.2gis.com/{код}`
`2gis.ru/{город}/firm/{id}`
`2gis.ru/{город}/geo/{id}`
`otello.ru/hotel/{id}` | `https://go.2gis.com/BQ8Og`
`https://2gis.ru/moscow/firm/70000001079615757` |
### Отели и путешествия [#url-hotels]
| `source_id` | Площадка | Формат ссылки | Пример |
| ----------- | ------------------ | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `100` | Alean.ru | `alean.ru/hotels/{страна}/{регион}/{город}/{отель}` | `https://www.alean.ru/hotels/rossiya/moskva/borodino_gostinitsa` |
| `101` | OneTwoTrip | `onetwotrip.com/ru/hotels/hotel/{название}-{id}` | `https://www.onetwotrip.com/ru/hotels/hotel/safmar-tverskaia-moskva-296002` |
| `103` | Tvil.ru | `tvil.ru/city/{город}/hotels/{id}` | `https://tvil.ru/city/adler/hotels/427565` |
| `104` | Яндекс.Путешествия | `travel.yandex.ru/hotels/{регион}/{отель}` | `https://travel.yandex.ru/hotels/moscow/borodino-kongress-otel` |
| `105` | 101Hotels | `101hotels.com/main/cities/{город}/{отель}.html` | `https://101hotels.com/main/cities/moskva/gostinitsa_borodino.html` |
| `106` | Ostrovok | `ostrovok.ru/hotel/{страна}/{город}/mid{id}/{отель}` | `https://ostrovok.ru/hotel/russia/moscow/mid7599836/borodino_hotel` |
| `107` | Суточно.ру | `sutochno.ru/front/searchapp/detail/{id}` | `https://sutochno.ru/front/searchapp/detail/2212277` |
| `108` | TopHotels.ru | `tophotels.ru/hotel/al{id}` | `https://tophotels.ru/hotel/al125379` |
| `109` | TripAdvisor | `tripadvisor.com/{Тип}_Review-g{гео}-d{id}-Reviews-{Название}.html` | `https://www.tripadvisor.com/Hotel_Review-g298484-d671754-Reviews-Borodino_Alliance_Hotel-Moscow_Central_Russia.html` |
| `110` | Airbnb | `airbnb.com/rooms/{id}` | `https://www.airbnb.com/rooms/1187653134175293144` |
| `111` | Booking.com | `booking.com/hotel/{код страны}/{отель}.html` | `https://www.booking.com/hotel/th/the-royal-p-phuket.ru.html` |
| `112` | Trip.com | `trip.com/hotels/detail/?hotelId={id}` | `https://ru.trip.com/hotels/detail/?hotelId=1530754` |
| `113` | Agoda | `agoda.com/{отель}/hotel/{город}-{код страны}.html` | `https://www.agoda.com/ru-ru/berlin-marriott-hotel_3/hotel/berlin-de.html` |
### Медицина [#url-medicine]
| `source_id` | Площадка | Формат ссылки | Пример |
| ----------- | --------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `300` | DocDoc.ru | `docdoc.ru/clinic/{клиника}`
`docdoc.ru/doctor/{врач}` | `https://docdoc.ru/clinic/klinika_temed_na_tehnoparke` |
| `301` | НаПоправку | `napopravku.ru/{город}/clinics/{клиника}`
`napopravku.ru/doctor-profile/{врач}` | `https://napopravku.ru/moskva/clinics/klinika-1-v-lyublino-mnogoprofilnyy-meditsinskiy-tsentr/` |
| `302` | Doctu.ru | `doctu.ru/{город}/doctor/{врач}`
`doctu.ru/{город}/clinic/{клиника}` | `https://doctu.ru/msk/doctor/zabelina-valerija-dmitrievna` |
| `303` | ПроДокторов | `prodoctorov.ru/{город}/lpu/{id}-{клиника}`
`prodoctorov.ru/{город}/vrach/{id}-{врач}` | `https://prodoctorov.ru/zelenograd/lpu/39192-nikor-med` |
| `305` | Яндекс.Медицина | `yandex.ru/medicine/clinic/{название}_{id}`
`yandex.ru/medicine/doctor/{название}_{id}` | `https://yandex.ru/medicine/clinic/opeka_13445109767` |
### Услуги и специалисты [#url-services]
| `source_id` | Площадка | Формат ссылки | Пример |
| ----------- | ------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `304` | B17.ru | `b17.ru/{ник специалиста}` | `https://www.b17.ru/svetlana_guedova` |
| `400` | Profi.ru | `profi.ru/profile/{ник}` | `https://profi.ru/profile/SidorenkoSV3/` |
| `401` | Яндекс.Услуги | `uslugi.yandex.ru/profile/{ник}` | `https://uslugi.yandex.ru/profile/DaniilR-152511` |
| `205` | Avito | `адрес страницы продавца с ?sellerId={sellerId}` | `https://www.avito.ru/brands/4390b5a759ee16a3f054904fe1d96187/all/transport?sellerId=96133ce69977bd69330485c589bf5451` |
### Справочники [#url-directories]
| `source_id` | Площадка | Формат ссылки | Пример |
| ----------- | ------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| `201` | Zoon.ru | `zoon.ru/{город}/{рубрика}/{название}` | `https://zoon.ru/msk/medical/meditsinskij_tsentr_nikor-med_v_andreevke` |
| `207` | Flamp | `{город}.flamp.ru/firm/{название}-{id}` | `https://moscow.flamp.ru/firm/aviator_sheremetevo_otel-4504127918577229` |
| `200` | Yell.ru | `yell.ru/{город}/com/{название}_{id}` | `https://www.yell.ru/moscow/com/sauna-zabava_12088202` |
| `202` | Otzovik | `otzovik.com/reviews/{объект}` | `https://otzovik.com/reviews/laboratoriya_invitro_russia` |
| `203` | iRecommend.ru | `irecommend.ru/content/{объект}` | `https://irecommend.ru/content/gorod-sochi-rossiya` |
| `204` | Т-Банк | `tbank.ru/reviews/company/{компания}/{id}` | `https://www.tbank.ru/reviews/company/kazanskaya-rivyera/105252` |
| `208` | ВКонтакте | `vk.ru/{имя сообщества}`
`vk.ru/club{id}`
`vk.ru/reviews-{id}` | `https://vk.ru/helix_lab`
`https://vk.ru/reviews-20155592` |
## Что полезно знать [#notes]
### Площадку и формат мы определяем сами [#autodetect]
* Указывать площадку при добавлении не нужно – она определяется по адресу.
* Адрес приводится к каноническому виду автоматически: лишние сегменты и параметры отбрасываются при сохранении. Например `2gis.ru/anapa/inside/104152…/firm/70000001088796893/37.30,44.94/tab/reviews` сохранится как `2gis.ru/anapa/inside/104152…/firm/70000001088796893`. Чистить адрес перед вставкой не нужно.
### Одна площадка – одна ссылка в объекте [#one-link-per-source]
* Вторая ссылка на ту же площадку внутри объекта вернёт ошибку `Only one … link allowed per group`. Несколько филиалов – это несколько объектов.
* При добавлении списком каждая ссылка получает свой результат: неверная вернёт `error` с ожидаемым форматом, остальные добавятся.
### Обязательные части адреса [#required-parts]
* **Google** – фрагмент `data=…!1s0x…:0x…`, это идентификатор места. Адреса `maps.google.com/?cid=…` и короткие адреса из строки поиска (`/maps/place/Fresh`, `/maps/search/?q=…`) его не содержат и отклоняются – в этом случае используйте кнопку «Поделиться».
* **Avito** – `?sellerId=` с 32-символьным идентификатором продавца. Без него продавца не определить.
* **Trip.com** – `?hotelId=`; языковой поддомен любой (`ru.`, `us.`, `www.`).
* **ВКонтакте** – годится любая ссылка на сообщество: `vk.ru/helix_lab`, `vk.ru/club29330414` или сама страница отзывов `vk.ru/reviews-29330414`. Личная страница не подойдёт – отзывы бывают только у сообществ.
### Короткая ссылка «Поделиться» – тоже ссылка [#short-links]
* Адреса `maps.app.goo.gl/…`, `yandex.ru/maps/-/…`, `go.2gis.com/…` принимаются как есть: система разворачивает их и сохраняет карточку, на которую они ведут.
* Делитесь **карточкой организации**, а не точкой на карте: у ссылки на произвольную точку идентификатора места нет, и отзывы по ней собрать нельзя.
### Зеркала и разные формы одного адреса [#mirrors]
* Страновые домены равнозначны: `yandex.com.tr`, `yandex.kz`, `2gis.kz`, `google.de` ведут на ту же карточку, и дубли не создаются.
* У 2ГИС `/firm/` и `/geo/`, у Яндекса `/maps/org/` и `/profile/` – одно и то же место. Городские поддомены вроде `spb.napopravku.ru` работают наравне с основным доменом.
* 2ГИС и Otello – одна площадка (`source_id: 3`): Otello это гостиничный раздел 2ГИС.
### Если ссылку не приняли [#rejected]
* Это не карточка объекта, а поиск, список или главная страница города.
* Потеряна обязательная часть адреса – `data=` у Google, `?sellerId=` у Avito, `?hotelId=` у Trip.com.
* Короткая ссылка «Поделиться» просрочена или площадка не ответила: откройте её в браузере и вставьте адрес карточки, на которую она ведёт.
* Площадка не поддерживается – актуальный список отдаёт `GET /v3/sources`.
* Причина другая – обязательно .
# Все методы (/methods)
Весь API целиком, одной страницей. Адрес `https://api.rewio.ru`, префикс `/v3`, ключ в заголовке
`X-API-Key` у каждого запроса. Название метода ведёт на его страницу с параметрами,
полями ответа и живым примером.
## Отзывы и аналитика [#reviews]
| Метод | Что делает |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| [`GET /v3/sources`](/sources/list) | Справочник площадок: `id`, машинное имя, шкала оценок, что площадка умеет. |
| [`GET /v3/reviews`](/reviews/list) | Отзывы объекта или папки: фильтры по оценке, дате, площадке и тексту, сортировка, страницы. |
| [`GET /v3/reviews/{review_id}`](/reviews/get) | Один отзыв по идентификатору – та же модель, что в ленте. |
| [`GET /v3/reviews/sync`](/reviews/sync) | Поток изменений с курсором – чтобы держать у себя актуальную копию отзывов. |
| [`GET /v3/analytics`](/analytics/summary) | Сводка по объекту или папке: средняя оценка, распределение, полнота и скорость ответов. |
| [`GET /v3/analytics/timeseries`](/analytics/timeseries) | Те же метрики по дням, неделям или месяцам. |
## Объекты и площадки [#objects]
Объект – ваш объект учёта (отель, клиника, филиал, врач). Ссылка – его страница на конкретной
площадке.
| Метод | Что делает |
| ----------------------------------------------------------------- | -------------------------------------------------- |
| [`POST /v3/objects`](/objects/create) | Создать объект, сразу со ссылками на площадки. |
| [`GET /v3/objects`](/objects/list) | Список объектов со ссылками и состоянием сбора. |
| [`GET /v3/objects/{object_id}`](/objects/get) | Один объект со всеми его ссылками. |
| [`PUT /v3/objects/{object_id}`](/objects/update) | Переименовать объект, включить или выключить сбор. |
| [`PUT /v3/objects`](/objects/toggle) | Массовый выключатель сразу для набора объектов. |
| [`DELETE /v3/objects/{object_id}`](/objects/delete) | Удалить объект вместе с его ссылками. |
| [`POST /v3/objects/{object_id}/links`](/links/add) | Подключить к объекту новые площадки. |
| [`GET /v3/objects/{object_id}/links`](/links/list) | Ссылки объекта и состояние сбора по каждой. |
| [`PUT /v3/objects/{object_id}/links/{link_id}`](/links/update) | Переименовать ссылку или остановить сбор по ней. |
| [`DELETE /v3/objects/{object_id}/links/{link_id}`](/links/remove) | Отключить площадку от объекта. |
## Папки [#folders]
Уровень выше: набор объектов, по которому можно спросить отзывы и
аналитику одним параметром `folder_id`.
| Метод | Что делает |
| --------------------------------------------------- | --------------------------------------------- |
| [`POST /v3/folders`](/folders/create) | Создать папку и положить в неё объекты. |
| [`GET /v3/folders`](/folders/list) | Все папки с подпапками и составом. |
| [`GET /v3/folders/{folder_id}`](/folders/get) | Одна папка со своими объектами и подпапками. |
| [`PATCH /v3/folders/{folder_id}`](/folders/update) | Переименовать, перенести или заменить состав. |
| [`DELETE /v3/folders/{folder_id}`](/folders/delete) | Удалить папку, не трогая объекты внутри. |
## Ответы на отзывы [#replies]
Ответ публикуется под отзывом на самой площадке. Включается по запросу – напишите на
; нужен полный тариф и ключ `full`. Подробности –
в руководстве [«Как отвечать на отзывы»](/guides/replies).
| Метод | Что делает |
| --------------------------------------------------------- | -------------------------------------------- |
| [`PUT /v3/reviews/{review_id}/reply`](/replies/publish) | Опубликовать ответ или заменить уже стоящий. |
| [`DELETE /v3/reviews/{review_id}/reply`](/replies/delete) | Удалить ваш ответ с площадки. |
## Вебхуки [#webhooks]
| Метод | Что делает |
| ------------------------------------------------------------ | --------------------------------------------------------- |
| [`POST /v3/webhooks`](/webhooks/create) | Получать новые отзывы и ответы пушем на свой адрес. |
| [`GET /v3/webhooks`](/webhooks/list) | Все вебхуки аккаунта и состояние доставки. |
| [`PATCH /v3/webhooks/{webhook_id}`](/webhooks/update) | Поменять адрес, объекты и фильтры или поставить на паузу. |
| [`DELETE /v3/webhooks/{webhook_id}`](/webhooks/delete) | Отключить доставку. |
| [`POST /v3/webhooks/{webhook_id}/test`](/webhooks/test) | Пробное событие с настоящей подписью. |
| [`GET /v3/webhooks/secret`](/webhooks/secret) | Ключ, которым подписаны все доставки аккаунта. |
| [`POST /v3/webhooks/rotate-secret`](/webhooks/rotate-secret) | Заменить ключ подписи, не теряя доставок. |
## Для виджетов [#widgets]
Скрыть неудачный отзыв и закрепить удачный. Действует на выдачу, а не на данные:
исходные отзывы остаются как есть.
| Метод | Что делает |
| ------------------------------------------------------------------------- | ------------------------------------- |
| [`POST /v3/objects/{object_id}/hidden-reviews`](/widgets/hidden-add) | Убрать отзывы из ленты этого объекта. |
| [`GET /v3/objects/{object_id}/hidden-reviews`](/widgets/hidden-list) | Что сейчас скрыто. |
| [`DELETE /v3/objects/{object_id}/hidden-reviews`](/widgets/hidden-remove) | Вернуть отзывы в ленту. |
| [`POST /v3/objects/{object_id}/pinned-reviews`](/widgets/pinned-add) | Закрепить отзывы наверху списка. |
| [`GET /v3/objects/{object_id}/pinned-reviews`](/widgets/pinned-list) | Что закреплено и в каком порядке. |
| [`PUT /v3/objects/{object_id}/pinned-reviews`](/widgets/pinned-set) | Заменить список закреплённых целиком. |
| [`DELETE /v3/objects/{object_id}/pinned-reviews`](/widgets/pinned-remove) | Открепить, оставив отзывы в ленте. |
## Дополнительно [#extra]
| Метод | Что делает |
| -------------------------------------------- | -------------------------------------------------------------------- |
| [`POST /v3/support/message`](/extra/support) | Написать разработчику. Работает с любым ключом, включая `read_only`. |
## Без ключа [#public]
| Метод | Что делает |
| ---------------------------------------- | ------------------------------------------------------------------ |
| [`GET /health`](/extra/health) | API на связи. |
| [`GET /health/read`](/extra/health-read) | Чтение работает – методы отзывов и аналитики ответят. Иначе `503`. |
Ключи и доступ описаны в [отдельном разделе](/access), формат ошибок – в [Ошибках](/errors),
а что такое объект и ссылка – в [Модели данных](/data-model).
# Обзор (/overview)
Rewio собирает отзывы о ваших объектах: отелях, клиниках, ресторанах, врачах и специалистах. Отзывы с трёх
десятков площадок приводятся к одному формату и отдаются через REST API.
## Первый запрос [#first-request]
Прямо сейчас вы можете протестировать отзывы демо-объекта тестовым ключом.
```bash
curl "https://api.rewio.ru/v3/reviews?object_id=20&limit=3" \
-H "X-API-Key: hrev_FaDGMKJEDDeCR5BCBgEg0aEjMogI7O7hOzE53YY4T1M"
```
## Что на этом строят [#use-cases]
* **Виджет отзывов** на сайте или в личном кабинете клиента.
* **Дашборды и BI** – средняя оценка, распределение, тренды по месяцам; выгрузка в Power BI,
Metabase или DataLens.
* **Мониторинг репутации** – новые отзывы пушем, эскалация негатива, контроль скорости ответов.
* **Отчёты по сети** – объекты складываются в [папки](/guides/folders), аналитика строится
по папке целиком.
* **Ответы на отзывы** – ответ публикуется под отзывом на площадке одним запросом.
* **Отзывы в CRM** – карточка филиала или клиента с его отзывами и оценкой.
* **Своя копия отзывов** – [синхронизация](/guides/sync) держит вашу базу в актуальном
состоянии: поток изменений с курсором, включая пропажи отзывов с площадок.
## Правила, общие для всего API [#rules]
| | |
| ------ | ---------------------------------------------------------------------------------------------- |
| Адрес | `https://api.rewio.ru` |
| Версия | `/v3` |
| Ключ | Заголовок `X-API-Key` в каждом запросе |
| Формат | JSON. Списки – `{data, total, limit, offset, has_next}` или `{data}`, элементы всегда в `data` |
| Оценки | Приведены к шкале 1–5, бывают дробными |
| Время | UTC в ISO 8601, кроме `review_date`: это местное время площадки |
| Ошибки | `{"detail": "…", "code": "…"}` |
## С чего начать [#start]
# Быстрый старт (/quickstart)
Попробуйте пробный запрос готовым ключом. Для создания своих объектов используйте ключ
из [личного кабинета](https://app.rewio.ru).
Тестовый ключ только на чтение. Боевой ключ (`full`/`read_only`) рекомендуем хранить только
на своём сервере: `read_only` тоже раскрывает ваши данные на чтение.
### Проверьте связь [#check-connection]
Два пробных запроса: доступность API и справочник площадок. Первый идёт без ключа. Пришёл
JSON-ответ – ключ и сеть в порядке.
```bash
# API на связи – ключ не нужен
curl https://api.rewio.ru/health
# площадки, с которых собираются отзывы
curl https://api.rewio.ru/v3/sources \
-H "X-API-Key: hrev_FaDGMKJEDDeCR5BCBgEg0aEjMogI7O7hOzE53YY4T1M"
```
```python
import requests
BASE = "https://api.rewio.ru/v3"
HEADERS = {"X-API-Key": "hrev_FaDGMKJEDDeCR5BCBgEg0aEjMogI7O7hOzE53YY4T1M"}
# API на связи – ключ не нужен
health = requests.get("https://api.rewio.ru/health").json()
print(health["status"])
# площадки, с которых собираются отзывы
sources = requests.get(f"{BASE}/sources", headers=HEADERS).json()
print(sources["data"][:3])
```
```javascript
const BASE = "https://api.rewio.ru/v3";
const HEADERS = { "X-API-Key": "hrev_FaDGMKJEDDeCR5BCBgEg0aEjMogI7O7hOzE53YY4T1M" };
// API на связи – ключ не нужен
const health = await (await fetch("https://api.rewio.ru/health")).json();
console.log(health.status);
// площадки, с которых собираются отзывы
const sources = await (await fetch(`${BASE}/sources`, { headers: HEADERS })).json();
console.log(sources.data.slice(0, 3));
```
```php
### Создайте объект [#create-object]
Объект – ваша бизнес-сущность: отель, клиника, филиал, врач или специалист. Ссылки на площадки передаются
при создании или отдельным методом [добавления ссылок](/links/add). Площадка определяется
автоматически по ссылке.
Для создания объекта используйте ключ из [личного кабинета](https://app.rewio.ru).
```bash
curl -X POST https://api.rewio.ru/v3/objects \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{
"name": "Мой отель",
"links": [
{"url": "https://yandex.ru/maps/org/national/1020542995"},
{"url": "https://2gis.ru/moscow/firm/70000001006369779"}
]
}'
```
```python
import requests
BASE = "https://api.rewio.ru/v3"
HEADERS = {"X-API-Key": "hrev_ВАШ_КЛЮЧ"}
obj = requests.post(f"{BASE}/objects", headers=HEADERS, json={
"name": "Мой отель",
"links": [
{"url": "https://yandex.ru/maps/org/national/1020542995"},
{"url": "https://2gis.ru/moscow/firm/70000001006369779"},
],
}).json()
object_id = obj["id"]
for result in obj["link_results"]:
print(result["url"], result["error"] or f'ok, link_id={result["link_id"]}')
```
```javascript
const BASE = "https://api.rewio.ru/v3";
const HEADERS = {
"X-API-Key": "hrev_ВАШ_КЛЮЧ",
"Content-Type": "application/json",
};
const res = await fetch(`${BASE}/objects`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
name: "Мой отель",
links: [
{ url: "https://yandex.ru/maps/org/national/1020542995" },
{ url: "https://2gis.ru/moscow/firm/70000001006369779" },
],
}),
});
const object = await res.json();
const objectId = object.id;
console.log(object.link_results);
```
```php
'Мой отель',
'links' => [
['url' => 'https://yandex.ru/maps/org/national/1020542995'],
['url' => 'https://2gis.ru/moscow/firm/70000001006369779'],
],
], JSON_UNESCAPED_UNICODE);
$ch = curl_init("$base/objects");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: $key", 'Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
$object = json_decode(curl_exec($ch), true);
curl_close($ch);
$objectId = $object['id'];
print_r($object['link_results']);
```
Какой адрес принимает каждая площадка, смотрите в [Форматах ссылок](/link-formats).
### Дождитесь сбора [#wait-for-scrape]
Сбор стартует автоматически после создания объекта и обычно занимает несколько минут.
Состояние видно и у объекта целиком, и у каждой ссылки, в поле `scrape_status`:
* `pending` – в очереди;
* `in_progress` – идёт;
* `success` – готово;
* `partial` – собрались не все площадки;
* `failed` – не получилось, причина в `scrape_error` у ссылки.
```bash
# статус по всему объекту
curl https://api.rewio.ru/v3/objects/20 \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ"
# или по каждой площадке отдельно
curl https://api.rewio.ru/v3/objects/20/links \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ"
```
```python
import time
def wait_for_scrape(object_id, timeout_min=10):
"""Опрашивает раз в 30 секунд, пока сбор не закончится."""
for _ in range(timeout_min * 2):
obj = requests.get(f"{BASE}/objects/{object_id}", headers=HEADERS).json()
if obj["scrape_status"] in ("success", "partial", "failed"):
return obj
time.sleep(30)
return obj
obj = wait_for_scrape(object_id)
print(obj["scrape_status"], obj["last_scraped_at"])
```
```javascript
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function waitForScrape(objectId, timeoutMin = 10) {
for (let i = 0; i < timeoutMin * 2; i++) {
const res = await fetch(`${BASE}/objects/${objectId}`, { headers: HEADERS });
const obj = await res.json();
if (["success", "partial", "failed"].includes(obj.scrape_status)) return obj;
await sleep(30_000);
}
}
const scraped = await waitForScrape(objectId);
console.log(scraped.scrape_status, scraped.last_scraped_at);
```
```php
Дальше объект обновляется сам, по расписанию. Чтобы узнавать о новых отзывах и ответах, не опрашивая
нас, подпишитесь на [вебхуки](/guides/webhooks).
### Получайте отзывы или аналитику [#get-reviews]
Получайте отзывы с помощью фильтров, агрегированную аналитику или аналитику по дням, неделям и месяцам.
```bash
# отзывы: свежие сначала, только с текстом
curl -G https://api.rewio.ru/v3/reviews \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ" \
--data-urlencode "object_id=20" \
--data-urlencode "limit=20" \
--data-urlencode "has_text=true"
# или аналитика: средняя оценка, распределение, ответы организации
curl "https://api.rewio.ru/v3/analytics?object_id=20" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ"
```
```python
# отзывы: свежие сначала, только с текстом
reviews = requests.get(f"{BASE}/reviews", headers=HEADERS, params={
"object_id": object_id, "limit": 20, "has_text": True,
}).json()
print(f'{reviews["total"]} отзывов подходит под фильтры')
# или аналитика: средняя оценка, распределение, ответы организации
stats = requests.get(f"{BASE}/analytics", headers=HEADERS,
params={"object_id": object_id}).json()
summary = stats["summary"]
print(summary["average_rating"], summary["rating_distribution"])
print("негатив без ответа:", summary["reply"]["unanswered_negative_count"])
```
```javascript
// отзывы: свежие сначала, только с текстом
const q = new URLSearchParams({ object_id: objectId, limit: 20, has_text: true });
const reviews = await (await fetch(`${BASE}/reviews?${q}`, { headers: HEADERS })).json();
console.log(reviews.total, reviews.data);
// или аналитика: средняя оценка, распределение, ответы организации
const stats = await (
await fetch(`${BASE}/analytics?object_id=${objectId}`, { headers: HEADERS })
).json();
console.log(stats.summary.average_rating, stats.summary.rating_distribution);
```
```php
Отзывы приходят страницей: сами записи в `data`, а `total` и `has_next` говорят, сколько подходит
под фильтры и есть ли что дальше.
Все фильтры отзывов и все поля аналитики описаны в справочнике:
[отзывы](/reviews/list), [аналитика](/analytics/summary).
## Дальше [#next]
# OpenAPI, Postman, Swagger (/tools)
Все методы можно открыть в интерактивном просмотрщике [Swagger](https://api.rewio.ru/v3/scalar). Там они не только описаны, но и вызываются прямо в окне браузера.
Машиночитаемое описание API – OpenAPI 3.1:
```
https://api.rewio.ru/v3/openapi.json
```
Это источник истины: если ответ API расходится с документацией, верен он.
## Postman [#postman]
**Import → Link**, вставьте адрес спецификации. Затем задайте в окружении переменную с заголовком
`X-API-Key`, и коллекция готова к вызовам.
## Свой клиент [#own-client]
Типизированный клиент генерируется из той же спецификации, например
[openapi-generator](https://openapi-generator.tech/):
```bash
# Python
openapi-generator-cli generate \
-i https://api.rewio.ru/v3/openapi.json \
-g python -o ./rewio-client-python
# TypeScript
openapi-generator-cli generate \
-i https://api.rewio.ru/v3/openapi.json \
-g typescript-fetch -o ./rewio-client-ts
```
## Документация для AI-агентов [#ai-docs]
Кроме спецификации есть текстовые версии документации, которые агент выкачивает сам:
[`llms.txt`](https://docs.rewio.ru/llms.txt) – карта разделов, а
[`llms-full.txt`](https://docs.rewio.ru/llms-full.txt) – всё одним файлом. Готовый промпт для агента есть
на странице [Интеграция через AI](/ai).
# Динамика оценки (/analytics/ratings-history)
Ряд изменений оценки по каждой ссылке. Текущее значение отдельно – на странице
[Оценка на площадках](/reviews/ratings).
Новое значение оценки записывается только в том случае, если оно изменилось.
Необходимо указать либо объект, либо папку.
## GET https://api.rewio.ru/v3/ratings/history
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `measured_from · measured_to` (дата, query, может быть null) – Границы по дате замера, включительно. Названный в верхней границе день входит целиком. Не переданы – вся история.
- `object_id · folder_id` (число, query, может быть null) – Объект или папка целиком. В запросе должно быть ровно одно из двух.
- `link_ids · source_ids` (список чисел, query, по умолчанию пусто) – Ограничить выдачу конкретными ссылками объекта или площадками. Пусто – без ограничения.
### Ответ 200
- `data` (список объектов, обязательный) – По строке на каждую ссылку, у которой есть история.
- `data[].link_id` (число, обязательный) – Ссылка, к которой относится ряд.
- `data[].source_id` (число, обязательный) – Площадка, на которой показана оценка.
- `data[].points` (список объектов, обязательный) – Точки ряда, свежая первой.
- `data[].points[].rating` (число, может быть null) – Какой стала оценка, шкала 1–5.
- `data[].points[].rating_count` (число, может быть null) – Сколько оценок её сформировали, вместе с оставленными без текста.
- `data[].points[].review_count` (число, может быть null) – Сколько из этих оценок – отзывы с текстом, по данным самой площадки.
- `data[].points[].measured_at` (дата и время, обязательный) – Когда мы последний раз видели это значение.
### Пример ответа
```json
{
"data": [
{
"link_id": 794,
"source_id": 1,
"points": [
{
"rating": 4.9,
"rating_count": 966,
"review_count": 286,
"measured_at": "2026-08-27T05:21:23"
},
{
"rating": 4.8,
"rating_count": 941,
"review_count": 279,
"measured_at": "2026-08-23T05:19:44"
}
]
},
{
"link_id": 823,
"source_id": 3,
"points": [
{
"rating": 4.8,
"rating_count": 237,
"review_count": 193,
"measured_at": "2026-08-27T05:21:40"
}
]
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Сводная аналитика (/analytics/summary)
Итоги по отзывам за период: средняя оценка, распределение по звёздам и то, как отвечали на отзывы –
целиком и в разбивке по площадкам.
Считать можно по одному объекту или по папке целиком: укажите ровно одно – `object_id` или `folder_id`.
## GET https://api.rewio.ru/v3/analytics
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `folder_id` (число, query, может быть null) – Папка целиком: все её объекты и подпапки. Вместе с `object_id` не передаётся.
- `object_id` (число, query, может быть null) – Объект, по которому считать.
- `source_ids` (список чисел, query, по умолчанию пусто) – Считать только по этим площадкам. Пусто – по всем площадкам объекта.
- `published_from` (дата, query, может быть null) – Начало периода по дате публикации отзыва. По умолчанию `2000-01-01`.
- `published_to` (дата, query, может быть null) – Конец периода по дате публикации. По умолчанию сегодня.
- `min_rating` (число, query, 0–5, по умолчанию 1) – Учитывать отзывы с оценкой не ниже указанной.
- `max_rating` (число, query, 1–5, по умолчанию 5) – Учитывать отзывы с оценкой не выше указанной.
### Ответ 200
- `meta` (объект, обязательный) – Что именно посчитано: фильтры в том виде, в котором применились.
- `meta.object_id` (число, может быть null) – Объект, по которому посчитано. В папочном режиме пусто.
- `meta.folder_id` (число, может быть null) – Папка, если считали по папке.
- `meta.object_ids` (список чисел, обязательный) – Все объекты, попавшие в расчёт. В папочном режиме это состав папки вместе с подпапками.
- `meta.published_from` (дата, обязательный) – Начало периода, которое применилось.
- `meta.published_to` (дата, обязательный) – Конец периода, который применился.
- `meta.source_ids` (список чисел, обязательный) – Площадки, которые применились. Пустой список – все площадки объекта.
- `meta.generated_at` (дата и время, обязательный) – Когда посчитано.
- `summary` (объект, обязательный) – Итоги за период.
- `summary.review_count` (число, обязательный) – Сколько всего отзывов за период.
- `summary.average_rating` (число, может быть null) – Средняя оценка по шкале 1–5, до двух знаков.
- `summary.reviews_with_text` (число, может быть null) – Сколько отзывов с текстом.
- `summary.reviews_with_images` (число, может быть null) – Сколько отзывов с фотографиями.
- `summary.rating_distribution` (объект, может быть null) – Сколько отзывов на каждую оценку. Ключи от `1` до `5`.
- `summary.reply` (объект, может быть null) – Как отвечали на отзывы за период.
- `summary.reply.reviews_with_reply` (число, обязательный) – Сколько отзывов получили ответ организации.
- `summary.reply.unanswered_negative_count` (число, обязательный) – Сколько отзывов с оценкой 1–2 остались без ответа.
- `summary.reply.reply_by_rating` (объект, может быть null) – Сколько ответов дано на отзывы каждой оценки.
- `summary.reply.last_reply_date` (дата, может быть null) – Когда организация отвечала в последний раз.
- `by_source` (список объектов, обязательный) – То же самое, но по каждой площадке отдельно.
- `by_source[].source_id` (число, обязательный) – Идентификатор площадки.
- `by_source[].source_name` (строка, обязательный) – Название площадки.
- `by_source[].review_count` (число, обязательный) – Сколько отзывов с этой площадки за период.
- `by_source[].average_rating` (число, может быть null) – Средняя оценка по площадке.
- `by_source[].reviews_with_reply` (число, может быть null) – Сколько отзывов с ответом организации.
- `by_source[].reviews_with_text` (число, может быть null) – Сколько отзывов с текстом.
- `by_source[].reviews_with_images` (число, может быть null) – Сколько отзывов с фотографиями.
- `by_source[].rating_distribution` (объект, может быть null) – Распределение отзывов площадки по оценкам.
- `by_source[].reply_distribution` (объект, может быть null) – Распределение ответов организации по оценке отзыва.
- `by_source[].latest_review_date` (дата, может быть null) – Дата самого свежего отзыва с этой площадки.
- `access` (объект, может быть null) – Ограничение выдачи, если показали не всё.
- `access.reason` (строка, обязательный) – Почему выдача ограничена. `trial` — пробный доступ, `manual` — ограничение, согласованное по вашему договору.
- `access.limit_per_link` (число, обязательный) – Сколько последних отзывов показывается по КАЖДОЙ ссылке (площадке объекта).
- `access.hidden_total` (число, обязательный) – Сколько отзывов уже собрано сверх показанных. Считается по всему архиву и не зависит от фильтров запроса.
- `access.message` (строка, обязательный) – Готовая формулировка для показа человеку.
### Пример ответа
```json
{
"meta": {
"object_id": 20,
"folder_id": null,
"object_ids": [
20
],
"published_from": "2000-01-01",
"published_to": "2026-08-25",
"source_ids": [
1,
2,
3,
104,
105,
106,
108
],
"generated_at": "2026-08-25T05:43:00.078703Z"
},
"summary": {
"review_count": 5838,
"average_rating": 4.51,
"reviews_with_text": 3929,
"reviews_with_images": 718,
"rating_distribution": {
"1": 254,
"2": 133,
"3": 358,
"4": 964,
"5": 4127
},
"reply": {
"reviews_with_reply": 1327,
"unanswered_negative_count": 287,
"reply_by_rating": {
"1": 65,
"2": 35,
"3": 104,
"4": 192,
"5": 931
},
"last_reply_date": "2026-08-20"
}
},
"by_source": [
{
"source_id": 2,
"source_name": "Google Maps",
"review_count": 3301,
"average_rating": 4.52,
"reviews_with_reply": 0,
"reviews_with_text": 1727,
"reviews_with_images": 207,
"rating_distribution": {
"1": 149,
"2": 70,
"3": 165,
"4": 457,
"5": 2460
},
"reply_distribution": {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0
},
"latest_review_date": "2026-08-24"
},
{
"source_id": 1,
"source_name": "Яндекс.Карты",
"review_count": 1092,
"average_rating": 4.48,
"reviews_with_reply": 1043,
"reviews_with_text": 1092,
"reviews_with_images": 346,
"rating_distribution": {
"1": 56,
"2": 30,
"3": 69,
"4": 119,
"5": 818
},
"reply_distribution": {
"1": 51,
"2": 26,
"3": 64,
"4": 115,
"5": 787
},
"latest_review_date": "2026-08-23"
},
{
"source_id": 106,
"source_name": "Островок",
"review_count": 698,
"average_rating": 4.54,
"reviews_with_reply": 159,
"reviews_with_text": 473,
"reviews_with_images": 63,
"rating_distribution": {
"1": 7,
"2": 15,
"3": 87,
"4": 303,
"5": 286
},
"reply_distribution": {
"1": 2,
"2": 5,
"3": 33,
"4": 71,
"5": 48
},
"latest_review_date": "2026-08-24"
},
{
"source_id": 3,
"source_name": "2ГИС",
"review_count": 442,
"average_rating": 4.47,
"reviews_with_reply": 125,
"reviews_with_text": 442,
"reviews_with_images": 51,
"rating_distribution": {
"1": 29,
"2": 11,
"3": 21,
"4": 43,
"5": 338
},
"reply_distribution": {
"1": 12,
"2": 4,
"3": 7,
"4": 6,
"5": 96
},
"latest_review_date": "2026-08-16"
},
{
"source_id": 104,
"source_name": "Яндекс.Путешествия",
"review_count": 251,
"average_rating": 4.56,
"reviews_with_reply": 0,
"reviews_with_text": 150,
"reviews_with_images": 39,
"rating_distribution": {
"1": 10,
"2": 6,
"3": 13,
"4": 25,
"5": 195
},
"reply_distribution": {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0
},
"latest_review_date": "2026-08-25"
},
{
"source_id": 108,
"source_name": "TopHotels",
"review_count": 32,
"average_rating": 4.32,
"reviews_with_reply": 0,
"reviews_with_text": 28,
"reviews_with_images": 12,
"rating_distribution": {
"1": 3,
"2": 1,
"3": 2,
"4": 9,
"5": 17
},
"reply_distribution": {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0
},
"latest_review_date": "2025-06-11"
},
{
"source_id": 105,
"source_name": "101Hotels",
"review_count": 22,
"average_rating": 4.76,
"reviews_with_reply": 0,
"reviews_with_text": 17,
"reviews_with_images": 0,
"rating_distribution": {
"1": 0,
"2": 0,
"3": 1,
"4": 8,
"5": 13
},
"reply_distribution": {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0
},
"latest_review_date": "2026-05-06"
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Динамика по периодам (/analytics/timeseries)
Те же метрики, что и в сводке, разложенные по дням, неделям или месяцам.
Ряд начинается с первого целого интервала. При шаге в месяц и `published_from=2026-05-06` май в ряд
не попадёт совсем – чтобы увидеть его, начните период с первого числа.
## GET https://api.rewio.ru/v3/analytics/timeseries
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `folder_id` (число, query, может быть null) – Папка целиком: все её объекты и подпапки. Вместе с `object_id` не передаётся.
- `object_id` (число, query, может быть null) – Объект, по которому строить ряд.
- `source_ids` (список чисел, query, по умолчанию пусто) – Отдельный ряд на каждую из этих площадок. По умолчанию `0`: общий ряд по всем.
- `granularity` (строка, query, day / week / month, по умолчанию «month») – Шаг ряда: по дням, неделям или месяцам.
- `published_from` (дата, query, может быть null) – Начало периода по дате публикации отзыва. По умолчанию 90 дней назад.
- `published_to` (дата, query, может быть null) – Конец периода по дате публикации. По умолчанию сегодня.
- `min_rating` (число, query, 0–5, по умолчанию 1) – Учитывать отзывы с оценкой не ниже указанной.
- `max_rating` (число, query, 1–5, по умолчанию 5) – Учитывать отзывы с оценкой не выше указанной.
### Ответ 200
- `meta` (объект, обязательный) – Что именно посчитано: фильтры в том виде, в котором применились.
- `meta.object_id` (число, может быть null) – Объект, по которому посчитано. В папочном режиме пусто.
- `meta.folder_id` (число, может быть null) – Папка, если считали по папке.
- `meta.object_ids` (список чисел, обязательный) – Все объекты, попавшие в расчёт.
- `meta.published_from` (дата, обязательный) – Начало периода, которое применилось.
- `meta.published_to` (дата, обязательный) – Конец периода, который применился.
- `meta.source_ids` (список чисел, обязательный) – Площадки, которые применились.
- `meta.generated_at` (дата и время, обязательный) – Когда посчитано.
- `meta.granularity` (строка, обязательный) – Шаг ряда, который применился.
- `series` (список объектов, обязательный) – Ряды, по одному на каждую запрошенную площадку.
- `series[].source_id` (число, обязательный) – Площадка ряда. `0` – общий ряд по всем площадкам.
- `series[].source_name` (строка, обязательный) – Название площадки. У общего ряда это «Все источники».
- `series[].points` (список объектов, обязательный) – Точки ряда по возрастанию времени.
- `series[].points[].period` (строка, обязательный) – Метка интервала: `2026-08-04` для дня, `2026-W32` для недели, `2026-08` для месяца.
- `series[].points[].review_count` (число, обязательный) – Сколько отзывов пришло за интервал.
- `series[].points[].average_rating` (число, может быть null) – Средняя оценка за интервал.
- `series[].points[].reply_count` (число, может быть null) – Сколько отзывов получили ответ организации.
- `series[].points[].reviews_with_text` (число, может быть null) – Сколько отзывов с текстом.
- `series[].points[].reviews_with_images` (число, может быть null) – Сколько отзывов с фотографиями.
- `series[].points[].rating_distribution` (объект, может быть null) – Распределение отзывов интервала по оценкам.
- `series[].points[].reply_distribution` (объект, может быть null) – Распределение ответов организации по оценке отзыва.
- `access` (объект, может быть null) – Ограничение выдачи, если показали не всё.
- `access.reason` (строка, обязательный) – Почему выдача ограничена. `trial` — пробный доступ, `manual` — ограничение, согласованное по вашему договору.
- `access.limit_per_link` (число, обязательный) – Сколько последних отзывов показывается по КАЖДОЙ ссылке (площадке объекта).
- `access.hidden_total` (число, обязательный) – Сколько отзывов уже собрано сверх показанных. Считается по всему архиву и не зависит от фильтров запроса.
- `access.message` (строка, обязательный) – Готовая формулировка для показа человеку.
### Пример ответа
```json
{
"meta": {
"object_id": 20,
"folder_id": null,
"object_ids": [
20
],
"published_from": "2026-05-01",
"published_to": "2026-08-01",
"source_ids": [
0
],
"generated_at": "2026-08-25T05:52:21.664180Z",
"granularity": "month"
},
"series": [
{
"source_id": 0,
"source_name": "Все источники",
"points": [
{
"period": "2026-05",
"review_count": 69,
"average_rating": 4.47,
"reply_count": 43,
"reviews_with_text": 51,
"reviews_with_images": 7,
"rating_distribution": {
"1": 5,
"2": 0,
"3": 3,
"4": 17,
"5": 44
},
"reply_distribution": {
"1": 4,
"2": 0,
"3": 1,
"4": 9,
"5": 29
}
},
{
"period": "2026-06",
"review_count": 87,
"average_rating": 4.28,
"reply_count": 48,
"reviews_with_text": 72,
"reviews_with_images": 11,
"rating_distribution": {
"1": 4,
"2": 3,
"3": 15,
"4": 20,
"5": 45
},
"reply_distribution": {
"1": 3,
"2": 2,
"3": 6,
"4": 7,
"5": 30
}
},
{
"period": "2026-07",
"review_count": 71,
"average_rating": 4.42,
"reply_count": 47,
"reviews_with_text": 54,
"reviews_with_images": 14,
"rating_distribution": {
"1": 5,
"2": 2,
"3": 5,
"4": 13,
"5": 46
},
"reply_distribution": {
"1": 3,
"2": 0,
"3": 4,
"4": 8,
"5": 32
}
},
{
"period": "2026-08",
"review_count": 0,
"average_rating": null,
"reply_count": null,
"reviews_with_text": null,
"reviews_with_images": null,
"rating_distribution": null,
"reply_distribution": null
}
]
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Создать папку (/folders/create)
Папка объединяет объекты, чтобы спрашивать отзывы и аналитику сразу по всем: `GET /v3/reviews?folder_id=…`
отдаёт единую ленту по всем объектам папки и её подпапок.
Глубина – два уровня: папка → подпапка → объекты. Один объект может лежать сразу в нескольких папках.
Объект, который положить не удалось, создать папку не мешает: он не попадёт в состав, а его
идентификатор вернётся в `ignored_object_ids`.
## POST https://api.rewio.ru/v3/folders
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `Idempotency-Key` (строка, header, может быть null) – Свой ключ запроса. Повтор с тем же ключом не создаст вторую папку.
### Тело запроса
- `name` (строка, обязательный) – Название папки, 1–255 символов.
- `parent_folder_id` (число, может быть null) – Положить папку внутрь другой. Не передано – папка окажется в корне.
- `object_ids` (список чисел, может быть null) – Объекты, которые сразу положить в папку, до 200.
### Пример запроса
```json
{
"name": "Москва",
"object_ids": [
20,
21
]
}
```
### Ответ 201
- `id` (число, обязательный) – Идентификатор созданной папки.
- `user_id` (число, обязательный) – Владелец папки.
- `name` (строка, обязательный) – Название папки.
- `created_at` (дата и время, обязательный) – Когда папка создана.
- `parent_folder_id` (число, может быть null) – Папка-родитель. Пусто – папка лежит в корне.
- `objects` (список объектов, по умолчанию пусто) – Объекты, лежащие непосредственно в этой папке.
- `objects[].id` (число, обязательный) – Идентификатор объекта.
- `objects[].name` (строка, обязательный) – Название объекта.
- `subfolders` (список объектов, по умолчанию пусто) – Подпапки этой папки. У новой папки список пустой.
- `subfolders[].id` (число, обязательный) – Идентификатор папки.
- `subfolders[].user_id` (число, обязательный) – Владелец папки.
- `subfolders[].name` (строка, обязательный) – Название папки.
- `subfolders[].created_at` (дата и время, обязательный) – Момент создания папки (UTC, ISO 8601).
- `subfolders[].parent_folder_id` (число, может быть null) – Родительская папка; `null` у папки верхнего уровня.
- `subfolders[].objects` (список объектов, по умолчанию пусто) – Собственный состав папки — объекты с названиями.
- `subfolders[].objects[].id` (число, обязательный) – Идентификатор объекта.
- `subfolders[].objects[].name` (строка, обязательный) – Название объекта.
- `subfolders[].subfolders` (список объектов, по умолчанию пусто) – Подпапки со своим составом.
- `subfolders[].subfolders[].id` (число, обязательный) – Идентификатор папки.
- `subfolders[].subfolders[].user_id` (число, обязательный) – Владелец папки.
- `subfolders[].subfolders[].name` (строка, обязательный) – Название папки.
- `subfolders[].subfolders[].created_at` (дата и время, обязательный) – Момент создания папки (UTC, ISO 8601).
- `subfolders[].subfolders[].parent_folder_id` (число, может быть null) – Родительская папка; `null` у папки верхнего уровня.
- `subfolders[].subfolders[].objects` (список объектов, по умолчанию пусто) – Собственный состав папки — объекты с названиями.
- `subfolders[].subfolders[].objects[].id` (число, обязательный) – Идентификатор объекта.
- `subfolders[].subfolders[].objects[].name` (строка, обязательный) – Название объекта.
- `subfolders[].subfolders[].subfolders` (список объектов, по умолчанию пусто) – Подпапки со своим составом.
- `subfolders[].subfolders[].subfolders[].id` (число, обязательный) – Идентификатор папки.
- `subfolders[].subfolders[].subfolders[].user_id` (число, обязательный) – Владелец папки.
- `subfolders[].subfolders[].subfolders[].name` (строка, обязательный) – Название папки.
- `subfolders[].subfolders[].subfolders[].created_at` (дата и время, обязательный) – Момент создания папки (UTC, ISO 8601).
- `subfolders[].subfolders[].subfolders[].parent_folder_id` (число, может быть null) – Родительская папка; `null` у папки верхнего уровня.
- `subfolders[].subfolders[].subfolders[].objects` (список объектов, по умолчанию пусто) – Собственный состав папки — объекты с названиями.
- `subfolders[].subfolders[].subfolders[].subfolders` (список объектов, по умолчанию пусто) – Подпапки со своим составом.
- `ignored_object_ids` (список чисел) – Идентификаторы из `object_ids`, которые в состав не попали: чужие, удалённые или несуществующие.
### Пример ответа
```json
{
"id": 12,
"user_id": 6,
"name": "Москва",
"created_at": "2026-07-28T16:50:24",
"parent_folder_id": null,
"objects": [
{
"id": 20,
"name": "Националь"
},
{
"id": 21,
"name": "Метрополь"
}
],
"subfolders": [],
"ignored_object_ids": []
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Удалить папку (/folders/delete)
Удаляет только саму папку. Объекты внутри остаются жить и продолжают собирать отзывы.
Если внутри есть подпапки, удаление не пройдёт – сначала перенесите или удалите их.
## DELETE https://api.rewio.ru/v3/folders/{folder_id}
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `folder_id` (число, path, обязательный) – Папка, которую нужно удалить.
### Ответ 200
- `id` (число, обязательный) – Идентификатор удалённой папки.
- `name` (строка, обязательный) – Название удалённой папки.
### Пример ответа
```json
{
"id": 13,
"name": "Центр"
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Получить папку (/folders/get)
Одна ветка дерева: сама папка и её подпапки со своим составом.
Запросить можно и подпапку – придёт она сама с пустым `subfolders`.
## GET https://api.rewio.ru/v3/folders/{folder_id}
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `folder_id` (число, path, обязательный) – Папка, которую нужно получить.
### Ответ 200
- `id` (число, обязательный) – Идентификатор папки.
- `user_id` (число, обязательный) – Владелец папки.
- `name` (строка, обязательный) – Название папки.
- `created_at` (дата и время, обязательный) – Когда папка создана.
- `parent_folder_id` (число, может быть null) – Папка-родитель. Пусто – папка лежит в корне.
- `objects` (список объектов, по умолчанию пусто) – Объекты, лежащие непосредственно в этой папке, без объектов подпапок.
- `objects[].id` (число, обязательный) – Идентификатор объекта.
- `objects[].name` (строка, обязательный) – Название объекта.
- `subfolders` (список объектов, по умолчанию пусто) – Подпапки этой папки, по названию по алфавиту.
- `subfolders[].id` (число, обязательный) – Идентификатор подпапки.
- `subfolders[].user_id` (число, обязательный) – Владелец подпапки.
- `subfolders[].name` (строка, обязательный) – Название подпапки.
- `subfolders[].created_at` (дата и время, обязательный) – Когда подпапка создана.
- `subfolders[].parent_folder_id` (число, может быть null) – Папка-родитель.
- `subfolders[].objects` (список объектов, по умолчанию пусто) – Объекты, лежащие непосредственно в подпапке.
- `subfolders[].objects[].id` (число, обязательный) – Идентификатор объекта.
- `subfolders[].objects[].name` (строка, обязательный) – Название объекта.
- `subfolders[].subfolders` (список объектов, по умолчанию пусто) – Всегда пусто: глубина ограничена двумя уровнями.
- `subfolders[].subfolders[].id` (число, обязательный) – Идентификатор папки.
- `subfolders[].subfolders[].user_id` (число, обязательный) – Владелец папки.
- `subfolders[].subfolders[].name` (строка, обязательный) – Название папки.
- `subfolders[].subfolders[].created_at` (дата и время, обязательный) – Момент создания папки (UTC, ISO 8601).
- `subfolders[].subfolders[].parent_folder_id` (число, может быть null) – Родительская папка; `null` у папки верхнего уровня.
- `subfolders[].subfolders[].objects` (список объектов, по умолчанию пусто) – Собственный состав папки — объекты с названиями.
- `subfolders[].subfolders[].objects[].id` (число, обязательный) – Идентификатор объекта.
- `subfolders[].subfolders[].objects[].name` (строка, обязательный) – Название объекта.
- `subfolders[].subfolders[].subfolders` (список объектов, по умолчанию пусто) – Подпапки со своим составом.
- `subfolders[].subfolders[].subfolders[].id` (число, обязательный) – Идентификатор папки.
- `subfolders[].subfolders[].subfolders[].user_id` (число, обязательный) – Владелец папки.
- `subfolders[].subfolders[].subfolders[].name` (строка, обязательный) – Название папки.
- `subfolders[].subfolders[].subfolders[].created_at` (дата и время, обязательный) – Момент создания папки (UTC, ISO 8601).
- `subfolders[].subfolders[].subfolders[].parent_folder_id` (число, может быть null) – Родительская папка; `null` у папки верхнего уровня.
- `subfolders[].subfolders[].subfolders[].objects` (список объектов, по умолчанию пусто) – Собственный состав папки — объекты с названиями.
- `subfolders[].subfolders[].subfolders[].subfolders` (список объектов, по умолчанию пусто) – Подпапки со своим составом.
### Пример ответа
```json
{
"id": 12,
"user_id": 6,
"name": "Москва",
"created_at": "2026-07-28T16:50:24",
"parent_folder_id": null,
"objects": [
{
"id": 20,
"name": "Националь"
},
{
"id": 21,
"name": "Метрополь"
}
],
"subfolders": [
{
"id": 13,
"user_id": 6,
"name": "Центр",
"created_at": "2026-07-28T16:52:11",
"parent_folder_id": 12,
"objects": [
{
"id": 22,
"name": "Балчуг"
}
],
"subfolders": []
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Список папок (/folders/list)
Всё дерево папок одним запросом: в `data` – корневые папки, подпапки лежат внутри своего родителя.
Состав у каждой папки свой: объекты подпапки не входят в состав родителя, хотя в выдачу по его
`folder_id` попадают.
## GET https://api.rewio.ru/v3/folders
Заголовок `X-API-Key` обязателен в каждом запросе.
### Ответ 200
- `data` (список объектов, обязательный) – Корневые папки, по названию по алфавиту. Пагинации нет.
- `data[].id` (число, обязательный) – Идентификатор папки.
- `data[].user_id` (число, обязательный) – Владелец папки.
- `data[].name` (строка, обязательный) – Название папки.
- `data[].created_at` (дата и время, обязательный) – Когда папка создана.
- `data[].parent_folder_id` (число, может быть null) – Папка-родитель. Пусто – папка лежит в корне.
- `data[].objects` (список объектов, по умолчанию пусто) – Объекты, лежащие непосредственно в этой папке, без объектов подпапок.
- `data[].objects[].id` (число, обязательный) – Идентификатор объекта.
- `data[].objects[].name` (строка, обязательный) – Название объекта.
- `data[].subfolders` (список объектов, по умолчанию пусто) – Подпапки этой папки, по названию по алфавиту.
- `data[].subfolders[].id` (число, обязательный) – Идентификатор подпапки.
- `data[].subfolders[].user_id` (число, обязательный) – Владелец подпапки.
- `data[].subfolders[].name` (строка, обязательный) – Название подпапки.
- `data[].subfolders[].created_at` (дата и время, обязательный) – Когда подпапка создана.
- `data[].subfolders[].parent_folder_id` (число, может быть null) – Папка-родитель.
- `data[].subfolders[].objects` (список объектов, по умолчанию пусто) – Объекты, лежащие непосредственно в подпапке.
- `data[].subfolders[].objects[].id` (число, обязательный) – Идентификатор объекта.
- `data[].subfolders[].objects[].name` (строка, обязательный) – Название объекта.
- `data[].subfolders[].subfolders` (список объектов, по умолчанию пусто) – Всегда пусто: глубина ограничена двумя уровнями.
- `data[].subfolders[].subfolders[].id` (число, обязательный) – Идентификатор папки.
- `data[].subfolders[].subfolders[].user_id` (число, обязательный) – Владелец папки.
- `data[].subfolders[].subfolders[].name` (строка, обязательный) – Название папки.
- `data[].subfolders[].subfolders[].created_at` (дата и время, обязательный) – Момент создания папки (UTC, ISO 8601).
- `data[].subfolders[].subfolders[].parent_folder_id` (число, может быть null) – Родительская папка; `null` у папки верхнего уровня.
- `data[].subfolders[].subfolders[].objects` (список объектов, по умолчанию пусто) – Собственный состав папки — объекты с названиями.
- `data[].subfolders[].subfolders[].subfolders` (список объектов, по умолчанию пусто) – Подпапки со своим составом.
### Пример ответа
```json
{
"data": [
{
"id": 12,
"user_id": 6,
"name": "Москва",
"created_at": "2026-07-28T16:50:24",
"parent_folder_id": null,
"objects": [
{
"id": 20,
"name": "Националь"
},
{
"id": 21,
"name": "Метрополь"
}
],
"subfolders": [
{
"id": 13,
"user_id": 6,
"name": "Центр",
"created_at": "2026-07-28T16:52:11",
"parent_folder_id": 12,
"objects": [
{
"id": 22,
"name": "Балчуг"
}
],
"subfolders": []
}
]
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Изменить папку (/folders/update)
Переименовывает, переносит и меняет состав папки за один вызов. Меняется только то, что передано.
`object_ids` заменяет состав целиком, а не дополняет его. Чтобы добавить один объект, передайте
её вместе с теми, что уже лежат в папке.
Объект, который положить не удалось, остальной состав не отменяет: он не попадёт в папку, а его
идентификатор вернётся в `ignored_object_ids`.
## PATCH https://api.rewio.ru/v3/folders/{folder_id}
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `folder_id` (число, path, обязательный) – Папка, которую нужно изменить.
### Тело запроса
- `name` (строка, может быть null) – Новое название, 1–255 символов. Не передано – остаётся прежним.
- `parent_folder_id` (число, может быть null) – Перенести папку внутрь другой. `null` – вынести в корень. Не передано – остаётся где была.
- `object_ids` (список чисел, может быть null) – Новый состав папки целиком. Пустой список очищает состав.
### Пример запроса
```json
{
"name": "Москва и область",
"object_ids": [
20,
21,
22,
99
]
}
```
### Ответ 200
- `id` (число, обязательный) – Идентификатор папки.
- `user_id` (число, обязательный) – Владелец папки.
- `name` (строка, обязательный) – Название папки.
- `created_at` (дата и время, обязательный) – Когда папка создана.
- `parent_folder_id` (число, может быть null) – Папка-родитель. Пусто – папка лежит в корне.
- `objects` (список объектов, по умолчанию пусто) – Объекты, лежащие непосредственно в этой папке.
- `objects[].id` (число, обязательный) – Идентификатор объекта.
- `objects[].name` (строка, обязательный) – Название объекта.
- `subfolders` (список объектов, по умолчанию пусто) – Подпапки этой папки, по названию по алфавиту.
- `subfolders[].id` (число, обязательный) – Идентификатор подпапки.
- `subfolders[].user_id` (число, обязательный) – Владелец подпапки.
- `subfolders[].name` (строка, обязательный) – Название подпапки.
- `subfolders[].created_at` (дата и время, обязательный) – Когда подпапка создана.
- `subfolders[].parent_folder_id` (число, может быть null) – Папка-родитель.
- `subfolders[].objects` (список объектов, по умолчанию пусто) – Объекты, лежащие непосредственно в подпапке.
- `subfolders[].objects[].id` (число, обязательный) – Идентификатор объекта.
- `subfolders[].objects[].name` (строка, обязательный) – Название объекта.
- `subfolders[].subfolders` (список объектов, по умолчанию пусто) – Всегда пусто: глубина ограничена двумя уровнями.
- `subfolders[].subfolders[].id` (число, обязательный) – Идентификатор папки.
- `subfolders[].subfolders[].user_id` (число, обязательный) – Владелец папки.
- `subfolders[].subfolders[].name` (строка, обязательный) – Название папки.
- `subfolders[].subfolders[].created_at` (дата и время, обязательный) – Момент создания папки (UTC, ISO 8601).
- `subfolders[].subfolders[].parent_folder_id` (число, может быть null) – Родительская папка; `null` у папки верхнего уровня.
- `subfolders[].subfolders[].objects` (список объектов, по умолчанию пусто) – Собственный состав папки — объекты с названиями.
- `subfolders[].subfolders[].objects[].id` (число, обязательный) – Идентификатор объекта.
- `subfolders[].subfolders[].objects[].name` (строка, обязательный) – Название объекта.
- `subfolders[].subfolders[].subfolders` (список объектов, по умолчанию пусто) – Подпапки со своим составом.
- `subfolders[].subfolders[].subfolders[].id` (число, обязательный) – Идентификатор папки.
- `subfolders[].subfolders[].subfolders[].user_id` (число, обязательный) – Владелец папки.
- `subfolders[].subfolders[].subfolders[].name` (строка, обязательный) – Название папки.
- `subfolders[].subfolders[].subfolders[].created_at` (дата и время, обязательный) – Момент создания папки (UTC, ISO 8601).
- `subfolders[].subfolders[].subfolders[].parent_folder_id` (число, может быть null) – Родительская папка; `null` у папки верхнего уровня.
- `subfolders[].subfolders[].subfolders[].objects` (список объектов, по умолчанию пусто) – Собственный состав папки — объекты с названиями.
- `subfolders[].subfolders[].subfolders[].subfolders` (список объектов, по умолчанию пусто) – Подпапки со своим составом.
- `ignored_object_ids` (список чисел) – Идентификаторы из `object_ids`, которые в состав не попали: чужие, удалённые или несуществующие.
### Пример ответа
```json
{
"id": 12,
"user_id": 6,
"name": "Москва и область",
"created_at": "2026-07-28T16:50:24",
"parent_folder_id": null,
"objects": [
{
"id": 20,
"name": "Националь"
},
{
"id": 21,
"name": "Метрополь"
},
{
"id": 22,
"name": "Балчуг"
}
],
"subfolders": [
{
"id": 13,
"user_id": 6,
"name": "Центр",
"created_at": "2026-07-28T16:52:11",
"parent_folder_id": 12,
"objects": [
{
"id": 22,
"name": "Балчуг"
}
],
"subfolders": []
}
],
"ignored_object_ids": [
99
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Как считать аналитику (/guides/analytics)
Аналитика отвечает на вопросы про массив отзывов, не заставляя вас его выкачивать:
«какой сейчас средний балл», «сколько негатива осталось без ответа», «где хуже всего»,
«растём мы или падаем». Считаем мы, вы получаете готовые числа.
Методов два, и различаются они не набором метрик, а срезом:
| Вопрос | Метод |
| --------------------------------------------------- | ------------------------------------------------------------------ |
| Как обстоят дела **сейчас**, за период целиком | [Сводка](/analytics/summary) – `GET /v3/analytics` |
| Как это **менялось** – по дням, неделям или месяцам | [Динамика](/analytics/timeseries) – `GET /v3/analytics/timeseries` |
## Что задаёт расчёт [#parameters]
Оба метода принимают одно и то же.
**Адресат – ровно один.** `object_id` для объекта либо `folder_id` для
[папки](/guides/folders) целиком; оба сразу – `400 object_and_folder_conflict`, ни одного –
`400 object_or_folder_required`. В папке считается по всем её объектам и объектам подпапок,
а ссылка, попавшая в несколько объектов одной папки, учитывается один раз: метрики не удваиваются.
**Период – по дате публикации отзыва** (`published_from` / `published_to`, включительно).
Это `review_date`, то есть когда отзыв появился на площадке, а не когда мы его забрали.
Умолчания у методов разные: сводка без границ считает всю историю, ряд – последние 90 дней.
**Фильтры** – `source_ids` (только эти площадки) и `min_rating` / `max_rating` (только эта
полоса оценок). Оба сужают массив до расчёта, а не после него.
## Сводка [#summary]
```bash
curl "https://api.rewio.ru/v3/analytics?object_id=20" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ"
```
Ответ – три блока:
* **`meta`** – что фактически посчитано: список объектов, реальные границы периода, применённые
площадки, момент расчёта. Читать его стоит всегда: именно здесь видно, какой период подставился
по умолчанию.
* **`summary`** – общие числа за период: `review_count`, `average_rating` (шкала 1–5),
`rating_distribution`, `reviews_with_text`, `reviews_with_images` и блок `reply`.
* **`by_source`** – те же числа по каждой площадке отдельно. В папочном режиме одна площадка
разных объектов схлопывается в одну строку с суммой.
Блок `reply` – это про работу с отзывами, а не про сами отзывы: `reviews_with_reply` – сколько
отвечено, `unanswered_negative_count` – сколько низких оценок осталось без ответа,
`reply_by_rating` – на какие оценки отвечают, `last_reply_date` – когда отвечали в последний раз.
Отсюда собираются типовые показатели:
| Что нужно | Как получить |
| ------------------------- | ----------------------------------------------------------- |
| Текущий рейтинг | `summary.average_rating` |
| Доля отвеченных | `summary.reply.reviews_with_reply` / `summary.review_count` |
| Очередь на ответ | `summary.reply.unanswered_negative_count` |
| Какая площадка тянет вниз | сравнить `average_rating` в строках `by_source` |
| Где отзывы вообще есть | `review_count` в строках `by_source` |
## Динамика [#dynamics]
```bash
curl "https://api.rewio.ru/v3/analytics/timeseries?object_id=20&granularity=month" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ"
```
`granularity` – шаг ряда: `day`, `week` или `month`.
Ответ – `series`: по ряду на каждую площадку плюс агрегированный ряд с `source_id: 0`
(«Все источники»). Точки лежат в `points`, в хронологическом порядке; в каждой – метка периода
`period`, `review_count`, `average_rating` и распределения.
Средняя оценка в точке считается по отзывам этого интервала, а не накопительно: это не
«рейтинг на дату», а «как оценивали в этом месяце». Накопительный рейтинг даёт сводка.
Без явных границ ряд строится за последние 90 дней, а не за всю историю. И ряд начинается
с первого целого интервала: при шаге в месяц и `published_from=2026-05-06` мая в ряду не будет
совсем; ставьте начало периода на первое число.
```python
import requests
BASE = "https://api.rewio.ru/v3"
HEADERS = {"X-API-Key": "hrev_ВАШ_КЛЮЧ"}
r = requests.get(f"{BASE}/analytics/timeseries", headers=HEADERS, params={
"object_id": 20, "granularity": "month",
"published_from": "2025-08-01", "published_to": "2026-08-01",
})
for series in r.json()["series"]:
print(series["source_name"])
for point in series["points"]:
print(f" {point['period']}: {point['average_rating']} ({point['review_count']} отз.)")
```
## Что в расчёт не входит [#exclusions]
* **Скрытые и закреплённые отзывы аналитику не двигают.** [Виджетные настройки](/guides/widgets)
оформляют показ, а не данные: скрыли отзыв с единицей – он пропал из ленты, а средний балл
остался прежним. Балл, согласованный с показанным, считайте сами по выдаче.
* **Выключенные объекты и ссылки данных не дают.** Внутри папки такой объект молча отсутствует,
прямой запрос по нему отвечает `404`.
Все параметры и все поля ответа – в справочнике: [сводка](/analytics/summary),
[динамика](/analytics/timeseries).
# Как работать с папками (/guides/folders)
Папка объединяет объекты, чтобы спрашивать данные сразу по всему набору.
Не нужно запрашивать объекты по одному и складывать результаты у себя: положите их в папку
и спрашивайте данные по ней целиком.
```bash
curl "https://api.rewio.ru/v3/reviews?folder_id=7&limit=50" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ"
curl "https://api.rewio.ru/v3/analytics?folder_id=7" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ"
```
## Как устроено [#how-it-works]
```
Папка «Сеть Север» (folder_id: 7)
├── Объект «Гранд Отель» → ссылки → отзывы…
├── Объект «Приморская» → ссылки → отзывы…
└── Подпапка «Москва» (folder_id: 12)
├── Объект «Тверская» → ссылки → отзывы…
└── Объект «Арбат» → ссылки → отзывы…
```
* **Максимальная глубина – два уровня.** Папка → подпапка → объекты. Попытка вложить
подпапку в подпапку – `400 folder_depth_exceeded`.
* **У подпапки один родитель**, а **объект может лежать в скольких угодно папках** –
например, и в «Сеть Север», и в «Премиум-сегмент».
* Запрос по родительской папке возвращает **объединение**: её собственные объекты
плюс объекты всех её подпапок. Ссылка, попавшая в несколько объектов одной папки,
учитывается **один раз**: ни отзывы, ни метрики не удваиваются.
* Папки **не тарифицируются**: место в лимите занимают объекты. Ограничение одно –
не больше 1000 живых папок на аккаунт.
## Управление папками [#managing]
Пять методов. Для чтения хватит ключа `read_only`, для остальных нужен `full`.
| Метод | Что делает |
| -------------------------------- | ------------------------------------------------------------- |
| `POST /v3/folders` | Создать – сразу с составом и, если нужно, внутри другой папки |
| `GET /v3/folders` | Все папки с подпапками и объектами в них |
| `GET /v3/folders/{folder_id}` | Одна папка со своими объектами и подпапками |
| `PATCH /v3/folders/{folder_id}` | Переименовать, перенести и сменить состав одним вызовом |
| `DELETE /v3/folders/{folder_id}` | Удалить папку. Объекты внутри остаются |
`GET /v3/folders` отдаёт дерево целиком: в `data` – корневые папки, подпапки лежат
внутри своего родителя в поле `subfolders`. Порядок и там, и там: по названию, при
совпадении – по `id`; сортировка регистронезависимая.
```json
{
"data": [
{
"id": 7,
"user_id": 42,
"name": "Сеть Север",
"created_at": "2026-07-28T09:14:02",
"parent_folder_id": null,
"objects": [{ "id": 20, "name": "Гранд Отель" }],
"subfolders": [
{
"id": 12,
"user_id": 42,
"name": "Москва",
"created_at": "2026-07-28T09:20:41",
"parent_folder_id": 7,
"objects": [{ "id": 31, "name": "Тверская" }],
"subfolders": []
}
]
}
]
}
```
Состав у каждой папки свой: объекты подпапки в `objects` родителя не попадают, хотя в
выдачу по его `folder_id` войдут. Нужна одна ветка вместо всего дерева, запросите
`GET /v3/folders/{folder_id}`; для подпапки он вернёт её саму с пустым `subfolders`.
`object_ids` в `POST` и `PATCH` – это **полная замена** состава, а не добавление.
Прислали `[3, 4]` – состав стал ровно `[3, 4]`. Прислали `[]` – состав очищен.
Не передали поле вовсе – состав не тронут.
Объект, которого у вас нет (чужой, удалённый или несуществующий), остальной состав не отменяет.
Он просто не попадёт в папку, а его идентификатор вернётся в `ignored_object_ids`.
На вход идут идентификаторы (`object_ids`), а в ответе состав приходит объектами
с названиями (`objects`), чтобы нарисовать дерево, не запрашивая объекты отдельно.
Повторный `POST` с тем же заголовком `Idempotency-Key` вернёт ту же папку и не
создаст вторую. Названия папок не обязаны быть уникальными, так что на таймауте
клиента это единственная защита от дубля.
Удалить папку с **живыми** подпапками нельзя – `409 folder_not_empty`: сначала
удалите или перенесите их. Удалённая подпапка удалению родителя не мешает.
## Что попадает в выдачу [#contents]
Состав папки и то, что реально вернётся, – не одно и то же:
* **Выключенный объект** (`is_active=false`) остаётся виден в составе через
`GET /v3/folders`, но отзывов и аналитики не даёт. Прямой запрос по такому
объекту отвечает `404`, а внутри папки он молча отсутствует.
* **Удалённый объект** исчезает и из состава, и из выдачи.
* **Выключенная ссылка** внутри живого объекта данных не даёт.
Если папка вернула пусто, проверьте состав через `GET /v3/folders`, а затем
`is_active` у объектов и ссылок.
## Адресация: ровно один из двух [#addressing]
`object_id` и `folder_id` взаимоисключимы на `GET /v3/reviews`,
`GET /v3/analytics` и `GET /v3/analytics/timeseries`:
| Что прислали | Ответ |
| ------------------ | ------------------------------- |
| только `object_id` | данные по одному объекту |
| только `folder_id` | данные по всей папке |
| оба сразу | `400 group_and_folder_conflict` |
| ни одного | `400 group_or_folder_required` |
`link_ids` и `source_ids` фильтруют **внутри** состава папки.
В аналитике `by_source` схлопывает одну площадку разных объектов в одну строку с
суммой, а `meta` показывает фактический состав, попавший в расчёт:
```json
{
"meta": {
"object_id": null,
"folder_id": 7,
"object_ids": [20, 21, 34],
"published_from": "2000-01-01",
"published_to": "2026-07-28",
"source_ids": [1, 3],
"generated_at": "2026-07-28T09:20:11Z"
}
}
```
Поле `meta.object_id` присутствует всегда: в папочном режиме оно приходит `null`,
а не пропадает.
# Как отвечать на отзывы (/guides/replies)
Ответ, отправленный через Rewio, появляется под отзывом на самой площадке, от лица вашей
организации.
```bash
curl -X PUT https://api.rewio.ru/v3/reviews/143046/reply \
-H "X-API-Key: hrev_ВАШ_FULL_КЛЮЧ" -H "Content-Type: application/json" \
-d '{"text": "Спасибо за отзыв! Рады, что всё понравилось."}'
# -> 202 { "text": "Спасибо за отзыв! …", "status": "pending", … }
```
## Что нужно до первого ответа [#requirements]
Три условия, все обязательные:
| | |
| ----------- | ----------------------------------------------------------------------------------------------------------- |
| Тариф | Полный. На остальных публикация отвечает `402` с кодом `reply_requires_upgrade`. |
| Подключение | Площадка должна пустить нас отвечать за вас. Делается на её стороне, один раз на объект – как именно, ниже. |
| Ключ | Для публикации и удаления необходим скоуп `full`. Ключ на чтение получит `403`. |
Для подключения новой площадки напишите на .
## Где это работает [#platforms]
Сейчас на трёх площадках:
| Площадка | Опубликовать | Заменить текст | Удалить |
| ------------ | ------------ | -------------- | ------- |
| Яндекс.Карты | да | да | да |
| 2ГИС | да | **нет** | да |
| ПроДокторов | да | **нет** | **нет** |
В 2ГИС площадка не разрешает переписывать опубликованный ответ: попытка вернёт `409` с кодом
`reply_edit_not_supported`. Чтобы поменять текст, [удалите ответ](/replies/delete) и опубликуйте
заново.
На ПроДокторов ответ публикует клиника, и площадка не даёт ни переписать, ни удалить
опубликованный ответ (`reply_edit_not_supported` / `reply_delete_not_supported`). Ответ
проходит модерацию площадки, поэтому появляется под отзывом не мгновенно.
## Как подключить площадки [#connect]
Возможность отвечать выдаётся на стороне площадки: владелец организации добавляет нашу
учётную запись в свой кабинет. Ниже – инструкции по подключению.
Организация должна быть зарегистрирована в [Яндекс Бизнесе](https://business.yandex.ru/),
доступ выдаёт её владелец.
**1. Откройте «Доступы»** – раздел в левом меню кабинета.
**2. Добавьте `review.answer@ya.ru` или свою почту с ролью «Представитель».**\
В случае собственной почты необходимо сообщить нам данные для входа.\
Рекомендуем завести для этого отдельную учётную запись.
Карточка организации должна быть подтверждена в [2ГИС для бизнеса](https://business.2gis.ru/),
приглашение отправляет её владелец.
**1. Откройте «Управление доступом»** – пункт в меню аккаунта, справа сверху.
**2. Пригласите `review.answer@ya.ru` или свою почту.**\
В случае собственной почты необходимо сообщить нам данные для входа.\
Рекомендуем завести для этого отдельную учётную запись.
Доступ выдаётся через мультилогин – [как его настроить](https://help.prodoctorov.ru/nastroyka-multilogina/).
**1. Добавьте `review.answer@ya.ru` или свою почту в мультилогин клиники.**
**2. Выдайте этой учётной записи права на работу с отзывами.**\
В случае собственной почты необходимо сообщить нам данные для входа.\
Рекомендуем завести для этого отдельную учётную запись.
## Ответить, заменить, удалить [#publish-edit-delete]
Ответ у отзыва ровно один, поэтому и метод один: [`PUT`](/replies/publish). Первый вызов
публикует, повторный с новым текстом – заменяет. Повтор с тем же текстом ничего не ломает: это
не дубль и не ошибка.
[`DELETE`](/replies/delete) удаляет ваш ответ с площадки. Удалить можно только свой: ответ,
опубликованный не через нас, методу не виден.
{/* Скрыто по решению владельца 24.08.2026: три раздела ниже не показываются на портале.
Текст сохранён — снять комментарий, когда решим показывать их снова.
## Что происходит с ответом
У ответа есть состояние – `status` в ответе на запрос:
| `status` | Что значит |
|---|---|
| `pending` | Мы приняли текст, ответ ждёт публикации. |
| `publishing` | Запрос к площадке в полёте. |
| `published` | Площадка приняла ответ. |
| `failed` | Опубликовать не удалось. |
| `deleted` | Ответ удалён с площадки. |
`PUT` и `DELETE` возвращают состояние на момент приёма, а это всегда `pending`: новый текст
начинает путь заново, даже если предыдущий ответ уже стоял на площадке. Что было дальше,
показывает [`GET /v3/reviews/{review_id}/reply`](/replies/get) – по тому же адресу, отдельного
идентификатора у ответа нет.
```bash
curl https://api.rewio.ru/v3/reviews/143046/reply -H "X-API-Key: hrev_ВАШ_КЛЮЧ"
# -> { "status": "published", "published_at": "…", "confirmed_at": "…", "error": null }
```
Чтение доступно и ключу на чтение, и не зависит от тарифа. Там же видна причина, если
опубликовать не удалось: `status` станет `failed`, а в `error` придёт код.
## Как узнать, что ответ опубликован
Опубликованный ответ виден там же, где живёт отзыв:
- **В самом отзыве.** Заполняются `reply_text` и `reply_date`
в [`GET /v3/reviews`](/reviews/list) – это подтверждение с площадки, а не наше обещание.
- **Событием.** Если настроены [вебхуки](/guides/webhooks), приходит `replies.new`.
Между `202` и появлением ответа на площадке проходит от нескольких секунд до нескольких минут.
## Если что-то пошло не так
Отказы, которые видны сразу, приходят в ответе на запрос: тариф без ответов, площадка не
поддерживает ответы, место под ответ занято другим аккаунтом, текст не прошёл проверку, доступ
отозван. Каждый – со своим кодом, все они на странице [Ошибки](/errors).
Дальше, при самой публикации, мешать может временное: наш доступ к площадке обновляется,
площадка просит подождать, сорвалась сеть. Такие случаи мы отрабатываем сами, повторами, а
результат виден в [состоянии ответа](/replies/get).
Если ответ не появился, напишите на .
*/}
# Как читать отзывы (/guides/reviews)
Все отзывы читаются одним методом: `GET /v3/reviews`. Отзывы могут запрашиваться только
по одному адресату: `object_id` – объект, `folder_id` – [папка целиком](/guides/folders).
```bash
curl "https://api.rewio.ru/v3/reviews?object_id=20&limit=20" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ"
```
## Страницы [#pagination]
Окно задают `limit` (по умолчанию 100) и `offset`.
`total` – сколько отзывов подходит под фильтры. `has_next` – есть ли следующая страница. Листайте, пока `has_next` не станет `false`:
```python
import requests
BASE = "https://api.rewio.ru/v3"
HEADERS = {"X-API-Key": "hrev_ВАШ_КЛЮЧ"}
def iter_reviews(object_id, **filters):
offset, limit = 0, 100
while True:
r = requests.get(f"{BASE}/reviews", headers=HEADERS, params={
"object_id": object_id, "limit": limit, "offset": offset, **filters,
})
r.raise_for_status()
page = r.json()
yield from page["data"]
if not page["has_next"]:
break
offset += limit
for review in iter_reviews(20, min_rating=1, max_rating=3):
print(review["rating"], review["author"])
```
Так читают ленту целиком. Чтобы узнавать о новых отзывах и ответах, не опрашивая
нас, подпишитесь на [вебхуки](/guides/webhooks) – мы сами пришлём событие.
## Альтернатива: обход курсором [#cursor]
Если задача не «показать страницу», а перенести отзывы в свою базу и дальше держать её
актуальной, листать `offset` не нужно. Для этого есть отдельный метод – поток изменений
с курсором: он не теряет отзывы, если лента меняется прямо во время обхода, а на следующем
проходе отдаёт только то, что изменилось.
Как это устроено – в [Синхронизации данных](/guides/sync).
## Фильтры [#filters]
Фильтры складываются: отзыв попадает в выдачу, если проходит все.
| Параметр | Что делает |
| --------------------------------------------------- | -------------------------------------------------------- |
| `source_ids` | Только эти площадки. Параметр повторяемый. |
| `link_ids` | Только эти ссылки внутри объекта. |
| `min_rating` / `max_rating` | Диапазон оценки по шкале 1–5. |
| `published_from` / `published_to` | Диапазон даты публикации. |
| `has_text`, `has_images`, `has_videos`, `has_reply` | Наличие текста, фото, видео, ответа организации. |
| `search` | Поиск подстроки в тексте, без морфологии и ранжирования. |
Сортировка – `sort_by` (`date`, `rating` или `reply_date`) и `sort_order` (`desc`
или `asc`). `reply_date` – дата ответа организации: `desc` показывает, на что
отвечали последним, `asc` – самые давние ответы. Отзывы без ответа при этом
уходят в конец списка в обоих направлениях; чтобы получить именно их, возьмите
`has_reply=false`.
Пример: негатив с фотографиями за квартал по двум площадкам.
```bash
curl -G "https://api.rewio.ru/v3/reviews" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ" \
--data-urlencode "object_id=20" \
--data-urlencode "max_rating=3" \
--data-urlencode "has_images=true" \
--data-urlencode "published_from=2026-04-01" \
--data-urlencode "published_to=2026-06-30" \
--data-urlencode "source_ids=1" \
--data-urlencode "source_ids=2"
```
## Даты отзыва [#dates]
У отзыва две даты, и означают они разное:
| Поле | Что означает | Зона | Чем фильтровать |
| ------------- | ------------------------------------ | ---------------------- | --------------------------------- |
| `review_date` | Когда отзыв опубликован на площадке. | Местное время площадки | `published_from` / `published_to` |
| `created_at` | Когда отзыв впервые захвачен. | UTC | нельзя |
Когда отзыв пропадает с площадки, мы помечаем его `is_deleted` и ставим
`deleted_at` – момент, когда мы это заметили. Такие отзывы остаются у вас в
истории, по умолчанию скрыты и возвращаются с `show_deleted`.
Новые отзывы и ответы приходят [вебхуками](/guides/webhooks) – опрашивать нас для
этого не нужно. Если у вас своя база и её надо держать в актуальном состоянии,
это отдельная работа: [синхронизация](/guides/sync) – поток изменений с курсором,
включая пропажи отзывов с площадок.
## Особые флаги [#flags]
* `show_deleted` и `only_deleted` – показать отзывы, пропавшие с площадки. По умолчанию они скрыты.
* `show_hidden` и `show_pinned` – вернуть [скрытые и закреплённые](/guides/widgets) отзывы
вместе с их флагами.
* `split_pros_cons` – разобрать слитый текст на «достоинства» и «недостатки» там, где площадка их разделяет.
Каждый параметр и все поля ответа есть в [справочнике](/reviews/list).
# Синхронизация данных (/guides/sync)
Синхронизация нужна, когда отзывы живут не только у нас, но и в вашей базе: в CRM, в
хранилище, в собственной аналитике. Одна ручка — [`GET /v3/reviews/sync`](/reviews/sync).
Если своей базы нет, эта страница вам не нужна: показать отзывы на сайте умеет
[лента](/guides/reviews), узнать о новом — [вебхук](/guides/webhooks).
## Цикл [#cycle]
```python
cursor = db.load_cursor() # None при первом запуске
params = {"cursor": cursor} if cursor else {"object_id": 20, "limit": 500}
while True:
page = httpx.get(f"{API}/reviews/sync", headers=H, params=params).json()
for review in page["data"]:
db.upsert_review(review) # склейка по review["id"]
db.save_cursor(page["next_cursor"]) # ПОСЛЕ каждой страницы
if not page["has_next"]:
break
params = {"cursor": page["next_cursor"]}
```
Первый прогон выгружает всю историю, следующие — только изменения.
Отзывы мы собираем дважды в сутки, поэтому возвращаться чаще, чем раз в пару часов,
смысла нет: между сборами поток отдаёт пустую страницу. Часового цикла хватает с запасом.
## Четыре правила [#rules]
**1. Курсор хранится в базе, а не в памяти процесса.** В нём весь смысл: завтрашний
запуск продолжает с той же точки, а не выкачивает всё заново.
**2. Сохранять после каждой страницы.** Тогда обрыв связи стоит одной страницы, а не
всего обхода.
**3. В режиме `all` — UPSERT по `id`, не INSERT.** Изменённый отзыв приходит со старым
`id`: новой строки мы не заводим, поэтому вставка даст дубли или ошибку уникальности.
В режиме `new` достаточно INSERT — там ничего не приходит дважды. Потерять отзыв поток
не может ни в том, ни в другом режиме, это его единственная твёрдая гарантия.
**4. `next_cursor` приходит всегда** — и на последней странице, и на пустой. Признак
«догнал» — это `has_next: false`, а не отсутствие курсора.
## Что приходит [#payload]
Изменением считается: правка текста или оценки на площадке, появившийся или изменившийся
ответ организации, изменившийся набор фотографий, пропажа отзыва с площадки
(`is_deleted: true`) и его возврат. Пересбор сам по себе — не изменение: если на площадке
ничего не поменялось, отзыв в поток не попадёт.
| `mode` | Что приходит | Как класть к себе |
| -------------------- | -------------------------- | ------------------------------------------------- |
| `all` (по умолчанию) | новые отзывы и изменения | **UPSERT** по `id` |
| `new` | только впервые появившиеся | **INSERT**: каждый приходит один раз за всю жизнь |
Режим запечён в курсор — для второго режима заведите второй курсор.
Отдельного режима «только изменения» нет, и он не нужен: когда вы догнали текущее
состояние в режиме `all`, дальше в нём приходят как раз одни изменения — новых-то
пока не появилось.
## Границы [#limits]
* **Порядок задан режимом, и своего параметра сортировки у метода нет.** В `all` строки идут
по времени последнего изменения отзыва, в `new` — по времени, когда мы его впервые
захватили. Дата публикации на площадке (`review_date`) порядок не задаёт ни там, ни там:
отзыв 2019 года, у которого вчера появился ответ, приедет в `all` вместе со вчерашними
изменениями, а не в хвосте истории. Нужен свой порядок — стройте его у себя.
* **Фильтров нет.** Отзыв, который правкой перестал подходить под фильтр, пришлось бы
присылать событием «выбыл» — такого события не существует, поэтому любой фильтр сделал
бы поток дырявым. Отбирайте у себя.
* **Состав объекта поток не отслеживает.** Убрали ссылку или выключили объект — его отзывы
просто перестанут приходить, отдельного события об этом не будет. Чистите у себя
по `link_id`. Обратный случай: если ссылку вернули, её старые отзывы сами не приедут —
курсор их уже прошёл. Чтобы забрать их снова, начните обход с нуля, без сохранённого
курсора.
* **Задержка несколько секунд.** Не запаздывание сбора, а защита: отзыв, который прямо
сейчас записывается, иначе оказался бы позади вашего курсора и не пришёл бы никогда.
## Если курсор не принят [#cursor-rejected]
`400 invalid_cursor` — курсор испорчен, изменён или выдан другому аккаунту: начните обход
заново, без `cursor`. `400 cursor_mismatch` — вместе с курсором передан другой `object_id`,
`folder_id` или `mode`: либо не передавайте их вовсе, либо передайте те же.
Полный список параметров и полей — в [справочнике метода](/reviews/sync).
# Как принимать вебхуки (/guides/webhooks)
Укажите свой HTTPS-адрес, и Rewio будет присылать на него POST при появлении новых отзывов и ответов.
Это замена опросу: узнать о новом отзыве в момент, когда он появился, а не когда вы в очередной
раз спросили.
На этом строят уведомления менеджеру о негативе, постановку задач на ответ, обновление
карточки филиала в CRM и пересчёт витрины на сайте. Если же вам нужна не реакция на событие,
а полная копия отзывов у себя, это [синхронизация](/guides/sync), а не вебхуки.
Проверка подписи описана в [§3](#signature), все методы есть в
[справочнике](/webhooks/list).
## 1. Создать вебхук [#create]
```bash
curl -X POST https://api.rewio.ru/v3/webhooks \
-H "X-API-Key: hrev_ВАШ_FULL_КЛЮЧ" -H "Content-Type: application/json" \
-d '{
"target": "https://your-server.com/hooks/reviews",
"event_types": ["reviews.new"],
"object_ids": [10, 20],
"bucket_window": "immediate",
"filters": {}
}'
# -> { "id": 456, "target": "...", "is_active": true, ... }
```
* `target` **обязан быть HTTPS**; приватные / loopback / metadata-адреса отклоняются.
* `object_ids` опущен или `null` – все ваши объекты; иначе только перечисленные.
[Папки](/guides/folders) в вебхуках не поддерживаются: перечислите объекты явно.
* Секрет в ответе не возвращается: он общий на аккаунт и берётся отдельно (см. §3).
* Проверить доставку в любой момент: `POST /v3/webhooks/{id}/test`.
### Фильтры (`filters`) [#filters]
Объект `filters` сужает, какие отзывы вызывают доставку. Поля объединяются по **И** –
отзыв доставляется, только если проходит каждый заданный фильтр. Опустите поле (или
весь объект), чтобы фильтр не действовал.
```json
{
"allowed_ratings": [1, 2, 3, 4, 5],
"allowed_sources": ["yandex_maps"],
"has_text": false,
"has_images": false
}
```
**`allowed_ratings`** – корзины оценок; отзыв проходит, если его корзина в списке:
| корзина | что попадает |
| ------- | ---------------------------------------------------------------------- |
| `5` | rating == 5.0 |
| `4` | 4.0 ≤ rating \< 5.0 |
| `3` | 3.0 ≤ rating \< 4.0 |
| `2` | 2.0 ≤ rating \< 3.0 |
| `1` | 0.0 ≤ rating \< 2.0 |
| `0` | **без числовой оценки** (текстовые площадки), только по явному запросу |
Поле опущено – это то же самое, что `[1,2,3,4,5]`: приходят все отзывы с оценкой,
без неоценённых. Чтобы получать и неоценённые, добавьте `0`.
Примеры: `[1,2]` – только негатив, `[5]` – только идеальные, `[0,1,2,3,4,5]` – вообще всё.
* **`allowed_sources`** – машинные имена площадок (поле `slug` в `GET /v3/sources`).
Опущено или `null` – все. Регистр не важен. Имя не сверяется с живым списком:
опечатка (`"yadnex_maps"`) молча ни с чем не совпадёт, ошибки не будет.
* **`has_text`** – `true` доставляет только отзывы с непустым текстом.
* **`has_images`** – `true` только с прикреплёнными фото.
## 2. Что вы получаете [#delivery]
`POST` на ваш `target` с JSON-телом. Одна доставка несёт пачку отзывов, а не один отзыв.
Разбирайте доставку по полю `event`: оно же дублируется в заголовке `X-Hrev-Event`.
| `event` | Когда срабатывает | Тело |
| --------------------- | ------------------------------------------------------------------ | ------------------------------------------------ |
| `reviews.new` | при сборе появились новые отзывы | отзывы, сгруппированные по объектам |
| `replies.new` | у отзыва **появился ответ** организации или отзыв пришёл уже с ним | та же форма; читайте `reply_text` и `reply_date` |
| `scrape.run.finished` | сбор завершился. Приходит, даже если нового нет | только `finished_at` |
`replies.new` срабатывает и на ответы, [опубликованные через нас](/guides/replies): для вас
это подтверждение, что ответ дошёл до площадки.
Подписывайтесь на любую комбинацию через `event_types`.
`filters` применяются к `reviews.new` и `replies.new`. Событие `scrape.run.finished` их
игнорирует: в нём нет отзывов. `bucket_window` действует на все три:
| `bucket_window` | Доставка |
| --------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `immediate` | один POST вскоре после каждого сбора. По умолчанию |
| `daily_HOUR` | один ответ раз в сутки в конкретный час; `HOUR` = 0–23, время в UTC. Напр. `daily_9` = 09:00 UTC, `daily_0` = полночь UTC |
Час дайджеста задаётся в UTC (как всё в системе), без ведущего нуля (`daily_9`, не `daily_09`).
### Заголовки [#headers]
| Заголовок | Смысл |
| -------------------- | ---------------------------------------------------------- |
| `X-Hrev-Event` | тип события |
| `X-Hrev-Delivery-Id` | **стабильный id доставки**, для идемпотентности |
| `X-Hrev-Event-Id` | id первого отзыва в пачке (`0` для `scrape.run.finished`) |
| `X-Hrev-Timestamp` | unix-секунды, участвуют в подписи |
| `X-Hrev-Signature` | `sha256=` – одна или несколько через запятую (см. §3) |
### Тело `reviews.new` [#reviews-new]
```json
{
"schema_version": "3",
"event": "reviews.new",
"delivery_id": 456,
"occurred_at": "2026-07-19T12:00:00",
"objects": [
{
"object_id": 20,
"name": "Grand Hotel",
"review_count": 2,
"reviews": [
{
"id": 78910,
"external_review_id": "yandex_maps_abc",
"source_id": 1,
"source_slug": "yandex_maps",
"rating": 4.5,
"text": "Отличный отель ...",
"author": "Иван П.",
"review_date": "2026-07-19T00:00:00",
"url": "https://yandex.ru/maps/org/1/reviews",
"review_url": "https://yandex.ru/maps/org/1/reviews?reviews[publicId]=abc",
"has_images": false,
"reply_text": null,
"reply_date": null
}
]
}
],
"summary": {
"review_count": 2,
"by_source": {"yandex_maps": 2},
"average_rating": 4.5,
"negative_rating_count": 0
},
"truncated": false
}
```
* `rating` – по шкале **1–5**, может быть дробным (`4.5`).
* Площадка приходит парой `source_id` + `source_slug` – теми же, что в
[`GET /v3/sources`](/sources/list).
* `reply_text` / `reply_date` несут ответ организации, если он есть (иначе `null`).
* `truncated: true` – payload превысил лимит размера, детализация по отзывам опущена
(`objects[].reviews` нет, но `summary` и `object_id`/`review_count` остаются).
Дотяните детали через REST при необходимости.
Форму тела выбирает подписка, а не запрос. Вебхук, созданный через `POST /v3/webhooks`,
получает `schema_version: "3"` – он и описан здесь. Подписки, созданные раньше, через `/v2`,
продолжают получать прежнее тело (`schema_version: "2"`, `groups[]` вместо `objects[]`,
площадка одной строкой `source`) – ровно то, что получали до этого.
### Тело `replies.new` [#replies-new]
Форма та же, что у `reviews.new`. В `objects[].reviews` приходят отзывы, которые только что
получили ответ или пришли уже с ним, читайте `reply_text` и `reply_date`.
Изменённый ответ повторно не доставляется, только впервые появившийся.
### Тело `scrape.run.finished` [#scrape-run-finished]
Событие без отзывов: сообщает, что сбор завершился. Приходит раз за цикл каждому
подписанному вебхуку, **даже если нового не найдено**.
```json
{
"schema_version": "3",
"event": "scrape.run.finished",
"delivery_id": 789,
"occurred_at": "2026-07-19T13:00:00",
"finished_at": "2026-07-19T13:00:00"
}
```
## 3. Проверка подписи (опционально) [#signature]
Проверка подписи защищает от подделок: адрес `target` публичный, и без проверки кто угодно,
узнав его, сможет прислать вам фейковые «новые отзывы». Формально она необязательна: без неё
принимайте POST и отвечайте `2xx`.
### Секрет подписи, один на аккаунт [#secret]
Все доставки со всех ваших вебхуков подписаны одним секретом аккаунта. Он виден всегда:
```bash
curl https://api.rewio.ru/v3/webhooks/secret \
-H "X-API-Key: hrev_ВАШ_FULL_КЛЮЧ"
# -> { "secret": "whsec_...", "rotated_at": null }
```
Нужен ключ со скоупом `full`: секрет позволяет подделывать подписи, поэтому ключу на чтение
он не отдаётся. Секрет действует, пока вы не перевыпустите его сами (см. §4).
### Как проверять [#verify]
Подписываются точные отправляемые байты, с префиксом-таймстампом:
```
signed = f"{X-Hrev-Timestamp}." + <сырые байты тела запроса>
sig = HMAC_SHA256(secret, signed) # hex
```
**Проверяйте по сырому телу, до парсинга JSON**: повторная сериализация меняет
байты и ломает подпись.
```python
import hashlib, hmac, time
def verify(headers, raw_body: bytes, secret: str) -> bool:
ts = headers["X-Hrev-Timestamp"]
# отбрасываем устаревшие / переигранные доставки (±5 мин)
if abs(int(time.time()) - int(ts)) > 300:
return False
expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body,
hashlib.sha256).hexdigest()
# при ротации заголовок может нести несколько подписей через запятую
for part in headers["X-Hrev-Signature"].split(","):
part = part.strip()
if part.startswith("sha256=") and hmac.compare_digest(part[7:], expected):
return True
return False
```
```javascript
const crypto = require("crypto");
function verify(headers, rawBody, secret) {
const ts = headers["x-hrev-timestamp"];
if (Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > 300) return false;
const expected = crypto.createHmac("sha256", secret)
.update(Buffer.concat([Buffer.from(ts + "."), rawBody])).digest("hex");
return headers["x-hrev-signature"].split(",").some((p) => {
p = p.trim();
if (!p.startsWith("sha256=")) return false;
const got = Buffer.from(p.slice(7));
const exp = Buffer.from(expected);
// длины обязательно сверить до timingSafeEqual: на разной длине он бросает
// RangeError, и приёмник упадёт 500-й вместо честного «подпись неверна»
return got.length === exp.length && crypto.timingSafeEqual(got, exp);
});
}
```
## 4. Ротация секрета (без простоя) [#rotation]
`POST /v3/webhooks/rotate-secret` перевыпускает секрет аккаунта и возвращает новый.
Следующие 24 часа каждая доставка подписывается и новым, и предыдущим секретом; через
запятую в `X-Hrev-Signature`. Проверка выше принимает любую из перечисленных подписей,
поэтому обновить приёмник можно в любой момент этого окна. После 24 часов остаётся только новый.
```bash
curl -X POST https://api.rewio.ru/v3/webhooks/rotate-secret \
-H "X-API-Key: hrev_ВАШ_FULL_КЛЮЧ"
# -> { "secret": "whsec_...", "rotated_at": "2026-07-22T12:00:00" }
```
## 5. Идемпотентность [#idempotency]
Доставки приходят как минимум один раз: сбой между отправкой и учётом может переслать ту же
доставку повторно. От этого защищают две вещи:
* **Та же доставка всегда переиспользует `X-Hrev-Delivery-Id`** – дедупьте по нему:
```python
if seen(delivery_id): # напр. Redis SET NX, TTL 24ч
return 200 # уже обработано
process(payload)
mark_seen(delivery_id)
```
* Отзывы дедуплицируются на нашей стороне: в одной доставке отзыв не встретится дважды.
Отвечайте `2xx` быстро, в пределах таймаута доставки. Тяжёлую работу выносите в фон.
### Что вернуть в ответе [#response]
Подтверждение получения – это сам HTTP-статус: любой `2xx` означает «принято, больше не
пересылать». Тело ответа не читается и не разбирается.
Возвращайте короткий JSON вместо пустого ответа: явный `Content-Type` не даст фреймворку
или прокси подсунуть HTML-страницу. Минимальный вариант:
```
HTTP/1.1 200 OK
Content-Type: application/json
{"ok": true}
```
Достаточно и `{}`: важен только код `2xx`. Любой не-2xx или таймаут считается недоставкой,
и включаются повторы из §6.
## 6. Ретраи и обработка ошибок [#retries]
| Ваш ответ | Результат |
| ----------------------------------------------- | ----------------------------- |
| `2xx` | успех |
| `408`, `429`, `5xx`, таймаут, ошибка соединения | ретрай с backoff |
| `3xx` и прочие `4xx` | постоянная ошибка, без ретрая |
Лестница backoff: `30с → 1м → 2м → 5м → 10м → 15м`, дальше каждые **30м**. Всего
до **30 попыток** за \~12 часов, после чего доставка уходит в `dead_letter`.
**Подписка не отключается сама.** Сколько бы доставок ни сорвалось, канал
продолжает работать: почините приёмник – события пойдут снова. На каждый 50-й
провал подряд мы пишем владельцу аккаунта. Выключить и включить подписку
вручную можно через `PATCH /v3/webhooks/{id}`.
Чем больше попыток, тем выше шанс задвоенной доставки, поэтому дедупликация по
`X-Hrev-Delivery-Id` (§5) обязательна.
Если доставка так и не дошла, напишите нам – историю попыток по вашему вебхуку
(статус, коды и тела ответов, времена) мы поднимем со своей стороны.
# Как собрать виджет отзывов (/guides/widgets)
Виджет показывает отзывы объекта на вашей странице. Управляют показом три инструмента:
| Что нужно | Чем делается |
| ------------------------------------------ | --------------------------------------- |
| Закрепить отзыв наверху списка | [Закрепление](/widgets/pinned-set) |
| Убрать отзыв из показа | [Скрытие](/widgets/hidden-add) |
| Показать плюсы и минусы отдельными блоками | [`split_pros_cons`](/widgets/pros-cons) |
Закрепление и скрытие действуют **внутри объекта** и не меняют собранные данные: в другом объекте
тот же отзыв останется как был, на самой площадке не изменится ничего.
## Закрепить наверху [#pinned]
Закреплённые отзывы образуют упорядоченный список, максимум 30 на объект.
[Добавить в конец](/widgets/pinned-add) – `POST`, [задать порядок целиком](/widgets/pinned-set) – `PUT`:
```bash
# добавить в конец
curl -X POST "https://api.rewio.ru/v3/objects/20/pinned-reviews" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ" -H "Content-Type: application/json" \
-d '{"review_ids": [143046]}'
# переставить: порядок в запросе становится порядком показа
curl -X PUT "https://api.rewio.ru/v3/objects/20/pinned-reviews" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ" -H "Content-Type: application/json" \
-d '{"ordered_review_ids": [142391, 143046]}'
```
`PUT` – замена целиком: отзывы, которых нет в списке, открепляются. Место каждого приходит
в `pin_position`, считая с нуля.
## Скрыть и вернуть [#hidden]
```bash
# скрыть
curl -X POST "https://api.rewio.ru/v3/objects/20/hidden-reviews" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ" -H "Content-Type: application/json" \
-d '{"review_ids": [143046, 142391]}'
# вернуть в ленту
curl -X DELETE "https://api.rewio.ru/v3/objects/20/hidden-reviews" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ" -H "Content-Type: application/json" \
-d '{"review_ids": [142391]}'
```
Скрытый отзыв пропадает из обычной ленты. Он остаётся доступен
[списком скрытых](/widgets/hidden-list) и в обычной ленте с `show_hidden=true`, там у него
приходит флаг `is_hidden`. За один запрос – до 200 идентификаторов.
Скрытие снимает закрепление. Снятие скрытия его не возвращает – закрепите заново.
## Как собрать страницу [#page]
В обычной ленте нет ни закреплённых, ни скрытых: `GET /v3/reviews` по умолчанию исключает и тех и
других. Поэтому страница собирается двумя запросами, и дублей между ними не бывает – закреплённые
сверху, за ними обычная лента.
```python
import requests
BASE = "https://api.rewio.ru/v3"
HEADERS = {"X-API-Key": "hrev_ВАШ_КЛЮЧ"}
OBJECT = 20
pinned = requests.get(f"{BASE}/objects/{OBJECT}/pinned-reviews", headers=HEADERS).json()["data"]
feed = requests.get(f"{BASE}/reviews", headers=HEADERS,
params={"object_id": OBJECT, "limit": 20}).json()["data"]
for review in pinned + feed: # скрытые не придут ни там, ни там
print(review["rating"], review["author"])
```
Нужны закреплённые прямо в общей ленте – передайте `show_pinned=true`, и они придут вперемешку
с остальными, с флагом `is_pinned`.
Ключ API открывает все данные аккаунта, поэтому в браузерный код он не попадает: запрашивайте
отзывы со своего бэкенда и отдавайте странице уже готовый ответ. Подробнее – в
[Доступе](/access).
## Плюсы и минусы отдельными блоками [#pros-cons]
Часть площадок спрашивает у автора достоинства и недостатки по отдельности. С
`split_pros_cons=true` они приходят полями `pros` и `cons`, и показать их можно двумя
колонками. Подробности и список площадок – на [отдельной странице](/widgets/pros-cons).
## Средний балл не зависит от показа [#rating]
[Аналитика](/analytics/summary) считает **все** собранные отзывы объекта. Скрыли отзыв с единицей –
он пропал из показа, но средний балл и распределение оценок не изменились.
Это сделано намеренно: скрытие оформляет показ, а не правит данные. Если рядом с отзывами нужен
балл, согласованный с показанными, считайте его сами по выдаче.
## В папках работает не всё [#folders]
[Папочная лента](/guides/folders) применяет скрытие, но не закрепление. В ленте по папке
закреплённый отзыв приходит обычной строкой, а флагов `is_pinned` и `is_hidden` там нет.
Поэтому лента по папке из одного объекта может вернуть **больше** отзывов, чем лента по этому же
объекту – ровно на число закреплённых.
# Расширенная проверка (/extra/health-detailed)
`GET /health/detailed` – проверка глубже двух предыдущих. Она смотрит не только на то,
отвечает ли API, но и на то, в порядке ли всё, от чего он зависит.
```bash
curl -i https://api.rewio.ru/health/detailed
```
Ключ не обязателен.
## Что она проверяет [#checks]
Четыре вещи, и каждая проверяется по-настоящему, а не по факту «процесс запущен»:
1. **Хранилище данных** – выполняется запрос к базе.
2. **Кэш** – проверяется отклик.
3. **Фоновая обработка** – отвечают ли исполнители задач сбора.
4. **Регулярные задачи** – не замолчала ли работа по расписанию. Это отдельная проверка,
потому что незапустившаяся задача не падает и другими способами не ловится.
Состав проверок может меняться: читайте поле `status`, а не список под ним.
## Ответ и коды [#response]
| Код | `status` | Что означает |
| ----- | ---------- | ------------------------------------ |
| `200` | `healthy` | Все проверки прошли. |
| `503` | `degraded` | **Хотя бы одна** проверка не прошла. |
Промежуточного состояния нет: одна незелёная проверка сразу даёт `503`, чтобы внешний
мониторинг видел отказ, а не `200` с плохим телом внутри.
```json
{
"status": "degraded",
"dependencies": {
"…": { "status": "healthy" },
"…": { "status": "unhealthy" }
}
}
```
В `dependencies` каждая проверка отмечена `healthy` или `unhealthy`. Набор и названия
проверок – наша внутренняя кухня: они меняются вместе с устройством сервиса, и
завязываться на них не стоит. Ориентир один – поле `status` и код ответа. Текст ошибки в
теле не показывается: он виден только администраторам сервиса.
Для постоянного мониторинга берите [`/health/read`](/extra/health-read): он дешевле,
отвечает на тот же вопрос «получу ли я сейчас данные» и не зависит от частей сервиса,
которые к выдаче отношения не имеют. Расширенная проверка нужна, когда что-то уже
сломалось и надо понять, насколько.
# Проверка чтения данных (/extra/health-read)
Проверка, которую стоит поставить в мониторинг. Отвечает `200`, если чтение данных
работает – значит методы отзывов и аналитики ответят. Если нет – `503`.
Ключ не нужен.
## GET https://api.rewio.ru/health/read
Заголовок `X-API-Key` обязателен в каждом запросе.
### Ответ 200
Тело ответа пустое.
### Пример ответа
```json
{
"status": "ready",
"checks": {
"mysql": "healthy"
}
}
```
## Чем отличается от `/health` [#vs-health]
[`/health`](/extra/health) говорит только «API на связи»: он ответит `200` и в тот момент,
когда отзывы прочитать нельзя. Эта проверка идёт дальше и трогает сами данные, поэтому
отвечает на вопрос, который вам действительно нужен, – «получу ли я сейчас отзывы».
Метод поддерживает `HEAD` – если вашему мониторингу тело ответа не нужно.
| Ответ | Что означает |
| ---------------------------- | --------------------------------------------- |
| `200`, `status: ready` | Читающие методы работают. |
| `503`, `status: unavailable` | Данные сейчас не читаются. Ждать и повторять. |
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Доступность API (/extra/health)
Самая дешёвая проверка: API на связи или нет. Ключ не нужен – заголовок `X-API-Key`
метод не читает вовсе.
Именно поэтому им удобно отделять проблему с сетью от проблемы с ключом. Молчит – дело
в сети или на нашей стороне. Отвечает, а ваш следующий запрос с ключом даёт `401` – дело
в ключе.
## GET https://api.rewio.ru/health
Заголовок `X-API-Key` обязателен в каждом запросе.
### Ответ 200
Тело ответа пустое.
### Пример ответа
```json
{
"status": "healthy",
"app": "Review Aggregator",
"environment": "production"
}
```
## Что в ответе [#response]
| Поле | Что означает |
| ------------- | --------------------------------------------------------------- |
| `status` | Всегда `healthy`, если ответ вообще пришёл. |
| `app` | Название сервиса. |
| `environment` | Контур, который вам ответил. У `api.rewio.ru` это `production`. |
Метод не проверяет ни данные, ни ваш доступ: он отвечает `200` и тогда, когда отзывы
временно не читаются. Для мониторинга берите [проверку чтения](/extra/health-read) –
она отвечает `503`, когда читающие методы недоступны.
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Написать разработчику (/extra/support)
Обращение к разработчику API: вопрос, ошибка в выдаче, просьба подключить площадку.
Номер аккаунта, почту и тариф добавляем сами, по ключу, которым сделан запрос. Писать их
в тексте не нужно – достаточно описать, что происходит.
## POST https://api.rewio.ru/v3/support/message
Заголовок `X-API-Key` обязателен в каждом запросе.
### Тело запроса
- `subject` (строка, обязательный) – О чём обращение, одной строкой. От 2 до 200 символов.
- `message` (строка, обязательный) – Текст обращения. От 5 до 5000 символов.
- `contact` (строка, может быть null) – Как с вами связаться, если почта аккаунта не подходит – телефон, телеграм, другой адрес. Необязательное поле.
### Пример запроса
```json
{
"subject": "Пустая выдача по объекту 20",
"message": "Со вчерашнего дня GET /v3/reviews по объекту 20 возвращает total: 0, хотя на площадке отзывы есть. Ключ read_only, тот же запрос работал в понедельник.",
"contact": "телеграм @petrov"
}
```
### Ответ 202
- `status` (строка, обязательный) – Всегда `accepted` – обращение принято.
### Пример ответа
```json
{
"status": "accepted"
}
```
## Что важно знать [#notes]
**Работает с любым ключом.** В том числе с `read_only`: сообщить о поломке – не запись
данных, и упереться в права доступа в такой момент неправильно. Из личного кабинета метод
тоже доступен.
**`contact` необязателен.** Без него мы отвечаем на почту аккаунта. Поле пригодится, если
ключ общий на несколько человек или ответ нужен не на почту: тогда напишите в `contact`,
как вас найти.
**Ответ `202`, а не `200`.** Он означает «обращение принято», а не «на него уже ответили».
Ответ приходит письмом.
**Не больше 10 обращений в час** с одного аккаунта. Одиннадцатое получает `429` с кодом
`rate_limited` – это защита от цикла в вашем коде, а не ограничение на общение. Если
обращений действительно нужно больше, напишите об этом в первом.
Если отправка временно недоступна, метод отвечает `503` с кодом `email_unavailable` –
обращение при этом **не** сохранено, повторите позже.
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Добавить ссылки (/links/add)
Подключает к объекту новые площадки. Тело запроса – **массив ссылок**, без объекта-обёртки,
до 200 за раз.
Примеры адресов ссылок и где их взять – в [Форматах ссылок](/link-formats).
Ссылка, которую не удалось добавить, не отменяет остальные: они добавятся, а по ней в
`link_results` придёт причина отказа.
На одну площадку в объекте приходится одна ссылка.
## POST https://api.rewio.ru/v3/objects/{object_id}/links
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, в который добавляются ссылки.
- `Idempotency-Key` (строка, header, может быть null) – Свой ключ запроса. Повтор с тем же ключом не добавит ссылки второй раз.
### Тело запроса
- `[].url` (ссылка, обязательный) – Адрес страницы объекта на площадке, как он открывается в браузере. Площадку определим сами.
- `[].is_active` (да/нет, по умолчанию да) – Собирать ли отзывы по этой ссылке.
- `[].link_name` (строка, может быть null) – Своё название ссылки внутри объекта, например «Филиал на Невском».
### Пример запроса
```json
[
{
"url": "https://2gis.ru/moscow/firm/70000001006369779",
"link_name": "2ГИС"
},
{
"url": "https://www.tripadvisor.ru/Hotel_Review-g298484-d299118",
"link_name": "Tripadvisor"
}
]
```
### Ответ 201
- `object_id` (число, обязательный) – Объект, в который добавлялись ссылки.
- `link_results` (список объектов, обязательный) – Итог по каждой переданной ссылке, в том же порядке.
- `link_results[].url` (строка, обязательный) – Адрес, к которому относится результат.
- `link_results[].link_id` (число, может быть null) – Идентификатор созданной ссылки. Пусто, если добавить не удалось.
- `link_results[].source_slug` (строка, может быть null) – Площадка, определённая по адресу.
- `link_results[].link_name` (строка, может быть null) – Своё название ссылки, если его задавали.
- `link_results[].error` (строка, может быть null) – Причина, по которой ссылку не удалось добавить.
- `link_results[].error_code` (строка, может быть null) – Машинная причина отказа – по ней и стоит ветвиться: `source_not_detected`, `source_url_invalid`, `source_requires_upgrade`, `source_already_linked`, `link_already_added`. Пусто при успехе.
### Пример ответа
```json
{
"object_id": 20,
"link_results": [
{
"url": "https://2gis.ru/moscow/firm/70000001006369779",
"link_id": 523,
"source_slug": "2gis",
"link_name": "2ГИС",
"error": null
},
{
"url": "https://www.tripadvisor.ru/Hotel_Review-g298484-d299118",
"link_id": null,
"source_slug": null,
"link_name": "Tripadvisor",
"error": "Площадка по этому адресу не поддерживается"
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Ссылки объекта (/links/list)
Площадки, подключённые к объекту, и то, чем кончился последний сбор по каждой.
## GET https://api.rewio.ru/v3/objects/{object_id}/links
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, ссылки которого нужны.
### Ответ 200
- `data` (список объектов, обязательный) – Ссылки объекта. Пагинации нет: на одну площадку в объекте приходится одна ссылка.
- `data[].id` (число, обязательный) – Идентификатор ссылки.
- `data[].url` (строка, обязательный) – Адрес страницы объекта на площадке.
- `data[].source_id` (число, обязательный) – Площадка, к которой относится ссылка.
- `data[].link_name` (строка, может быть null) – Своё название ссылки внутри объекта.
- `data[].auto_name` (строка, может быть null) – Название, прочитанное с площадки, с её подписью впереди (`2ГИС: Кофейня Север`). Пока имя не прочитано, остаётся одна подпись. Своё `link_name` важнее.
- `data[].is_active` (да/нет, обязательный) – Собираются ли отзывы по этой ссылке.
- `data[].scrape_status` (строка, обязательный, pending / in_progress / success / failed) – Чем кончился последний сбор: `pending`, `in_progress`, `success` или `failed`.
- `data[].scrape_error` (строка, может быть null) – Что помешало последнему сбору.
- `data[].last_scraped_at` (дата и время, может быть null) – Когда по ссылке последний раз собирали отзывы.
- `data[].last_scraped_review_date` (дата и время, может быть null) – Дата публикации самого свежего собранного отзыва.
- `data[].created_at` (дата и время, обязательный) – Когда ссылка добавлена в объект.
### Пример ответа
```json
{
"data": [
{
"id": 177,
"url": "https://yandex.com/maps/org/national/1020542995",
"source_id": 1,
"link_name": "Яндекс Националь",
"auto_name": "Яндекс.Карты: Националь",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:06:03",
"last_scraped_review_date": "2026-08-03T09:42:39",
"created_at": "2026-02-23T17:30:52"
},
{
"id": 178,
"url": "https://ostrovok.ru/hotel/russia/moscow/mid7467340/gostinitsa_natsional_moskva",
"source_id": 106,
"link_name": "Островок Националь",
"auto_name": "Ostrovok: Гостиница Националь Москва",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:06:04",
"last_scraped_review_date": "2026-08-02T13:04:59",
"created_at": "2026-02-23T17:32:06"
},
{
"id": 179,
"url": "https://101hotels.com/opinions/hotel/moskva/gostinitsa_natsional.html",
"source_id": 105,
"link_name": "101hotels Националь",
"auto_name": "101Hotels: Гостиница Националь Москва",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:20:05",
"last_scraped_review_date": "2026-05-06T00:00:00",
"created_at": "2026-02-23T17:33:18"
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Удалить ссылку (/links/remove)
Отключает площадку от объекта: ссылка удаляется, собранные по ней отзывы больше не отдаются.
Чтобы остановить сбор, сохранив накопленные отзывы, [выключите ссылку](/links/update)
через `is_active: false`.
## DELETE https://api.rewio.ru/v3/objects/{object_id}/links/{link_id}
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, которому принадлежит ссылка.
- `link_id` (число, path, обязательный) – Ссылка, которую нужно удалить.
### Ответ 200
- `message` (строка, обязательный) – Сообщение об итоге операции.
- `object_id` (число, обязательный) – Объект, из которого удалена ссылка.
- `link_id` (число, обязательный) – Идентификатор удалённой ссылки.
### Пример ответа
```json
{
"message": "Link removed from object",
"object_id": 20,
"link_id": 523
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Обновить ссылку (/links/update)
Меняет название ссылки внутри объекта и то, собирать ли по ней отзывы. Сам адрес поменять нельзя –
для другой страницы добавьте новую ссылку.
Выключенная ссылка (`is_active: false`) перестаёт обновляться, но уже собранные отзывы остаются в объекте.
## PUT https://api.rewio.ru/v3/objects/{object_id}/links/{link_id}
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, которому принадлежит ссылка.
- `link_id` (число, path, обязательный) – Ссылка, которую нужно изменить.
### Тело запроса
- `link_name` (строка, может быть null) – Новое название ссылки внутри объекта. Не передано – остаётся прежним.
- `is_active` (да/нет, может быть null) – Собирать ли отзывы по этой ссылке. Не передано – остаётся как было.
### Пример запроса
```json
{
"link_name": "Яндекс Карты — Националь",
"is_active": true
}
```
### Ответ 200
- `id` (число, обязательный) – Идентификатор ссылки.
- `url` (строка, обязательный) – Адрес страницы объекта на площадке.
- `source_id` (число, обязательный) – Площадка, к которой относится ссылка.
- `link_name` (строка, может быть null) – Своё название ссылки внутри объекта.
- `auto_name` (строка, может быть null) – Название, прочитанное с площадки, с её подписью впереди (`2ГИС: Кофейня Север`). Пока имя не прочитано, остаётся одна подпись. Своё `link_name` важнее.
- `is_active` (да/нет, обязательный) – Собираются ли отзывы по этой ссылке.
- `scrape_status` (строка, обязательный, pending / in_progress / success / failed) – Чем кончился последний сбор: `pending`, `in_progress`, `success` или `failed`.
- `scrape_error` (строка, может быть null) – Что помешало последнему сбору.
- `last_scraped_at` (дата и время, может быть null) – Когда по ссылке последний раз собирали отзывы.
- `last_scraped_review_date` (дата и время, может быть null) – Дата публикации самого свежего собранного отзыва.
- `created_at` (дата и время, обязательный) – Когда ссылка добавлена в объект.
### Пример ответа
```json
{
"id": 177,
"url": "https://yandex.com/maps/org/national/1020542995",
"source_id": 1,
"link_name": "Яндекс Карты — Националь",
"auto_name": "Яндекс.Карты: Националь",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:06:03",
"last_scraped_review_date": "2026-08-03T09:42:39",
"created_at": "2026-02-23T17:30:52"
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Создать объект (/objects/create)
Объект – ваш объект: отель, клиника, филиал, врач или специалист. Ссылки на площадки передаются
при создании или отдельным методом [добавления ссылок](/links/add).
Ссылка, которую не удалось добавить, не отменяет остальные: они добавятся, а по ней в
`link_results` придёт причина отказа.
Какой адрес принимает каждая площадка, смотрите в [Форматах ссылок](/link-formats).
## POST https://api.rewio.ru/v3/objects
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `Idempotency-Key` (строка, header, может быть null) – Свой ключ запроса. Повтор с тем же ключом вернёт первый объект, а не создаст второй.
### Тело запроса
- `name` (строка, обязательный) – Название объекта. Его видно в личном кабинете и в ответах API.
- `links` (список объектов, может быть null) – Ссылки на страницы объекта на площадках. Можно не передавать и добавить позже.
- `links[].url` (ссылка, обязательный) – Адрес страницы объекта на площадке, как он открывается в браузере. Площадку определим сами.
- `links[].is_active` (да/нет, по умолчанию да) – Собирать ли отзывы по этой ссылке.
- `links[].link_name` (строка, может быть null) – Своё название ссылки внутри объекта, например «Филиал на Невском».
### Пример запроса
```json
{
"name": "Гостиница «Националь»",
"links": [
{
"url": "https://yandex.com/maps/org/national/1020542995",
"link_name": "Яндекс Карты"
},
{
"url": "https://ostrovok.ru/hotel/russia/moscow/mid7467340/gostinitsa_natsional_moskva",
"link_name": "Островок"
}
]
}
```
### Ответ 201
- `id` (число, обязательный) – Идентификатор созданного объекта.
- `name` (строка, обязательный) – Название объекта.
- `created_at` (дата и время, обязательный) – Когда объект создан.
- `link_results` (список объектов, может быть null) – Итог по каждой переданной ссылке, в том же порядке.
- `link_results[].url` (строка, обязательный) – Адрес, к которому относится результат.
- `link_results[].link_id` (число, может быть null) – Идентификатор созданной ссылки. Пусто, если ссылку добавить не удалось.
- `link_results[].source_slug` (строка, может быть null) – Площадка, определённая по адресу.
- `link_results[].link_name` (строка, может быть null) – Своё название ссылки, если его задавали.
- `link_results[].error` (строка, может быть null) – Причина, по которой ссылку не удалось добавить.
- `link_results[].error_code` (строка, может быть null) – Машинная причина отказа — по ней и следует ветвиться: `source_not_detected`, `source_url_invalid`, `source_requires_upgrade`, `source_already_linked` (площадка в объекте может быть только одна), `link_already_added`. `null` при успехе.
### Пример ответа
```json
{
"name": "Гостиница «Националь»",
"id": 218,
"created_at": "2026-08-03T13:41:08",
"link_results": [
{
"url": "https://yandex.com/maps/org/national/1020542995",
"link_id": 521,
"source_slug": "yandex_maps",
"link_name": "Яндекс Карты",
"error": null
},
{
"url": "https://ostrovok.ru/hotel/russia/moscow/mid7467340/gostinitsa_natsional_moskva",
"link_id": 522,
"source_slug": "ostrovok",
"link_name": "Островок",
"error": null
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Удалить объект (/objects/delete)
Удаляет объект вместе со всеми его ссылками. Собранные отзывы по ней больше не отдаются.
Чтобы убрать объект из выдачи на время, [выключите его](/objects/update) через `is_active: false`.
## DELETE https://api.rewio.ru/v3/objects/{object_id}
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, который нужно удалить.
### Ответ 200
- `id` (число, обязательный) – Идентификатор удалённого объекта.
- `name` (строка, обязательный) – Название удалённого объекта.
### Пример ответа
```json
{
"id": 218,
"name": "Гостиница «Националь»"
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Получить объект (/objects/get)
Один объект со всеми его ссылками и состоянием сбора по каждой.
## GET https://api.rewio.ru/v3/objects/{object_id}
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, который нужно получить.
### Ответ 200
- `id` (число, обязательный) – Идентификатор объекта.
- `name` (строка, обязательный) – Название объекта.
- `is_active` (да/нет, обязательный) – Включён ли объект. У выключенного сбор остановлен, отзывы и аналитика не отдаются.
- `scrape_status` (строка, обязательный, pending / in_progress / success / partial / failed) – Итог последнего сбора по объекту: `success` – собрались все площадки, `partial` – часть, `failed` – ни одной, `in_progress` – сбор идёт, `pending` – собирать пока нечего. Выключенные ссылки не в счёт.
- `last_scraped_at` (дата и время, может быть null) – Когда по объекту последний раз собирали отзывы: самая поздняя дата среди включённых ссылок.
- `created_at` (дата и время, обязательный) – Когда объект создан.
- `links` (список объектов, по умолчанию пусто) – Страницы объекта на площадках.
- `links[].id` (число, обязательный) – Идентификатор ссылки.
- `links[].url` (строка, обязательный) – Адрес страницы объекта на площадке.
- `links[].source_id` (число, обязательный) – Площадка, к которой относится ссылка.
- `links[].link_name` (строка, может быть null) – Своё название ссылки внутри объекта.
- `links[].auto_name` (строка, может быть null) – Название, прочитанное с площадки, с её подписью впереди (`2ГИС: Кофейня Север`). Пока имя не прочитано, остаётся одна подпись. Своё `link_name` важнее.
- `links[].is_active` (да/нет, обязательный) – Собираются ли отзывы по этой ссылке.
- `links[].scrape_status` (строка, обязательный, pending / in_progress / success / failed) – Чем кончился последний сбор: `pending`, `in_progress`, `success` или `failed`.
- `links[].scrape_error` (строка, может быть null) – Что помешало последнему сбору.
- `links[].last_scraped_at` (дата и время, может быть null) – Когда по ссылке последний раз собирали отзывы.
- `links[].last_scraped_review_date` (дата и время, может быть null) – Дата публикации самого свежего собранного отзыва.
- `links[].created_at` (дата и время, обязательный) – Когда ссылка добавлена в объект.
### Пример ответа
```json
{
"id": 20,
"name": "Националь",
"is_active": true,
"scrape_status": "success",
"last_scraped_at": "2026-08-04T05:06:04",
"created_at": "2026-02-23T17:29:38",
"links": [
{
"id": 177,
"url": "https://yandex.com/maps/org/national/1020542995",
"source_id": 1,
"link_name": "Яндекс Националь",
"auto_name": "Яндекс.Карты: Националь",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:06:03",
"last_scraped_review_date": "2026-08-03T09:42:39",
"created_at": "2026-02-23T17:30:52"
},
{
"id": 178,
"url": "https://ostrovok.ru/hotel/russia/moscow/mid7467340/gostinitsa_natsional_moskva",
"source_id": 106,
"link_name": "Островок Националь",
"auto_name": "Ostrovok: Гостиница Националь Москва",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:06:04",
"last_scraped_review_date": "2026-08-02T13:04:59",
"created_at": "2026-02-23T17:32:06"
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Список объектов (/objects/list)
Ваши объекта вместе со ссылками на площадки – от новых к старым.
## GET https://api.rewio.ru/v3/objects
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `limit` (число, query, 1–1000, по умолчанию 100) – Сколько объектов вернуть за один запрос.
- `offset` (число, query, не меньше 0, по умолчанию 0) – Сколько объектов пропустить с начала списка.
### Ответ 200
- `data` (список объектов, обязательный) – Объекты текущей страницы, от новых к старым.
- `data[].id` (число, обязательный) – Идентификатор объекта.
- `data[].name` (строка, обязательный) – Название объекта.
- `data[].is_active` (да/нет, обязательный) – Включён ли объект. У выключенного сбор остановлен, отзывы и аналитика не отдаются.
- `data[].scrape_status` (строка, обязательный, pending / in_progress / success / partial / failed) – Итог последнего сбора по объекту: `success` – собрались все площадки, `partial` – часть, `failed` – ни одной, `in_progress` – сбор идёт, `pending` – собирать пока нечего. Выключенные ссылки не в счёт.
- `data[].last_scraped_at` (дата и время, может быть null) – Когда по объекту последний раз собирали отзывы: самая поздняя дата среди включённых ссылок.
- `data[].created_at` (дата и время, обязательный) – Когда объект создан.
- `data[].links` (список объектов, по умолчанию пусто) – Страницы объекта на площадках.
- `data[].links[].id` (число, обязательный) – Идентификатор ссылки.
- `data[].links[].url` (строка, обязательный) – Адрес страницы объекта на площадке.
- `data[].links[].source_id` (число, обязательный) – Площадка, к которой относится ссылка.
- `data[].links[].link_name` (строка, может быть null) – Своё название ссылки внутри объекта.
- `data[].links[].auto_name` (строка, может быть null) – Название, прочитанное с площадки, с её подписью впереди (`2ГИС: Кофейня Север`). Пока имя не прочитано, остаётся одна подпись. Своё `link_name` важнее.
- `data[].links[].is_active` (да/нет, обязательный) – Собираются ли отзывы по этой ссылке.
- `data[].links[].scrape_status` (строка, обязательный, pending / in_progress / success / failed) – Чем кончился последний сбор: `pending`, `in_progress`, `success` или `failed`.
- `data[].links[].scrape_error` (строка, может быть null) – Что помешало последнему сбору.
- `data[].links[].last_scraped_at` (дата и время, может быть null) – Когда по ссылке последний раз собирали отзывы.
- `data[].links[].last_scraped_review_date` (дата и время, может быть null) – Дата публикации самого свежего собранного отзыва.
- `data[].links[].created_at` (дата и время, обязательный) – Когда ссылка добавлена в объект.
- `total` (число, обязательный) – Сколько всего у вас объектов.
- `limit` (число, обязательный) – Размер страницы, применённый к запросу.
- `offset` (число, обязательный) – Смещение, применённое к запросу.
- `has_next` (да/нет, обязательный) – Есть ли ещё объекта за текущей страницей.
### Пример ответа
```json
{
"data": [
{
"id": 20,
"name": "Националь",
"is_active": true,
"scrape_status": "success",
"last_scraped_at": "2026-08-04T05:06:04",
"created_at": "2026-02-23T17:29:38",
"links": [
{
"id": 177,
"url": "https://yandex.com/maps/org/national/1020542995",
"source_id": 1,
"link_name": "Яндекс Националь",
"auto_name": "Яндекс.Карты: Националь",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:06:03",
"last_scraped_review_date": "2026-08-03T09:42:39",
"created_at": "2026-02-23T17:30:52"
},
{
"id": 178,
"url": "https://ostrovok.ru/hotel/russia/moscow/mid7467340/gostinitsa_natsional_moskva",
"source_id": 106,
"link_name": "Островок Националь",
"auto_name": "Ostrovok: Гостиница Националь Москва",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:06:04",
"last_scraped_review_date": "2026-08-02T13:04:59",
"created_at": "2026-02-23T17:32:06"
}
]
}
],
"total": 14,
"limit": 100,
"offset": 0,
"has_next": false
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Включить или выключить объекты (/objects/toggle)
Включает или выключает до 200 объектов за один вызов.
Переключается всё, что доступно: чужой, несуществующий или удалённый `object_id` молча пропускается,
остальные объекта набора всё равно переключаются. В ответе – только те, что переключились.
## PUT https://api.rewio.ru/v3/objects
Заголовок `X-API-Key` обязателен в каждом запросе.
### Тело запроса
- `object_ids` (список чисел, обязательный) – Объекты, которые нужно переключить, от 1 до 200 за запрос. Повторы схлопываются.
- `is_active` (да/нет, обязательный) – Куда переключить весь набор: `true` включить, `false` выключить.
### Пример запроса
```json
{
"object_ids": [
20,
21
],
"is_active": false
}
```
### Ответ 200
- `data` (список объектов, обязательный) – Объекты после переключения. Пагинации нет.
- `data[].id` (число, обязательный) – Идентификатор объекта.
- `data[].name` (строка, обязательный) – Название объекта.
- `data[].is_active` (да/нет, обязательный) – Включён ли объект.
- `data[].scrape_status` (строка, обязательный, pending / in_progress / success / partial / failed) – Итог последнего сбора по объекту: `success` – собрались все площадки, `partial` – часть, `failed` – ни одной, `in_progress` – сбор идёт, `pending` – собирать пока нечего. Выключенные ссылки не в счёт.
- `data[].last_scraped_at` (дата и время, может быть null) – Когда по объекту последний раз собирали отзывы: самая поздняя дата среди включённых ссылок.
- `data[].created_at` (дата и время, обязательный) – Когда объект создан.
- `data[].links` (список объектов, по умолчанию пусто) – Страницы объекта на площадках.
- `data[].links[].id` (число, обязательный) – Идентификатор ссылки.
- `data[].links[].url` (строка, обязательный) – Адрес страницы объекта на площадке.
- `data[].links[].source_id` (число, обязательный) – Площадка, к которой относится ссылка.
- `data[].links[].link_name` (строка, может быть null) – Своё название ссылки внутри объекта.
- `data[].links[].auto_name` (строка, может быть null) – Название, прочитанное с площадки, с её подписью впереди (`2ГИС: Кофейня Север`). Пока имя не прочитано, остаётся одна подпись. Своё `link_name` важнее.
- `data[].links[].is_active` (да/нет, обязательный) – Собираются ли отзывы по этой ссылке.
- `data[].links[].scrape_status` (строка, обязательный, pending / in_progress / success / failed) – Чем кончился последний сбор: `pending`, `in_progress`, `success` или `failed`.
- `data[].links[].scrape_error` (строка, может быть null) – Что помешало последнему сбору.
- `data[].links[].last_scraped_at` (дата и время, может быть null) – Когда по ссылке последний раз собирали отзывы.
- `data[].links[].last_scraped_review_date` (дата и время, может быть null) – Дата публикации самого свежего собранного отзыва.
- `data[].links[].created_at` (дата и время, обязательный) – Когда ссылка добавлена в объект.
### Пример ответа
```json
{
"data": [
{
"id": 20,
"name": "Националь",
"is_active": false,
"scrape_status": "success",
"last_scraped_at": "2026-08-04T05:06:04",
"created_at": "2026-02-23T17:29:38",
"links": [
{
"id": 177,
"url": "https://yandex.com/maps/org/national/1020542995",
"source_id": 1,
"link_name": "Яндекс Националь",
"auto_name": "Яндекс.Карты: Националь",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:06:03",
"last_scraped_review_date": "2026-08-03T09:42:39",
"created_at": "2026-02-23T17:30:52"
},
{
"id": 178,
"url": "https://ostrovok.ru/hotel/russia/moscow/mid7467340/gostinitsa_natsional_moskva",
"source_id": 106,
"link_name": "Островок Националь",
"auto_name": "Ostrovok: Гостиница Националь Москва",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:06:04",
"last_scraped_review_date": "2026-08-02T13:04:59",
"created_at": "2026-02-23T17:32:06"
}
]
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Обновить объект (/objects/update)
Меняет название объекта и его выключатель. Передавайте только то, что нужно изменить.
Выключенный объект (`is_active: false`) перестаёт отдавать отзывы и аналитику – на запросы по нему
приходит 404, сбор по ссылкам останавливается. Сам объект остаётся: его видно в списке,
можно переименовать и включить обратно.
## PUT https://api.rewio.ru/v3/objects/{object_id}
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, который нужно изменить.
### Тело запроса
- `name` (строка, может быть null) – Новое название, 1–255 символов. Не передано – остаётся прежним.
- `is_active` (да/нет, может быть null) – Включить или выключить объект. Не передано – остаётся как было.
### Пример запроса
```json
{
"name": "Гостиница «Националь»"
}
```
### Ответ 200
- `id` (число, обязательный) – Идентификатор объекта.
- `name` (строка, обязательный) – Название объекта.
- `is_active` (да/нет, обязательный) – Включён ли объект.
- `scrape_status` (строка, обязательный, pending / in_progress / success / partial / failed) – Итог последнего сбора по объекту: `success` – собрались все площадки, `partial` – часть, `failed` – ни одной, `in_progress` – сбор идёт, `pending` – собирать пока нечего. Выключенные ссылки не в счёт.
- `last_scraped_at` (дата и время, может быть null) – Когда по объекту последний раз собирали отзывы: самая поздняя дата среди включённых ссылок.
- `created_at` (дата и время, обязательный) – Когда объект создан.
- `links` (список объектов, по умолчанию пусто) – Страницы объекта на площадках.
- `links[].id` (число, обязательный) – Идентификатор ссылки.
- `links[].url` (строка, обязательный) – Адрес страницы объекта на площадке.
- `links[].source_id` (число, обязательный) – Площадка, к которой относится ссылка.
- `links[].link_name` (строка, может быть null) – Своё название ссылки внутри объекта.
- `links[].auto_name` (строка, может быть null) – Название, прочитанное с площадки, с её подписью впереди (`2ГИС: Кофейня Север`). Пока имя не прочитано, остаётся одна подпись. Своё `link_name` важнее.
- `links[].is_active` (да/нет, обязательный) – Собираются ли отзывы по этой ссылке.
- `links[].scrape_status` (строка, обязательный, pending / in_progress / success / failed) – Чем кончился последний сбор: `pending`, `in_progress`, `success` или `failed`.
- `links[].scrape_error` (строка, может быть null) – Что помешало последнему сбору.
- `links[].last_scraped_at` (дата и время, может быть null) – Когда по ссылке последний раз собирали отзывы.
- `links[].last_scraped_review_date` (дата и время, может быть null) – Дата публикации самого свежего собранного отзыва.
- `links[].created_at` (дата и время, обязательный) – Когда ссылка добавлена в объект.
### Пример ответа
```json
{
"id": 20,
"name": "Гостиница «Националь»",
"is_active": true,
"scrape_status": "success",
"last_scraped_at": "2026-08-04T05:06:04",
"created_at": "2026-02-23T17:29:38",
"links": [
{
"id": 177,
"url": "https://yandex.com/maps/org/national/1020542995",
"source_id": 1,
"link_name": "Яндекс Националь",
"auto_name": "Яндекс.Карты: Националь",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:06:03",
"last_scraped_review_date": "2026-08-03T09:42:39",
"created_at": "2026-02-23T17:30:52"
},
{
"id": 178,
"url": "https://ostrovok.ru/hotel/russia/moscow/mid7467340/gostinitsa_natsional_moskva",
"source_id": 106,
"link_name": "Островок Националь",
"auto_name": "Ostrovok: Гостиница Националь Москва",
"is_active": true,
"scrape_status": "success",
"scrape_error": null,
"last_scraped_at": "2026-08-04T05:06:04",
"last_scraped_review_date": "2026-08-02T13:04:59",
"created_at": "2026-02-23T17:32:06"
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Список площадок (/sources/list)
Площадки, с которых Rewio собирает отзывы – Яндекс Карты, Google, 2ГИС и ещё почти три десятка сервисов.
Идентификатор площадки подставляется в фильтры отзывов и аналитики.
## GET https://api.rewio.ru/v3/sources
Заголовок `X-API-Key` обязателен в каждом запросе.
### Ответ 200
- `data` (список объектов, обязательный) – Площадки. Пагинации нет: список возвращается целиком.
- `data[].id` (число, обязательный) – Идентификатор площадки. Его подставляют в фильтр `source_ids` у отзывов и аналитики.
- `data[].slug` (строка, обязательный) – Машинное имя площадки: латиница, не меняется.
- `data[].name` (строка, обязательный) – Название площадки для интерфейса.
- `data[].base_url` (строка, может быть null) – Домен площадки. Ссылки в объект принимаются только на него.
- `data[].rating_scale` (число, может быть null) – Шкала оценок самой площадки: 5, 10 и так далее. В отзывах оценка уже приведена к 1–5.
- `data[].supports_replies` (да/нет, обязательный) – Площадка позволяет организации отвечать на отзывы.
- `data[].supports_images` (да/нет, обязательный) – К отзывам на площадке можно прикладывать фотографии.
- `data[].can_publish_reply` (да/нет, обязательный) – Мы умеем публиковать ответы на этой площадке. Не то же самое, что `supports_replies`.
- `data[].created_at` (дата и время, обязательный) – Когда площадка появилась в Rewio.
### Пример ответа
```json
{
"data": [
{
"id": 1,
"slug": "yandex_maps",
"name": "Яндекс.Карты",
"base_url": "https://yandex.ru/maps/",
"rating_scale": 5,
"supports_replies": true,
"supports_images": true,
"can_publish_reply": true,
"created_at": "2026-02-08T18:33:17"
},
{
"id": 2,
"slug": "google",
"name": "Google Maps",
"base_url": "https://www.google.com/maps",
"rating_scale": 5,
"supports_replies": true,
"supports_images": true,
"can_publish_reply": false,
"created_at": "2026-02-23T05:51:28"
},
{
"id": 3,
"slug": "2gis",
"name": "2ГИС",
"base_url": "https://2gis.ru/",
"rating_scale": 5,
"supports_replies": true,
"supports_images": true,
"can_publish_reply": true,
"created_at": "2026-02-08T18:33:19"
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Создать вебхук (/webhooks/create)
Rewio отправляет события на ваш адрес, когда появляются новые отзывы.
Каждая доставка подписана заголовком `X-Hrev-Signature`. Секрет общий на все вебхуки аккаунта –
заберите его на [отдельной странице](/webhooks/secret).
## POST https://api.rewio.ru/v3/webhooks
Заголовок `X-API-Key` обязателен в каждом запросе.
### Тело запроса
- `target` (строка, обязательный) – Адрес, на который слать события. Только HTTPS.
- `object_ids` (список чисел, может быть null) – Объекты, события которых доставлять. Не передано – все ваши объекты, включая будущие.
- `event_types` (список строк, обязательный) – Какие события доставлять: `reviews.new`, `replies.new`, `scrape.run.finished`.
- `bucket_window` (строка, по умолчанию «immediate») – Как часто доставлять: `immediate` – сразу после сбора, `daily_<час>` – сводкой в указанный час UTC (`daily_09`).
- `filters` (объект) – Что доставлять, а что пропускать. Условия складываются: событие проходит, только если выполнены все.
- `filters.allowed_ratings` (список чисел) – Доставлять только отзывы с этими оценками. Пусто – с любыми.
- `filters.allowed_sources` (список строк, может быть null) – Доставлять только с этих площадок, машинными именами из справочника. Пусто – со всех.
- `filters.has_text` (да/нет, по умолчанию нет) – Доставлять только отзывы с текстом.
- `filters.has_images` (да/нет, по умолчанию нет) – Доставлять только отзывы с фотографиями.
### Пример запроса
```json
{
"target": "https://example.com/hooks/rewio",
"object_ids": [
20
],
"event_types": [
"reviews.new",
"replies.new"
],
"bucket_window": "immediate",
"filters": {
"allowed_ratings": [
1,
2,
3
],
"has_text": true
}
}
```
### Ответ 201
- `id` (число, обязательный) – Идентификатор созданного вебхука.
- `target` (строка, обязательный) – Адрес, на который приходят события.
- `object_ids` (список чисел, может быть null) – Объекты, события которых доставляются.
- `event_types` (список строк, обязательный) – Какие события доставляются.
- `bucket_window` (строка, обязательный) – Как часто доставляются.
- `filters` (объект, обязательный) – Применённые фильтры доставки.
- `filters.allowed_ratings` (список чисел) – Оценки, при которых доставлять.
- `filters.allowed_sources` (список строк, может быть null) – Площадки, при которых доставлять.
- `filters.has_text` (да/нет, по умолчанию нет) – Только отзывы с текстом.
- `filters.has_images` (да/нет, по умолчанию нет) – Только отзывы с фотографиями.
- `is_active` (да/нет, обязательный) – Включена ли доставка.
- `failure_count` (число, обязательный) – Сколько неудачных доставок подряд.
- `last_failure_at` (дата и время, может быть null) – Когда доставка не удалась в последний раз.
- `created_at` (дата и время, обязательный) – Когда вебхук создан.
- `updated_at` (дата и время, обязательный) – Когда его меняли в последний раз.
### Пример ответа
```json
{
"id": 41,
"target": "https://example.com/hooks/rewio",
"object_ids": [
20
],
"event_types": [
"reviews.new",
"replies.new"
],
"bucket_window": "immediate",
"filters": {
"allowed_ratings": [
1,
2,
3
],
"allowed_sources": null,
"has_text": true,
"has_images": false
},
"is_active": true,
"failure_count": 0,
"last_failure_at": null,
"created_at": "2026-08-01T09:14:02",
"updated_at": "2026-08-01T09:14:02"
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Удалить вебхук (/webhooks/delete)
Удаляет вебхук. Доставка на его адрес прекращается сразу, история доставок пропадает вместе с ним.
Чтобы приостановить доставку, не теряя настройки, вебхук проще выключить: `PATCH /v3/webhooks/{webhook_id}`
с `is_active: false`.
## DELETE https://api.rewio.ru/v3/webhooks/{webhook_id}
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `webhook_id` (число, path, обязательный) – Вебхук, который нужно удалить.
### Ответ 204
Тело ответа пустое.
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Список вебхуков (/webhooks/list)
Вебхуки аккаунта и состояние доставки по каждому.
Секрет подписи здесь не приходит – он один на аккаунт и лежит на [своей странице](/webhooks/secret).
## GET https://api.rewio.ru/v3/webhooks
Заголовок `X-API-Key` обязателен в каждом запросе.
### Ответ 200
- `data` (список объектов, обязательный) – Вебхуки аккаунта. Пагинации нет.
- `data[].id` (число, обязательный) – Идентификатор вебхука.
- `data[].target` (строка, обязательный) – Адрес, на который приходят события. Только HTTPS.
- `data[].object_ids` (список чисел, может быть null) – Объекты, события которых доставляются. Пусто – все ваши объекты.
- `data[].event_types` (список строк, обязательный) – Какие события доставлять: `reviews.new`, `replies.new`, `scrape.run.finished`.
- `data[].bucket_window` (строка, обязательный) – Как часто доставлять: `immediate` – сразу после сбора, `daily_<час>` – сводкой в указанный час UTC.
- `data[].filters` (объект, обязательный) – Что доставлять, а что пропускать. Условия складываются: событие проходит, только если выполнены все.
- `data[].filters.allowed_ratings` (список чисел) – Доставлять только отзывы с этими оценками. Пусто – с любыми.
- `data[].filters.allowed_sources` (список строк, может быть null) – Доставлять только с этих площадок, машинными именами. Пусто – со всех.
- `data[].filters.has_text` (да/нет, по умолчанию нет) – Доставлять только отзывы с текстом.
- `data[].filters.has_images` (да/нет, по умолчанию нет) – Доставлять только отзывы с фотографиями.
- `data[].is_active` (да/нет, обязательный) – Включена ли доставка.
- `data[].failure_count` (число, обязательный) – Сколько неудачных доставок подряд.
- `data[].last_failure_at` (дата и время, может быть null) – Когда доставка не удалась в последний раз.
- `data[].created_at` (дата и время, обязательный) – Когда вебхук создан.
- `data[].updated_at` (дата и время, обязательный) – Когда его меняли в последний раз.
### Пример ответа
```json
{
"data": [
{
"id": 41,
"target": "https://example.com/hooks/rewio",
"object_ids": [
20
],
"event_types": [
"reviews.new",
"replies.new"
],
"bucket_window": "immediate",
"filters": {
"allowed_ratings": [
1,
2,
3
],
"allowed_sources": null,
"has_text": true,
"has_images": false
},
"is_active": true,
"failure_count": 0,
"last_failure_at": null,
"created_at": "2026-08-01T09:14:02",
"updated_at": "2026-08-01T09:14:02"
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Перевыпустить секрет (/webhooks/rotate-secret)
Выдаёт новый секрет подписи для всего аккаунта.
Старый работает ещё сутки: всё это время доставки подписываются обоими секретами. Обновите
проверку подписи в течение этого окна.
## POST https://api.rewio.ru/v3/webhooks/rotate-secret
Заголовок `X-API-Key` обязателен в каждом запросе.
### Ответ 200
- `secret` (строка, обязательный) – Новый секрет подписи.
- `rotated_at` (дата и время, может быть null) – Момент перевыпуска. От него считаются сутки, пока действует и старый секрет.
### Пример ответа
```json
{
"secret": "whsec_3Vn7Kp1Zs5Xq9Md2Gt6Yb4Wr8Ac0Ej2Hl4Nu6Rf8Ti1Ow3Pd5Km7Bz9Xv2Qs4",
"rotated_at": "2026-08-04T07:31:12"
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Секрет подписи (/webhooks/secret)
Секрет, которым подписаны доставки. Он один на аккаунт, а не на вебхук, и виден всегда.
Нужен ключ со scope `full`: ключом только на чтение секрет не получить.
## GET https://api.rewio.ru/v3/webhooks/secret
Заголовок `X-API-Key` обязателен в каждом запросе.
### Ответ 200
- `secret` (строка, обязательный) – Секрет подписи. Им проверяется заголовок `X-Hrev-Signature` на вашей стороне.
- `rotated_at` (дата и время, может быть null) – Когда секрет перевыпускали в последний раз. Пусто – он ни разу не менялся.
### Пример ответа
```json
{
"secret": "whsec_8Qf2Lm4Rt6Yv9Bn3Kd5Wp7Zx1Cs0Hj2Ng4Mq6Tr8Uv1Ay3Ew5Ik7Ol9Pb2Xd4",
"rotated_at": null
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Тестовая доставка (/webhooks/test)
Отправляет на ваш адрес пробное событие с такой же подписью, как у настоящей доставки.
Отправка асинхронная: ответ приходит сразу, а результат виден в состоянии вебхука – [список вебхуков](/webhooks/list).
## POST https://api.rewio.ru/v3/webhooks/{webhook_id}/test
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `webhook_id` (число, path, обязательный) – Вебхук, на который отправить пробное событие.
### Ответ 202
- `status` (строка, обязательный) – Состояние отправки. `queued` – доставка поставлена в очередь.
- `test_id` (строка, обязательный) – Идентификатор пробной доставки. По нему её видно в истории доставок.
### Пример ответа
```json
{
"status": "queued",
"test_id": "test_9Kd2mQx7ZpLr4Vn1"
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Изменить вебхук (/webhooks/update)
Меняет адрес, набор объектов, события и фильтры. Передавайте только то, что нужно изменить.
`is_active: false` ставит доставку на паузу, сохраняя настройки – события за время паузы не накапливаются.
## PATCH https://api.rewio.ru/v3/webhooks/{webhook_id}
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `webhook_id` (число, path, обязательный) – Вебхук, который нужно изменить.
### Тело запроса
- `target` (строка, может быть null) – Новый адрес доставки. Только HTTPS.
- `object_ids` (список чисел, может быть null) – Новый набор объектов. Пустой список – события всех ваших объектов.
- `event_types` (список строк, может быть null) – Новый набор событий.
- `bucket_window` (строка, может быть null) – Новая частота доставки: `immediate` или `daily_<час>`.
- `filters` (объект, может быть null) – Новые фильтры доставки. Передаются целиком и заменяют прежние.
- `filters.allowed_ratings` (список чисел) – Доставлять только отзывы с этими оценками.
- `filters.allowed_sources` (список строк, может быть null) – Доставлять только с этих площадок.
- `filters.has_text` (да/нет, по умолчанию нет) – Доставлять только отзывы с текстом.
- `filters.has_images` (да/нет, по умолчанию нет) – Доставлять только отзывы с фотографиями.
- `is_active` (да/нет, может быть null) – Включить доставку или поставить её на паузу.
### Пример запроса
```json
{
"is_active": false
}
```
### Ответ 200
- `id` (число, обязательный) – Идентификатор вебхука.
- `target` (строка, обязательный) – Адрес, на который приходят события.
- `object_ids` (список чисел, может быть null) – Объекты, события которых доставляются.
- `event_types` (список строк, обязательный) – Какие события доставляются.
- `bucket_window` (строка, обязательный) – Как часто доставляются.
- `filters` (объект, обязательный) – Применённые фильтры доставки.
- `filters.allowed_ratings` (список чисел) – Оценки, при которых доставлять.
- `filters.allowed_sources` (список строк, может быть null) – Площадки, при которых доставлять.
- `filters.has_text` (да/нет, по умолчанию нет) – Только отзывы с текстом.
- `filters.has_images` (да/нет, по умолчанию нет) – Только отзывы с фотографиями.
- `is_active` (да/нет, обязательный) – Включена ли доставка.
- `failure_count` (число, обязательный) – Сколько неудачных доставок подряд.
- `last_failure_at` (дата и время, может быть null) – Когда доставка не удалась в последний раз.
- `created_at` (дата и время, обязательный) – Когда вебхук создан.
- `updated_at` (дата и время, обязательный) – Когда его меняли в последний раз.
### Пример ответа
```json
{
"id": 41,
"target": "https://example.com/hooks/rewio",
"object_ids": [
20
],
"event_types": [
"reviews.new",
"replies.new"
],
"bucket_window": "immediate",
"filters": {
"allowed_ratings": [
1,
2,
3
],
"allowed_sources": null,
"has_text": true,
"has_images": false
},
"is_active": false,
"failure_count": 0,
"last_failure_at": null,
"created_at": "2026-08-01T09:14:02",
"updated_at": "2026-08-04T07:22:40"
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Скрыть отзывы (/widgets/hidden-add)
Убирает отзывы из ленты объекта: они перестают приходить в [`GET /v3/reviews`](/reviews/list).
Скрытие действует внутри объекта: в другом объекте тот же отзыв останется на месте. Сам отзыв
остаётся доступен – в [списке скрытых](/widgets/hidden-list) и в обычной ленте с `show_hidden=true`.
За один запрос – до 200 идентификаторов. Уже скрытый отзыв можно передать повторно, ошибки не будет.
Скрытие **снимает закрепление**: если отзыв был закреплён, он открепляется, а остальные закреплённые
сдвигаются вверх. Обратное [снятие скрытия](/widgets/hidden-remove) закрепление не возвращает –
закрепите заново.
Аналитика скрытые отзывы **продолжает считать**: `GET /v3/analytics` отдаёт средний балл и
распределение оценок по всем собранным отзывам объекта. Скрытие – это оформление выдачи, а не
правка данных.
## POST https://api.rewio.ru/v3/objects/{object_id}/hidden-reviews
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, в котором скрываются отзывы.
### Тело запроса
- `review_ids` (список чисел, обязательный) – Отзывы, которые нужно скрыть.
### Пример запроса
```json
{
"review_ids": [
143046,
142391
]
}
```
### Ответ 200
- `message` (строка, обязательный) – Сообщение об итоге операции.
- `hidden_count` (число, обязательный) – Сколько отзывов скрыто в объекте после этой операции: всего, а не за вызов.
### Пример ответа
```json
{
"message": "Reviews hidden",
"hidden_count": 2
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Скрытые отзывы (/widgets/hidden-list)
Отзывы, спрятанные в этом объекте – те, что не попадают в обычную ленту. Экран модерации строят
на этой ручке: показать скрытое и дать [вернуть обратно](/widgets/hidden-remove).
Скрытие действует внутри объекта: тот же отзыв в другом объекте останется видимым.
Фильтры, сортировка и постраничная выдача устроены как в [общей ленте](/reviews/list), но список
параметров короче – он весь ниже. В частности, разбора текста на достоинства и недостатки здесь
нет: `split_pros_cons` эта ручка не принимает.
## GET https://api.rewio.ru/v3/objects/{object_id}/hidden-reviews
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, скрытые отзывы которого нужны.
- `link_ids` (список чисел, query, по умолчанию пусто) – Только по этим ссылкам внутри объекта. Пусто – по всем.
- `source_ids` (список чисел, query, по умолчанию пусто) – Только с этих площадок. Пусто – со всех.
- `sort_by` (строка, query, review_date / rating / reply_date, по умолчанию «review_date») – По дате публикации, по оценке или по дате ответа организации (`reply_date`).
- `sort_order` (строка, query, desc / asc, по умолчанию «desc») – `desc` – сначала новые (или высокие оценки), `asc` – наоборот.
- `min_rating` (число, query, может быть null, 1–5) – Оценка не ниже указанной, по шкале 1–5.
- `max_rating` (число, query, может быть null, 1–5) – Оценка не выше указанной.
- `published_from` (дата, query, может быть null) – Отзывы, опубликованные не раньше этой даты.
- `published_to` (дата, query, может быть null) – Отзывы, опубликованные не позже этой даты. Названный день входит в период целиком.
- `has_images` (да/нет, query, может быть null) – `true` – только с изображениями, `false` – только без.
- `has_text` (да/нет, query, может быть null) – `true` – только с текстом, `false` – только оценки без текста.
- `has_reply` (да/нет, query, может быть null) – `true` – только с ответом организации, `false` – только без ответа.
- `search` (строка, query, может быть null) – Поиск подстроки в тексте отзыва, без учёта регистра.
- `limit` (число, query, 1–1000, по умолчанию 100) – Сколько отзывов вернуть за один запрос.
- `offset` (число, query, не меньше 0, по умолчанию 0) – Сколько отзывов пропустить с начала выборки.
- `show_deleted` (да/нет, query, по умолчанию нет) – Добавить к выдаче отзывы, пропавшие с площадки.
- `only_deleted` (да/нет, query, по умолчанию нет) – Только пропавшие с площадки отзывы.
### Ответ 200
- `data` (список объектов, обязательный) – Скрытые отзывы текущей страницы.
- `data[].id` (число, обязательный) – Идентификатор отзыва в Rewio.
- `data[].link_id` (число, обязательный) – Ссылка, с которой собран отзыв.
- `data[].source_id` (число, обязательный) – Площадка, с которой собран отзыв.
- `data[].author` (строка, может быть null) – Имя автора так, как его показывает площадка. Фамилии маскируются.
- `data[].text` (строка, может быть null) – Текст отзыва.
- `data[].review_date` (дата и время, может быть null) – Когда отзыв опубликован, по времени площадки.
- `data[].rating_original` (строка, может быть null) – Оценка в шкале самой площадки, как есть.
- `data[].rating` (число, может быть null) – Та же оценка, приведённая к шкале 1–5.
- `data[].review_url` (строка, может быть null) – Прямая ссылка на отзыв на площадке.
- `data[].reply_text` (строка, может быть null) – Ответ организации на отзыв.
- `data[].reply_date` (дата и время, может быть null) – Когда организация ответила.
- `data[].has_images` (да/нет, обязательный) – Есть ли у отзыва изображения.
- `data[].images` (список объектов, по умолчанию пусто) – Изображения отзыва.
- `data[].images[].template_url` (строка, обязательный) – Адрес с плейсхолдером `{size}`, в таком виде его отдаёт площадка.
- `data[].images[].preview` (строка, может быть null) – Уменьшенная версия для списков и сеток.
- `data[].images[].image` (строка, может быть null) – Полноразмерная версия для просмотра.
- `data[].has_videos` (да/нет, по умолчанию нет) – Есть ли у отзыва видео.
- `data[].videos` (список объектов, по умолчанию пусто) – Видео отзыва.
- `data[].videos[].kind` (строка, обязательный, embed / file / hls) – `embed` – плеер площадки, `file` – прямой файл, `hls` – поток.
- `data[].videos[].video_url` (строка, может быть null) – Адрес видео или плеера, смотря какой `kind`.
- `data[].videos[].thumbnail` (строка, может быть null) – Кадр-заставка.
- `data[].videos[].width` (число, может быть null) – Ширина в пикселях, если площадка её сообщает.
- `data[].videos[].height` (число, может быть null) – Высота в пикселях, если площадка её сообщает.
- `data[].videos[].duration_ms` (число, может быть null) – Длительность в миллисекундах, если площадка её сообщает.
- `data[].is_deleted` (да/нет, по умолчанию нет) – Отзыв пропал с площадки.
- `data[].deleted_at` (дата и время, может быть null) – Когда мы заметили пропажу.
- `data[].is_hidden` (да/нет, по умолчанию нет) – Всегда `true`: здесь только скрытые.
- `total` (число, обязательный) – Сколько отзывов скрыто в этом объекте.
- `limit` (число, обязательный) – Размер страницы, применённый к запросу.
- `offset` (число, обязательный) – Смещение, применённое к запросу.
- `has_next` (да/нет, обязательный) – Есть ли ещё отзывы за текущей страницей.
### Пример ответа
```json
{
"data": [
{
"id": 143046,
"link_id": 177,
"source_id": 1,
"author": "Сергей",
"text": "Замечательно. Приеду ещё. ",
"review_date": "2026-07-21T18:07:03",
"rating_original": "5",
"rating": 5,
"review_url": "https://yandex.ru/maps/org/1020542995/reviews?reviews[publicId]=u182gzp1r9rg4urew906ky9dd0",
"reply_text": "Уважаемый Гость,\nБлагодарим Вас за визит в гостиницу «Националь» и высокую оценку нашего обслуживания. Ждем Вас снова!\nС наилучшими пожеланиями,\t\nЕлена Позолотина\nДиректор по операционной деятельности.\n",
"reply_date": "2026-07-23T13:27:07",
"has_images": true,
"images": [
{
"template_url": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/M",
"image": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/M",
"image": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/M",
"image": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/XXXL"
}
],
"has_videos": false,
"videos": [],
"is_hidden": true
}
],
"total": 1,
"limit": 100,
"offset": 0,
"has_next": false
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Вернуть отзывы в ленту (/widgets/hidden-remove)
Снимает скрытие: отзывы снова приходят в ленту объекта.
За один запрос – до 200 идентификаторов. Отзыв, который не был скрыт, пропускается без ошибки.
Закрепление, снятое при скрытии, само не возвращается – [закрепите заново](/widgets/pinned-add).
## DELETE https://api.rewio.ru/v3/objects/{object_id}/hidden-reviews
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, в котором снимается скрытие.
### Тело запроса
- `review_ids` (список чисел, обязательный) – Отзывы, которые нужно вернуть в ленту.
### Пример запроса
```json
{
"review_ids": [
142391
]
}
```
### Ответ 200
- `message` (строка, обязательный) – Сообщение об итоге операции.
- `hidden_count` (число, обязательный) – Сколько отзывов осталось скрыто в объекте после этой операции.
### Пример ответа
```json
{
"message": "Reviews unhidden",
"hidden_count": 1
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Закрепить отзывы (/widgets/pinned-add)
Поднимает отзывы в начало выдачи, добавляя их в конец списка закреплённых.
Уже закреплённые отзывы второй раз не добавляются и своё место не теряют. Чтобы задать порядок
целиком, используйте [`PUT`](/widgets/pinned-set).
Закрепить можно **не больше 30 отзывов на объект**. Запрос, после которого их стало бы больше,
отклоняется целиком – `400 pinned_limit_exceeded`.
В ответ приходит весь список закреплённых после операции, в порядке показа.
## POST https://api.rewio.ru/v3/objects/{object_id}/pinned-reviews
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, в котором закрепляются отзывы.
### Тело запроса
- `review_ids` (список чисел, обязательный) – Отзывы, которые нужно закрепить.
### Пример запроса
```json
{
"review_ids": [
143046
]
}
```
### Ответ 200
- `data` (список объектов, обязательный) – Все закреплённые отзывы объекта после операции, в их порядке.
- `data[].id` (число, обязательный) – Идентификатор отзыва в Rewio.
- `data[].link_id` (число, обязательный) – Ссылка, с которой собран отзыв.
- `data[].source_id` (число, обязательный) – Площадка, с которой собран отзыв.
- `data[].author` (строка, может быть null) – Имя автора так, как его показывает площадка. Фамилии маскируются.
- `data[].text` (строка, может быть null) – Текст отзыва.
- `data[].review_date` (дата и время, может быть null) – Когда отзыв опубликован, по времени площадки.
- `data[].rating_original` (строка, может быть null) – Оценка в шкале самой площадки, как есть.
- `data[].rating` (число, может быть null) – Та же оценка, приведённая к шкале 1–5.
- `data[].review_url` (строка, может быть null) – Прямая ссылка на отзыв на площадке.
- `data[].reply_text` (строка, может быть null) – Ответ организации на отзыв.
- `data[].reply_date` (дата и время, может быть null) – Когда организация ответила.
- `data[].has_images` (да/нет, обязательный) – Есть ли у отзыва изображения.
- `data[].images` (список объектов, по умолчанию пусто) – Изображения отзыва.
- `data[].images[].template_url` (строка, обязательный) – Адрес с плейсхолдером `{size}`, в таком виде его отдаёт площадка.
- `data[].images[].preview` (строка, может быть null) – Уменьшенная версия для списков и сеток.
- `data[].images[].image` (строка, может быть null) – Полноразмерная версия для просмотра.
- `data[].has_videos` (да/нет, по умолчанию нет) – Есть ли у отзыва видео.
- `data[].videos` (список объектов, по умолчанию пусто) – Видео отзыва.
- `data[].videos[].kind` (строка, обязательный, embed / file / hls) – `embed` – плеер площадки, `file` – прямой файл, `hls` – поток.
- `data[].videos[].video_url` (строка, может быть null) – Адрес видео или плеера, смотря какой `kind`.
- `data[].videos[].thumbnail` (строка, может быть null) – Кадр-заставка.
- `data[].videos[].width` (число, может быть null) – Ширина в пикселях, если площадка её сообщает.
- `data[].videos[].height` (число, может быть null) – Высота в пикселях, если площадка её сообщает.
- `data[].videos[].duration_ms` (число, может быть null) – Длительность в миллисекундах, если площадка её сообщает.
- `data[].is_deleted` (да/нет, по умолчанию нет) – Отзыв пропал с площадки.
- `data[].deleted_at` (дата и время, может быть null) – Когда мы заметили пропажу.
- `data[].is_pinned` (да/нет, по умолчанию нет) – Всегда `true`.
- `data[].pin_position` (число, может быть null) – Место отзыва среди закреплённых, считая с нуля.
### Пример ответа
```json
{
"data": [
{
"id": 143046,
"link_id": 177,
"source_id": 1,
"author": "Сергей",
"text": "Замечательно. Приеду ещё. ",
"review_date": "2026-07-21T18:07:03",
"rating_original": "5",
"rating": 5,
"review_url": "https://yandex.ru/maps/org/1020542995/reviews?reviews[publicId]=u182gzp1r9rg4urew906ky9dd0",
"reply_text": "Уважаемый Гость,\nБлагодарим Вас за визит в гостиницу «Националь» и высокую оценку нашего обслуживания. Ждем Вас снова!\nС наилучшими пожеланиями,\t\nЕлена Позолотина\nДиректор по операционной деятельности.\n",
"reply_date": "2026-07-23T13:27:07",
"has_images": true,
"images": [
{
"template_url": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/M",
"image": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/M",
"image": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/M",
"image": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/XXXL"
}
],
"has_videos": false,
"videos": [],
"is_pinned": true,
"pin_position": 0
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Закреплённые отзывы (/widgets/pinned-list)
Отзывы, поднятые в начало выдачи, в том порядке, в котором их закрепили. Этим порядком и собирают
верх страницы: закреплённые берут отсюда, остальное – [общей лентой](/reviews/list).
Без `sort_by` порядок в ответе – порядок закрепления. Передали `sort_by` – закреплённые
пересортируются по дате, оценке или дате ответа, а `pin_position` останется прежним.
Закрепление работает внутри объекта: в папочной ленте (`folder_id`) оно не применяется, и такие отзывы
приходят обычными строками.
## GET https://api.rewio.ru/v3/objects/{object_id}/pinned-reviews
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, закреплённые отзывы которого нужны.
- `link_ids` (список чисел, query, по умолчанию пусто) – Только по этим ссылкам внутри объекта. Пусто – по всем.
- `source_ids` (список чисел, query, по умолчанию пусто) – Только с этих площадок. Пусто – со всех.
- `sort_by` (строка, query, review_date / rating / reply_date, по умолчанию «review_date») – Дата публикации, оценка или дата ответа организации (`reply_date`). Не передано – сохраняется порядок закрепления.
- `sort_order` (строка, query, desc / asc, по умолчанию «desc») – `desc` – сначала новые (или высокие оценки), `asc` – наоборот.
- `min_rating` (число, query, может быть null, 1–5) – Оценка не ниже указанной, по шкале 1–5.
- `max_rating` (число, query, может быть null, 1–5) – Оценка не выше указанной.
- `published_from` (дата, query, может быть null) – Отзывы, опубликованные не раньше этой даты.
- `published_to` (дата, query, может быть null) – Отзывы, опубликованные не позже этой даты. Названный день входит в период целиком.
- `has_images` (да/нет, query, может быть null) – `true` – только с изображениями, `false` – только без.
- `has_text` (да/нет, query, может быть null) – `true` – только с текстом, `false` – только оценки без текста.
- `has_reply` (да/нет, query, может быть null) – `true` – только с ответом организации, `false` – только без ответа.
- `search` (строка, query, может быть null) – Поиск подстроки в тексте отзыва, без учёта регистра.
- `limit` (число, query, 1–1000, по умолчанию 100) – Сколько отзывов вернуть за один запрос.
- `offset` (число, query, не меньше 0, по умолчанию 0) – Сколько отзывов пропустить с начала выборки.
- `show_deleted` (да/нет, query, по умолчанию нет) – Добавить к выдаче отзывы, пропавшие с площадки.
- `only_deleted` (да/нет, query, по умолчанию нет) – Только пропавшие с площадки отзывы.
### Ответ 200
- `data` (список объектов, обязательный) – Закреплённые отзывы в порядке закрепления.
- `data[].id` (число, обязательный) – Идентификатор отзыва в Rewio.
- `data[].link_id` (число, обязательный) – Ссылка, с которой собран отзыв.
- `data[].source_id` (число, обязательный) – Площадка, с которой собран отзыв.
- `data[].author` (строка, может быть null) – Имя автора так, как его показывает площадка. Фамилии маскируются.
- `data[].text` (строка, может быть null) – Текст отзыва.
- `data[].review_date` (дата и время, может быть null) – Когда отзыв опубликован, по времени площадки.
- `data[].rating_original` (строка, может быть null) – Оценка в шкале самой площадки, как есть.
- `data[].rating` (число, может быть null) – Та же оценка, приведённая к шкале 1–5.
- `data[].review_url` (строка, может быть null) – Прямая ссылка на отзыв на площадке.
- `data[].reply_text` (строка, может быть null) – Ответ организации на отзыв.
- `data[].reply_date` (дата и время, может быть null) – Когда организация ответила.
- `data[].has_images` (да/нет, обязательный) – Есть ли у отзыва изображения.
- `data[].images` (список объектов, по умолчанию пусто) – Изображения отзыва.
- `data[].images[].template_url` (строка, обязательный) – Адрес с плейсхолдером `{size}`, в таком виде его отдаёт площадка.
- `data[].images[].preview` (строка, может быть null) – Уменьшенная версия для списков и сеток.
- `data[].images[].image` (строка, может быть null) – Полноразмерная версия для просмотра.
- `data[].has_videos` (да/нет, по умолчанию нет) – Есть ли у отзыва видео.
- `data[].videos` (список объектов, по умолчанию пусто) – Видео отзыва.
- `data[].videos[].kind` (строка, обязательный, embed / file / hls) – `embed` – плеер площадки, `file` – прямой файл, `hls` – поток.
- `data[].videos[].video_url` (строка, может быть null) – Адрес видео или плеера, смотря какой `kind`.
- `data[].videos[].thumbnail` (строка, может быть null) – Кадр-заставка.
- `data[].videos[].width` (число, может быть null) – Ширина в пикселях, если площадка её сообщает.
- `data[].videos[].height` (число, может быть null) – Высота в пикселях, если площадка её сообщает.
- `data[].videos[].duration_ms` (число, может быть null) – Длительность в миллисекундах, если площадка её сообщает.
- `data[].is_deleted` (да/нет, по умолчанию нет) – Отзыв пропал с площадки.
- `data[].deleted_at` (дата и время, может быть null) – Когда мы заметили пропажу.
- `data[].is_pinned` (да/нет, по умолчанию нет) – Всегда `true`: здесь только закреплённые.
- `data[].pin_position` (число, может быть null) – Место отзыва среди закреплённых, считая с нуля.
- `total` (число, обязательный) – Общее число элементов, подходящих под фильтры (без учёта limit/offset).
- `limit` (число, обязательный) – Размер страницы, применённый к запросу.
- `offset` (число, обязательный) – Смещение от начала выборки, применённое к запросу.
- `has_next` (да/нет, обязательный) – Есть ли ещё элементы за пределами текущей страницы.
### Пример ответа
```json
{
"data": [
{
"id": 143046,
"link_id": 177,
"source_id": 1,
"author": "Сергей",
"text": "Замечательно. Приеду ещё. ",
"review_date": "2026-07-21T18:07:03",
"rating_original": "5",
"rating": 5,
"review_url": "https://yandex.ru/maps/org/1020542995/reviews?reviews[publicId]=u182gzp1r9rg4urew906ky9dd0",
"reply_text": "Уважаемый Гость,\nБлагодарим Вас за визит в гостиницу «Националь» и высокую оценку нашего обслуживания. Ждем Вас снова!\nС наилучшими пожеланиями,\t\nЕлена Позолотина\nДиректор по операционной деятельности.\n",
"reply_date": "2026-07-23T13:27:07",
"has_images": true,
"images": [
{
"template_url": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/M",
"image": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/M",
"image": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/M",
"image": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/XXXL"
}
],
"has_videos": false,
"videos": [],
"is_pinned": true,
"pin_position": 0
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Открепить отзывы (/widgets/pinned-remove)
Снимает закрепление: отзывы перестают висеть наверху, но остаются в обычной ленте объекта.
Оставшиеся закреплённые сдвигаются вверх, их порядок между собой сохраняется, а `pin_position`
пересчитывается без пропусков.
В ответ приходит список закреплённых, оставшихся после операции.
## DELETE https://api.rewio.ru/v3/objects/{object_id}/pinned-reviews
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, в котором снимается закрепление.
### Тело запроса
- `review_ids` (список чисел, обязательный) – Отзывы, которые нужно открепить.
### Пример запроса
```json
{
"review_ids": [
143046
]
}
```
### Ответ 200
- `data` (список объектов, обязательный) – Закреплённые отзывы, оставшиеся после операции.
- `data[].id` (число, обязательный) – Идентификатор отзыва в Rewio.
- `data[].link_id` (число, обязательный) – Ссылка, с которой собран отзыв.
- `data[].source_id` (число, обязательный) – Площадка, с которой собран отзыв.
- `data[].author` (строка, может быть null) – Имя автора так, как его показывает площадка. Фамилии маскируются.
- `data[].text` (строка, может быть null) – Текст отзыва.
- `data[].review_date` (дата и время, может быть null) – Когда отзыв опубликован, по времени площадки.
- `data[].rating_original` (строка, может быть null) – Оценка в шкале самой площадки, как есть.
- `data[].rating` (число, может быть null) – Та же оценка, приведённая к шкале 1–5.
- `data[].review_url` (строка, может быть null) – Прямая ссылка на отзыв на площадке.
- `data[].reply_text` (строка, может быть null) – Ответ организации на отзыв.
- `data[].reply_date` (дата и время, может быть null) – Когда организация ответила.
- `data[].has_images` (да/нет, обязательный) – Есть ли у отзыва изображения.
- `data[].images` (список объектов, по умолчанию пусто) – Изображения отзыва.
- `data[].images[].template_url` (строка, обязательный) – Адрес с плейсхолдером `{size}`, в таком виде его отдаёт площадка.
- `data[].images[].preview` (строка, может быть null) – Уменьшенная версия для списков и сеток.
- `data[].images[].image` (строка, может быть null) – Полноразмерная версия для просмотра.
- `data[].has_videos` (да/нет, по умолчанию нет) – Есть ли у отзыва видео.
- `data[].videos` (список объектов, по умолчанию пусто) – Видео отзыва.
- `data[].videos[].kind` (строка, обязательный, embed / file / hls) – `embed` – плеер площадки, `file` – прямой файл, `hls` – поток.
- `data[].videos[].video_url` (строка, может быть null) – Адрес видео или плеера, смотря какой `kind`.
- `data[].videos[].thumbnail` (строка, может быть null) – Кадр-заставка.
- `data[].videos[].width` (число, может быть null) – Ширина в пикселях, если площадка её сообщает.
- `data[].videos[].height` (число, может быть null) – Высота в пикселях, если площадка её сообщает.
- `data[].videos[].duration_ms` (число, может быть null) – Длительность в миллисекундах, если площадка её сообщает.
- `data[].is_deleted` (да/нет, по умолчанию нет) – Отзыв пропал с площадки.
- `data[].deleted_at` (дата и время, может быть null) – Когда мы заметили пропажу.
- `data[].is_pinned` (да/нет, по умолчанию нет) – Всегда `true`.
- `data[].pin_position` (число, может быть null) – Место отзыва среди закреплённых, считая с нуля.
### Пример ответа
```json
{
"data": []
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Задать порядок закреплённых (/widgets/pinned-set)
Задаёт список закреплённых целиком: порядок в запросе становится порядком в выдаче.
Это замена, а не добавление – отзывы, которых нет в списке, открепляются, а пустой список снимает
закрепление со всех. Первый в списке получает `pin_position: 0`, следующий – `1`, и так далее.
Не больше 30 отзывов, без повторов: и то и другое – `400`.
Так удобнее переставлять: вы присылаете желаемый итог, а не двигаете отзывы по одному. Два
одновременных запроса к одному объекту выполняются по очереди, порядок не перемешается.
## PUT https://api.rewio.ru/v3/objects/{object_id}/pinned-reviews
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id` (число, path, обязательный) – Объект, в котором задаётся порядок.
### Тело запроса
- `ordered_review_ids` (список чисел, обязательный) – Закреплённые отзывы целиком, в нужном порядке. Первый в списке окажется первым в выдаче.
### Пример запроса
```json
{
"ordered_review_ids": [
142391,
143046
]
}
```
### Ответ 200
- `data` (список объектов, обязательный) – Закреплённые отзывы после операции, в заданном порядке.
- `data[].id` (число, обязательный) – Идентификатор отзыва в Rewio.
- `data[].link_id` (число, обязательный) – Ссылка, с которой собран отзыв.
- `data[].source_id` (число, обязательный) – Площадка, с которой собран отзыв.
- `data[].author` (строка, может быть null) – Имя автора так, как его показывает площадка. Фамилии маскируются.
- `data[].text` (строка, может быть null) – Текст отзыва.
- `data[].review_date` (дата и время, может быть null) – Когда отзыв опубликован, по времени площадки.
- `data[].rating_original` (строка, может быть null) – Оценка в шкале самой площадки, как есть.
- `data[].rating` (число, может быть null) – Та же оценка, приведённая к шкале 1–5.
- `data[].review_url` (строка, может быть null) – Прямая ссылка на отзыв на площадке.
- `data[].reply_text` (строка, может быть null) – Ответ организации на отзыв.
- `data[].reply_date` (дата и время, может быть null) – Когда организация ответила.
- `data[].has_images` (да/нет, обязательный) – Есть ли у отзыва изображения.
- `data[].images` (список объектов, по умолчанию пусто) – Изображения отзыва.
- `data[].images[].template_url` (строка, обязательный) – Адрес с плейсхолдером `{size}`, в таком виде его отдаёт площадка.
- `data[].images[].preview` (строка, может быть null) – Уменьшенная версия для списков и сеток.
- `data[].images[].image` (строка, может быть null) – Полноразмерная версия для просмотра.
- `data[].has_videos` (да/нет, по умолчанию нет) – Есть ли у отзыва видео.
- `data[].videos` (список объектов, по умолчанию пусто) – Видео отзыва.
- `data[].videos[].kind` (строка, обязательный, embed / file / hls) – `embed` – плеер площадки, `file` – прямой файл, `hls` – поток.
- `data[].videos[].video_url` (строка, может быть null) – Адрес видео или плеера, смотря какой `kind`.
- `data[].videos[].thumbnail` (строка, может быть null) – Кадр-заставка.
- `data[].videos[].width` (число, может быть null) – Ширина в пикселях, если площадка её сообщает.
- `data[].videos[].height` (число, может быть null) – Высота в пикселях, если площадка её сообщает.
- `data[].videos[].duration_ms` (число, может быть null) – Длительность в миллисекундах, если площадка её сообщает.
- `data[].is_deleted` (да/нет, по умолчанию нет) – Отзыв пропал с площадки.
- `data[].deleted_at` (дата и время, может быть null) – Когда мы заметили пропажу.
- `data[].is_pinned` (да/нет, по умолчанию нет) – Всегда `true`.
- `data[].pin_position` (число, может быть null) – Место отзыва среди закреплённых, считая с нуля.
### Пример ответа
```json
{
"data": [
{
"id": 142391,
"link_id": 177,
"source_id": 1,
"author": "Сергей",
"text": "Замечательно. Приеду ещё. ",
"review_date": "2026-07-21T18:07:03",
"rating_original": "5",
"rating": 5,
"review_url": "https://yandex.ru/maps/org/1020542995/reviews?reviews[publicId]=u182gzp1r9rg4urew906ky9dd0",
"reply_text": "Уважаемый Гость,\nБлагодарим Вас за визит в гостиницу «Националь» и высокую оценку нашего обслуживания. Ждем Вас снова!\nС наилучшими пожеланиями,\t\nЕлена Позолотина\nДиректор по операционной деятельности.\n",
"reply_date": "2026-07-23T13:27:07",
"has_images": true,
"images": [
{
"template_url": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/M",
"image": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/M",
"image": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/M",
"image": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/XXXL"
}
],
"has_videos": false,
"videos": [],
"is_pinned": true,
"pin_position": 0
},
{
"id": 143046,
"link_id": 177,
"source_id": 1,
"author": "Сергей",
"text": "Замечательно. Приеду ещё. ",
"review_date": "2026-07-21T18:07:03",
"rating_original": "5",
"rating": 5,
"review_url": "https://yandex.ru/maps/org/1020542995/reviews?reviews[publicId]=u182gzp1r9rg4urew906ky9dd0",
"reply_text": "Уважаемый Гость,\nБлагодарим Вас за визит в гостиницу «Националь» и высокую оценку нашего обслуживания. Ждем Вас снова!\nС наилучшими пожеланиями,\t\nЕлена Позолотина\nДиректор по операционной деятельности.\n",
"reply_date": "2026-07-23T13:27:07",
"has_images": true,
"images": [
{
"template_url": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/M",
"image": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/M",
"image": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/M",
"image": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/XXXL"
}
],
"has_videos": false,
"videos": [],
"is_pinned": true,
"pin_position": 1
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Достоинства и недостатки (/widgets/pros-cons)
Часть площадок спрашивает у автора плюсы и минусы отдельными полями. Мы храним такой отзыв
одним текстом – с подписями, по которым его можно разобрать обратно.
Так он приходит по умолчанию, в поле `text`:
```text
Что понравилось:
Исторический отель в котором стоит хотя бы раз остановиться, конечно интерьер завораживает
Что не понравилось:
Не понравилась организация завтрака: маленький зал, много людей, не все официанты
ориентированы на гостей.
```
## Как получить разбор [#how-to-get]
Параметр `split_pros_cons=true` у [`GET /v3/reviews`](/reviews/list):
```bash
curl -G "https://api.rewio.ru/v3/reviews" \
-H "X-API-Key: hrev_ВАШ_КЛЮЧ" \
--data-urlencode "object_id=20" \
--data-urlencode "split_pros_cons=true"
```
Подписанные блоки уезжают в отдельные поля `pros` и `cons`:
```json
{
"id": 268268,
"author": "Tatiana",
"rating": 4.8,
"pros": "Исторический отель в котором стоит хотя бы раз остановиться, конечно интерьер завораживает",
"cons": "Не понравилась организация завтрака: маленький зал, много людей, не все официанты ориентированы на гостей."
}
```
Разобранные блоки **убираются из `text`**. Если у отзыва не было ничего, кроме плюсов и минусов, –
как в примере выше, – поля `text` в ответе не будет вовсе. Виджет, который читает `text` не глядя,
на таком отзыве покажет пустоту.
Часть площадок к плюсам и минусам добавляет ещё и свободный текст. У таких отзывов он и остаётся
в `text`, а `pros` и `cons` приходят рядом.
## На каких площадках это есть [#sources]
Отзыв изначально разделён на площадках: **Островок**, **Суточно.ру**, **OneTwoTrip**,
**Booking.com**, **Agoda**, **Otzovik**, **Авито**.
На остальных отзыв – один текст, делить в нём нечего. С `split_pros_cons=true` такой отзыв придёт
как обычно: весь текст в `text`, полей `pros` и `cons` нет.
Отдельного признака в [справочнике площадок](/sources/list) для этого нет. Надёжный способ –
проверять наличие полей у самого отзыва:
```python
text = review.get("text")
pros = review.get("pros")
cons = review.get("cons")
```
## Где параметра нет [#unsupported]
`split_pros_cons` работает только в общей ленте отзывов. [Скрытые](/widgets/hidden-list) и
[закреплённые](/widgets/pinned-list) отдаются слитым текстом: `pros` и `cons` там не
приходят никогда.
# Получить отзыв по id (/reviews/get)
Один отзыв по его идентификатору. Ответ – ровно та же модель, что приходит элементом
`data` в [ленте отзывов](/reviews/list).
Метод нужен там, где идентификатор уже на руках, а объекта под ним нет: вебхук приносит
`id` отзыва, а не объект; после публикации ответа проверить `reply_text` больше нечем.
Достаточно ключа со скоупом `read_only`.
## GET https://api.rewio.ru/v3/reviews/{review_id}
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `review_id` (число, path, обязательный) – Идентификатор отзыва в Rewio – тот же `id`, что в ленте отзывов и в событии вебхука.
### Ответ 200
- `id` (число, обязательный) – Идентификатор отзыва в Rewio.
- `link_id` (число, обязательный) – Ссылка, с которой собран отзыв.
- `source_id` (число, обязательный) – Площадка, с которой собран отзыв.
- `author` (строка, может быть null) – Имя автора так, как его показывает площадка. Фамилии маскируются.
- `text` (строка, может быть null) – Текст отзыва.
- `review_date` (дата и время, может быть null) – Когда отзыв опубликован, по времени площадки.
- `rating_original` (строка, может быть null) – Оценка в шкале самой площадки, как есть.
- `rating` (число, может быть null) – Та же оценка, приведённая к шкале 1–5.
- `review_url` (строка, может быть null) – Прямая ссылка на отзыв на площадке.
- `reply_text` (строка, может быть null) – Ответ организации на отзыв.
- `reply_date` (дата и время, может быть null) – Когда организация ответила.
- `has_images` (да/нет, обязательный) – Есть ли у отзыва изображения.
- `images` (список объектов, по умолчанию пусто) – Изображения отзыва.
- `images[].template_url` (строка, обязательный) – Адрес с плейсхолдером `{size}`, в таком виде его отдаёт площадка.
- `images[].preview` (строка, может быть null) – Уменьшенная версия для списков и сеток.
- `images[].image` (строка, может быть null) – Полноразмерная версия для просмотра.
- `has_videos` (да/нет, по умолчанию нет) – Есть ли у отзыва видео.
- `videos` (список объектов, по умолчанию пусто) – Видео отзыва.
- `videos[].kind` (строка, обязательный, embed / file / hls) – `embed` – плеер площадки, `file` – прямой файл, `hls` – поток.
- `videos[].video_url` (строка, может быть null) – Адрес видео или плеера, смотря какой `kind`.
- `videos[].thumbnail` (строка, может быть null) – Кадр-заставка.
- `videos[].width` (число, может быть null) – Ширина в пикселях, если площадка её сообщает.
- `videos[].height` (число, может быть null) – Высота в пикселях, если площадка её сообщает.
- `videos[].duration_ms` (число, может быть null) – Длительность в миллисекундах, если площадка её сообщает.
- `is_deleted` (да/нет, по умолчанию нет) – Отзыв пропал с площадки.
- `deleted_at` (дата и время, может быть null) – Когда мы заметили пропажу.
- `is_hidden` (да/нет, по умолчанию нет) – Отзыв скрыт в объекте.
- `is_pinned` (да/нет, по умолчанию нет) – Отзыв закреплён в объекте.
- `pin_position` (число, может быть null) – Место отзыва среди закреплённых, считая с нуля.
- `pros` (строка, может быть null) – Блок «достоинства».
- `cons` (строка, может быть null) – Блок «недостатки».
### Пример ответа
```json
{
"id": 918342,
"link_id": 57,
"source_id": 1,
"author": "Марина К.",
"text": "Номер чистый, завтрак хороший. Заселили раньше времени, спасибо.",
"review_date": "2026-08-14T09:41:00",
"rating_original": 9,
"rating": 4.5,
"review_url": "https://yandex.ru/maps/org/12345/reviews/?reviews%5BpublicId%5D=abc123",
"reply_text": "Спасибо за отзыв! Ждём вас снова.",
"reply_date": "2026-08-15T07:12:00",
"has_images": false,
"images": [],
"has_videos": false,
"videos": []
}
```
## Чего в ответе не будет [#not-included]
Отзыв **чужого аккаунта** и отзыв, **которого не существует**, отвечают одинаково –
`404` с кодом `not_found`. Ответы намеренно неразличимы: иначе перебор идентификаторов
рассказывал бы о чужих данных.
Видимость та же, что у ленты по умолчанию:
* отзыв, **пропавший с площадки**, по идентификатору не отдаётся;
* отзыв, **скрытый** в объекте, тоже не отдаётся;
* когда глубина выдачи ограничена (блок `access` в ленте), здесь действует тот же
потолок видимости – отзыв старше этого потолка отвечает `404`.
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Получить отзывы (/reviews/list)
Отзывы одного объекта или всей папки – с фильтрами, сортировкой и постраничной выдачей.
Объект или папку указывают ровно одну: либо `object_id`, либо `folder_id`.
## GET https://api.rewio.ru/v3/reviews
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id · folder_id` (число, query, может быть null) – Объект или папка целиком. В запросе должно быть ровно одно из двух.
- `link_ids · source_ids` (список чисел, query, по умолчанию пусто) – Ограничить выдачу конкретными ссылками объекта или площадками. Пусто – без ограничения.
- `sort_by · sort_order` (строка, query, review_date / rating / reply_date, по умолчанию «review_date», desc / asc, по умолчанию «desc») – Поле и направление сортировки: дата публикации, оценка или дата ответа организации (`reply_date`). При сортировке по `reply_date` отзывы без ответа всегда в конце.
- `min_rating · max_rating` (число, query, может быть null, 0–5, 1–5) – Границы оценки по шкале 1–5, включительно.
- `published_from · published_to` (дата, query, может быть null) – Границы даты публикации, включительно. Названный в верхней границе день входит в период целиком.
- `has_images · has_videos` (да/нет, query, может быть null) – Наличие фотографий и видео. `true` – только с ними, `false` – только без.
- `has_text · has_reply` (да/нет, query, может быть null) – Наличие текста отзыва и ответа организации. `true` – только с ними, `false` – только без.
- `search` (строка, query, может быть null) – Поиск подстроки в тексте отзыва, без учёта регистра. Запрос короче двух символов игнорируется.
- `limit · offset` (число, query, 1–1000, по умолчанию 100, не меньше 0, по умолчанию 0) – Сколько отзывов вернуть за запрос и сколько пропустить с начала выборки.
- `show_deleted · only_deleted` (да/нет, query, по умолчанию нет) – Отзывы, пропавшие с площадки: добавить их к выдаче или оставить только их.
- `show_hidden · show_pinned` (да/нет, query, по умолчанию нет) – Показывать отзывы, скрытые или закреплённые в этом объекте. По умолчанию лента не отдаёт ни тех, ни других – [как это устроено](/guides/widgets).
- `split_pros_cons` (да/нет, query, по умолчанию нет) – Разбирать слитый текст на «достоинства» и «недостатки»: часть площадок отдаёт их одной строкой. Разобранные блоки убираются из `text` – [подробнее](/widgets/pros-cons).
### Ответ 200
- `data` (список объектов, обязательный) – Отзывы текущей страницы.
- `data[].id` (число, обязательный) – Идентификатор отзыва в Rewio.
- `data[].link_id` (число, обязательный) – Ссылка, с которой собран отзыв.
- `data[].source_id` (число, обязательный) – Площадка, с которой собран отзыв.
- `data[].author` (строка, может быть null) – Имя автора так, как его показывает площадка. Фамилии маскируются.
- `data[].text` (строка, может быть null) – Текст отзыва.
- `data[].review_date` (дата и время, может быть null) – Когда отзыв опубликован, по времени площадки.
- `data[].rating_original` (строка, может быть null) – Оценка в шкале самой площадки, как есть.
- `data[].rating` (число, может быть null) – Та же оценка, приведённая к шкале 1–5.
- `data[].review_url` (строка, может быть null) – Прямая ссылка на отзыв на площадке.
- `data[].reply_text` (строка, может быть null) – Ответ организации на отзыв.
- `data[].reply_date` (дата и время, может быть null) – Когда организация ответила.
- `data[].has_images` (да/нет, обязательный) – Есть ли у отзыва изображения.
- `data[].images` (список объектов, по умолчанию пусто) – Изображения отзыва.
- `data[].images[].template_url` (строка, обязательный) – Адрес с плейсхолдером `{size}`, в таком виде его отдаёт площадка.
- `data[].images[].preview` (строка, может быть null) – Уменьшенная версия для списков и сеток.
- `data[].images[].image` (строка, может быть null) – Полноразмерная версия для просмотра.
- `data[].has_videos` (да/нет, по умолчанию нет) – Есть ли у отзыва видео.
- `data[].videos` (список объектов, по умолчанию пусто) – Видео отзыва.
- `data[].videos[].kind` (строка, обязательный, embed / file / hls) – `embed` – плеер площадки, `file` – прямой файл, `hls` – поток.
- `data[].videos[].video_url` (строка, может быть null) – Адрес видео или плеера, смотря какой `kind`.
- `data[].videos[].thumbnail` (строка, может быть null) – Кадр-заставка.
- `data[].videos[].width` (число, может быть null) – Ширина в пикселях, если площадка её сообщает.
- `data[].videos[].height` (число, может быть null) – Высота в пикселях, если площадка её сообщает.
- `data[].videos[].duration_ms` (число, может быть null) – Длительность в миллисекундах, если площадка её сообщает.
- `data[].is_deleted` (да/нет, по умолчанию нет) – Отзыв пропал с площадки.
- `data[].deleted_at` (дата и время, может быть null) – Когда мы заметили пропажу.
- `data[].is_hidden` (да/нет, по умолчанию нет) – Отзыв скрыт в этом объекте. Приходит только при `show_hidden=true`.
- `data[].is_pinned` (да/нет, по умолчанию нет) – Отзыв закреплён в этом объекте. Приходит только при `show_pinned=true`.
- `data[].pin_position` (число, может быть null) – Место отзыва среди закреплённых, считая с нуля.
- `data[].pros` (строка, может быть null) – Блок «достоинства». Приходит только при `split_pros_cons=true`.
- `data[].cons` (строка, может быть null) – Блок «недостатки». Приходит только при `split_pros_cons=true`.
- `total` (число, обязательный) – Сколько отзывов подходит под фильтры, без учёта `limit`.
- `limit` (число, обязательный) – Размер страницы, применённый к запросу.
- `offset` (число, обязательный) – Смещение, применённое к запросу.
- `has_next` (да/нет, обязательный) – Есть ли ещё отзывы за текущей страницей.
- `access` (объект, может быть null) – Ограничение выдачи. Приходит только когда показали не всё.
- `access.reason` (строка, обязательный) – Почему выдача ограничена. `trial` — пробный доступ, `manual` — ограничение, согласованное по вашему договору.
- `access.limit_per_link` (число, обязательный) – Сколько последних отзывов показывается по КАЖДОЙ ссылке (площадке объекта).
- `access.hidden_total` (число, обязательный) – Сколько отзывов уже собрано сверх показанных. Считается по всему архиву и не зависит от фильтров запроса.
- `access.message` (строка, обязательный) – Готовая формулировка для показа человеку.
- `collecting` (объект, может быть null) – Первого сбора по части площадок объекта ещё не было. Приходит только пока это так.
- `collecting.link_count` (число, обязательный) – Сколько площадок участвует в выдаче.
- `collecting.links_collecting` (число, обязательный) – По скольким из них сбор идёт прямо сейчас.
- `collecting.links_failed` (число, обязательный) – По скольким из них первый сбор не удался.
- `collecting.message` (строка, обязательный) – Готовая формулировка на английском.
### Пример ответа
```json
{
"data": [
{
"id": 143046,
"link_id": 177,
"source_id": 1,
"author": "Сергей",
"text": "Замечательно. Приеду ещё. ",
"review_date": "2026-07-21T18:07:03",
"rating_original": "5",
"rating": 5,
"review_url": "https://yandex.ru/maps/org/1020542995/reviews?reviews[publicId]=u182gzp1r9rg4urew906ky9dd0",
"reply_text": "Уважаемый Гость,\nБлагодарим Вас за визит в гостиницу «Националь» и высокую оценку нашего обслуживания. Ждем Вас снова!\nС наилучшими пожеланиями,\t\nЕлена Позолотина\nДиректор по операционной деятельности.\n",
"reply_date": "2026-07-23T13:27:07",
"has_images": true,
"images": [
{
"template_url": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/M",
"image": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/M",
"image": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/M",
"image": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/XXXL"
}
],
"has_videos": false,
"videos": []
}
],
"total": 397,
"limit": 1,
"offset": 0,
"has_next": true
}
```
## Пустая выдача сразу после создания объекта [#empty-after-create]
Сбор по новой площадке занимает от нескольких секунд до нескольких минут, и всё
это время отзывов по ней ещё нет. Чтобы пустую страницу не пришлось трактовать
наугад, в ответе появляется блок `collecting`:
```json
{
"collecting": {
"link_count": 2,
"links_collecting": 1,
"links_failed": 0,
"message": "First collection is still running for 1 of 2 platforms — reviews appear here as soon as it finishes."
},
"total": 0, "limit": 100, "offset": 0, "has_next": false, "data": []
}
```
Блок приходит **только пока по какой-то площадке не было ни одного успешного
сбора**. Как только сбор прошёл, ключ пропадает навсегда — даже если отзывов у
объекта не оказалось вовсе: пустая выдача без `collecting` означает, что мы всё
собрали и отзывов нет.
Если `links_failed` больше нуля, первый сбор по этим площадкам не удался;
подробности — в полях `scrape_status` и `scrape_error` у ссылок объекта
([Получить объект](/objects/get)).
Ждать готовности удобнее по объекту, а не по отзывам: `scrape_status` объекта
меняется на `success`, `partial` или `failed`, когда сбор закончен — готовый
цикл ожидания есть в [Быстром старте](/quickstart).
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Оценка на площадках (/reviews/ratings)
Оценка объекта – числовые значения рейтинга, выбранные пользователями. Отзыв –
оценка с текстом.
Необходимо указать либо объект, либо папку.
Чтобы отслеживать динамику оценки, воспользуйтесь методом
[Динамика оценки](/analytics/ratings-history).
## GET https://api.rewio.ru/v3/ratings
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `object_id · folder_id` (число, query, может быть null) – Объект или папка целиком. В запросе должно быть ровно одно из двух.
- `link_ids · source_ids` (список чисел, query, по умолчанию пусто) – Ограничить выдачу конкретными ссылками объекта или площадками. Пусто – без ограничения.
### Ответ 200
- `data` (список объектов, обязательный) – По строке на каждую ссылку, у которой есть замер.
- `data[].link_id` (число, обязательный) – Ссылка, к которой относится оценка.
- `data[].source_id` (число, обязательный) – Площадка, на которой она показана.
- `data[].rating` (число, может быть null) – Оценка объекта на площадке, шкала 1–5.
- `data[].rating_count` (число, может быть null) – Сколько оценок её сформировали, вместе с оставленными без текста.
- `data[].review_count` (число, может быть null) – Сколько из этих оценок – отзывы с текстом, по данным самой площадки.
- `data[].measured_at` (дата и время, обязательный) – Когда мы последний раз сверялись с площадкой и видели это значение.
### Пример ответа
```json
{
"data": [
{
"link_id": 794,
"source_id": 1,
"rating": 4.9,
"rating_count": 966,
"review_count": 286,
"measured_at": "2026-08-27T05:21:23"
},
{
"link_id": 823,
"source_id": 3,
"rating": 4.8,
"rating_count": 237,
"review_count": 193,
"measured_at": "2026-08-27T05:21:40"
},
{
"link_id": 830,
"source_id": 3,
"rating": 4.8,
"rating_count": 21,
"review_count": 17,
"measured_at": "2026-08-27T05:21:48"
}
]
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors).
# Синхронизация отзывов (/reviews/sync)
Поток изменений: отзывы, которые появились или изменились у нас после точки, на
которой вы остановились. Нужен там, где у вас своя база и её надо держать в
актуальном состоянии.
Обычная [лента отзывов](/reviews/list) отвечает на вопрос «покажи»,
синхронизация — на вопрос «догони, ничего не потеряв». Поэтому здесь нет ни
фильтров, ни сортировки, ни `offset`.
## GET https://api.rewio.ru/v3/reviews/sync
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `cursor` (строка, query, может быть null) – Курсор из `next_cursor` предыдущего ответа. С ним остальные параметры можно не передавать.
- `mode` (строка, query, может быть null) – Что присылать: `all` — новые и изменения, `new` — только впервые появившиеся отзывы.
- `limit` (число, query, может быть null, 1–1000) – Размер страницы, 1–1000. Запекается в курсор, дальше его можно не повторять.
- `object_id · folder_id` (число, query, может быть null) – Объект или папка целиком. В запросе должно быть ровно одно из двух — но только на первом запросе, дальше адресат едет внутри курсора.
### Ответ 200
- `data` (список объектов, обязательный) – Отзывы, изменившиеся после вашей позиции.
- `data[].id` (число, обязательный) – Идентификатор отзыва в Rewio. По нему и склеивайте у себя.
- `data[].link_id` (число, обязательный) – Ссылка, с которой собран отзыв.
- `data[].source_id` (число, обязательный) – Площадка, с которой собран отзыв.
- `data[].author` (строка, может быть null) – Имя автора так, как его показывает площадка. Фамилии маскируются.
- `data[].text` (строка, может быть null) – Текст отзыва.
- `data[].review_date` (дата и время, может быть null) – Когда отзыв опубликован, по времени площадки.
- `data[].rating_original` (строка, может быть null) – Оценка в шкале самой площадки, как есть.
- `data[].rating` (число, может быть null) – Та же оценка, приведённая к шкале 1–5.
- `data[].review_url` (строка, может быть null) – Прямая ссылка на отзыв на площадке.
- `data[].reply_text` (строка, может быть null) – Ответ организации на отзыв.
- `data[].reply_date` (дата и время, может быть null) – Когда организация ответила.
- `data[].has_images` (да/нет, обязательный) – Есть ли у отзыва изображения.
- `data[].images` (список объектов, по умолчанию пусто) – Изображения отзыва.
- `data[].images[].template_url` (строка, обязательный) – URL изображения на CDN площадки в исходном виде. У части площадок содержит шаблон размера (`{size}` или `{width}`/`{height}`) и потому не предназначен для прямой вставки — для показа берите `preview` или `image`.
- `data[].images[].preview` (строка, может быть null) – Готовый URL уменьшенной версии (превью) для списков и сеток.
- `data[].images[].image` (строка, может быть null) – Готовый URL полноразмерной версии для просмотра.
- `data[].has_videos` (да/нет, по умолчанию нет) – Есть ли у отзыва видео.
- `data[].videos` (список объектов, по умолчанию пусто) – Видео отзыва.
- `data[].videos[].kind` (строка, обязательный, embed / file / hls) – Тип вложения: `embed` — встраиваемый плеер площадки (открывать в sandbox-iframe), `file` — прямая ссылка на видеофайл, `hls` — HLS-поток (`.m3u8`).
- `data[].videos[].video_url` (строка, может быть null) – URL видео или встраиваемого плеера в зависимости от `kind`.
- `data[].videos[].thumbnail` (строка, может быть null) – URL кадра-заставки (превью) видео.
- `data[].videos[].width` (число, может быть null) – Ширина видео в пикселях, если известна.
- `data[].videos[].height` (число, может быть null) – Высота видео в пикселях, если известна.
- `data[].videos[].duration_ms` (число, может быть null) – Длительность видео в миллисекундах, если известна.
- `data[].is_deleted` (да/нет, по умолчанию нет) – Отзыв пропал с площадки. В синхронизации такие приходят всегда.
- `data[].deleted_at` (дата и время, может быть null) – Когда мы заметили пропажу.
- `data[].is_hidden` (да/нет, по умолчанию нет) – Отзыв скрыт в рамках объекта (приходит только когда `true`).
- `data[].is_pinned` (да/нет, по умолчанию нет) – Отзыв закреплён в рамках объекта (приходит только когда `true`).
- `data[].pin_position` (число, может быть null) – Позиция закреплённого отзыва: меньше — выше.
- `data[].pros` (строка, может быть null) – Блок «достоинства» (только при `split_pros_cons=true`).
- `data[].cons` (строка, может быть null) – Блок «недостатки» (только при `split_pros_cons=true`).
- `next_cursor` (строка, обязательный) – Позиция, с которой продолжить. Приходит всегда — сохраните её у себя.
- `has_next` (да/нет, обязательный) – Есть ли ещё изменения прямо сейчас.
- `access` (объект, может быть null) – Ограничение глубины выдачи. Приходит, только пока оно действует.
- `access.reason` (строка, обязательный) – Почему выдача ограничена. `trial` — пробный доступ, `manual` — ограничение, согласованное по вашему договору.
- `access.limit_per_link` (число, обязательный) – Сколько последних отзывов показывается по КАЖДОЙ ссылке (площадке объекта).
- `access.hidden_total` (число, обязательный) – Сколько отзывов уже собрано сверх показанных. Считается по всему архиву и не зависит от фильтров запроса.
- `access.message` (строка, обязательный) – Готовая формулировка для показа человеку.
### Пример ответа
```json
{
"data": [
{
"id": 143046,
"link_id": 177,
"source_id": 1,
"author": "Сергей",
"text": "Замечательно. Приеду ещё. ",
"review_date": "2026-07-21T18:07:03",
"rating_original": "5",
"rating": 5,
"review_url": "https://yandex.ru/maps/org/1020542995/reviews?reviews[publicId]=u182gzp1r9rg4urew906ky9dd0",
"reply_text": "Уважаемый Гость,\nБлагодарим Вас за визит в гостиницу «Националь» и высокую оценку нашего обслуживания. Ждем Вас снова!\nС наилучшими пожеланиями,\t\nЕлена Позолотина\nДиректор по операционной деятельности.\n",
"reply_date": "2026-07-23T13:27:07",
"has_images": true,
"images": [
{
"template_url": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/M",
"image": "https://avatars.mds.yandex.net/get-altay/17743308/2a0000019f85dc34d7ee3b24b4b9ec82bdbd/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/M",
"image": "https://avatars.mds.yandex.net/get-altay/18473509/2a0000019f85dc42d3b7bbf4e10e375c612e/XXXL"
},
{
"template_url": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/{size}",
"preview": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/M",
"image": "https://avatars.mds.yandex.net/get-altay/20191917/2a0000019f85dc255b9c227cfbc55fc57724/XXXL"
}
],
"has_videos": false,
"videos": []
},
{
"id": 143051,
"link_id": 177,
"source_id": 1,
"author": "Марина",
"text": "Номер не соответствовал описанию.",
"review_date": "2026-07-19T09:14:00",
"rating_original": "2",
"rating": 2,
"review_url": "https://yandex.ru/maps/org/1020542995/reviews?reviews[publicId]=u182gzp1r9rg4urew906ky9dd0",
"has_images": false,
"images": [],
"has_videos": false,
"videos": [],
"is_deleted": true,
"deleted_at": "2026-08-23T04:12:07"
}
],
"next_cursor": "eyJhIjoiMjAyNi0wOC0yM1QxNDowMzoxMSIsImQiOjE0MzA1MSwiaSI6MjAsImwiOjEwMCwibSI6ImFsbCIsInQiOiJnIiwidSI6MzAsInYiOjF9.k3Qw9x0nJ8Zr5wQfB2vLZA",
"has_next": true
}
```
## Как это работает [#how-it-works]
Первый запрос — с адресом объекта, дальше достаточно одного `cursor`:
```
GET /v3/reviews/sync?object_id=20&limit=200
→ 200 отзывов, next_cursor: "eyJ…", has_next: true
GET /v3/reviews/sync?cursor=eyJ…
→ следующие 200, next_cursor: "eyJ…", has_next: true
…
→ has_next: false — вы догнали текущее состояние
```
Сохраните последний `next_cursor` у себя и вернитесь с ним завтра — придёт
только то, что изменилось за сутки.
```python
cursor = load_cursor() # None при первом запуске
params = {"cursor": cursor} if cursor else {"object_id": 20, "limit": 200}
while True:
page = get("/v3/reviews/sync", params=params).json()
for review in page["data"]:
upsert(review) # склейка по review["id"]
cursor = page["next_cursor"]
save_cursor(cursor) # сохраняем ПОСЛЕ каждой страницы
if not page["has_next"]:
break
params = {"cursor": cursor}
```
Курсор — это закладка, а не сохранённая выдача: он не устаревает и его можно
хранить сколько угодно. Обрыв связи стоит одной страницы, а не всего обхода.
Внутрь заглядывать не нужно и не получится — устройство курсора мы оставляем за
собой, чтобы менять его, не ломая вашу интеграцию.
## Один отзыв может прийти дважды [#duplicates]
Если отзыв изменится, пока вы читаете страницы, он придёт ещё раз — уже с новым
содержимым и **с тем же `id`**: новой строки мы не заводим. Поэтому в режиме
`all` кладите к себе **UPSERT по `id`**, а не INSERT — иначе дубли или ошибка
уникальности. В режиме `new` достаточно INSERT: там ничего не приходит дважды.
Потерять отзыв поток не может: это и есть его единственная твёрдая гарантия.
## Что считается изменением [#what-counts-as-change]
Правка текста или оценки на площадке, появившийся или изменившийся ответ
организации, изменившийся набор фотографий и видео, пропажа отзыва с площадки
(`is_deleted: true`) и обратное восстановление. Пересбор сам по себе изменением
не считается: если на площадке ничего не поменялось, отзыв в поток не попадёт.
## Два режима [#modes]
| `mode` | Что приходит | Повторы | Как класть к себе |
| -------------------- | --------------------------------- | -------- | ------------------ |
| `all` (по умолчанию) | новые отзывы и изменения | возможны | **UPSERT** по `id` |
| `new` | только впервые появившиеся отзывы | нет | **INSERT** |
Режим запекается в курсор: курсор одного режима не подходит другому — у них
разные оси. Если нужны оба потока сразу, держите два курсора, это нормально.
`mode=new` удобен, когда отзывы уходят в CRM или в оповещения: правки и пропажи
вас не интересуют, апсерт не нужен, каждая строка приходит ровно один раз.
## Первый обход — всегда с начала истории [#first-pass]
Переключателя «начать с сегодняшнего дня» нет: первый запрос отдаёт самые давние
записи, и обход идёт до конца. Синхронизация — это сперва полная копия, потом
приращения.
Если полная копия вам не нужна и интересны только новые отзывы по мере
появления, это не эта ручка, а [вебхуки](/guides/webhooks): они и присылают
только новое, без архива.
## Свежесть [#freshness]
Изменения появляются в потоке с задержкой в несколько секунд. Это не
запаздывание сбора, а защита от потери: отзыв, который в этот момент
записывается, иначе оказался бы позади вашего курсора и не пришёл бы никогда.
Опрашивать чаще раза в минуту смысла нет — отзывы на площадках так быстро не
появляются. Практичный режим для актуальной копии: раз в 5–15 минут.
## Границы [#limits]
* **Синхронизация не следит за составом объекта.** Убрали ссылку из объекта,
выключили объект или перенесли его — отзывы по нему просто перестанут
приходить, отдельного события об этом не будет. Такие строки чистите у себя
по `link_id`. Если ссылку потом вернули, её старые отзывы заново не поедут:
курсор идёт по времени изменения, а возврат ссылки ничего в отзывах не меняет.
Начните для этого объекта обход с нуля, без сохранённого курсора.
* **Скрытые и закреплённые отзывы** приходят обычными строками, без флагов
`is_hidden` и `is_pinned`: скрытие и закрепление — настройка витрины объекта,
а не свойство отзыва, и в поток изменений они не попадают.
* **Ограниченная выдача** работает здесь ровно так же, как в ленте: не больше
`access.limit_per_link` последних отзывов на каждую площадку объекта (на
пробном доступе — 20), и в ответе появляется блок `access`. Пропажи отзывов,
оставшихся за этой границей, в поток не попадают.
## Ошибки курсора [#cursor-errors]
| Статус | `code` | Когда |
| ------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | `invalid_cursor` | Курсор не читается: испорчен, изменён или выдан другому аккаунту. Начните обход заново, без `cursor`. |
| `400` | `cursor_mismatch` | Вместе с курсором передан `object_id`, `folder_id` или `mode`, не совпадающий с тем, для которого курсор выдан. Либо не передавайте их, либо передайте те же. |
Остальные коды общие для всего API и описаны на странице [Ошибки](/errors).
# Удалить ответ (/replies/delete)
Удаляет ваш ответ с площадки. Удалить можно только свой ответ: тот, что опубликован не через нас,
методу не виден.
Удаление асинхронное, как и публикация: `202` означает, что мы приняли задачу. Когда площадка
её выполнит, поле `reply_text` у [отзыва](/reviews/list) опустеет.
Удалять умеют не все площадки. Там, где ответ удалить нельзя, приходит `409` с кодом
`reply_delete_not_supported` – в таком случае замените текст [публикацией](/replies/publish).
## DELETE https://api.rewio.ru/v3/reviews/{review_id}/reply
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `review_id` (число, path, обязательный) – Отзыв, ответ на который нужно удалить.
### Ответ 202
- `text` (строка, обязательный) – Пустая строка. Она и означает, что ответ удалён.
- `status` (строка, обязательный, pending / publishing / published / failed / deleted) – Состояние вашего ответа: `pending` – принят и ждёт публикации, `publishing` – запрос к площадке в полёте, `published` – площадка приняла, `failed` – опубликовать не удалось, `deleted` – ответ удалён. В ответе на этот запрос всегда `pending`: публикация асинхронная. Что ответ действительно стоит на площадке, видно по полям `reply_text` и `reply_date` самого [отзыва](/reviews/list) и по событию `replies.new`.
- `updated_at` (дата и время, обязательный) – Когда мы приняли задачу.
### Пример ответа
```json
{
"text": "",
"status": "pending",
"updated_at": "2026-08-16T10:20:41"
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors). Отказы, свойственные
именно ответам, собраны там в отдельной таблице.
# Опубликовать ответ (/replies/publish)
Публикует ваш ответ под отзывом на самой площадке или заменяет уже стоящий. Ответ на отзыв
ровно один, поэтому повторный запрос с тем же текстом – не дубль и не ошибка.
Нужен ключ со скоупом `full`, полный тариф и подключённая площадка. Что для этого сделать –
в руководстве [«Как отвечать на отзывы»](/guides/replies).
Публикация асинхронная: `202` означает, что мы приняли задачу. Опубликованный ответ появляется
в самом отзыве, в поле `reply_text` [метода отзывов](/reviews/list), и приходит событием
`replies.new`, если у вас настроены [вебхуки](/guides/webhooks).
## PUT https://api.rewio.ru/v3/reviews/{review_id}/reply
Заголовок `X-API-Key` обязателен в каждом запросе.
### Параметры
- `review_id` (число, path, обязательный) – Отзыв, на который отвечаете.
### Тело запроса
- `text` (строка, обязательный) – Текст ответа. Пустым он не бывает: чтобы убрать ответ, есть [удаление](/replies/delete).
### Пример запроса
```json
{
"text": "Спасибо за отзыв! Рады, что всё понравилось — ждём вас снова."
}
```
### Ответ 202
- `text` (строка, обязательный) – Текст, который мы приняли в работу.
- `status` (строка, обязательный, pending / publishing / published / failed / deleted) – Состояние вашего ответа: `pending` – принят и ждёт публикации, `publishing` – запрос к площадке в полёте, `published` – площадка приняла, `failed` – опубликовать не удалось, `deleted` – ответ удалён. В ответе на этот запрос всегда `pending`: публикация асинхронная. Что ответ действительно стоит на площадке, видно по полям `reply_text` и `reply_date` самого [отзыва](/reviews/list) и по событию `replies.new`.
- `updated_at` (дата и время, обязательный) – Когда мы приняли задачу.
### Пример ответа
```json
{
"text": "Спасибо за отзыв! Рады, что всё понравилось — ждём вас снова.",
"status": "pending",
"updated_at": "2026-08-16T10:12:03"
}
```
Коды ошибок общие для всего API и описаны на странице [Ошибки](/errors). Отказы, свойственные
именно ответам, собраны там в отдельной таблице.