{"openapi":"3.1.0","info":{"title":"delivery.gumy.space (platform-delivery)","description":"Расчёт стоимости и геометрии доставки транспортного средства от города отправления до города назначения.\n\n## Контракт\n\nВход — два стабильных идентификатора из `geo.proride.io`:\n- `city_from_geonameid` — GeoNames id города отправления.\n- `city_to_geonameid` — GeoNames id города назначения.\nСтрана выводится из города (через `geo.proride.io/v1/cities/{id}`).\n\n## Что делает\n\n- `/v1/price` — **главный endpoint.** Один запрос → один оптимальный маршрут со всем нужным для отрисовки на карте: координаты концов каждого плеча, способ перевозки (`transport`), русская подпись (`label_ru`), distance_km, стоимость, ETA. Итог округляется **вверх до $100** — точные суммы при ±15% входных данных не имеют смысла.\n- `/v1/route` — устаревший, только геометрия без цены. Оставлен для обратной совместимости. Новые клиенты — берите `/v1/price`.\n\n## Режимы маршрутизации\n\n- **intra_country** — обе точки в одной стране. Один сегмент road. Сейчас поддерживается только `RU` (например, Тамбов → Москва).\n- **cross_border** — разные страны. Hub-and-spoke: `city_from → HUB1 (порт/переход) → HUB2 (вход в РФ) → city_to`. Пустые плечи (`city_from == HUB1` или `HUB2 == city_to`) вылетают из ответа.\n\nПоддерживаемые источники для cross_border: `KR`, `JP`, `CN`, `AE`, `US`, `DE`, `GE`, `TR`, `KZ`. Назначение — только `RU`.\n\n## Способы перевозки (`transport`)\n\n- `sea_roro` — морем (RoRo). Международные плечи с морским хабом.\n- `land_border` — международный сухопутный переход (например, Хоргос).\n- `rail_setka` — ж/д вагон-сетка. Внутренние плечи ≥ 1500 км.\n- `road_carrier` — автовоз. Короткие и средние внутренние плечи.\n\n## Что НЕ делает\n\n- Не считает таможню, утильсбор, НДС, акциз — это задача вызывающего сервиса.\n- Не строит маршрут по дорогам — only great-circle между хабами и точками (через `geo.proride.io/v1/distance`).\n- Не показывает альтернативные маршруты — отдаётся один оптимальный.\n\n## Типы транспорта (`vehicle_type`)\n\nSlug из `api.proride.io` `dictionaries.vehicle_types` (`frontend.ref_vehicle_types`). На сегодня: `car`, `motorcycle`, `atv`, `snowmobile`, `jet_ski`, `truck`, `construction`. Если апстрим добавит новый slug, сервис подхватит его автоматически (валидация при запросе, кэш 24ч). Для слугов без явного мультипликатора возвращается тариф легкового авто + пометка в `notes`.\n\n## Цены\n\n- **International**: фикс per-country (см. `pricing.SOURCES`). Морской фрахт по слотам, расстояние слабо влияет.\n- **Inland**: 2-ступенчатая per-km формула, `< 1000 км → 5000 + 35₽/км`, `≥ 1000 км → 15 000 + 12₽/км`.\n- **Конвертация**: внутренний курс `FX_RUB_PER_USD` (env, по умолчанию 90). Возвращается в ответе для воспроизводимости.","version":"0.5.0"},"paths":{"/health":{"get":{"tags":["meta"],"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/price":{"post":{"tags":["delivery"],"summary":"Price","description":"Один оптимальный маршрут с ценой и геометрией. Готово для отрисовки на карте.\n\nКаждое плечо несёт координаты концов, способ перевозки (`transport`,\n`label_ru`, `mode` для стиля линии), distance_km, RUB-стоимость и ETA.\nИтог округлён вверх до $100.","operationId":"price_v1_price_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PriceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/route":{"post":{"tags":["delivery"],"summary":"Route","description":"Геометрия маршрута для карты: сегменты с координатами и distance_km.","operationId":"route_v1_route_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"CitySchema":{"properties":{"geonameid":{"type":"integer","title":"Geonameid","examples":[1835848]},"name":{"type":"string","title":"Name","examples":["Сеул"]},"country_iso2":{"type":"string","title":"Country Iso2","examples":["KR"]},"lat":{"type":"number","title":"Lat","examples":[37.5665]},"lng":{"type":"number","title":"Lng","examples":[126.978]}},"type":"object","required":["geonameid","name","country_iso2","lat","lng"],"title":"CitySchema"},"DeliveryRequest":{"properties":{"city_from_geonameid":{"type":"integer","exclusiveMinimum":0.0,"title":"City From Geonameid","description":"GeoNames id города отправления (например, 1835848 = Сеул).","examples":[1835848]},"city_to_geonameid":{"type":"integer","exclusiveMinimum":0.0,"title":"City To Geonameid","description":"GeoNames id города назначения (например, 524901 = Москва).","examples":[524901]},"vehicle_type":{"type":"string","title":"Vehicle Type","description":"Slug типа транспорта из `api.proride.io` `dictionaries.vehicle_types` (`frontend.ref_vehicle_types.slug`). Валидируется на каждый запрос против апстрима — если апстрим недоступен, валидация пропускается (fail-open).","default":"car","examples":["car","motorcycle","atv","snowmobile","jet_ski","truck","construction"]}},"type":"object","required":["city_from_geonameid","city_to_geonameid"],"title":"DeliveryRequest"},"GeoPointSchema":{"properties":{"name":{"type":"string","title":"Name","examples":["Пусан"]},"country":{"type":"string","title":"Country","description":"ISO 3166-1 alpha-2.","examples":["KR"]},"lat":{"type":"number","title":"Lat","examples":[35.10168]},"lng":{"type":"number","title":"Lng","examples":[129.03004]},"geonameid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Geonameid","description":"GeoNames id точки.","examples":[1838524]}},"type":"object","required":["name","country","lat","lng"],"title":"GeoPointSchema"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"PriceLeg":{"properties":{"kind":{"type":"string","enum":["feeder","international","domestic","intra_country"],"title":"Kind"},"mode":{"type":"string","enum":["sea","road","rail","air","land"],"title":"Mode","description":"Широкая категория для стиля линии на карте (sea/road/land)."},"transport":{"type":"string","enum":["sea_roro","road_carrier","rail_setka","land_border"],"title":"Transport","description":"Конкретный способ перевозки на этом плече."},"label_ru":{"type":"string","title":"Label Ru","description":"Русская подпись для маркера/тултипа на карте.","examples":["Морем (RoRo)"]},"from_point":{"$ref":"#/components/schemas/GeoPointSchema"},"to_point":{"$ref":"#/components/schemas/GeoPointSchema"},"distance_km":{"type":"number","title":"Distance Km"},"cost_rub":{"type":"integer","title":"Cost Rub","description":"Стоимость плеча в RUB (точная, до округления итога)."},"days_min":{"type":"integer","title":"Days Min"},"days_max":{"type":"integer","title":"Days Max"}},"type":"object","required":["kind","mode","transport","label_ru","from_point","to_point","distance_km","cost_rub","days_min","days_max"],"title":"PriceLeg"},"PriceResponse":{"properties":{"request":{"$ref":"#/components/schemas/DeliveryRequest"},"city_from":{"$ref":"#/components/schemas/CitySchema"},"city_to":{"$ref":"#/components/schemas/CitySchema"},"legs":{"items":{"$ref":"#/components/schemas/PriceLeg"},"type":"array","title":"Legs"},"total_rub_raw":{"type":"integer","title":"Total Rub Raw","description":"Точная сумма по плечам, без округления."},"total_rub":{"type":"integer","title":"Total Rub","description":"Итог в RUB, соответствующий округлённому до $100 вверх итогу в USD."},"total_usd":{"type":"integer","title":"Total Usd","description":"Итог в USD, округлён вверх до ближайших $100."},"fx_rub_per_usd":{"type":"number","title":"Fx Rub Per Usd","description":"Курс RUB/USD, применённый при расчёте. Для воспроизводимости."},"total_days_min":{"type":"integer","title":"Total Days Min"},"total_days_max":{"type":"integer","title":"Total Days Max"},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","default":[]}},"type":"object","required":["request","city_from","city_to","legs","total_rub_raw","total_rub","total_usd","fx_rub_per_usd","total_days_min","total_days_max"],"title":"PriceResponse"},"RouteResponse":{"properties":{"request":{"$ref":"#/components/schemas/DeliveryRequest"},"city_from":{"$ref":"#/components/schemas/CitySchema"},"city_to":{"$ref":"#/components/schemas/CitySchema"},"segments":{"items":{"$ref":"#/components/schemas/RouteSegmentSchema"},"type":"array","title":"Segments"}},"type":"object","required":["request","city_from","city_to","segments"],"title":"RouteResponse"},"RouteSegmentSchema":{"properties":{"kind":{"type":"string","enum":["feeder","international","domestic","intra_country"],"title":"Kind","description":"feeder: city_from→HUB1, international: HUB1→HUB2, domestic: HUB2→city_to, intra_country: city_from→city_to внутри одной страны."},"mode":{"type":"string","enum":["sea","road","rail","air","land"],"title":"Mode"},"from_point":{"$ref":"#/components/schemas/GeoPointSchema"},"to_point":{"$ref":"#/components/schemas/GeoPointSchema"},"distance_km":{"type":"number","title":"Distance Km"}},"type":"object","required":["kind","mode","from_point","to_point","distance_km"],"title":"RouteSegmentSchema"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}