openapi: 3.1.0
info:
  title: scrapix.ru API
  version: "1.0"
  description: |
    REST API данных авторынков и каталогов-справочников Азии (Корея, Китай, Япония).

    **Три отдельных продукта, каждый со своей подпиской** (доступ по категории ключа):
    - `korea` — живые объявления Кореи + каталог-справочник Кореи
    - `china` — живые объявления Китая + каталог-справочник Китая
    - `japan` — каталог-справочник Японии

    Ключ от одной страны НЕ открывает другую (вернётся `403`). Лоты отдаются от **2010 года** и новее.

    ### Доступ
    Все `/v1/*` (кроме `/v1/health`) требуют заголовок `X-API-Key`. Лимиты: запросов/мин и запросов/сутки
    (при превышении — `429`). Ответы кэшируются на стороне сервера и отдают `ETag` — присылайте
    `If-None-Match` и обрабатывайте `304`.

    ### Цены и валюты
    Лоты: `price.amount` в валюте страны (KRW/CNY), `price.rub` сейчас `null` (считайте по курсу у себя).
    Каталог: `msrp` (рекоменд. цена нового авто) в нац. валюте.
servers:
  - url: https://api.scrapix.ru
    description: Боевой сервер

security:
  - ApiKeyAuth: []

tags:
  - name: Служебное
  - name: Корея — лоты
  - name: Китай — лоты
  - name: Корея — каталог
  - name: Китай — каталог
  - name: Япония — каталог
  - name: Калькулятор растаможки

paths:
  /v1/health:
    get:
      tags: [Служебное]
      summary: Проверка доступности (без ключа)
      security: []
      responses:
        "200":
          description: Сервис жив
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: ok }

  /v1/korea/lots:
    get:
      tags: [Корея — лоты]
      summary: Список лотов Кореи
      parameters: [ $ref: "#/components/parameters/make", $ref: "#/components/parameters/model",
        $ref: "#/components/parameters/fuel", $ref: "#/components/parameters/body_type",
        $ref: "#/components/parameters/transmission", $ref: "#/components/parameters/drive",
        $ref: "#/components/parameters/year_min", $ref: "#/components/parameters/year_max",
        $ref: "#/components/parameters/price_min", $ref: "#/components/parameters/price_max",
        $ref: "#/components/parameters/mileage_max", $ref: "#/components/parameters/q",
        $ref: "#/components/parameters/sort", $ref: "#/components/parameters/limit",
        $ref: "#/components/parameters/offset" ]
      responses:
        "200": { $ref: "#/components/responses/LotList" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /v1/korea/lots/tree:
    get:
      tags: [Корея — лоты]
      summary: Дерево «марка → модель» со счётчиками
      responses:
        "200": { $ref: "#/components/responses/LotsTree" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /v1/korea/lots/changes:
    get:
      tags: [Корея — лоты]
      summary: Дельта изменений (новые/изменённые + ушедшие)
      description: |
        Инкрементальная синхронизация: один раз сделайте bootstrap (полный список), запомните момент старта,
        далее периодически запрашивайте `changes?since=<этот момент>`. `as_of` — НЕПРОЗРАЧНЫЙ курсор для
        следующего запроса: передавайте его в `since` ВЕРБАТИМ (не парсите/не округляйте — у него микросекунды).
        `lots` — добавленные/изменённые карточки, `removed` — id ушедших с продажи (окно надгробий ~5 дней).
        Ответ `no-store` (не кэшируется). При `truncated=true` повторите с новым `since=as_of`.
        Обрабатывайте весь массив: страница обычно ≤ `limit`, но группа правок с одинаковой меткой времени
        может прийти целиком (больше `limit`), чтобы курсор не потерял её хвост.
      parameters: [ $ref: "#/components/parameters/since", $ref: "#/components/parameters/limit_changes" ]
      responses:
        "200": { $ref: "#/components/responses/Changes" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
  /v1/korea/lots/{lot_id}:
    get:
      tags: [Корея — лоты]
      summary: Карточка лота Кореи (полная, с историей)
      parameters: [ $ref: "#/components/parameters/lot_id" ]
      responses:
        "200":
          description: Лот
          content:
            application/json:
              schema: { $ref: "#/components/schemas/LotDetail" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/Validation" }

  /v1/china/lots:
    get:
      tags: [Китай — лоты]
      summary: Список лотов Китая
      parameters: [ $ref: "#/components/parameters/make", $ref: "#/components/parameters/model",
        $ref: "#/components/parameters/fuel", $ref: "#/components/parameters/body_type",
        $ref: "#/components/parameters/transmission", $ref: "#/components/parameters/drive",
        $ref: "#/components/parameters/year_min", $ref: "#/components/parameters/year_max",
        $ref: "#/components/parameters/price_min", $ref: "#/components/parameters/price_max",
        $ref: "#/components/parameters/mileage_max", $ref: "#/components/parameters/q",
        $ref: "#/components/parameters/sort", $ref: "#/components/parameters/limit",
        $ref: "#/components/parameters/offset" ]
      responses:
        "200": { $ref: "#/components/responses/LotList" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /v1/china/lots/tree:
    get:
      tags: [Китай — лоты]
      summary: Дерево «марка → модель» со счётчиками
      responses:
        "200": { $ref: "#/components/responses/LotsTree" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /v1/china/lots/changes:
    get:
      tags: [Китай — лоты]
      summary: Дельта изменений (новые/изменённые + ушедшие)
      parameters: [ $ref: "#/components/parameters/since", $ref: "#/components/parameters/limit_changes" ]
      responses:
        "200": { $ref: "#/components/responses/Changes" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
  /v1/china/lots/{lot_id}:
    get:
      tags: [Китай — лоты]
      summary: Карточка лота Китая (VIN маскирован источником, истории нет)
      parameters: [ $ref: "#/components/parameters/lot_id" ]
      responses:
        "200":
          description: Лот
          content:
            application/json:
              schema: { $ref: "#/components/schemas/LotDetail" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/Validation" }

  /v1/japan/moto/lots:
    get:
      tags: [Япония — мото]
      summary: Мото-лоты Японии — список (фильтры make/model/year/price/cc/mileage/q + sort; цена ¥). Детали полей — гайд lots_japan_moto.md
      parameters: [ $ref: "#/components/parameters/make", $ref: "#/components/parameters/model",
        $ref: "#/components/parameters/year_min", $ref: "#/components/parameters/year_max",
        $ref: "#/components/parameters/q", $ref: "#/components/parameters/limit", $ref: "#/components/parameters/offset" ]
      responses:
        "200": { description: "{total,limit,offset,items[]} — см. lots_japan_moto.md", content: { application/json: { schema: { type: object } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
  /v1/japan/moto/lots/tree:
    get:
      tags: [Япония — мото]
      summary: Дерево «марка → модель» (мото)
      responses:
        "200": { description: "{country,tree}", content: { application/json: { schema: { type: object } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /v1/japan/moto/lots/changes:
    get:
      tags: [Япония — мото]
      summary: Дельта мото (изменённые + removed; as_of вербатим в следующий since)
      parameters: [ $ref: "#/components/parameters/since", $ref: "#/components/parameters/limit_changes" ]
      responses:
        "200": { description: "{as_of,truncated,lots[],removed[]}", content: { application/json: { schema: { type: object } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
  /v1/japan/moto/lots/{lot_id}:
    get:
      tags: [Япония — мото]
      summary: Карточка мото-лота (ТТХ + condition_scores[5] + обложка/галерея на нашем pic.scrapix.ru; осмотр/видео — часть на CDN, часть внешний источник)
      parameters: [ $ref: "#/components/parameters/lot_id" ]
      responses:
        "200": { description: "Лот — см. lots_japan_moto.md", content: { application/json: { schema: { type: object } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/Validation" }

  /v1/korea/catalog:
    get:
      tags: [Корея — каталог]
      summary: Каталог-справочник Кореи (список)
      parameters: [ $ref: "#/components/parameters/make", $ref: "#/components/parameters/model",
        $ref: "#/components/parameters/model_type", $ref: "#/components/parameters/fuel",
        $ref: "#/components/parameters/body_type", $ref: "#/components/parameters/year_min",
        $ref: "#/components/parameters/year_max", $ref: "#/components/parameters/q",
        $ref: "#/components/parameters/limit", $ref: "#/components/parameters/offset" ]
      responses:
        "200": { $ref: "#/components/responses/CatalogList" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
  /v1/korea/catalog/tree:
    get:
      tags: [Корея — каталог]
      summary: Дерево «марка → модель»
      responses:
        "200": { $ref: "#/components/responses/CatalogTree" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /v1/korea/catalog/{card_id}:
    get:
      tags: [Корея — каталог]
      summary: Карточка комплектации (полные ТТХ)
      parameters: [ $ref: "#/components/parameters/card_id" ]
      responses:
        "200":
          description: Комплектация
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CatalogDetail" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/Validation" }

  /v1/china/catalog:
    get:
      tags: [Китай — каталог]
      summary: Каталог-справочник Китая (список)
      parameters: [ $ref: "#/components/parameters/make", $ref: "#/components/parameters/model",
        $ref: "#/components/parameters/model_type", $ref: "#/components/parameters/fuel",
        $ref: "#/components/parameters/body_type", $ref: "#/components/parameters/year_min",
        $ref: "#/components/parameters/year_max", $ref: "#/components/parameters/q",
        $ref: "#/components/parameters/limit", $ref: "#/components/parameters/offset" ]
      responses:
        "200": { $ref: "#/components/responses/CatalogList" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
  /v1/china/catalog/tree:
    get:
      tags: [Китай — каталог]
      summary: Дерево «марка → модель»
      responses:
        "200": { $ref: "#/components/responses/CatalogTree" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /v1/china/catalog/{card_id}:
    get:
      tags: [Китай — каталог]
      summary: Карточка комплектации (полные ТТХ)
      parameters: [ $ref: "#/components/parameters/card_id" ]
      responses:
        "200":
          description: Комплектация
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CatalogDetail" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/Validation" }

  /v1/catalog:
    get:
      tags: [Япония — каталог]
      summary: Каталог-справочник Японии (список)
      description: Поддерживает фильтр `country=JAPAN|CHINA` в рамках этого фида (по умолчанию весь фид каталога Японии).
      parameters: [ $ref: "#/components/parameters/make", $ref: "#/components/parameters/model",
        $ref: "#/components/parameters/model_type", $ref: "#/components/parameters/country",
        $ref: "#/components/parameters/fuel", $ref: "#/components/parameters/body_type",
        $ref: "#/components/parameters/year_min", $ref: "#/components/parameters/year_max",
        $ref: "#/components/parameters/q", $ref: "#/components/parameters/limit",
        $ref: "#/components/parameters/offset" ]
      responses:
        "200": { $ref: "#/components/responses/CatalogList" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
  /v1/catalog/tree:
    get:
      tags: [Япония — каталог]
      summary: Дерево «страна → марка → модель»
      responses:
        "200":
          description: Дерево навигации
          content:
            application/json:
              schema:
                type: object
                properties:
                  tree:
                    type: object
                    additionalProperties:
                      type: object
                      additionalProperties: { $ref: "#/components/schemas/TreeMake" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
  /v1/catalog/{card_id}:
    get:
      tags: [Япония — каталог]
      summary: Карточка комплектации (полные ТТХ)
      parameters: [ $ref: "#/components/parameters/card_id" ]
      responses:
        "200":
          description: Комплектация
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CatalogDetail" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "422": { $ref: "#/components/responses/Validation" }

  /v1/japan/calculator:
    get:
      tags: [Калькулятор растаможки]
      summary: Расчёт «под ключ» — Япония (растаможка + расходы компании)
      description: |
        Оценочный расчёт для физлица. **Часть A (гос-платежи, по курсу ЦБ):** пошлина + утильсбор +
        таможенный сбор; для электромобилей вместо пошлины — 15% + акциз + НДС 22% (режим СТП).
        **Часть B (расходы компании, по курсу коммерческого банка ATB):** расходы в стране, комиссия,
        фрахт, оформление/брокер, доставка. Итог — `under_key_rub` (= A + B).
        Санкционность (цена > 6 млн ¥ / объём > 1900 см³ / гибрид / электро) определяется автоматически
        (можно переопределить `sanctioned`) и добавляет строку «доставка санкционного авто», если она
        задана в тарифах компании. Льготный утильсбор применяется при `personal=1` и `first_car=1`.
      parameters: [ $ref: "#/components/parameters/calcPrice", $ref: "#/components/parameters/calcYear",
        $ref: "#/components/parameters/calcEngineCc", $ref: "#/components/parameters/calcPower",
        $ref: "#/components/parameters/calcFuel", $ref: "#/components/parameters/calcPersonal",
        $ref: "#/components/parameters/calcFirstCar", $ref: "#/components/parameters/calcDate",
        $ref: "#/components/parameters/calcSanctioned" ]
      responses:
        "200":
          description: Расчёт «под ключ»
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CalcResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /v1/china/calculator:
    get:
      tags: [Калькулятор растаможки]
      summary: Расчёт «под ключ» — Китай (растаможка + расходы компании)
      description: |
        Как Япония. Отличия: цена в CNY; банк для расходов компании — ATB; санкционных ограничений нет.
        Формула гос-платежей идентична (закон РФ зависит от ТТХ, не от страны).
      parameters: [ $ref: "#/components/parameters/calcPrice", $ref: "#/components/parameters/calcYear",
        $ref: "#/components/parameters/calcEngineCc", $ref: "#/components/parameters/calcPower",
        $ref: "#/components/parameters/calcFuel", $ref: "#/components/parameters/calcPersonal",
        $ref: "#/components/parameters/calcFirstCar", $ref: "#/components/parameters/calcDate",
        $ref: "#/components/parameters/calcSanctioned" ]
      responses:
        "200":
          description: Расчёт «под ключ»
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CalcResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /v1/korea/calculator:
    get:
      tags: [Калькулятор растаможки]
      summary: Расчёт «под ключ» — Корея (растаможка + расходы компании)
      description: |
        Как Япония. Отличия: цена в KRW; санкционных ограничений нет. Формула гос-платежей идентична.
        ⚠️ Валютные строки расходов компании в KRW временно конвертируются по курсу ЦБ (коммерческий
        источник по воне обновляется).
      parameters: [ $ref: "#/components/parameters/calcPrice", $ref: "#/components/parameters/calcYear",
        $ref: "#/components/parameters/calcEngineCc", $ref: "#/components/parameters/calcPower",
        $ref: "#/components/parameters/calcFuel", $ref: "#/components/parameters/calcPersonal",
        $ref: "#/components/parameters/calcFirstCar", $ref: "#/components/parameters/calcDate",
        $ref: "#/components/parameters/calcSanctioned" ]
      responses:
        "200":
          description: Расчёт «под ключ»
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CalcResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "422": { $ref: "#/components/responses/Validation" }
        "429": { $ref: "#/components/responses/RateLimited" }

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Ключ вида `sk_…`. Доступ к категории определяется подпиской (иначе 403).

  parameters:
    make: { name: make, in: query, schema: { type: string, maxLength: 40 }, description: "Марка (точное совпадение, регистронезависимо)" }
    model: { name: model, in: query, schema: { type: string, maxLength: 60 }, description: "Модель (точное совпадение)" }
    model_type: { name: model_type, in: query, schema: { type: string, maxLength: 40 }, description: "Код кузова 型式 (точный матч комплектации)" }
    country: { name: country, in: query, schema: { type: string, enum: [JAPAN, CHINA] }, description: "Фильтр фида каталога по стране" }
    fuel: { name: fuel, in: query, schema: { type: string, maxLength: 20 }, description: "Топливо: RU-ярлык (Бензин/Дизель/Гибрид/Электро…) или код (petrol/diesel/hybrid/ev…)" }
    body_type: { name: body_type, in: query, schema: { type: string, maxLength: 40 }, description: "Тип кузова (RU-ярлык; у лотов — точное русское значение)" }
    transmission: { name: transmission, in: query, schema: { type: string, maxLength: 20 }, description: "КПП: RU-ярлык или код (auto/manual/cvt)" }
    drive: { name: drive, in: query, schema: { type: string, maxLength: 20 }, description: "Привод: RU-ярлык или код (fwd/rwd/awd/4wd)" }
    year_min: { name: year_min, in: query, schema: { type: integer, minimum: 1900, maximum: 2100 }, description: "Год от" }
    year_max: { name: year_max, in: query, schema: { type: integer, minimum: 1900, maximum: 2100 }, description: "Год до" }
    price_min: { name: price_min, in: query, schema: { type: number, minimum: 0 }, description: "Цена от (в валюте страны)" }
    price_max: { name: price_max, in: query, schema: { type: number, minimum: 0 }, description: "Цена до (в валюте страны)" }
    mileage_max: { name: mileage_max, in: query, schema: { type: integer, minimum: 0 }, description: "Пробег до, км" }
    q: { name: q, in: query, schema: { type: string, maxLength: 80 }, description: "Поиск по марке/модели/грейду" }
    sort:
      name: sort
      in: query
      description: Сортировка (по умолчанию date_desc — свежие сверху)
      schema:
        type: string
        enum: [date_desc, price_asc, price_desc, year_desc, year_asc, mileage_asc, mileage_desc]
    limit: { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 24 }, description: "Размер страницы" }
    offset: { name: offset, in: query, schema: { type: integer, minimum: 0, maximum: 100000, default: 0 }, description: "Смещение" }
    limit_changes: { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 1000, default: 500 }, description: "Размер страницы дельты" }
    since: { name: since, in: query, required: true, schema: { type: string, format: date-time }, description: "Момент, с которого нужны изменения (ISO 8601)" }
    lot_id: { name: lot_id, in: path, required: true, schema: { type: string, pattern: "^[0-9]+$" }, description: "Числовой id лота" }
    card_id: { name: card_id, in: path, required: true, schema: { type: string, pattern: "^[0-9A-Za-z_-]+$" }, description: "id комплектации каталога" }
    calcPrice: { name: price, in: query, required: true, schema: { type: number, minimum: 1 }, description: "Цена авто в валюте страны (JPY/CNY/KRW)" }
    calcYear: { name: year, in: query, required: true, schema: { type: integer, minimum: 1980, maximum: 2100 }, description: "Год выпуска (влияет на ставку пошлины и утиля)" }
    calcEngineCc: { name: engine_cc, in: query, schema: { type: integer, minimum: 0, maximum: 10000, default: 0 }, description: "Объём двигателя, см³ (для электромобилей 0)" }
    calcPower: { name: power, in: query, required: true, schema: { type: integer, minimum: 0, maximum: 3000 }, description: "Мощность, л.с. (акциз для EV, порог утиля)" }
    calcFuel: { name: fuel, in: query, required: true, schema: { type: string, enum: [petrol, diesel, hybrid, ev] }, description: "Топливо: petrol/diesel/hybrid (power-split, ДВС ведущий) / ev (чистый электро)" }
    calcPersonal: { name: personal, in: query, schema: { type: string, enum: ["0","1","true","false"], default: "1" }, description: "Личное пользование → льготный утильсбор. Дефолт да" }
    calcFirstCar: { name: first_car, in: query, schema: { type: string, enum: ["0","1","true","false"], default: "1" }, description: "Первый ввоз авто в этом году (условие льготного утиля). Дефолт да" }
    calcDate: { name: date, in: query, schema: { type: string, pattern: "^\\d{4}-\\d{2}-\\d{2}$" }, description: "Дата для курса валют (по умолчанию — последний доступный)" }
    calcSanctioned: { name: sanctioned, in: query, schema: { type: string, enum: ["0","1","true","false"] }, description: "Переопределить авто-детект санкционности (иначе по ТТХ/цене)" }

  responses:
    LotList:
      description: Страница лотов
      content:
        application/json:
          schema:
            type: object
            properties:
              total: { type: integer }
              limit: { type: integer }
              offset: { type: integer }
              items: { type: array, items: { $ref: "#/components/schemas/LotCard" } }
    CatalogList:
      description: Страница каталога
      content:
        application/json:
          schema:
            type: object
            properties:
              total: { type: integer }
              limit: { type: integer }
              offset: { type: integer }
              items: { type: array, items: { $ref: "#/components/schemas/CatalogCard" } }
    Changes:
      description: Дельта изменений
      content:
        application/json:
          schema:
            type: object
            properties:
              as_of: { type: string, format: date-time, description: "Непрозрачный курсор (date-time с микросекундами) — передайте в since ВЕРБАТИМ, без переразбора/округления" }
              truncated: { type: boolean, description: "true → есть ещё, повторите с since=as_of (пока не false)" }
              lots: { type: array, items: { $ref: "#/components/schemas/LotCard" } }
              removed: { type: array, items: { type: integer }, description: "id ушедших с продажи" }
    LotsTree:
      description: Дерево навигации
      content:
        application/json:
          schema:
            type: object
            properties:
              country: { type: string }
              tree:
                type: object
                additionalProperties: { $ref: "#/components/schemas/TreeMake" }
    CatalogTree:
      description: Дерево навигации
      content:
        application/json:
          schema:
            type: object
            properties:
              tree:
                type: object
                additionalProperties: { $ref: "#/components/schemas/TreeMake" }
    Unauthorized:
      description: Нет/неверный ключ
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Forbidden:
      description: Категория не входит в подписку
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { detail: "forbidden: category not in your plan" }
    NotFound:
      description: Не найдено
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    RateLimited:
      description: Превышен лимит (в минуту или дневная квота)
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Validation:
      description: Ошибка валидации параметров
      content: { application/json: { schema: { $ref: "#/components/schemas/ValidationError" } } }

  schemas:
    CalcResult:
      type: object
      description: Расчёт «под ключ» = гос-платежи (A) + расходы компании (B)
      properties:
        country: { type: string, example: JAPAN }
        currency: { type: string, example: JPY }
        regime: { type: string, enum: [ЕТС, СТП], description: "ЕТС — ДВС/гибрид; СТП — электромобиль (15% + акциз + НДС 22%)" }
        sanctioned: { type: boolean }
        sanction_reasons: { type: array, items: { type: string } }
        input:
          type: object
          properties:
            price: { type: number }
            year: { type: integer }
            engine_cc: { type: integer }
            power: { type: integer }
            fuel: { type: string }
        car_price:
          type: object
          description: "Стоимость самого авто в стране покупки → ₽ по курсу КОММЕРЧЕСКОГО банка (не ЦБ: клиент так реально платит за авто)"
          properties:
            native: { type: number, description: "Цена в валюте страны (как во входе)" }
            currency: { type: string }
            rate: { type: number, description: "Курс банка к ₽" }
            rub: { type: number, description: "Стоимость авто в ₽" }
        customs:
          type: object
          description: "Гос-платежи (часть A), ₽ — по курсу ЦБ"
          properties:
            customs_value_rub: { type: number }
            duty_rub: { type: number, description: "Таможенная пошлина" }
            excise_rub: { type: number, description: "Акциз (только EV/СТП, иначе 0)" }
            vat_rub: { type: number, description: "НДС 22% (только EV/СТП, иначе 0)" }
            clearance_fee_rub: { type: number, description: "Таможенный сбор за оформление" }
            util_preferential_rub: { type: number, nullable: true, description: "Льготный утильсбор (если положен)" }
            util_commercial_rub: { type: number, description: "Коммерческий утильсбор" }
            util_applied_rub: { type: number, description: "Применённый утиль (при личном ввозе — льготный)" }
            total_rub: { type: number, description: "ИТОГО гос-платежи (A)" }
        breakdown: { type: array, items: { type: object }, description: "Пошаговая расшифровка расчёта" }
        business:
          type: array
          description: "Строки расходов компании (часть B) — сконвертированы в ₽ по курсу банка"
          items:
            type: object
            properties:
              line_key: { type: string, example: freight, description: "country_expenses/commission/freight/clearance/delivery_city/sanction_delivery" }
              amount: { type: number }
              currency: { type: string }
              rate: { type: number, description: "Курс к ₽ (1 для RUB)" }
              rub: { type: number }
        business_total_rub: { type: number, description: "Сумма B, ₽ (0 если тарифы компании не заданы)" }
        under_key_rub: { type: number, description: "ИТОГО «ПОД КЛЮЧ» = car_price.rub (авто) + customs.total_rub (налоги A) + business_total_rub (расходы B)" }
        business_bank:
          type: object
          properties:
            source: { type: string, example: atb, description: "Коммерческий банк конвертации B" }
            warnings: { type: array, items: { type: string } }
        rates_used:
          type: object
          properties:
            source: { type: string, example: cbr }
            eur_rub: { type: number }
            currency_rub: { type: number }
            date: { type: string }
        disclaimer: { type: string }
    Error:
      type: object
      properties:
        detail: { type: string }
    ValidationError:
      type: object
      properties:
        detail:
          type: array
          items:
            type: object
            properties:
              loc: { type: array, items: { type: string } }
              msg: { type: string }
              type: { type: string }
    TreeMake:
      type: object
      properties:
        count: { type: integer }
        models:
          type: array
          items:
            type: object
            properties:
              model: { type: [string, "null"] }
              count: { type: integer }
    LotPower:
      type: object
      properties:
        kw: { type: [number, "null"] }
        hp: { type: [number, "null"], description: "У Кореи расчётный из кВт" }
    LotPrice:
      type: [object, "null"]
      properties:
        amount: { type: number }
        currency: { type: [string, "null"], description: "KRW | CNY" }
        rub: { type: [number, "null"], description: "Сейчас null — считайте по курсу" }
    LotCard:
      type: object
      properties:
        id: { type: integer }
        country: { type: string, description: "KOREA | CHINA" }
        make: { type: [string, "null"] }
        model: { type: [string, "null"] }
        grade: { type: [string, "null"], description: "Сырая строка комплектации источника" }
        body_type: { type: [string, "null"], description: "Локализован RU" }
        year: { type: [integer, "null"] }
        mileage_km: { type: [integer, "null"] }
        engine_cc: { type: [integer, "null"], description: "0/NULL у электро → null" }
        power: { $ref: "#/components/schemas/LotPower" }
        fuel: { type: [string, "null"] }
        transmission: { type: [string, "null"] }
        drive: { type: [string, "null"] }
        color: { type: [string, "null"] }
        seats: { type: [integer, "null"] }
        had_accident: { type: [boolean, "null"], description: "Была ли ДТП. KR — по гос-истории. CN — по ТИПУ ДЕФЕКТА из отчёта осмотра (true только при следах удара/ремонта: деформация/рихтовка/замена/вмятина/трещина/сварка; ржавчина/вода/тюнинг НЕ считаются — см. inspection_report). true=была, false=чисто, null=НЕ ПРОВЕРЕНО (отчёта нет), НЕ 'без ДТП'" }
        price: { $ref: "#/components/schemas/LotPrice" }
        cover_photo: { type: [string, "null"], description: "URL обложки (наш CDN pic.scrapix.ru). ⚠️ Формат может быть .webp или .jpg — НЕ хардкодьте расширение, берите URL как есть; при 404 перечитайте лот." }
        listed_at: { type: [string, "null"], description: "Дата публикации объявления на площадке, ISO8601. KR — источник (KST, без 'Z'). CN — реальная дата публикации у источника, приведена к UTC (с 'Z'); пока лот не обогащён (свежий стаб) — дата добавления у нас. Есть и в карточке списка, и в детали — можно сортировать/фильтровать «свежесть» без догрузки детали." }
    LotDetail:
      allOf:
        - $ref: "#/components/schemas/LotCard"
        - type: object
          properties:
            right_hand_drive: { type: [boolean, "null"] }
            production_year: { type: [integer, "null"], description: "Год изготовления (вход растаможки)" }
            production_month: { type: [integer, "null"] }
            reg_year: { type: [integer, "null"], description: "Первая регистрация" }
            reg_month: { type: [integer, "null"] }
            vin: { type: [string, "null"], description: "Корея — реальный; Китай — null (полный маскирован источником)" }
            vin_masked: { type: [string, "null"], description: "CN: частично-замаскированный VIN (завод+хвост); KR=null" }
            owner_count: { type: [integer, "null"], description: "Только Корея" }
            accident_cnt: { type: [integer, "null"], description: "Число ДТП из гос-истории. ТОЛЬКО Корея. CN — всегда null (счёт пунктов осмотра вводил в заблуждение; для CN-ДТП см. had_accident + inspection_report)" }
            grade_detail: { type: [string, "null"], description: "Детальный трим (точнее grade)" }
            body_shape: { type: [string, "null"], description: "Уточнённая форма кузова, RU (напр. 'Седан 4-дв.')" }
            title_ru: { type: [string, "null"], description: "Готовое RU-имя марка+модель+грейд" }
            modified_at: { type: [string, "null"], description: "Дата последней модификации лота, ISO (KST). KR only; CN=null" }
            msrp:
              type: [object, "null"]
              description: "Цена новой машины (заводская), KRW"
              properties:
                amount: { type: number }
                currency: { type: string, description: "KRW" }
            equipment:
              type: [array, "null"]
              description: "Оснащение, сгруппировано по категориям (RU), только Корея"
              items:
                type: object
                properties:
                  category: { type: string }
                  items: { type: array, items: { type: string } }
            catalog_specs:
              type: [object, "null"]
              description: "ТТХ (габариты/масса/двигатель). KR — из спец-каталога; CN — из справочника площадки (+doors/segment)"
              properties:
                length_mm: { type: [integer, "null"] }
                width_mm: { type: [integer, "null"] }
                height_mm: { type: [integer, "null"] }
                wheelbase_mm: { type: [integer, "null"], description: "только KR" }
                weight_kg: { type: [integer, "null"] }
                fuel_tank_l: { type: [integer, "null"], description: "только KR" }
                engine_model: { type: [string, "null"] }
                doors: { type: [integer, "null"], description: "только CN" }
                segment: { type: [string, "null"], description: "класс авто, RU; только CN" }
                gearbox: { type: [string, "null"], description: "детальная РУ-расшифровка КПП; только CN" }
            spec:
              type: [array, "null"]
              description: "CN: полный тех.лист по группам (RU), ~66 параметров. KR=null"
              items:
                type: object
                properties:
                  group: { type: string }
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        name: { type: string }
                        value: {}
            emission_standard: { type: [string, "null"], description: "CN: эко-класс Euro N (из 国/欧). KR=null" }
            city: { type: [string, "null"], description: "CN: город продажи (латиница, напр. 'beijing') — ориентир логистики. KR=null" }
            inspection_report:
              type: [object, "null"]
              description: "CN: разобранный блок осмотра кузова (по 车况报告). null если отчёта нет. KR=null (история — в history)"
              properties:
                had_accident: { type: boolean, description: "Есть дефект-УДАР (деформация/рихтовка/замена/вмятина/трещина/резка/сварка). Ржавчина/вода/тюнинг сюда НЕ входят" }
                had_flood: { type: boolean, description: "Следы подтопления" }
                had_fire: { type: boolean, description: "Следы возгорания" }
                damage_points:
                  type: array
                  description: "Выявленные дефекты на русском"
                  items:
                    type: object
                    properties:
                      part: { type: [string, "null"], description: "Точка кузова (RU)" }
                      issue: { type: string, description: "Дефект (RU)" }
                      structural: { type: boolean, description: "Силовой ли элемент" }
            service_book:
              type: [object, "null"]
              description: "CN: сервисная книжка + история страховых (по отчёту площадки). null если данных нет. Поле РЕДКОЕ (~5-7% лотов), но точное. Только в детали /{id}. KR=null"
              properties:
                service_records: { type: [integer, "null"], description: "Число записей ТО в сервисной книжке" }
                last_service_mileage: { type: [integer, "null"], description: "Пробег (км) по последнему ТО" }
                insurance_claims: { type: [integer, "null"], description: "Число страховых случаев (глубина истории ДТП)" }
                odometer_check: { type: [string, "null"], enum: ["ok", "suspect_rollback", null], description: "Сверка одометра: suspect_rollback = последнее ТО было на пробеге БОЛЬШЕ заявленного (признак скрутки); ok = противоречия нет; null = пробег последнего ТО неизвестен" }
            inspection_sheets:
              type: array
              description: "URL сканов листа техосмотра (перёд/зад + страницы листа), только Корея. ПРЯМЫЕ ссылки на внешний источник (НЕ наш pic.scrapix.ru) — открываются напрямую без Referer; проксируйте/кэшируйте у себя."
              items: { type: string }
            history:
              type: [object, "null"]
              description: "Курированная история по гос-отчётам (только Корея; без госномера/PII)"
              properties:
                accidents:
                  type: object
                  properties:
                    mine: { type: integer }
                    other: { type: integer }
                    cost_mine: { type: [number, "null"] }
                    cost_other: { type: [number, "null"] }
                    list:
                      type: array
                      items:
                        type: object
                        properties:
                          date: { type: [string, "null"] }
                          payout: { type: [number, "null"] }
                          parts: { type: [number, "null"] }
                          labor: { type: [number, "null"] }
                          paint: { type: [number, "null"] }
                total_loss: { type: boolean }
                flood: { type: boolean }
                theft: { type: boolean }
                owner_changes: { type: [integer, "null"] }
                owner_change_dates: { type: [array, "null"], items: { type: string }, description: "Даты смен владельцев" }
                plate_changes: { type: [integer, "null"] }
                use_business: { type: boolean }
                use_government: { type: boolean }
                seizure:
                  type: [object, "null"]
                  description: "Юр-чистота: счётчики арестов/залогов (0 = чисто)"
                  properties:
                    arrest: { type: [integer, "null"] }
                    pledge: { type: [integer, "null"] }
                frame_accident: { type: [boolean, "null"] }
                body_repair: { type: [boolean, "null"] }
                inspector_comments: { type: [string, "null"], description: "Замечания оценщика, переведены на RU (контакты удалены); может быть null" }
                inspection_summary:
                  type: [object, "null"]
                  description: "Итоговый вердикт осмотрщика (RU); показывать рядом с inspector_comments"
                  properties:
                    condition: { type: string, description: "Общее состояние (Хорошее/Коррозия/…)" }
                    body: { type: string, description: "Состояние кузова/рамы" }
                    guarantee: { type: string, description: "Кто гарантирует осмотр (Страховая компания/Продавец)" }
                inspection_detail:
                  type: [object, "null"]
                  description: "Данные техосмотра; набор ключей может различаться"
                  properties:
                    odometer_km: { type: [integer, "null"] }
                    recall: { type: [boolean, "null"] }
                    tuning: { type: [boolean, "null"] }
                    flood: { type: [boolean, "null"] }
                    motor_code: { type: [string, "null"] }
                    emission_co: { type: [string, "null"] }
                    emission_hc: { type: [string, "null"] }
                    valid_until: { type: [string, "null"], description: "Дата YYYY-MM-DD" }
                panels:
                  type: array
                  items:
                    type: object
                    properties:
                      part: { type: [string, "null"] }
                      status: { type: [string, "null"] }
                diagnosis:
                  type: array
                  description: "Состояние агрегатов, сгруппировано по системам (RU). Пункты без оценки/дубли отброшены"
                  items:
                    type: object
                    properties:
                      system: { type: string }
                      items:
                        type: array
                        items:
                          type: object
                          properties:
                            unit: { type: string }
                            status: { type: string }
            last_seen_at: { type: [string, "null"], format: date-time }
            updated_at: { type: [string, "null"], format: date-time }
            photos: { type: array, items: { type: string }, description: "Галерея (наш CDN pic.scrapix.ru). ⚠️ Формат .webp/.jpg — не хардкодьте расширение, берите URL как есть; при 404 перечитайте лот." }
    CatalogMsrp:
      type: [object, "null"]
      properties:
        amount: { type: number }
        currency: { type: [string, "null"] }
    CatalogPower:
      type: object
      properties:
        ps: { type: [number, "null"] }
        kw: { type: [number, "null"] }
    CatalogCard:
      type: object
      properties:
        id: { type: string, description: "id комплектации (card_id)" }
        country: { type: string, description: "JAPAN | CHINA | KOREA" }
        make: { type: [string, "null"] }
        model: { type: [string, "null"] }
        model_type: { type: [string, "null"], description: "Код кузова 型式" }
        grade: { type: [string, "null"] }
        body_type: { type: [string, "null"], description: "Локализован RU" }
        year: { type: [integer, "null"] }
        month: { type: [integer, "null"] }
        msrp: { $ref: "#/components/schemas/CatalogMsrp" }
        power: { $ref: "#/components/schemas/CatalogPower" }
        displacement_cc: { type: [integer, "null"] }
        fuel: { type: [string, "null"] }
        drive: { type: [string, "null"] }
        transmission: { type: [string, "null"] }
        seats: { type: [integer, "null"] }
        cover_photo: { type: [string, "null"] }
    CatalogDetail:
      allOf:
        - $ref: "#/components/schemas/CatalogCard"
        - type: object
          properties:
            month_name: { type: [string, "null"] }
            dimensions:
              type: object
              properties:
                length: { type: [integer, "null"] }
                width: { type: [integer, "null"] }
                height: { type: [integer, "null"] }
                wheelbase: { type: [integer, "null"] }
            weight_kg: { type: [integer, "null"] }
            fuel_tank_l: { type: [integer, "null"] }
            engine_model: { type: [string, "null"] }
            engine_type: { type: [string, "null"] }
            torque: { type: [string, "null"] }
            compression_ratio: { type: [string, "null"] }
            bore_stroke: { type: [string, "null"] }
            fuel_injection: { type: [string, "null"] }
            suspension:
              type: object
              properties:
                front: { type: [string, "null"] }
                rear: { type: [string, "null"] }
            brakes:
              type: object
              properties:
                front: { type: [string, "null"] }
                rear: { type: [string, "null"] }
            tires:
              type: object
              properties:
                front: { type: [string, "null"] }
                rear: { type: [string, "null"] }
            track:
              type: object
              properties:
                front: { type: [string, "null"] }
                rear: { type: [string, "null"] }
            clearance: { type: [string, "null"] }
            turning_radius: { type: [string, "null"] }
            fuel_consumption: { type: [string, "null"] }
            equipment:
              type: array
              items:
                type: object
                properties:
                  name: { type: string }
                  value: {}
            spec:
              type: array
              description: "Полный тех-лист (все поля, RU-метки)"
              items:
                type: object
                properties:
                  name: { type: string }
                  value: {}
            photos: { type: array, items: { type: string } }
