API

Базовый адрес: https://api.desperatemeasure.tech

API только на чтение и без авторизации. Нет токенов, сессий и заголовков аутентификации — каталог публичный и замороженный. Ограничений по частоте запросов нет.

Схема OpenAPI доступна по /openapi.json, интерактивная документация — по /docs.

Общие правила

Кодировка. Все ответы в UTF-8, Content-Type: application/json. Названия и описания на русском языке.

Кэширование. Ответы отдаются с Cache-Control: public, max-age=300. Изображения — с max-age=31536000, immutable, потому что путь меняется вместе с содержимым. 3D-модели — max-age=86400, поскольку файл может быть заменён на месте.

Цены — целые числа в рублях, без копеек. Значение null в price_from означает «цена по запросу»; в этом случае price_on_request равно true.

Пути к файлам относительные (/media/..., /models/...). Дописывайте базовый адрес на стороне клиента.

Ошибки возвращаются в формате FastAPI:

{ "detail": "Товар «nope» не найден" }
Код Когда
200 Успех
404 Товар с таким slug не существует
422 Некорректный параметр запроса (например, price_min=-5)

GET /health

Проверка живости. Не кэшируется, годится для мониторинга.

curl https://api.desperatemeasure.tech/health
{ "status": "ok", "products": 114, "materials": 221 }

GET /v1/products

Список товаров в краткой форме — без конфигуратора, отделок и галереи. Отсортирован по категории, внутри категории по возрастанию цены.

Параметры

Параметр Тип Описание
category string Идентификатор категории, см. /v1/categories
price_min int ≥ 0 Минимальная цена «от»
price_max int ≥ 0 Максимальная цена «от»
is_new bool Только новинки либо только не-новинки
has_ar bool Только товары с 3D-моделью либо только без неё

Параметры комбинируются по «и». Товары с ценой по запросу (price_from: null) из результата исключаются, если задан любой из ценовых фильтров.

Пример

curl "https://api.desperatemeasure.tech/v1/products?category=chairs&price_max=80000"
[
  {
    "slug": "scarlett-light",
    "title": "SCARLETT LIGHT / Скарлетт лайт",
    "category": "chairs",
    "is_new": false,
    "price_from": 63500,
    "price_on_request": false,
    "has_ar": false,
    "image": {
      "thumb": "/media/scarlett-light/1-400.webp",
      "detail": "/media/scarlett-light/1-1200.webp",
      "full": "/media/scarlett-light/1-2000.webp"
    }
  }
]

Поле image содержит первое изображение товара или null, если их нет. На текущих данных изображение есть у всех 114 товаров.


GET /v1/products/{slug}

Полная карточка: описание, галерея, конфигуратор с правилами расчёта цены, отделки.

curl https://api.desperatemeasure.tech/v1/products/elio

Структура ответа

Поле Тип Описание
slug string Идентификатор, он же часть URL
title string Название, обычно латиницей и кириллицей
category string Идентификатор категории
is_new bool Товар в разделе «Новинки»
description string Описание; абзацы разделены \n\n
price_from int | null Минимальная возможная конфигурация
price_to int | null Максимальная: дорогие материалы и все опции
price_on_request bool Цены на сайте нет
price_warnings string[] Замечания к данным, см. ниже
images Image[] Галерея, три размера на каждое изображение
has_ar bool Есть ли 3D-модель на сервере
model_url string | null Путь к модели
base_group BaseGroup Базовая группа выбора
delta_groups ChoiceGroup[] Группы надбавок
extras Extra[] Опции-галочки
finishes FinishSection[] Доступные отделки

Конфигуратор

Это главная часть ответа, и устроена она так, чтобы клиент считал цену одной формулой без ветвлений по типу товара.

base_group — ровно одна группа с абсолютными ценами. У столов это размер, у стульев — ткань. Выбор в ней обязателен и всегда единственный.

"base_group": {
  "id": "size",
  "title": "Выберите Размер, мм (высота - 750)",
  "options": [
    {
      "id": "1000",
      "label": "⌀ 1000",
      "price": 194000,
      "dimensions": {
        "shape": "round",
        "diameter_mm": 1000,
        "diameter_extended_mm": null,
        "width_mm": null,
        "width_extended_mm": null,
        "depth_mm": null,
        "height_mm": 750,
        "height_options_mm": [],
        "parsed": true
      },
      "price_matrix": {
        "materials": { "derevo": 0, "keramika": 40000, "keramika_v_dereve": 84000 }
      },
      "option_prices": {
        "vtoroy_vraschayuschiysya_uroven": 35000,
        "okras_podstolya": 15000
      }
    }
  ]
}

price_matrix — надбавки за варианты из delta_groups, уже разрешённые под этот конкретный базовый вариант. Ключ первого уровня — id группы, второго — id варианта. На сайте надбавка за материал зависит от размера (керамика в дереве стоит 84 000 ₽ на диаметре 1000 и 106 000 ₽ на 1600), поэтому матрица своя у каждого базового варианта.

option_prices — то же самое для опций-галочек.

delta_groups описывают только состав и подписи; цены в них не дублируются.

"delta_groups": [
  {
    "id": "materials",
    "title": "Выберите материал столешницы",
    "options": [
      { "id": "derevo", "label": "Дерево", "swatches": [] },
      { "id": "keramika", "label": "Керамика", "swatches": [] }
    ]
  }
]

Возможные id групп: materials (материал столешницы или ткань), facade (материал фасада, встречается у 8 товаров).

extras — опции с множественным выбором. Поле price в них годится для показа по умолчанию; фактическая цена берётся из option_prices выбранного базового варианта.

Расчёт цены

итог = base_group.options[b].price
     + Σ по выбранным группам g:  price_matrix[g][выбранный вариант]
     + Σ по отмеченным опциям o:  option_prices[o]

Проверка на ELIO: диаметр 1600, керамика в дереве, обе опции — 373000 + 106000 + 35000 + 15000 = 529000 ₽.

Готовая реализация на Swift — в IOS.md.

Габариты

dimensions заполняется по мере распознавания; parsed говорит, удалось ли извлечь что-то пригодное для дополненной реальности.

Поле Когда заполняется
shape round, rect или other
diameter_mm Круглые столешницы
diameter_extended_mm Раздвижные круглые: второй диаметр
width_mm, depth_mm Прямоугольные
width_extended_mm Раздвижные: ширина в разложенном виде
height_mm Высота; для стульев — общая высота изделия
height_options_mm Несколько высот, например [600, 700]

Габариты распознаны у 485 вариантов из 495. Оставшиеся 10 — это четыре товара без конфигуратора и два барных стула, у которых размеров нет на самом сайте. Проверяйте parsed перед тем, как строить геометрию.

Предупреждения о данных

price_warnings содержит замечания к качеству исходных данных. Показывать их пользователю не нужно, но при отладке они экономят время.

Текст Товары
цена на сайте не указана lazy-susan, zoe, alice-mirror, amelie
подозрительная цена ... на фоне максимума ... harry

У harry размер 2400 мм стоит 35 900 ₽ при соседних 325 000 и 392 000 ₽ — на сайте потеряна цифра. Значение оставлено как в источнике.

Отделки

"finishes": [
  {
    "part": "Столешница",
    "category": "Керамика - 1 категория",
    "items": [
      { "id": "bianco_michelangelo", "label": "Bianco Michelangelo",
        "image": "/media/_finishes/5-bianco-michelangelo-1-400.webp" }
    ]
  }
]

part — часть изделия (Столешница, Подстолье), category — серия отделки. Изображения образцов общие для всех товаров и лежат в /media/_finishes/, поэтому кэшируются один раз на всё приложение.


GET /v1/categories

Категории с числом товаров. Пустые категории не возвращаются.

[
  { "id": "tables", "title": "Столы", "count": 43 },
  { "id": "coffee_tables", "title": "Журнальные столики", "count": 17 },
  { "id": "tables_sliding", "title": "Раздвижные столы", "count": 9 }
]

Полный список: tables, tables_sliding, chairs, bar_stools, coffee_tables, consoles, mirrors, accessories, commodes, shelving, cabinets, desks.


GET /v1/materials

Сводный каталог отделок по всем товарам. Один образец встречается у многих изделий, поэтому записи объединены, а used_in перечисляет товары.

Параметры

Параметр Тип Описание
category string Фильтр по серии отделки
[
  {
    "id": "blek",
    "label": "Блэк",
    "category": "Бизнес - Серия",
    "image": "/media/_finishes/1_black-400.webp",
    "used_in": ["james-2", "mellow", "eric"]
  }
]

221 запись в 17 сериях.


Статические файлы

Раздаются Caddy напрямую с диска, минуя приложение.

Изображения

/media/{slug}/{n}-{размер}.webp     фото товара
/media/_finishes/{имя}-400.webp     образец отделки

Размеры: 400, 1200, 2000 пикселей по длинной стороне, формат WebP. Образцы отделок только в 400.

Пути приходят готовыми в полях thumb, detail, full — собирать их вручную не нужно.

3D-модели

/models/{slug}.usdz
/models/{slug}.glb

Отдаются с типом model/vnd.usdz+zip и model/gltf-binary соответственно. Первый обязателен, иначе iOS не откроет AR Quick Look.

Наличие модели проверяется на диске в момент запроса, поэтому новый файл подхватывается без перезапуска сервиса. Пока файла нет, has_ar равно false и model_url равно null.