API для разработчиков
Публичный API мониторинга объектов недвижимости. Для доступа к данным необходим бесплатный API-токен — получите его ниже за несколько секунд.
Получить API-токен
Бесплатно, без регистрации. Обязателен он для SSE-стрима (/events/stream) и пакетного запроса (POST /objects/batch); остальные data-эндпоинты работают и без токена, но анонимные запросы ограничены общим лимитом на IP — по умолчанию 120 запросов в минуту с часовым баном при превышении, против 600 запросов в минуту на токен без бана. Оба значения — настраиваемые администратором дефолты (кэш 60 с), а не жёсткие константы, и могут измениться без предупреждения. Токенный лимит применяется при любом из двух способов передать токен — заголовком X-API-Key или query-параметром api_token (единственный способ передать его в EventSource для SSE). Токен при этом должен быть действующим: недействительный уходит под общий лимит на IP, наличия заголовка или параметра самого по себе недостаточно. Authorization: Bearer под токенный лимит не попадает — в этом заголовке приходит и сессионный токен личного кабинета.
Токен действителен до тех пор, пока активно используется. После 90 дней без обращений токен удаляется автоматически. При злоупотреблениях (нарушения rate limit) токен может быть отозван.
Базовая информация
Условия использования
- ✓Бесплатный API-токен — выдаётся мгновенно, регистрация не нужна
- ✓Коммерческое использование данных допустимо с соблюдением авторских прав первоисточников
- ⚠Rate limit: по умолчанию не более 600 запросов в минуту на токен — заголовком X-API-Key или query-параметром ?api_token=, лимит один и тот же. Администратор может изменить его в любой момент
- ⚠Размер тела запроса — не более 15 МБ; всё, что больше, отбивается кодом 413 ещё до разбора и обработки
- ✗SLA не гарантируется — API предоставляется «как есть»
- ✗Сервис не несёт ответственности за действия, совершённые на основе данных API
Sandbox — тестовая среда
Для разработки и отладки интеграции доступен sandbox — основные публичные эндпоинты с случайными моковыми данными. Sandbox не обращается к базе данных и не требует авторизации, но подчиняется тому же общему лимиту на IP, что и остальной API (по умолчанию 120 запросов в минуту). SSE-стрим непрерывно генерирует случайные события каждые 3–7 секунд.
Пример — объекты
curl "/api/v1/sandbox/objects?page_size=3"
# Ответ — случайные объекты каждый раз
# { "total": 142, "page": 1, "items": [...] }Пример — SSE-стрим
curl -N "/api/v1/sandbox/events/stream"
# event: REGISTRATION_OPENED
# id: sandbox-1
# data: {"event_id":"...","developer_id":"pionier",...}
# event: OBJECT_UPDATED
# id: sandbox-2
# data: {"event_id":"...","changed_fields":"[...]",...}Доступные sandbox-эндпоинты повторяют структуру основного API: /developers, /developers/{id}/objects, /events, /events/stream, /registration-status, /health и другие. Полный список — в индексе sandbox.
Справочник эндпоинтов
/developersОпубликованные застройщики, включая приостановленных (active: false). objects_count — объекты застройщика плюс те, где он указан соавтором или причастным; снятые с продажи и карточки-контейнеры (дома и ЖК) не в счёт; buildings_count и complexes_count — дома и жилые комплексы застройщика
Параметры:
- в ответе contacts — контакты застройщика в том же формате, что у карточки услуги: phones, emails, websites, websites_display (домен без схемы, для показа) и social_links
- в ответе юрблок: legal_form, legal_name, unp (в каталоге услуг то же поле зовётся tax_id), registered_year и legal_checked_at — дата сверки. legal_form приходит кодом из закрытого набора: ooo, odo, oao, zao, ip, self_employed, individual. Юрданные вводятся руками или берутся из открытого реестра, поэтому без даты сверки читать их как факт нельзя. Юрадреса здесь нет
- в ответе is_claimed — карточку забрал представитель застройщика; is_verified — владелец подтвердил доступ к карточке. Ни то, ни другое не означает, что данные сверены нами с реестром
- ответ кэшируется на 60 секунд
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/developers"Ответ
[
{
"id": "ghb",
"name": "ГХБ",
"website": "https://ghb.by",
"telegram_channel": "https://t.me/ghb_objects",
"active": true,
"registration_enabled": true,
"objects_count": 245,
"buildings_count": 12,
"complexes_count": 3,
"contacts": {
"phones": ["+375291234567"],
"emails": [],
"websites": ["https://ghb.by"],
"websites_display": ["ghb.by"],
"social_links": []
},
"legal_form": "ooo",
"legal_name": "ГХБ",
"unp": "100000000",
"registered_year": 2005,
"legal_checked_at": "2026-08-20",
"is_claimed": false,
"is_verified": false,
"last_check_at": "2026-03-29T10:15:00Z"
}
]/developers/{id}/destinationsЦелевые направления застройщика: Telegram-каналы, Threads-аккаунты, Viber-каналы единым списком
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/developers/ghb/destinations"Ответ
[
{
"platform": "telegram",
"title": "ГХБ Новостройки",
"username": "ghb_objects",
"url": "https://t.me/ghb_objects",
"visibility": "public",
"member_count": 3200
},
{
"platform": "viber",
"title": "ГХБ Viber",
"username": null,
"url": null,
"visibility": "private",
"member_count": null
}
]/developers/{id}/channelsТолько Telegram-каналы застройщика. Для всех платформ разом используйте /developers/{id}/destinations
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/developers/ghb/channels"Ответ
[
{
"id": "...",
"title": "ГХБ Новостройки",
"type": "public_channel",
"visibility": "public",
"username": "ghb_objects",
"url": "https://t.me/ghb_objects",
"member_count": 3200
}
]/developers/{id}/objectsОбъекты застройщика с фильтрацией и пагинацией
Параметры:
- search — по названию, external_id или адресу
- status, object_type, object_type_slug, registration_open, building, city
- building_id — фильтр по дому как сущности (в отличие от building — корпуса из данных застройщика)
- фильтра по жилому комплексу здесь нет: complex_id принимает только сквозной /objects — за объектами комплекса идите туда, /objects?developer_id=ghb&complex_id=UUID
- min_price, max_price, min_area, max_area, rooms
- sort (price_asc | price_desc | area_asc | area_desc | updated_at_desc)
- group=building — свернуть квартиры одного дома в одну запись ленты; форма ответа та же, что у /objects?group=building (см. ниже). Дом сворачивается, только если под фильтр подходит 2+ его объекта
- дома и жилые комплексы, отданные источником отдельной карточкой, из выдачи исключены всегда, в том числе в ленте group=building: у них нет ни комнат, ни площади, ни цены. По прямой ссылке /developers/{id}/objects/{ext_id} такая карточка по-прежнему открывается
- attribution — own (по умолчанию) отдаёт объекты застройщика, participated — чужие объекты, где он указан соавтором или причастным
- page, page_size (макс. 100)
- в ответе currency — валюта price, estimated_price и price_per_sqm; заполнена всегда (умолчание застройщика либо переопределение объекта); сортировка price_asc/price_desc и фильтры min_price/max_price сравнивают не эту валюту, а BYN-эквивалент по последнему известному курсу НБРБ
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/developers/ghb/objects?registration_open=true&sort=price_asc"Ответ
{
"total": 3,
"page": 1,
"page_size": 50,
"items": [{ "external_id": "OBJ-101", "title": "...", ... }]
}/developers/{id}/objects/{ext_id}Один объект по его external_id
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/developers/ghb/objects/OBJ-101"Ответ
{ "external_id": "OBJ-101", "title": "...", "price": "85000.00", "currency": "BYN", ... }/objects/{object_id}/historyЛента изменений объекта. object_id — внутренний UUID объекта (поле id в схеме объекта), а не пара developer_id/external_id
Параметры:
- limit (1–200, по умолчанию 50)
- before — вернуть события до этой даты (ISO datetime)
- fields — ограничить историю списком полей через запятую
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/objects/3fa85f64-5717-4562-b3fc-2c963f66afa6/history"Ответ
{
"object_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sources": [{ "developer_slug": "ghb", "external_id": "OBJ-101" }],
"entries": [
{
"occurred_at": "...",
"event_type": "OBJECT_UPDATED",
"changes": [{ "field": "price", "old": "84000.00", "new": "85000.00" }]
}
],
"next_before": null
}/objects/{object_id}/price-historyСобранная серия отображаемой цены объекта для графика (не сырые события, как /history). object_id — внутренний UUID объекта, а не пара developer_id/external_id
Параметры:
- max_points (2–2000, по умолчанию 2000) — жёсткий потолок: серия прореживается до одной точки в день, при необходимости остаются последние max_points точек
- currency в ответе — валюта объекта (переопределение объекта важнее валюты застройщика); вся серия приведена к ней пересчётом по курсам НБРБ
- rate_adjustments — участки серии, пересчитанные в currency по курсу НБРБ: before — до какого момента действует пересчёт (застройщик вёл цену в другой валюте до этой даты), from_currency — из какой валюты, rate_date/rate — курс НБРБ, использованный для пересчёта (может быть раньше before, если на этот день курс не публиковался)
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/objects/3fa85f64-5717-4562-b3fc-2c963f66afa6/price-history"Ответ
{
"object_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"currency": "BYN",
"first_observed_at": "2026-03-14T10:00:00Z",
"points": [
{ "date": "2026-03-14T10:00:00Z", "price": "84000.00", "kind": "total" },
{ "date": "2026-05-02T09:00:00Z", "price": "86400.00", "kind": "estimated" }
],
"downsampled": false,
"rate_adjustments": [
{ "before": "2026-05-02T09:00:00Z", "from_currency": "USD", "rate_date": "2026-05-01", "rate": "3.2000" }
]
}/objects/{object_id}/galleryКартинки карточки объекта в порядке источника. object_id — внутренний UUID объекта, а не пара developer_id/external_id
Параметры:
- ответ — массив { section, image_url, thumb_url }, та же форма, что у /buildings/{building_id}/gallery
- строки наполняет джоба gallery_sync из extra источника; у pzs это рендер и планировка квартиры
- обложка объекта (поле image_url карточки) входит в галерею первым кадром, дублировать её на клиенте не нужно
- пока картинка не перекачана в наше хранилище, адреса ведут на сайт источника
- скрытый или архивированный застройщик даёт 404, как и несуществующий объект
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/objects/3fa85f64-5717-4562-b3fc-2c963f66afa6/gallery"Ответ
[
{ "section": "photos", "image_url": "https://...", "thumb_url": "https://..." },
{ "section": "photos", "image_url": "https://...", "thumb_url": "https://..." }
]/object-typesДерево типов объектов — источник допустимых значений object_type_slug для фильтров /objects и /developers/{id}/objects
Параметры:
- lang — язык подписи (по умолчанию ru)
Запрос
curl "/api/v1/object-types"Ответ
[
{
"slug": "apartment",
"label": "Квартира",
"parent_slug": null,
"is_leaf": false,
"children": [
{ "slug": "apartment.studio", "label": "Студия", "parent_slug": "apartment", "is_leaf": true, "children": [] }
]
}
]/objectsКаталог объектов сразу по всем застройщикам (архивные и тестовые исключены)
Параметры:
- developer_id — ограничить одним застройщиком
- search, status, object_type, object_type_slug, registration_open, building, city
- building_id — фильтр по дому как сущности (в отличие от building — корпуса из данных застройщика)
- complex_id — фильтр по жилому комплексу; независим от building_id, потому что complex_id заполнен и у объектов без дома
- min_price, max_price, min_area, max_area, rooms
- finishing — отделка: none (без отделки), pre_finish (предчистовая), finished (с отделкой); объекты, у которых источник отделку не сообщил, не попадают ни в один срез
- completion — пресет по сроку сдачи: delivered (сдан), this_year (в этом году), next_year (в следующем), later (позже); объекты без срока сдачи не попадают ни в один пресет
- sort (price_asc | price_desc | area_asc | area_desc | updated_at_desc)
- дома и жилые комплексы, отданные источником отдельной карточкой, из плоского списка исключены: у них нет ни комнат, ни площади, ни цены. По прямой ссылке /developers/{id}/objects/{ext_id} такая карточка по-прежнему открывается. В ленте group=building исключение действует не всегда — см. карточку ленты ниже
- group=building — свернуть квартиры одного дома в одну запись ленты (см. ниже)
- page, page_size (макс. 100)
- в ответе currency — валюта price, estimated_price и price_per_sqm; в общем каталоге она разная у разных застройщиков, но сортировка по цене (price_asc/price_desc) и фильтры min_price/max_price сравнивают не эту валюту, а BYN-эквивалент по последнему известному курсу НБРБ; для валюты без известного курса объект уходит в конец сортировки, а не сравнивается сырым числом
- в ответе finishing и construction_stage — отделка и стадия строительства объекта; construction_stage — ось, независимая от status: сданный дом продолжает продаваться, а null означает, что источник о стройке молчит, а не что она не идёт
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/objects?city=Минск&rooms=2&sort=price_asc"Ответ
{
"total": 1842,
"page": 1,
"page_size": 50,
"items": [{ "developer_id": "ghb", "external_id": "OBJ-101", ... }]
}/objects/recentОбъекты, впервые обнаруженные за последние N часов
Параметры:
- since_hours — окно в часах (1–168, по умолчанию 24)
- те же фильтры, что у /objects, кроме object_type_slug и complex_id — этих двух здесь нет. Группировки group=building здесь тоже нет: ответ всегда плоский список
- дома и комплексы, отданные источником отдельной карточкой, исключены всегда — оговорки про ленту, что у /objects, здесь не действуют
- sort — те же варианты, что у /objects, плюс first_seen_at_desc (значение по умолчанию)
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/objects/recent?since_hours=48"Ответ
{ "total": 17, "page": 1, "page_size": 50, "items": [...] }/objects/citiesСписок городов, по которым есть объекты — для фильтров
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/objects/cities"Ответ
["Барановичи", "Брест", "Витебск", "Минск"]/objects?group=buildingСмешанная лента: квартиры одного дома приходят одной записью, одиночные объекты — отдельными
Параметры:
- те же фильтры, что у /objects
- total считает записи ленты, а не квартиры
- дом сворачивается, если под фильтр подходит 2+ его объекта — либо если у дома есть собственная карточка-контейнер: тогда он приходит домом даже в одиночку. Всё остальное приходит обычным объектом
- в отличие от плоского списка, карточка дома, отданная источником целиком, в ленту попадает — но только если это дом (не жилой комплекс), он привязан к сущности дома и у него есть цена за м². Как только запрошен любой из rooms, min_price, max_price, min_area, max_area, такие карточки снова исключаются: комнатность, цена квартиры и площадь к ним неприменимы
- registration_open=true — исключение из предыдущего правила: дом с открытой регистрацией приходит и в ленту, и в плоский список, причём без требований к цене и привязке. У застройщика, публикующего дом целиком, регистрация открывается именно на дом (#1957)
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/objects?group=building&city=Минск"Ответ
{
"total": 42,
"page": 1,
"page_size": 50,
"items": [
{
"kind": "building",
"building": {
"id": "...", "developer_id": "ghb", "name": "ул. Ленина, 5",
"matched_count": 12, "total_count": 40,
"min_price": "100000.00", "max_price": "250000.00",
"rooms_available": [1, 2, 3], "type_counts": { "apartment": 10, "commercial": 2 },
"preview": [...]
},
"object": null
},
{ "kind": "object", "building": null, "object": { ... } }
]
}/complexesЖилые комплексы со сводкой: сколько домов и объектов в продаже, диапазоны цен и площадей, доступная комнатность
Параметры:
- developer_id — слаг застройщика
- city
- page, page_size (макс. 100)
- сортировка одна — по названию комплекса, параметра нет
- в выдачу попадает только ЖК, у которого есть хотя бы одно: видимая единица продажи, видимый дом (см. /buildings) либо живая карточка-контейнер — своя или дома комплекса. Цены карточка не требует: застройщик, публикующий комплекс целиком, мог её не опубликовать вовсе. ЖК вовсе без содержимого из выдачи исключён; снятые с продажи и аренда не в счёт
- в ответе buildings_count и objects_count считают только видимое: снятые с продажи и карточки-контейнеры не в счёт
- в ответе currency — валюта min_price и max_price; комплекс принадлежит одному застройщику, берётся его валюта, переопределения отдельных объектов в агрегаты не попадают
- в ответе container_prices — цена за м² с карточек-контейнеров: как самого комплекса, так и его домов. Список непустой только у комплекса, где нет ни одной видимой единицы продажи. Строки те же, что у /buildings: object_type, min_price_per_sqm, max_price_per_sqm, vat_included, currency, min_area, max_area
- в ответе container_content — содержание карточки самого комплекса: description, site_url, flats_total, area_range, ceiling_height, flats_available, sold_out и construction_stage. В отличие от container_prices карточки домов сюда не сводятся — эти поля описывают предмет карточки, а не набор: описание одного дома не описывает комплекс, и распроданный дом не делает распроданным весь ЖК. Заполняется по тому же правилу, что и цена, — только комплексу без видимых единиц продажи. Набор полей у застройщиков разный, поэтому пустое поле — норма, а не пробел в данных; null у всего объекта означает, что своей карточки нет
- в ответе container_object_id — uuid карточки-контейнера комплекса, на которую ссылается его лента изменений (GET /objects/{object_id}/history). Карточки его домов сюда не попадают — каждая описывает свой дом, а не комплекс целиком. В отличие от container_prices и container_content заполняется у любого комплекса с такой карточкой, а не только у комплекса без единиц продажи
- в ответе container_object_url — страница той же карточки у застройщика. Это не container_content.site_url: тот приходит из таблицы источника и ведёт на отдельный сайт комплекса
- в ответе container_external_id — external_id той же карточки; вместе со слагом застройщика это ключ, по которому комплекс, опубликованный целиком, кладут в «Мои объекты». Карточки домов сюда не попадают, и заполняется поле по правилу container_prices — только у комплекса без видимых единиц продажи
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/complexes?developer_id=ghb&city=Минск"Ответ
{ "total": 7, "page": 1, "page_size": 20, "items": [{ "id": "...", "developer_id": "ghb", "name": "Минск Мир", "city": "Минск", "district": "Октябрьский", "buildings_count": 7, "objects_count": 214, "min_price": "95000.00", "max_price": "410000.00", "currency": "BYN", "min_area": "28.40", "max_area": "121.00", "rooms_available": [1, 2, 3, 4], "image_url": null, "thumb_url": null, "container_prices": [], "container_content": { ... }, "container_object_id": "...", "container_object_url": "https://developer.example/projects/grand", "container_external_id": "zhk-grand" }] }/complexes/{complex_id}Сводка одного комплекса вместе со списком его домов
Параметры:
- complex_id — UUID комплекса; 404, если его нет или застройщик скрыт
- в ответе buildings — те же поля, что у /buildings; пустой список законен: часть источников публикует комплекс целиком, без разбивки на дома
- список buildings и счётчик buildings_count подчиняются тому же правилу видимости, что и /buildings: дом без единиц продажи и без цены за метр на своей карточке-контейнере в список домов ЖК не попадает
- в ответе container_prices — как у /complexes: цена за м² с карточек-контейнеров, только когда видимых единиц продажи нет вовсе
- в ответе container_object_id, container_object_url и container_external_id — то же, что у /complexes
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/complexes/UUID"Ответ
{ "id": "...", "developer_id": "ghb", "name": "Минск Мир", "buildings_count": 7, "objects_count": 214, "rooms_available": [1, 2, 3], "container_object_id": "...", "container_object_url": "https://developer.example/projects/grand", "container_external_id": "zhk-grand", "buildings": [{ "id": "...", "name": "ул. Ленина, 5", "complex_id": "...", "complex_name": "Минск Мир", ... }] }/buildingsДома со сводкой: диапазоны цен и площадей, доступная комнатность, счётчики
Параметры:
- developer_id — слаг застройщика
- ids — список id домов через запятую, до 100 за запрос; пагинация при этом не применяется
- city
- kind — residential | parking | mixed. Без него паркинги в выдачу не попадают: выдача новостроек по умолчанию жилая. Спросите kind=parking явно — получите их. На выборку по ids это ограничение не распространяется: там возвращаются ровно запрошенные дома, паркинги в том числе
- page, page_size (макс. 100)
- в ответе kind — то же значение residential | parking | mixed; mixed у дома, где есть и квартиры, и паркинг
- в ответе completion_date — срок сдачи дома (может быть null)
- в ответе image_url — изображение дома, thumb_url — его уменьшенная копия для списков (может быть null)
- в ответе currency — валюта min_price и max_price; берётся у застройщика дома, переопределения отдельных объектов в агрегаты не попадают
- в ответе type_counts — состав дома под текущим фильтром: тип объекта → сколько его; счётчик в карточке дома подписывается по нему, а не словом «квартир»
- в ответе street, house, quarter — компоненты адреса, разобранные парсером, рядом с полной строкой address. address приходит от источника как есть и может не нести номер дома — house заполняется отдельно, оба поля нужны, чтобы его показать
- в ответе complex_id и complex_name — жилой комплекс дома (см. /complexes); имя стоит рядом с id намеренно, чтобы карточка дома ставила ссылку на комплекс без второго запроса. Оба поля null, если источник не разбивает застройку на комплексы
- total_count и matched_count считают только то, что видит покупатель: снятые с продажи и карточки-контейнеры (сам дом, сам комплекс) в счёт не идут
- в ответе container_prices — цена за м² с собственной карточки дома. Список непустой только у дома, где видимых единиц продажи нет вовсе: есть настоящие квартиры — цену берут по ним, и второе, расходящееся число рядом не показывается. Строка на каждую пару «тип объекта + база НДС»: object_type, min_price_per_sqm, max_price_per_sqm, vat_included (null — источник базу не указал), currency, min_area и max_area той же группы
- в ответе container_external_id — external_id той же карточки-контейнера дома; вместе со слагом застройщика — ключ, по которому дом, опубликованный целиком, можно положить в «Мои объекты». Заполняется по тому же правилу, что и container_prices — только у дома без видимых единиц продажи
- в ответе container_object_id — uuid карточки-контейнера дома, на которую ссылается его лента изменений (GET /objects/{object_id}/history). В отличие от container_external_id и container_prices заполняется у любого дома с такой карточкой, а не только у пустого: события карточки существуют независимо от того, видны ли под домом квартиры
- в ответе container_object_url — страница той же карточки у застройщика: у дома, который публикуется целиком, карточки квартиры не существует, и другого выхода на источник у него нет. Заполняется по тому же правилу, что и container_object_id
- в ответе registration_open, registration_url и registration_start_date — запись с карточки-контейнера дома. В отличие от container_prices считается у любого дома, а не только у пустого: у застройщика, публикующего дом целиком, запись открывается на дом, а не на квартиру (#1957). До открытия флаг ещё false и ссылки нет — остаётся только дата старта (#2001)
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/buildings?developer_id=ghb"Ответ
[{ "id": "...", "name": "ул. Ленина, 5", "kind": "residential", "completion_date": "2027-06-30", "address": "ул. Ленина, 5", "street": "Ленина", "house": "5", "total_count": 40, "min_price": "100000.00", "currency": "BYN", "type_counts": { "apartment": 38, "parking": 2 }, "container_prices": [], "container_external_id": null, "container_object_id": "...", "container_object_url": "https://developer.example/houses/18", ... }]/buildings/{building_id}Сводка одного дома
Параметры:
- в ответе street, house, quarter — компоненты адреса из парсера рядом с полной строкой address (см. /buildings)
- в ответе complex_id и complex_name — жилой комплекс дома, см. /buildings
- в ответе kind, completion_date и container_prices — те же поля, что у /buildings
- в ответе container_external_id, container_object_id и container_object_url — те же поля, что у /buildings
- в ответе registration_open, registration_url и registration_start_date — те же поля, что у /buildings
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/buildings/UUID"Ответ
{ "id": "...", "developer_id": "ghb", "name": "ул. Ленина, 5", "kind": "residential", "completion_date": "2027-06-30", "address": "ул. Ленина, 5", "street": "Ленина", "house": "5", "rooms_available": [1, 2], "type_counts": { "apartment": 12 }, "container_prices": [], "container_external_id": null, "container_object_id": "...", "container_object_url": "https://developer.example/houses/18", "preview": [...], ... }/buildings/{building_id}/objectsОбъекты дома — те же фильтры и сортировки, что у каталога
Параметры:
- search, status, object_type, object_type_slug, registration_open, city
- min_price, max_price, min_area, max_area, rooms
- sort (по умолчанию price_asc)
- page, page_size (макс. 100)
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/buildings/UUID/objects?rooms=2"Ответ
{ "total": 12, "page": 1, "page_size": 50, "items": [...] }/buildings/{building_id}/price-tableЦеновые таблицы карточек-контейнеров дома — то, из чего сведены container_prices в сводке. Отдельной ручкой, а не полем сводки: у одного дома это до 89 строк, разбор идёт на лету при каждом запросе
Параметры:
- ответ — массив таблиц; пустой список, если у дома нет карточек-контейнеров с ценовыми таблицами
- object_type — тип объекта карточки-источника
- kind — list (поимённый список квартир: номер, этаж, площадь, цена за метр) или matrix (цены по этажам и группам площадей; площади строки здесь нет — она спрятана в названии группы)
- headers — заголовки колонок как у источника; notes — подписи под таблицей, которые колонками не являются (условия оплаты и подобное)
- vat_included (null — источник базу не указал) и currency — общие для всей таблицы, currency берётся у застройщика дома. Показываются только рублёвые (BYN) таблицы источника: где те же квартиры продублированы в долларах, долларовая копия отбрасывается — иначе одна квартира считалась бы дважды
- rows[].cells — ячейки строки как есть (форм таблиц у источника одиннадцать, сводить их к общему набору полей значило бы часть данных выбросить); rows[].area, price_per_sqm, total_price — разобранные из cells числа, могут быть null, если соответствующей колонки не было
- rows[].total_is_estimate — true, если total_price не пришёл из своей колонки источника, а посчитан нами как area × price_per_sqm
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/buildings/UUID/price-table"Ответ
[
{
"object_type": "apartment",
"kind": "list",
"headers": ["№ квартиры", "Этаж", "Площадь, м²", "Цена, бел.руб. с НДС"],
"rows": [
{
"cells": ["12", "3", "54.20", "162 600"],
"area": "54.20",
"price_per_sqm": "3000.00",
"total_price": "162600.00",
"total_is_estimate": false
}
],
"notes": ["Цена указана с учётом рассрочки"],
"vat_included": true,
"currency": "BYN"
}
]/buildings/{building_id}/galleryПланировки, схемы и фотографии из карточек-контейнеров дома, в порядке источника
Параметры:
- ответ — массив { section, image_url, thumb_url }
- section — slug раздела источника: layouts, site_plan, photos, construction_progress или other; подписи разделов остаются на стороне клиента
- снятая с продажи карточка-контейнер своих картинок не отдаёт — тем же правилом, что и container_prices
- пока картинка не перекачана в наше хранилище, image_url и thumb_url ведут на сайт источника; после перекачки — на наш бакет
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/buildings/UUID/gallery"Ответ
[
{ "section": "layouts", "image_url": "https://...", "thumb_url": "https://..." },
{ "section": "site_plan", "image_url": "https://...", "thumb_url": null }
]/buildings/{building_id}/historyЛента «что менялось» по дому: те же события, что у ленты объекта, но по всем объектам дома и свёрнутые по циклу прогона — одна строка вместо двенадцати одинаковых
Параметры:
- limit (1–200, по умолчанию 50) — работает как «не меньше»: страница дотягивается до границы цикла прогона, поэтому строк может прийти больше
- before — вернуть события до этой даты (ISO datetime); для следующей страницы брать next_before из ответа
- kind строки — objects_added, objects_removed, registration_opened, registration_closed, price_up, price_down или field_changed; field заполнен только у field_changed и изменений цены
- scope — building (карточка дома/ЖК, отданная источником отдельно) или units (обычные объекты продажи)
- objects_total — сколько объектов в строке всего, objects — первые десять из них
- values — заполнено только для полей из белого списка (completion_date, price, price_per_sqm), у остальных null и строка остаётся голым счётчиком. form — pair (одно old/new на всю группу) или range (группа разошлась — range_min/range_max, разброс сдвига, а не сырые значения); currency — только у price и price_per_sqm
- truncated: true — выборка упёрлась в потолок в 5000 событий, лента считана по срезу
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/buildings/UUID/history?limit=50"Ответ
{
"building_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"entries": [
{
"occurred_at": "2026-05-02T09:00:00Z",
"kind": "price_up",
"field": "price",
"scope": "units",
"objects_total": 12,
"objects": [{ "external_id": "OBJ-101", "title": "1-комн., 42.5 м²" }],
"values": { "form": "range", "old": null, "new": null, "range_min": "500.00", "range_max": "1200.00", "currency": "BYN" }
},
{
"occurred_at": "2026-04-18T07:30:00Z",
"kind": "registration_opened",
"field": null,
"scope": "building",
"objects_total": 3,
"objects": [...],
"values": null
}
],
"next_before": "2026-04-18T07:30:00Z",
"truncated": false
}/objects/{developer_id}/{external_id}/share-urlsСсылки «поделиться» карточкой объекта — по одной на трекаемый редирект /r/{token} на каждый запрошенный канал
Параметры:
- тело запроса — { channels } (необязательно): список из tg_miniapp, tg_link, tg_forward, wa, vk, ok, copy_link, copy_text, native, qr; неизвестное значение — 422. Без поля создаются ссылки на все каналы
- ответ — объект { channel: url, ..., text }: ключ — имя канала, значение — https://stroi.homes/r/{token}. Канал пропадает из ответа только если создать редирект не вышло (сбой инфраструктуры), а не потому что его не запросили
- text — готовая подпись: адрес, тип/комнатность/площадь, цена, застройщик и ссылка на основной канал (tg_miniapp, иначе copy_link, иначе первый доступный)
- developer_id — слаг застройщика, external_id — идентификатор объекта у источника; 404, если застройщик скрыт или объект не найден
- токена не требует
Запрос
curl -X POST -H "Content-Type: application/json" \
-d '{"channels":["tg_miniapp","copy_link"]}' \
"/api/v1/objects/ghb/OBJ-101/share-urls"Ответ
{
"tg_miniapp": "https://stroi.homes/r/AbC12XyZ",
"copy_link": "https://stroi.homes/r/Qw9Zt5Lm",
"text": "📍 ул. Ленина, 5\n🏠 Квартира · 2-комн. · 54.2 м²\n💰 85 000 BYN\n🏗 ГХБ\n\nhttps://stroi.homes/r/AbC12XyZ"
}/buildings/{building_id}/share-urlsСсылки «поделиться» домом — для застройщика, публикующего дом целиком, у которого нет отдельной карточки квартиры (#2157)
Параметры:
- тело запроса — { channels }, тот же формат и та же валидация, что у /objects/{developer_id}/{external_id}/share-urls
- building_id — UUID дома; мусор в пути и отсутствующий дом — 404
- text — адрес дома (город, если адреса нет), год сдачи (если есть), застройщик и ссылка; строка про цену показывается только если у дома она вообще есть — «По запросу» вместо неё не подставляется
- токена не требует
Запрос
curl -X POST -H "Content-Type: application/json" \
-d '{"channels":["tg_miniapp","copy_link"]}' \
"/api/v1/buildings/UUID/share-urls"Ответ
{
"tg_miniapp": "https://stroi.homes/r/Nb3F8kPq",
"copy_link": "https://stroi.homes/r/Ht7Ls2Wc",
"text": "🏠 ул. Ленина, 5\n📍 ул. Ленина, 5\n🗓 Сдача в 2027\n🏗 ГХБ\n\nhttps://stroi.homes/r/Nb3F8kPq"
}/complexes/{complex_id}/share-urlsТо же для жилого комплекса, который источник публикует целиком (#2157)
Параметры:
- тело запроса — { channels }, тот же формат, что у /objects/{developer_id}/{external_id}/share-urls
- complex_id — UUID комплекса; мусор в пути и отсутствующий комплекс — 404
- text — то же, что у дома, но иконка 🏢 и адрес — город комплекса
- токена не требует
Запрос
curl -X POST -H "Content-Type: application/json" \
-d '{}' \
"/api/v1/complexes/UUID/share-urls"Ответ
{
"tg_miniapp": "https://stroi.homes/r/Zx4Rp9Nc",
"tg_link": "https://stroi.homes/r/Yv2Km6Ts",
"copy_link": "https://stroi.homes/r/Ju8Dw1Fa",
"text": "🏢 Минск Мир\n📍 Минск\n🏗 ГХБ\n\nhttps://stroi.homes/r/Zx4Rp9Nc"
}/services/{service_id}/share-urlsСсылки «поделиться» карточкой услуги
Параметры:
- тело запроса — { channels }, тот же формат, что у /objects/{developer_id}/{external_id}/share-urls
- service_id — UUID услуги; 404 у снятой с публикации, объединённой с другой карточкой или несуществующей
- text — имя, первая категория (как есть, без перевода в человекочитаемую подпись), рейтинг и число отзывов, город (если есть) и ссылка
- токена не требует
Запрос
curl -X POST -H "Content-Type: application/json" \
-d '{"channels":["tg_miniapp","wa"]}' \
"/api/v1/services/abc123/share-urls"Ответ
{
"tg_miniapp": "https://stroi.homes/r/Pl5Bx3Ge",
"wa": "https://stroi.homes/r/Sr7Cn0Qi",
"text": "👤 Плиточник Иван\n🏷 remont-pod-klyuch\n⭐ 4.7 18 отзывов\n📍 Минск\n\nhttps://stroi.homes/r/Pl5Bx3Ge"
}/objects/{developer_id}/{external_id}/share-statsШеринги и переходы по объекту за период, разрезом по каналам
Параметры:
- period — период в днях, 7–365, по умолчанию 30
- ответ — { total_shares, total_opens, by_channel: [{ channel, shares, opens }], period_days }
- opens считается по клик-таблице с ретеншеном 7 дней: при period больше недели shares придут за весь запрошенный период, а opens — только за последние 7 дней
- developer_id — слаг застройщика; 404, если он скрыт. Существование объекта с данным external_id не проверяется — для неизвестного external_id ответ будет нулевым, а не 404
- токена не требует
Запрос
curl "/api/v1/objects/ghb/OBJ-101/share-stats?period=30"Ответ
{
"total_shares": 12,
"total_opens": 34,
"by_channel": [
{ "channel": "tg_miniapp", "shares": 8, "opens": 25 },
{ "channel": "copy_link", "shares": 4, "opens": 9 }
],
"period_days": 30
}/services/{service_id}/share-statsШеринги и переходы по услуге за период, разрезом по каналам
Параметры:
- period — период в днях, 7–365, по умолчанию 30
- ответ — та же форма, что у /objects/{developer_id}/{external_id}/share-stats, с той же семидневной оговоркой про opens
- service_id — существование не проверяется: для мусора в пути и несуществующей услуги ответ тоже нулевой, а не 404
- токена не требует
Запрос
curl "/api/v1/services/abc123/share-stats?period=30"Ответ
{
"total_shares": 5,
"total_opens": 11,
"by_channel": [
{ "channel": "wa", "shares": 3, "opens": 7 },
{ "channel": "tg_miniapp", "shares": 2, "opens": 4 }
],
"period_days": 30
}/objects/suggestionsПодсказки для поисковой строки: названия, адреса и корпуса. Есть нечёткий поиск и раскрытие сокращений (ул., кв.)
Параметры:
- q — строка запроса (обязательна)
- developer_id — ограничить одним застройщиком
- limit (1–20, по умолчанию 8)
- карточки домов и комплексов в подсказки не попадают — как и в каталог
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/objects/suggestions?q=маяк"Ответ
{ "items": [{ "text": "Минск Мир, дом Маяк", "kind": "title" }] }/objects/batchПакетный запрос объектов по парам developer_id + external_id (до 100 за раз). Токен обязателен
Параметры:
- Тело: { "items": [{ "developer_id": "ghb", "external_id": "OBJ-101" }] }
Запрос
curl -X POST -H "X-API-Key: $API_TOKEN" -H "Content-Type: application/json" \
-d '{"items":[{"developer_id":"ghb","external_id":"OBJ-101"}]}' \
"/api/v1/objects/batch"Ответ
{
"items": [{ "developer_id": "ghb", "external_id": "OBJ-101", ... }],
"missing": [{ "developer_id": "ghb", "external_id": "OBJ-999" }]
}/developers/{id}/reviewsОтзывы о застройщике со средней оценкой. Запись через это API недоступна: отзыв оставляет только владелец объекта застройщика, из приложения stroi.homes, под своим аккаунтом — токена для этого недостаточно
Параметры:
- page, page_size (макс. 100)
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/developers/ghb/reviews"Ответ
{
"total": 24,
"rating_avg": 4.2,
"items": [{ "id": "...", "rating": 5, "text": "...", "created_at": "..." }]
}/developers/{id}/statsПубличная статистика интереса к застройщику за период. Значения ниже порога (10) не показываются — приходит null
Параметры:
- period — период в днях (7–365, по умолчанию 30)
Запрос
curl "/api/v1/developers/ghb/stats?period=30"Ответ
{
"developer_slug": "ghb",
"period_days": 30,
"page_views": 1420,
"object_views_top": [{ "object_external_id": "OBJ-101", "title": "...", "views": 340 }],
"clicks_to_developer": 58,
"events_count": 12,
"subscribers_approx": 3200
}/developers/{id}/reputationФакты мониторинга по застройщику: сдвиги заявленных сроков сдачи, переоценки, дома с наступившим сроком сдачи и свежесть данных. Источник — собственная история событий, не отзывы; оценок здесь нет, только наблюдённые факты с датами, формулировки собирает клиент
Параметры:
- has_data — false означает, что блок показывать не стоит: по застройщику ничего не наблюдалось
- observed_since, last_change_at — момент первого и последнего наблюдённого изменения отслеживаемых полей (срок сдачи, цена)
- change_batches_90d — сколько раз за 90 дней мониторинг фиксировал изменения; единица счёта — цикл прогона, а не событие: один прогон меняет сразу много объектов
- completion_shifts_total и completion_shifts — сдвиги срока сдачи; в каждом occurred_at, old_date, new_date, shift_days (положительное — срок отодвинулся, отрицательное — приблизился), objects_total и objects (до 10 объектов группы: external_id, title)
- price_increases_total, price_decreases_total и price_moves — переоценки; в каждом occurred_at, direction (up | down), objects_total и objects — та же форма, до 10 штук
- completion_reached_total и completion_reached_buildings — дома, чей заявленный срок сдачи уже наступил (статуса «сдан» в данных нет, утверждается только наступление срока); элемент — { name, completion_date }
- truncated — true, если выборка событий упёрлась в потолок (20 000) и счётчики посчитаны по срезу
- поля completion_date и price, скрытые в /field-configs (public_value_visibility=hidden), из фактов исключаются; если скрыты оба — 404
Запрос
curl "/api/v1/developers/ghb/reputation"Ответ
{
"has_data": true,
"observed_since": "2026-01-10T08:00:00Z",
"last_change_at": "2026-08-30T09:00:00Z",
"change_batches_90d": 14,
"completion_shifts_total": 2,
"completion_shifts": [
{
"occurred_at": "2026-07-01T09:00:00Z",
"old_date": "2027-03-31",
"new_date": "2027-06-30",
"shift_days": 91,
"objects_total": 40,
"objects": [{ "external_id": "OBJ-101", "title": "..." }]
}
],
"price_increases_total": 6,
"price_decreases_total": 1,
"price_moves": [
{
"occurred_at": "2026-08-30T09:00:00Z",
"direction": "up",
"objects_total": 5,
"objects": [{ "external_id": "OBJ-205", "title": "..." }]
}
],
"completion_reached_total": 1,
"completion_reached_buildings": [{ "name": "ул. Ленина, 5", "completion_date": "2026-06-30" }],
"truncated": false
}/developers/{id}/objects/{external_id}/statsПросмотры конкретного объекта за период. null, если просмотров меньше порога (10)
Параметры:
- period — период в днях (7–365, по умолчанию 30)
Запрос
curl "/api/v1/developers/ghb/objects/OBJ-101/stats?period=30"Ответ
{ "object_external_id": "OBJ-101", "period_days": 30, "views": 340 }/reviews/{entity_type}/{entity_id}Лента отзывов о материале, доме/объекте или застройщике вместе со средней оценкой; entity_type — material | object | developer, entity_id — внутренний UUID сущности, а у застройщика ещё и его слаг (наружу застройщик отдаётся только слагом). Это же верно для всех ручек ниже с entity_id в адресе. У объекта своих звёзд нет — оценка идёт по четырём аспектам (yard, noise, deadlines, finishing), и rating_avg тогда считается как средняя по средним аспектов, а не отдельная звёздная шкала. can_review / can_review_reason показывают, вправе ли текущий пользователь оставить отзыв и если нет — почему: anonymous (нет PWA Bearer), no_estimate, not_in_projects, too_recent, not_owner, unknown_entity, already_reviewed. Запись отзыва работает не как в остальном API: пишет только аккаунт приложения stroi.homes и лишь при выполненном предметном условии — см. ручки записи ниже, они, как и /developers/{id}/reviews выше, обычным API-токеном не открываются
Параметры:
- page (по умолчанию 1), page_size (1–100, по умолчанию 20)
- sort — date | helpful; без параметра выбирается автоматически по числу отзывов сущности: helpful при более чем 10, иначе date — порог не зависит от текущих фильтров
- with_photos — только отзывы с показанным (прошедшим модерацию) фото, по умолчанию false
- critical — только с оценкой ниже 4 (у объекта — по средней аспектов отзыва), по умолчанию false
Запрос
curl "/api/v1/reviews/object/3fa85f64-5717-4562-b3fc-2c963f66afa6?sort=helpful"Ответ
{
"total": 32,
"filtered_total": 32,
"rating_avg": 4.25,
"aspects": { "yard": 4.5, "noise": 3.5, "deadlines": 4.5, "finishing": 4.5 },
"can_review": false,
"can_review_reason": "already_reviewed",
"sort": "helpful",
"items": [
{
"id": "5c2e2b0a-...",
"rating": null,
"text": "Двор ухоженный, но слышно дорогу",
"aspects": { "yard": 5, "noise": 2, "deadlines": 5, "finishing": 4 },
"created_at": "2026-08-01T10:00:00Z",
"can_delete": false,
"reply_text": null,
"reply_at": null,
"reply_role": null,
"photos": [{ "id": "a1b2c3d4-...", "url": "https://...", "thumb_url": "https://...", "status": "approved" }],
"helpful_count": 6,
"not_helpful_count": 1,
"my_vote": true
}
]
}/reviews/{entity_type}/{entity_id}Оставить отзыв. Запись через это API недоступна для сторонних интеграций: нужен аккаунт приложения (PWA Bearer, не X-API-Key из /developer-tokens) и выполненное предметное условие — материал был посчитан в смете пользователя, дом дольше месяца в «Моих объектах», у застройщика есть завершённая сделка (interest_status на пост-покупательной стадии). Одна сущность — один живой отзыв от аккаунта, отдельной квоты в минуту нет. Тело — либо rating (1–5) у материала и застройщика, либо aspects у объекта (обязательны все четыре: yard, noise, deadlines, finishing, каждое 1–5); смешать нельзя — 422. text необязателен, проходит автомодерацию
Параметры:
- 422 "Entity is rated by aspects, not by stars" — прислан rating у объекта; 422 "Aspects required: …" — не хватает аспекта; 422 "Entity has no aspect ratings" / "Rating is required" — у материала и застройщика
- 403 с причиной из can_review_reason выше, если право не выполнено; 409 already_reviewed, если отзыв уже есть; 404 — сущности нет
Запрос
curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Bearer $PWA_TOKEN" \
-d '{"aspects":{"yard":5,"noise":4,"deadlines":5,"finishing":4},"text":"Двор ухоженный"}' \
"/api/v1/reviews/object/3fa85f64-5717-4562-b3fc-2c963f66afa6"Ответ
{
"id": "5c2e2b0a-...",
"rating": null,
"text": "Двор ухоженный",
"aspects": { "yard": 5, "noise": 4, "deadlines": 5, "finishing": 4 },
"created_at": "2026-09-05T10:00:00Z",
"can_delete": true,
"reply_text": null,
"reply_at": null,
"reply_role": null,
"photos": [],
"helpful_count": 0,
"not_helpful_count": 0,
"my_vote": null
}/reviews/{entity_type}/{entity_id}/{review_id}Удалить свой отзыв (мягко). Только автор — 403 «Not the author» иначе. Требует тот же PWA Bearer, что и при создании; API-токен не подходит. 204 без тела, 404 — отзыв не найден или относится к другой сущности
Запрос
curl -X DELETE -H "Authorization: Bearer $PWA_TOKEN" \
"/api/v1/reviews/object/3fa85f64-5717-4562-b3fc-2c963f66afa6/5c2e2b0a-..."Ответ
204 No Content/reviews/{entity_type}/{entity_id}/{review_id}/replyОтвет представителя застройщика на отзыв о доме/объекте или о самом застройщике. У отзыва о материале второй стороны нет — ответить нельзя ни при каких правах. Отвечать может только участник с активным членством (DeveloperMembership) у соответствующего застройщика — 403 «Not a representative» без него. Пустой text снимает ответ. Требует PWA Bearer представителя, недоступно по API-токену
Параметры:
- entity_type — object | developer (у material ответа не бывает)
- Тело: { "text": "…" | null }
Запрос
curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Bearer $PWA_TOKEN" \
-d '{"text":"Спасибо за отзыв, передадим в УК"}' \
"/api/v1/reviews/object/3fa85f64-5717-4562-b3fc-2c963f66afa6/5c2e2b0a-.../reply"Ответ
{
"id": "5c2e2b0a-...",
"rating": null,
"text": "Двор ухоженный",
"aspects": { "yard": 5, "noise": 4, "deadlines": 5, "finishing": 4 },
"created_at": "2026-09-01T10:00:00Z",
"can_delete": false,
"reply_text": "Спасибо за отзыв, передадим в УК",
"reply_at": "2026-09-05T12:00:00Z",
"reply_role": "developer",
"photos": [],
"helpful_count": 6,
"not_helpful_count": 1,
"my_vote": null
}/reviews/{entity_type}/{entity_id}/{review_id}/photosПриложить фото к своему отзыву — до 5 неотклонённых на отзыв, сверх лимита 422 «Больше 5 фотографий к отзыву не приложить». Показано будет только после модерации (status: pending → approved/rejected); до решения фото видно лишь автору. Заливка идёт тем же конвейером, что у карточки услуги — файл жмётся в WebP. Доступно только автору отзыва (403 «Not the author»), только с PWA Bearer
Параметры:
- multipart/form-data, поле file — изображение
Запрос
curl -X POST -H "Authorization: Bearer $PWA_TOKEN" \
-F "file=@yard.jpg" \
"/api/v1/reviews/object/3fa85f64-5717-4562-b3fc-2c963f66afa6/5c2e2b0a-.../photos"Ответ
{
"id": "a1b2c3d4-...",
"url": "https://...",
"thumb_url": "https://...",
"status": "pending"
}/reviews/{entity_type}/{entity_id}/{review_id}/photos/{photo_id}Снять своё фото у отзыва. Только автор отзыва, 404 — фото не найдено или относится к другому отзыву. PWA Bearer обязателен
Запрос
curl -X DELETE -H "Authorization: Bearer $PWA_TOKEN" \
"/api/v1/reviews/object/3fa85f64-5717-4562-b3fc-2c963f66afa6/5c2e2b0a-.../photos/a1b2c3d4-..."Ответ
204 No Content/reviews/{entity_type}/{entity_id}/{review_id}/vote«Помогло» / «не помогло» — голос смотрящего, не автора: 403 «Cannot vote for own review» на свой же отзыв. helpful: true | false | null, null снимает голос. PUT, а не POST/PATCH: состояние задаётся целиком, повторная отправка того же тела после обрыва сети ничего не ломает. PWA Bearer обязателен
Параметры:
- Тело: { "helpful": true | false | null }
Запрос
curl -X PUT -H "Content-Type: application/json" \
-H "Authorization: Bearer $PWA_TOKEN" \
-d '{"helpful":true}' \
"/api/v1/reviews/object/3fa85f64-5717-4562-b3fc-2c963f66afa6/5c2e2b0a-.../vote"Ответ
{ "helpful_count": 7, "not_helpful_count": 1, "my_vote": true }/reviews/{entity_type}/{entity_id}/{review_id}/reportПожаловаться на отзыв — вход в очередь модератора, сам отзыв не меняется. Причины: spam, offensive, fake, personal_data, other. Отдельной квоты нет: повторная жалоба того же аккаунта на тот же отзыв не плодит строк, а аккаунт здесь обязателен — PWA Bearer, не API-токен
Параметры:
- Тело: { "reason": "spam" | "offensive" | "fake" | "personal_data" | "other", "comment"?: "…" }
Запрос
curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Bearer $PWA_TOKEN" \
-d '{"reason":"fake"}' \
"/api/v1/reviews/object/3fa85f64-5717-4562-b3fc-2c963f66afa6/5c2e2b0a-.../report"Ответ
{ "status": "pending" }/servicesКаталог специалистов и подрядчиков. Сортировка по умолчанию — relevance
Параметры:
- q — поиск по названию, описанию и тегам
- category, tag, country, city — фильтры справочников; карточки «по всей стране» попадают в выдачу любого города
- district — район исполнителя (до 100 символов)
- business — слаг бизнеса, витрина одной компании
- verified, has_photo, has_reviews — булевы флаги, по умолчанию false
- price_from, price_to (≥ 0), rating_min (1–5)
- object_type, provider_type, feature — коды из /services/facets
- sort (relevance | rating | reviews | price_asc | price_desc | new)
- page, page_size (1–100, по умолчанию 20)
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/services?category=remont-pod-klyuch&city=Минск&sort=rating"Ответ
{
"total": 214,
"page": 1,
"page_size": 20,
"items": [{ "id": "abc123", "name": "…", "categories": ["…"], "rating_avg": 4.7, ... }]
}/servicesСоздать карточку специалиста или подрядчика — публично, токен не обязателен. name проходит автомодерацию (как и description, price_notes, tags). Владельцем карточки становится тот, кто её создал: если запрос несёт X-TG-Init-Data или PWA Bearer, дальше только с той же идентичностью можно будет отредактировать или удалить карточку через PATCH/DELETE ниже. Без них у карточки не будет публичного владельца вообще — поправить её потом сможет только админ или кто угодно через /services/{service_id}/suggestions. В ответе can_edit всегда false, даже для создателя — его считает только следующий запрос с той же идентичностью. Суточная квота на запись без токена — как у остальных публичных форм: у анонимов минимальная, у авторизованных выше, при превышении 429 и мини-игра-проверка; с API-токеном вместо неё действует общий per-token rate limit
Параметры:
- Тело: name (обязательно); также, как у карточки: description, categories, custom_categories, tags, features, object_types, provider_type, attributes, locations, price_min, price_max, price_currency, price_unit, price_notes, contacts — все необязательны
- Юрблок в теле: legal_form (код: ooo | odo | oao | zao | ip | self_employed | individual), legal_name, tax_id (УНП), registered_year (1900 — текущий год) и legal_checked_at. Прислать любое из первых четырёх без legal_checked_at нельзя — 422 «Юрданные принимаются только с датой сверки (legal_checked_at)»
Запрос
curl -X POST -H "Content-Type: application/json" \
-H "X-TG-Init-Data: $INIT_DATA" \
-d '{"name":"Плиточник Иван","categories":["remont-pod-klyuch"]}' \
"/api/v1/services"Ответ
{
"id": "abc123",
"name": "Плиточник Иван",
"categories": ["remont-pod-klyuch"],
"can_edit": false,
"can_suggest": true,
"status": "draft",
...
}/services/{service_id}Карточка услуги целиком: категории, теги, характеристики, локации, цены, контакты, изображения, услуги исполнителя и агрегаты отзывов. can_suggest — можно ли предложить правку карточки, пока её не забрал бизнес (см. /services/{service_id}/suggestions); custom_categories — категории карточки вне публичной таксономии, ожидающие модерации или отклонённые (утверждённые отдаёт /services/categories)
Параметры:
- в ответе юрблок: legal_form, legal_name, tax_id (УНП; у застройщика то же поле зовётся unp), registered_year и legal_checked_at — дата сверки. legal_form приходит кодом из того же набора, что у застройщика: ooo, odo, oao, zao, ip, self_employed, individual. Записать юрданные без даты сверки нельзя (см. POST и PATCH ниже), на сайте блок без неё не показывается
- offerings — услуги исполнителя, каждая со своей ценой, категорией и адресом: id, title, slug (считается из title при создании и дальше не меняется), category_slug, description, price_min, price_max, price_currency, price_unit, price_notes, sort_order, status. В публичном ответе только опубликованные (status: published) — черновики сюда не попадают
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/services/abc123"Ответ
{
"id": "abc123",
"name": "Ремонт под ключ",
"description": "…",
"categories": ["remont-pod-klyuch", "warm-floor-install"],
"tags": ["ремонт", "отделка"],
"features": ["smeta"],
"object_types": ["flat"],
"provider_type": "brigada",
"attributes": { "material": ["gipsokarton"] },
"locations": [{ "country": "BY", "city": "Минск" }],
"price_min": "1200.00",
"price_max": "2500.00",
"price_currency": "BYN",
"price_unit": "sqm",
"price_notes": null,
"legal_form": "ip",
"legal_name": "Иванов И. И.",
"tax_id": "100000000",
"registered_year": 2019,
"legal_checked_at": "2026-08-20",
"contacts": {
"phones": ["+375291234567"],
"emails": [],
"websites": ["https://example.by"],
"websites_display": ["example.by"],
"social_links": []
},
"logo_url": null,
"images": [],
"offerings": [
{
"id": "…",
"title": "Укладка плитки",
"slug": "ukladka-plitki",
"category_slug": "remont-pod-klyuch",
"description": null,
"price_min": "25.00",
"price_max": "40.00",
"price_currency": "BYN",
"price_unit": "sqm",
"price_notes": null,
"sort_order": 0,
"status": "published"
}
],
"social_stats": [],
"rating_avg": 4.7,
"review_count": 18,
"positive_count": 16,
"can_suggest": true,
"custom_categories": [
{ "slug": "warm-floor-install", "label": "Тёплый пол", "parent_slug": null, "status": "pending" }
]
}/services/{service_id}Отредактировать карточку напрямую. Доступно только автору — тот же X-TG-Init-Data (Telegram) или тот же PWA Bearer, что были при создании, — либо сервисному API-токену админа (kind="service"); обычный публичный токен из POST /developer-tokens прав не даёт (403 «Only the author can modify this record»). 403 «Service is claimed by a business», если карточку уже забрал бизнес — правка тогда только в бизнес-портале. На практике доступно не всем: у карточки без зафиксированного при создании владельца (без X-TG-Init-Data/Bearer на POST /services) отредактировать её напрямую не может никто, кроме админа, — единственный публичный путь для таких карточек — POST /services/{service_id}/suggestions ниже. Сайт stroi.homes не отправляет ни X-TG-Init-Data, ни PWA Bearer, поэтому кнопки прямого редактирования на сайте нет — ручка для внешних интеграций и Mini App. Заполняются только присланные поля (частичное обновление), текст проходит автомодерацию, суточная квота на запись — как у POST /services выше
Параметры:
- Тело: те же поля, что у создания (см. POST /services выше) — все необязательны, меняются только присланные
- Юрблок правится теми же полями и с тем же условием: legal_form, legal_name, tax_id или registered_year в патче требуют legal_checked_at, иначе 422
Запрос
curl -X PATCH -H "Content-Type: application/json" \
-H "X-TG-Init-Data: $INIT_DATA" \
-d '{"price_min":1300}' \
"/api/v1/services/abc123"Ответ
{
"id": "abc123",
"price_min": "1300.00",
"can_edit": true,
...
}/services/{service_id}/suggestionsПредложить правку карточки — доступно кому угодно, авторизация не нужна, пока карточку не забрал бизнес (403, если забрал). Безопасная часть патча (справочники, известные категории/теги/города, цена в разумных пределах) применяется сразу; остальное уходит в очередь модерации. Суточная квота на запись — как у остальных публичных форм: у анонимов минимальная, у авторизованных выше, при превышении 429 и мини-игра-проверка. Отдельная суточная квота на автоприменения при исчерпании не отклоняет правку, а целиком переводит её в очередь
Параметры:
- Тело: те же поля, что у карточки — name, description, categories, custom_categories, tags, features, object_types, provider_type, attributes, locations, price_min, price_max, price_currency, price_unit, price_notes, contacts и юрблок (legal_form, legal_name, tax_id, registered_year, legal_checked_at) — все необязательны, плюс comment (записка модератору). Условие даты сверки то же, что у PATCH
Запрос
curl -X POST -H "Content-Type: application/json" \
-d '{"price_min":1300,"comment":"Подняли цену с сезона"}' \
"/api/v1/services/abc123/suggestions"Ответ
{
"applied_fields": ["price_min"],
"pending_fields": [],
"suggestion_ids": ["b1f6c2b0-..."]
}/services/{service_id}/reportsПожаловаться на карточку: закрылись, дубль, спам. Замена публичного удаления — сама жалоба карточку не меняет, её разбирает админ. Действует та же суточная квота на автора, что и у других публичных форм. Отвечает 204 без тела
Параметры:
- Тело: { "reason": "closed" | "duplicate" | "spam" | "other", "comment"?: "…" }
Запрос
curl -X POST -H "Content-Type: application/json" \
-d '{"reason":"closed"}' \
"/api/v1/services/abc123/reports"Ответ
204 No Content/catalog/{entity_type}/{entity_id}/suggestionsКакие поля объекта, дома или застройщика кто-то уже оспорил. Токен не нужен. В ответе нет ни предложенного текста, ни того, что стоит сейчас: «как правильно» пишет кто угодно, а показ непроверенного текста на карточке — готовое место под рекламу. Клиенту хватает знать, что поле оспорено
Параметры:
- entity_type — object | building | developer
- entity_id — внутренний UUID сущности (поле id в её схеме), а не external_id объекта. Невалидный UUID — 404; по хорошо сформированному UUID, которому ничего не соответствует, приходит 200 с пустым items — существование сущности эта ручка не проверяет
- отдаются только правки, ожидающие модератора: до 50 штук, новые сверху
Запрос
curl "/api/v1/catalog/object/3fa85f64-5717-4562-b3fc-2c963f66afa6/suggestions"Ответ
{
"items": [
{
"id": "b1f6c2b0-...",
"field_name": "title",
"status": "pending",
"created_at": "2026-09-01T10:15:00Z"
}
]
}/catalog/{entity_type}/{entity_id}/suggestionsПредложить правку данных объекта, дома или застройщика — доступно кому угодно, авторизация не нужна. Автоприменения здесь нет вовсе, в отличие от карточек услуг: все правимые поля — свободный текст, то есть место под рекламу, поэтому каждая правка ждёт модератора и до его решения ничего в каталоге не меняет. Одна правка — одно поле: форма спрашивает «что не так → что сейчас → как правильно → откуда знаете». Суточная квота на запись общая с каталогом услуг и действует только без API-токена; с токеном вместо неё работает общий per-token rate limit
Параметры:
- entity_type — object | building | developer; entity_id — внутренний UUID сущности
- Тело: { "field_name": "…", "suggested_value": "…", "current_value"?: "…", "source_note"?: "…" }. suggested_value обязателен и не может быть пустым, current_value — до 512 символов
- Правимые поля объекта: title, address, street, house, quarter, city, district
- Правимые поля дома: name, city, address, street, house, quarter
- Правимые поля застройщика: name, website, description
- Список именно белый: числа, даты и перечисления не правятся (их пришлось бы приводить к типу при применении), контакты застройщика — тоже, потому что подмена телефона это прямой увод лидов. Поле вне списка — 422 «Field is not editable: <имя>»
- Длина suggested_value ограничена самой колонкой; там, где у колонки нет своей длины, потолок 2000 символов. Превышение — 422. Несуществующая сущность — 404
- 201 с телом правки: id, field_name, status (всегда pending), created_at
Запрос
curl -X POST -H "Content-Type: application/json" \
-d '{"field_name":"title","current_value":"Нововиленская ул. д. 12.2 по ГП","suggested_value":"ул. Нововиленская, 12","source_note":"Табличка на доме"}' \
"/api/v1/catalog/object/3fa85f64-5717-4562-b3fc-2c963f66afa6/suggestions"Ответ
{
"id": "b1f6c2b0-...",
"field_name": "title",
"status": "pending",
"created_at": "2026-09-01T10:15:00Z"
}/catalog/{entity_type}/{entity_id}/reportsСообщить о проблеме с объектом, домом или застройщиком: дубль, снятая позиция, чужие фото. Жалоба сама ничего не меняет — её разбирает админ. Авторизация не нужна, та же суточная квота на запись, что у правки выше. Отвечает 204 без тела
Параметры:
- entity_type — object | building | developer; entity_id — внутренний UUID сущности
- Тело: { "reason": "duplicate" | "removed" | "wrong_photos" | "other", "comment"?: "…" } — набор причин свой, он не совпадает с набором у /services/{service_id}/reports
- Несуществующая сущность — 404
Запрос
curl -X POST -H "Content-Type: application/json" \
-d '{"reason":"duplicate","comment":"Тот же объект уже есть"}' \
"/api/v1/catalog/building/3fa85f64-5717-4562-b3fc-2c963f66afa6/reports"Ответ
204 No Content/services/{service_id}Удалить карточку (soft-delete) — то же право доступа, что у PATCH выше: только автор той же идентичности или сервисный токен админа, иначе 403 «Only the author can modify this record»; 403 «Service is claimed by a business», если её забрал бизнес. Доступно на практике только для карточек с зафиксированным при создании владельцем — как и у PATCH, у карточек без него удалить может лишь админ. Суточная квота на запись — как у POST /services выше. Тело запроса необязательное: старый клиент, который его не шлёт, удаляет карточку как раньше, без причины (#1932). 204 без тела, 404 — если карточки уже нет
Параметры:
- Тело (необязательно): { "reasons"?: ["reasonStale"|"reasonDuplicate"|"reasonIncorrect"|"reasonStopped"|"reasonRules", ...], "comment"?: "…" }
- reasons — неизвестные ключи отбрасываются, а не отклоняют запрос; comment обрезается до 500 символов. Оба поля служебные: их видит только админ, наружу карточка их не отдаёт
Запрос
curl -X DELETE -H "X-TG-Init-Data: $INIT_DATA" -H "Content-Type: application/json" \
-d '{"reasons":["reasonStopped"],"comment":"Стройка заморожена"}' \
"/api/v1/services/abc123"Ответ
204 No Content/services/{service_id}/reviewsОтзывы о карточке услуги вместе со средней оценкой. Скрытые модерацией отзывы не отдаются
Параметры:
- page, page_size (1–100, по умолчанию 20)
- is_verified в элементах — отзыв связан с закрытой заявкой к этой же карточке от того же PWA-аккаунта; сам lead_id наружу не отдаётся
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/services/abc123/reviews"Ответ
{
"total": 18,
"rating_avg": 4.7,
"items": [
{
"id": "…",
"rating": 5,
"text": "…",
"created_at": "2026-05-02T09:00:00Z",
"can_delete": false,
"business_reply": "Спасибо!",
"business_reply_at": "2026-05-03T10:00:00Z",
"is_verified": true
}
]
}/services/{service_id}/reviewsОставить отзыв о карточке услуги — публично, токен не обязателен. rating (1–5) обязателен, text — свободный, проходит автомодерацию. Тело может нести lead_id — заявку, из закрытия которой отправлен отзыв: связь ставится только при PWA Bearer у автора отзыва и только если заявка закрыта, оформлена к этой же карточке услуги и её автор — тот же PWA-аккаунт; без PWA Bearer, с чужой заявкой или заявкой к другой карточке связь не устанавливается, а отзыв уходит обычным анонимным — невалидный или чужой lead_id отзыв не блокирует. Повторные отзывы одного автора не ограничены — здесь нет правила «один отзыв на сущность», которое действует в общем тракте отзывов (/reviews/{entity_type}/{entity_id}) выше. can_delete в ответе не декоративен: у чисто анонимного автора (без X-TG-Init-Data и PWA Bearer) он всегда false, потому что авторство никогда не определяется по IP — такой отзыв автор не сможет удалить сам, только админ через сервисный токен. Суточная квота на запись без токена — как у остальных публичных форм каталога, при превышении 429 и мини-игра-проверка. 404, если карточки нет
Параметры:
- Тело: { "rating": 1..5, "text"?: "…", "lead_id"?: "…" }
- Ответ (201) — та же форма, что у элементов items в GET .../reviews выше, включая is_verified
Запрос
curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Bearer $PWA_TOKEN" \
-d '{"rating":5,"text":"Отличная работа","lead_id":"5c2e2b0a-..."}' \
"/api/v1/services/abc123/reviews"Ответ
{
"id": "5c2e2b0a-...",
"rating": 5,
"text": "Отличная работа",
"created_at": "2026-09-01T10:15:00Z",
"can_delete": false,
"business_reply": null,
"business_reply_at": null,
"is_verified": true
}/services/{service_id}/reviews/{review_id}Удалить свой отзыв (мягко). Право — как у PATCH/DELETE карточки услуги выше: совпадение Telegram- или PWA-идентичности с автором отзыва, либо сервисный токен админа, иначе 403 «Only the author can modify this record». Анонимный отзыв (без X-TG-Init-Data/PWA Bearer при создании) под это правило не подпадает никогда — авторство по IP не считается, снять такой отзыв может только админ. review_id обязан принадлежать сервису из пути, иначе 404 (#2404). Суточная квота на запись — как у остальных публичных форм. 204 без тела, 404 — если отзыва уже нет
Запрос
curl -X DELETE -H "X-TG-Init-Data: $INIT_DATA" \
"/api/v1/services/abc123/reviews/5c2e2b0a-..."Ответ
204 No Content/services/{service_id}/reviews/{review_id}/reportПожаловаться на отзыв — вход в очередь модератора, сам отзыв не меняется. Причины — те же пять, что у общего тракта отзывов: spam, offensive, fake, personal_data, other; reason принимается как произвольная строка, а не enum, поэтому неизвестное значение отклоняет не стандартный pydantic-конверт, а 422 от обработчика каталожных правил. Жалоба Telegram- или PWA-идентичности дедуплицируется — повторная жалоба того же аккаунта на тот же отзыв не плодит строк и возвращает уже существующую запись; анонимная (по IP) не дедуплицируется — от накрутки её защищает только суточная квота на запись, как у остальных публичных форм. Ответ (201) — { "status": "pending" }. 404, если отзыва нет или он принадлежит другому сервису (#2404)
Параметры:
- Тело: { "reason": "spam" | "offensive" | "fake" | "personal_data" | "other", "comment"?: "…" }
Запрос
curl -X POST -H "Content-Type: application/json" \
-d '{"reason":"spam","comment":"Похоже на накрутку"}' \
"/api/v1/services/abc123/reviews/5c2e2b0a-.../report"Ответ
{ "status": "pending" }/services/{service_id}/reviews/{review_id}/replyПубличный ответ владельца карточки на отзыв. Право — совпадение Telegram- или PWA-идентичности с автором карточки (не отзыва), либо сервисный токен админа, иначе 403 «Only the author can modify this record»; в отличие от PATCH/DELETE карточки выше, claim бизнесом (business_id) здесь не проверяется — решает только совпадение автора карточки. review_id должен принадлежать указанному service_id, иначе 404 «Review not found» — ту же сверку с #2404 делают DELETE и жалоба. Пустой или отсутствующий text снимает ответ. Суточная квота на запись — как у остальных публичных форм, действует и для владельца. Ответ — полная форма отзыва, как у items в GET .../reviews, с обновлёнными business_reply и business_reply_at
Параметры:
- Тело: { "text": "…" | null }
Запрос
curl -X POST -H "Content-Type: application/json" \
-H "X-TG-Init-Data: $INIT_DATA" \
-d '{"text":"Спасибо за отзыв!"}' \
"/api/v1/services/abc123/reviews/5c2e2b0a-.../reply"Ответ
{
"id": "5c2e2b0a-...",
"rating": 5,
"text": "Отличная работа",
"created_at": "2026-09-01T10:15:00Z",
"can_delete": false,
"business_reply": "Спасибо за отзыв!",
"business_reply_at": "2026-09-01T11:00:00Z"
}/challengeВыдать одно задание мини-игры-проверки — анаграмму или квиз по строительной терминологии, наугад из типов, включённых в админке (chal:games_enabled). Токен и авторизация не нужны; ожидаемый ответ хранится только в Redis и в ответе не отдаётся, так что сам запрос его не подсказывает. Задание живёт 300 секунд и допускает 3 неверных попытки — по истечении срока или после третьей неверной попытки оно удаляется, а следующая проверка получит 410. Выдача расходует отдельную от 429-й суточную квоту на автора (не ту, что привела к 429) — админ-настраиваемый потолок задач в сутки; при его превышении 429 «Слишком много проверок за сегодня». Если в админке не включён ни один тип игры — 503 «Проверка временно недоступна»
Параметры:
- payload зависит от game_type: у anagram — { "scrambled": "…" } (перемешанные буквы загаданного слова), у quiz — { "question": "…", "options": ["…", "…", "…", "…"] } (4 варианта, порядок каждый раз перемешан, правильный не помечен)
- attempts_left — стартовое число разрешённых неверных попыток (сейчас 3), а не текущий остаток: он не обновляется в ответах POST .../verify
- expires_at — момент, после которого задание больше не примут (сейчас через 300 секунд после выдачи)
- Идентичность автора для суточной квоты выдачи — та же логика, что у остальных публичных форм: Telegram (X-TG-Init-Data), иначе PWA Bearer, иначе IP
Запрос
curl -X POST "/api/v1/challenge"Ответ
{
"challenge_id": "9G3f...",
"game_type": "anagram",
"payload": { "scrambled": "ТИЛОНОМ" },
"expires_at": "2026-09-01T10:20:00Z",
"attempts_left": 3
}/challenge/{challenge_id}/verifyПроверить решение задания. Совпадение снимает не свою квоту, а суточный счётчик записи — тот же, что дал 429 у публичных форм каталога, — но только для той же идентичности автора, что была у исходного запроса: Telegram (X-TG-Init-Data), иначе PWA Bearer, иначе IP. Присылать нужно те же заголовки, что были у запроса, упёршегося в лимит, иначе снимется чужой счётчик. Сброс сам ограничен отдельной суточной квотой на автора (по умолчанию 10, настраивается в админке) — при её исчерпании 429 «Лимит проверок на сегодня исчерпан». Неверное решение — 400 «Неверный ответ», попытка засчитывается в счётчик задания; после третьей неверной задание удаляется. Просроченное, неизвестное challenge_id или уже исчерпавшее попытки задание — 410 «Проверка истекла»
Параметры:
- Тело: { "solution": "…" } — для anagram: разгаданное слово без учёта регистра; для quiz: точный текст выбранного варианта из options, без учёта пробелов по краям
- challenge_id — из ответа POST /challenge
- Успешная проверка не увеличивает суточный лимит записи навсегда, а обнуляет счётчик текущих суток для той же идентичности — новый лимит копится с нуля
- Ответ (200): { "ok": true }
Запрос
curl -X POST -H "Content-Type: application/json" \
-d '{"solution":"МОНОЛИТ"}' \
"/api/v1/challenge/9G3f.../verify"Ответ
{ "ok": true }/services/hubДанные главной каталога: счётчик, топ карточек, популярные теги и порядок категорий
Параметры:
- top_limit (1–20, по умолчанию 6)
- tag_limit (1–50, по умолчанию 15)
- category_limit (1–50, по умолчанию 12)
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/services/hub"Ответ
{ "total": 214, "top": [ ... ], "tags": ["ремонт"], "categories": ["remont-pod-klyuch"] }/services/recommendedПохожие карточки в той же категории — для блока рекомендаций
Параметры:
- category — слаг категории
- categories — несколько категорий через запятую, объединяются с category в один список поиска; если не задан ни category, ни categories — список пустой
- limit (1–10, по умолчанию 3)
- exclude — id карточек через запятую, которые нужно пропустить
- city — не фильтрует выдачу: поднимает карточки с этой локацией в начало списка. Если совпадений нет, список не пустеет — возвращается обычный топ категории
- exclude_provider_types — форматы исполнителя через запятую, которые в подборку не годятся (например, showroom, manufacturer — те, кто продаёт, а не выполняет работы)
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/services/recommended?category=remont-pod-klyuch&limit=3&city=Минск"Ответ
[{ "id": "abc123", "name": "…", "rating_avg": 4.7, ... }]/services/categoriesДерево категорий каталога. Родитель задаётся полем parent_slug
Параметры:
- lang — язык подписи, по умолчанию ru; при отсутствии перевода отдаётся русский
- stage — этап пути ремонта: before_deal | design | preparation | rough | finish | furnishing | moving_in. Порядок значений и есть порядок пути. С этим параметром приходят категории запрошенного этапа плюс сквозные (см. is_cross_stage)
- в ответе stage — этап категории или null, если категория вне пути
- в ответе is_cross_stage — категория покрывает несколько этапов сразу («под ключ», генподряд, технадзор) и потому попадает в выборку любого этапа
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/services/categories?stage=finish"Ответ
[
{
"slug": "remont-pod-klyuch",
"label": "Ремонт под ключ",
"labels": { "ru": "Ремонт под ключ", "en": "Turnkey renovation" },
"parent_slug": null,
"sort_order": 10,
"status": "approved",
"stage": null,
"is_cross_stage": true
}
]/services/tagsПодсказки по тегам с числом карточек — для автодополнения
Параметры:
- q — фильтр по префиксу
- limit (1–50, по умолчанию 20)
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/services/tags?q=рем"Ответ
[{ "tag": "ремонт", "count": 132 }]/services/citiesСправочник городов каталога услуг с областью и слагом для адреса посадочной
Параметры:
- q — фильтр по префиксу
- limit (1–100, по умолчанию 20)
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/services/cities?q=мин"Ответ
[{ "name": "Минск", "region": "Минская область", "slug": "minsk" }]/services/featuresСправочник признаков услуги (коды для фильтра feature)
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/services/features"Ответ
[{ "code": "smeta", "label": "Смета" }]/services/facetsЗначения фасетов для фильтров: типы объектов, форматы исполнителя, единицы цены
Запрос
curl -H "X-API-Key: $API_TOKEN" "/api/v1/services/facets"Ответ
{
"object_types": [{ "code": "flat", "label": "Квартира" }],
"provider_types": [{ "code": "brigada", "label": "Бригада" }],
"price_units": [{ "code": "sqm", "label": "за м²" }]
}/services/attributesХарактеристики, применимые к категории, с допустимыми значениями. multi — можно ли выбрать несколько
Параметры:
- category — обязателен
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/services/attributes?category=remont-pod-klyuch"Ответ
[
{
"code": "material",
"label": "Материал",
"multi": true,
"values": [{ "code": "gipsokarton", "label": "Гипсокартон" }]
}
]/services/filter-countsСколько карточек останется при добавлении каждого значения фильтра к текущей выборке. Принимает те же фильтры, что /services (кроме сортировки, цены, рейтинга и пагинации)
Параметры:
- q, category, tag, country, city, business, verified, has_photo, has_reviews
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/services/filter-counts?category=remont-pod-klyuch"Ответ
{
"object_types": { "flat": 120, "house": 44 },
"provider_types": { "brigada": 64 },
"features": { "smeta": 88 },
"countries": { "BY": 214 }
}/services/landingСрез «категория × город» для посадочной страницы вместе с перелинковкой. Общестрановые карточки в срез не попадают. 404, если город или категория неизвестны
Параметры:
- category — слаг категории, обязателен
- city — слаг города латиницей (minsk), обязателен
- lang — язык подписи категории, по умолчанию ru
- в ответе price — ориентир цены среза: { currency, unit, min, median, max, sample_size } либо null. Считается по карточкам среза с ценой в паре «валюта + единица» — берётся самый представительный по числу карточек сегмент; медиана — по серединам ценовых вилок карточек. null, если в этом сегменте меньше 5 карточек с ценой
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/services/landing?category=remont-pod-klyuch&city=minsk"Ответ
{
"category": { "slug": "remont-pod-klyuch", "label": "Ремонт под ключ" },
"city": "Минск",
"city_slug": "minsk",
"total": 47,
"price": { "currency": "BYN", "unit": "sqm", "min": "18.00", "median": "26.50", "max": "45.00", "sample_size": 12 },
"sibling_categories": [{ "slug": "otdelka", "label": "Отделка", "total": 31 }],
"nearby_cities": [{ "slug": "brest", "name": "Брест", "total": 12 }]
}/services/{id}/statsПросмотры карточки услуги за период. null, если просмотров меньше порога (10)
Параметры:
- period — период в днях (7–365, по умолчанию 30)
Запрос
curl "/api/v1/services/abc123/stats?period=30"Ответ
{ "service_id": "abc123", "period_days": 30, "views": 210 }/pending-developersЗаявка на подключение застройщика. Если сайт уже есть в системе — вернёт данные существующего застройщика вместо создания заявки; если заявка на этот сайт уже подана — вернёт её. Токен не нужен
Параметры:
- Тело: { "name": "…", "website": "…", "notes"?: "…" }
- Ограничения длины: name — 256, website — 512, notes — 500 символов
Запрос
curl -X POST -H "Content-Type: application/json" \
-d '{"name":"ЖилБылСтрой","website":"https://hwb.by"}' \
"/api/v1/pending-developers"Ответ
{
"id": "...",
"name": "ЖилБылСтрой",
"website": "https://hwb.by",
"notes": null,
"status": "pending",
"created_at": "...",
"updated_at": "..."
}/registration-statusВсе объекты с открытой регистрацией прямо сейчас (кэш 30 с, токен не требуется)
Запрос
curl "/api/v1/registration-status"Ответ
[{ "developer_id": "ghb", "external_id": "OBJ-101", "registration_url": "..." }]/eventsИстория событий мониторинга
Параметры:
- developer_id, object_id, event_type (OBJECT_APPEARED|OBJECT_REMOVED|OBJECT_UPDATED|REGISTRATION_OPENED|REGISTRATION_CLOSED|COMPLETION_SOON)
- since, until (ISO datetime)
- limit (макс. 500), page
- COMPLETION_SOON — календарное, не diff-событие: дом сдаётся в ближайшие N дней, порог настраивается на направлении. Раз в день его получают дома, чей срок сдачи вошёл в окно; object_id у такого события — синтетический building:{id}, а не id объекта
Запрос
curl -H "X-API-Key: $API_TOKEN" \
"/api/v1/events?developer_id=ghb&event_type=REGISTRATION_OPENED&limit=10"Ответ
{
"total": 42,
"items": [{ "event_type": "REGISTRATION_OPENED", "occurred_at": "...", ... }]
}/events/streamSSE-стрим событий в реальном времени
Параметры:
- developer_id — фильтр по застройщику
- event_types — через запятую (OBJECT_APPEARED, OBJECT_REMOVED, OBJECT_UPDATED, REGISTRATION_OPENED, REGISTRATION_CLOSED, COMPLETION_SOON)
- api_token — токен query-параметром (для EventSource)
- Last-Event-ID — восстановление пропущенных событий
Запрос
curl -N -H "X-API-Key: $API_TOKEN" \
"/api/v1/events/stream?developer_id=ghb&event_types=REGISTRATION_OPENED"Ответ
event: REGISTRATION_OPENED
id: 1711234567890-0
data: {"external_id":"OBJ-101",...}
: keepalive/field-configsМетаданные публичных полей объекта: подписи и шаблоны текстов для событий появления/удаления/изменения поля. Кэш 5 минут
Запрос
curl "/api/v1/field-configs"Ответ
[
{
"field_name": "price",
"alias": "Цена",
"description": null,
"value_kind": "money",
"empty_policy": "hide",
"public_value_visibility": "visible",
"appeared_template": "...",
"removed_template": "...",
"changed_template": "...",
"updated_at": "..."
}
]/healthСтатус сервиса и парсеров (токен не требуется)
Запрос
curl "/api/v1/health"Ответ
{ "status": "ok", "version": "1.0.0", "developers": [...] }SSE-стрим (Server-Sent Events)
Стрим позволяет получать события в реальном времени без постоянного опроса. Соединение остаётся открытым; сервер отправляет keepalive-пинги каждые 15 секунд. При разрыве браузер автоматически переподключается с заголовком Last-Event-ID, и сервер отдаёт все пропущенные события из буфера.
bash
# curl — события регистрации от застройщика ghb
curl -N -H "X-API-Key: $API_TOKEN" \
"/api/v1/events/stream?developer_id=ghb&event_types=REGISTRATION_OPENED"
# С восстановлением пропущенных (Last-Event-ID)
curl -N -H "X-API-Key: $API_TOKEN" -H "Last-Event-ID: 1711234567000-0" \
"/api/v1/events/stream?developer_id=ghb"python
import httpx
import asyncio
async def stream_events():
async with httpx.AsyncClient() as client:
async with client.stream(
"GET",
"/api/v1/events/stream",
headers={"X-API-Key": "sh_ваш_токен"},
params={"developer_id": "ghb", "event_types": "REGISTRATION_OPENED"},
timeout=None,
) as r:
async for line in r.aiter_lines():
if line.startswith("data:"):
import json
data = json.loads(line[5:].strip())
print(data)
asyncio.run(stream_events())javascript
// Браузер или Node.js (EventSource API)
// EventSource не поддерживает заголовки —
// токен передаётся query-параметром api_token
const es = new EventSource(
"/api/v1/events/stream?developer_id=ghb&api_token=sh_ваш_токен"
);
es.addEventListener("REGISTRATION_OPENED", (e) => {
const data = JSON.parse(e.data);
console.log("Открылась регистрация:", data.external_id);
});
es.onerror = () => {
// Браузер автоматически переподключается
console.log("Переподключение...");
};go
package main
import (
"bufio"
"fmt"
"net/http"
"os"
"strings"
)
func main() {
req, _ := http.NewRequest("GET",
"/api/v1/events/stream?developer_id=ghb", nil)
req.Header.Set("X-API-Key", os.Getenv("API_TOKEN"))
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "data:") {
fmt.Println(strings.TrimPrefix(line, "data: "))
}
}
}Схема данных объекта
| Поле | Тип | Описание |
|---|---|---|
id | string | UUID объекта в stroi.homes |
developer_id | string | Слаг застройщика (например, ghb) |
external_id | string | Уникальный ID объекта у застройщика |
title | string | Название объекта |
object_type | enum | apartment | apartments | commercial | parking | storage |
status | enum | for_sale | booked | sold | removed | rent |
object_url | string | Ссылка на объект на сайте застройщика |
registration_open | boolean | Открыта ли онлайн-регистрация |
registration_url | string? | Ссылка на форму регистрации |
price | decimal? | Цена целиком, в валюте поля currency |
price_per_sqm | decimal? | Цена за м² |
estimated_price | decimal? | Расчётная цена (area × price_per_sqm). Заполняется только когда price отсутствует |
currency | string | Валюта price, price_per_sqm и estimated_price. Заполнена всегда: либо валюта, заданная самому объекту, либо валюта его застройщика. Сортировка и фильтры по цене сравнивают не эту валюту, а BYN-эквивалент по последнему известному курсу НБРБ |
has_price_history | boolean | Есть ли смысл запрашивать /objects/{object_id}/price-history. Надмножество: возможен true и при пустой серии |
price_delta_pct | decimal? | Последнее изменение цены в процентах, со знаком (−2.4 — подешевел). Считается только у объектов с настоящей ценой целиком; у расчётной estimated_price его нет. null, если цена ещё не менялась, у события изменения не сохранилось прежнее значение или изменение вышло нулевым |
price_changed_at | datetime? | Когда произошло это изменение цены. null там же, где null price_delta_pct |
area | decimal? | Площадь в м² |
rooms | int? | Количество комнат (0 = студия) |
floor | int? | Этаж |
total_floors | int? | Этажность дома |
building | string? | Корпус / секция |
building_id | string? | UUID дома, к которому отнесён объект; null, если дом определить не удалось |
country | string? | Страна |
region | string? | Область |
district | string? | Район |
city | string? | Город |
address | string? | Полный адрес строкой, как его отдаёт источник |
street | string? | Улица — разобранная часть адреса. Может быть заполнена одновременно с address |
house | string? | Номер дома — разобранная часть адреса |
quarter | string? | Квартал / микрорайон — разобранная часть адреса |
completion_date | date? | Срок сдачи дома |
sales_start_date | date? | Дата старта продаж |
registration_start_date | datetime? | Дата и время старта онлайн-регистрации |
offline_booking_date | date? | Дата старта офлайн-бронирования |
image_url | string? | Изображение объекта |
thumb_url | string? | Уменьшенная копия изображения для списков. Может быть null, когда миниатюра ещё не готова — в этом случае используйте image_url |
participants | array | Застройщики-соавторы: developer_id, name, role (contributor | attributed). Заполняется в карточке одного объекта и в плоском списке /developers/{id}/objects; в остальных выдачах со схемой объекта поле приходит пустым массивом |
promotions | array? | Акции застройщика |
extra | object? | Дополнительные поля конкретного застройщика. Состав не фиксирован — не полагайтесь на него в интеграции |
first_seen_at | datetime | Дата первого обнаружения |
updated_at | datetime | Дата последнего обновления |
Поля перечислены полностью. Машиночитаемая версия схемы — в OpenAPI-спецификации.
Changelog API
| Версия | Дата | Изменения |
|---|---|---|
| v1.16.0 | 2026-09-11 | У дома и жилого комплекса появились свои ссылки для шеринга: POST /buildings/{building_id}/share-urls и POST /complexes/{complex_id}/share-urls — тело и форма ответа те же, что у объекта и услуги, каждая ссылка ведёт через собственный редирект /r/{token}, и переходы по ней считаются отдельно (#2171). В ответах /complexes и /buildings добавлено поле container_object_id — идентификатор карточки-контейнера, по которой у дома и комплекса теперь есть лента изменений (#2183). Рядом с container_prices появились container_external_id (у дома) и container_content.external_id (у комплекса): внешний идентификатор той же карточки, заполняется по тому же правилу — только когда у дома или комплекса нет видимых единиц продажи. |
| v1.15.0 | 2026-09-07 | У /buildings в ответе задокументированы registration_open, registration_url и registration_start_date — запись с карточки-контейнера дома: у застройщика, публикующего дом целиком, регистрация открывается на сам дом, а не на квартиру (#1957). До открытия флаг остаётся false и ссылки нет — видна только дата старта (#2001). У DELETE /services/{service_id} появилось необязательное тело { "reasons": [...], "comment": "…" } — причина, по которой карточку сняли; старый клиент, который его не шлёт, удаляет карточку как раньше (#1990). Появился новый тип события COMPLETION_SOON — дом сдаётся в ближайшие N дней, порог настраивается на направлении; событие доступно в истории /events и в подписке /events/stream (#1960) |
| v1.14.0 | 2026-09-06 | Ключ сущности в группе /reviews/{entity_type}/{entity_id} у застройщика принимает не только UUID, но и слаг — тот самый, что публичное API отдаёт в поле id; внутреннего идентификатора у клиента не было вовсе. Действует одинаково во всех восьми ручках группы, видимость застройщика проверяется как в остальном публичном API. У дома и материала ключ остаётся UUID |
| v1.13.0 | 2026-09-05 | Появилась группа полиморфных отзывов: GET /reviews/{entity_type}/{entity_id} — лента отзывов о материале, доме/объекте или застройщике вместе со средней оценкой (у объекта — по аспектам yard/noise/deadlines/finishing) и правом текущего пользователя оставить свой отзыв. Запись — POST/DELETE самого отзыва, POST .../reply (ответ представителя застройщика), POST/DELETE .../photos, PUT .../vote и POST .../report — как и у /developers/{id}/reviews, через обычный API-токен недоступна: пишет только аккаунт приложения при выполненном предметном условии |
| v1.12.0 | 2026-09-05 | Ломающее: /complexes теперь отдаёт конверт { total, page, page_size, items } вместо голого списка — та же форма, что у /objects и /objects/recent. Раньше ручка принимала page/page_size, но не отдавала total: узнать, есть ли следующая страница, было неоткуда |
| v1.11.0 | 2026-09-04 | У /complexes ЖК без единиц продажи, видимых домов и цены с карточки-контейнера теперь целиком исключён из выдачи — раньше такая пустая карточка всё равно приходила и открывала пустую страницу. Список buildings и счётчик buildings_count в /complexes/{complex_id} используют то же правило видимости домов, что и /buildings: дом без единиц продажи и без цены за метр на своей карточке-контейнере в список домов ЖК не попадает |
| v1.10.0 | 2026-09-03 | Появилась группа ручек правки каталога: GET и POST /catalog/{entity_type}/{entity_id}/suggestions и POST /catalog/{entity_type}/{entity_id}/reports — правки и жалобы по объектам, домам и застройщикам, каждая ждёт модератора. Изменилось поведение ленты /objects?group=building: дома, чью карточку источник наполнил ценой за м², снова приходят в неё — домом, а не объектом, и такой дом сворачивается даже в одиночку; под фильтрами rooms, min_price, max_price, min_area, max_area и registration_open они по-прежнему исчезают. В схеме объекта задокументированы currency, price_delta_pct и price_changed_at. У /buildings — параметр kind и то, что без него паркинги в выдачу не попадают; в сводке дома видны kind, completion_date и container_prices, в сводке комплекса — container_prices. У /developers в ответе задокументированы contacts и юрблок (legal_form, legal_name, unp, registered_year, legal_checked_at, is_claimed, is_verified), у /services/{service_id} и его POST/PATCH — свой юрблок. У /services/categories — параметр stage и поля stage и is_cross_stage. Задокументирован group=building у /developers/{id}/objects; у него же убрано упоминание несуществующего фильтра complex_id |
| v1.9.0 | 2026-09-01 | Появились /complexes и /complexes/{complex_id} — жилые комплексы со сводкой и списком домов, у /buildings в ответе complex_id и complex_name. У /objects новый фильтр complex_id. Ломающее для тех, кто читает выдачу целиком: дома и комплексы, отданные источником отдельной карточкой, больше не приходят в /objects, /objects/recent и /objects/suggestions и не считаются в total_count дома — по прямой ссылке такая карточка открывается по-прежнему |
| v1.8.0 | 2026-08-30 | Per-token rate limit 600 req/мин теперь действует и при токене в query-параметре ?api_token=, а не только в заголовке X-API-Key: интегратор с действующим токеном больше не рискует часовым баном по IP. Недействительный токен по-прежнему считается по общему лимиту на IP |
| v1.7.0 | 2026-08-26 | Задокументирован параметр city у /services/recommended: поднимает карточки с этой локацией, не фильтрует. В /buildings и /buildings/{building_id} видны поля street, house, quarter. Уточнено: per-token rate limit 600 req/мин действует только при токене в заголовке X-API-Key — токен query-параметром ?api_token= считается по общему лимиту на IP |
| v1.6.0 | 2026-08-24 | Задокументированы POST /services, PATCH /services/{service_id} и DELETE /services/{service_id}: создание карточки специалиста и прямая правка/удаление её автором |
| v1.5.0 | 2026-08-18 | Поле thumb_url у объекта и дома — уменьшенная копия изображения для списков. Поле participants в карточке одного объекта: застройщики-соавторы с ролью contributor или attributed. Параметр attribution=own|participated у /developers/{id}/objects — отдаёт чужие объекты, где застройщик указан соавтором |
| v1.4.0 | 2026-07-31 | Поле estimated_price в объекте (area × price_per_sqm, когда застройщик не публикует итоговую цену). Задокументированы сквозной каталог /objects, /objects/recent, /objects/cities, /objects/suggestions, POST /objects/batch и отзывы застройщика. Уточнено: токен обязателен только для /events/stream и POST /objects/batch |
| v1.3.0 | 2026-06-27 | GET /developers/{id}/destinations — целевые направления застройщика (Telegram, Threads, Viber) единым списком |
| v1.2.0 | 2026-06-04 | Обязательные API-токены для data-эндпоинтов (X-API-Key / Bearer / ?api_token=). Бесплатная выдача через POST /api/v1/developer-tokens. Per-token rate limit 600 req/мин, авто-отзыв за злоупотребления, удаление после 90 дней неактивности. /health и /registration-status — без токена |
| v1.1.0 | 2026-04-18 | Sandbox API (/api/v1/sandbox/*) — тестовая среда с моковыми данными и SSE-стримом для разработки интеграций |
| v1.0.0 | 2026-03-29 | Первый публичный релиз: /developers, /objects, /events, /events/stream, /registration-status, /health |
Политика: минорные изменения (новые поля) — обратно совместимы. Мажорная смена версии — через новый префикс /api/v2/.