Доставка и точки самовывоза
Способы доставки и точки самовывоза можно читать и менять, если у приложения есть право на настройки магазина.
Если этого права нет, ответ 403:
{"status":"error","message":"Недостаточно прав для операции"}
Доставка
GET /admin/delivery_variants.json отдаёт все способы доставки магазина, по полю position. GET /admin/delivery_variants/by_ids.json отвечает 404, тело — HTML-страница. Несколько способов читаются общим списком или отдельным GET /admin/delivery_variants/:id.json. Неизвестный id тоже отвечает 404 и HTML.
В общем объекте есть id, position, title, description, type, charge_up_to, inverted, show_zip_code, min_order_sum, payment_delivery_variants, delivery_locations, delivery_time_intervals, created_at, updated_at. У типов с датой доставки рядом лежат show_calendar_in_checkout, delivery_date_required, forbid_weekends, utc_from_address, estimated_delivery_period, order_time_is_later, forbidden_days, show_time_intervals_in_checkout, time_intervals_required.
payment_delivery_variants — связи со способами оплаты. Пока связь не создана, покупатель этот способ оплаты для этой доставки не видит. add_payment_gateways: true при создании привязывает все способы оплаты, которые уже есть в магазине. В ответе этого ключа нет.
min_order_sum в ответе есть. Передача 1000 в теле создания оставляет в объекте null.
Ключ delivery_locations — города и регионы, для которых способ доступен. Локация передаётся массивом delivery_locations_attributes или объектом с числовыми ключами. В каждой локации нужны country и region. city можно передать null: локация остаётся на весь регион. Регион обл Ленинградская при city: null сохраняется этой же строкой. Город без региона отвечает 422:
{"delivery_locations.sanitized_region_name": ["Не заполнены обязательные поля"]}
Неизвестное имя региона сохраняется как передано. Удаление локации — тот же массив с id и _destroy: 1.
У способа «от локации» тарифные зоны живут в delivery_zones. Город зоны задаётся в locations_attributes внутри зоны. delivery_locations_attributes у этого типа пишет отдельный список delivery_locations и зону не меняет.
Тип вне списка, например DeliveryVariant::Unknown или DeliveryVariant::NovaPoshta, отвечает 422. Текст ошибки — отсутствующий перевод not_in_variants_list.
В списке магазина встречаются и другие типы. DeliveryVariant::None тоже хранит price. DeliveryVariant::NoDelivery — способ «доставка не требуется», у него нет полей даты доставки. Точки самовывоза относятся к DeliveryVariant::PickUp, а не к способу с названием «Самовывоз», если у того другой type.
DELETE /admin/delivery_variants/:id.json отвечает 200 и {"status":"ok"}.
Дата и интервалы
У фиксированной доставки, внешней, от локации, от суммы и самовывоза в том же объекте задаются календарь и интервалы времени. Список интервалов пишется строкой time_intervals, пары часов через запятую: 10-14,14-18. В ответе строка уже разобрана в delivery_time_intervals: у каждого интервала time_from, time_to и дни недели. Для такой строки все семь дней приходят true.
Повторная запись time_intervals оставляет уже сохранённые интервалы. Флаги календаря при этом меняются: show_calendar_in_checkout, estimated_delivery_period, forbidden_days, delivery_date_required, forbid_weekends, order_time_is_later, show_time_intervals_in_checkout, time_intervals_required.
Часы вроде 10:00-14:00 отвечают 422:
{"time_intervals": ["Некорректный формат! Пример: 10-14, 14-18, 18-22\n"]}
| Параметр | Описание |
|---|---|
delivery_variant[time_intervals] необязательныйстрока |
Интервалы часов: 10-14,14-18. |
delivery_variant[show_time_intervals_in_checkout] необязательныйлогический |
Показывать интервалы при оформлении. |
delivery_variant[time_intervals_required] необязательныйлогический |
Интервал обязателен. |
delivery_variant[show_calendar_in_checkout] необязательныйлогический |
Показывать календарь даты доставки. |
delivery_variant[delivery_date_required] необязательныйлогический |
Дата доставки обязательна. |
delivery_variant[forbid_weekends] необязательныйлогический |
Запретить выходные. |
delivery_variant[estimated_delivery_period] необязательныйчисло |
Срок в днях. |
delivery_variant[order_time_is_later] необязательныйстрока |
Время отсечки, например 15:00. |
delivery_variant[forbidden_days] необязательныйстрока |
Запрещённые дни: 01.01,09.05. |
POST /admin/delivery_variants.json
Authorization: Basic API_KEY:API_PASSWORD
Accept: application/json
Content-Type: application/json
{
"delivery_variant": {
"type": "DeliveryVariant::PriceDepend",
"title": "docs-api интервалы",
"min_price": 0,
"rules_attributes": [
{"order_price": 1, "price": 100}
],
"show_calendar_in_checkout": true,
"delivery_date_required": true,
"forbid_weekends": true,
"estimated_delivery_period": 2,
"order_time_is_later": "15:00",
"forbidden_days": "01.01,09.05",
"show_time_intervals_in_checkout": true,
"time_intervals_required": true,
"time_intervals": "10-14,14-18"
}
}
Ответ · 201
{
"id": 105,
"type": "DeliveryVariant::PriceDepend",
"show_calendar_in_checkout": true,
"delivery_date_required": true,
"forbid_weekends": true,
"estimated_delivery_period": 2,
"order_time_is_later": "15:00",
"forbidden_days": "01.01,09.05",
"show_time_intervals_in_checkout": true,
"time_intervals_required": true,
"delivery_time_intervals": [
{
"time_from": "10:00",
"time_to": "14:00",
"monday": true,
"tuesday": true,
"wednesday": true,
"thursday": true,
"friday": true,
"saturday": true,
"sunday": true
},
{
"time_from": "14:00",
"time_to": "18:00",
"monday": true,
"tuesday": true,
"wednesday": true,
"thursday": true,
"friday": true,
"saturday": true,
"sunday": true
}
]
}
Фиксированная
DeliveryVariant::Fixed требует price. Цена ноль и больше допустима. charge_up_to — сумма товаров, начиная с которой доставка бесплатна.
| Параметр | Описание |
|---|---|
delivery_variant[type] обязательныйстрока |
DeliveryVariant::Fixed. |
delivery_variant[title] обязательныйстрока |
Название в чекауте и в бэк-офисе. |
delivery_variant[price] обязательныйчисло |
Цена доставки. Ноль и больше. |
delivery_variant[description] необязательныйстрока |
Описание. Сохраняется как переданный текст. |
delivery_variant[charge_up_to] необязательныйчисло |
Сумма товаров, с которой доставка бесплатна. |
delivery_variant[add_payment_gateways] необязательныйлогический |
Привязать все текущие способы оплаты. |
delivery_variant[delivery_locations_attributes] необязательныймассив |
Локации. У элемента country и region. |
POST /admin/delivery_variants.json
Authorization: Basic API_KEY:API_PASSWORD
Accept: application/json
Content-Type: application/json
{
"delivery_variant": {
"type": "DeliveryVariant::Fixed",
"title": "docs-api фиксированная",
"price": 350,
"description": "По городу",
"charge_up_to": "5000.0",
"add_payment_gateways": true,
"delivery_locations_attributes": [
{"country": "RU", "region": "Москва", "city": "Москва"}
]
}
}
Ответ · 201
{
"id": 49,
"position": 3,
"type": "DeliveryVariant::Fixed",
"title": "docs-api фиксированная",
"description": "По городу",
"price": "350.0",
"charge_up_to": "5000.0",
"inverted": false,
"show_zip_code": false,
"min_order_sum": null,
"javascript": null,
"payment_delivery_variants": [
{"id": 4, "payment_gateway_id": 6101770}
],
"delivery_locations": [
{"id": 1, "country": "RU", "region": "Москва", "city": "Москва"}
],
"delivery_time_intervals": []
}
Без price ответ 422, тело {"price":["Не заполнены обязательные поля"]}.
Внешняя
DeliveryVariant::External считает стоимость на адресе url. api_version равен v2 или остаётся пустым. Пустое значение запускает расчёт первой версии. В объекте ответа этого ключа нет, поэтому переданное v2 хранят у себя. javascript сохраняется и возвращается строкой. customer_pickup тоже есть в ответе.
Адрес http://127.0.0.1/... отвечает 422, тело {"url":["Недопустимый URL"]}.
| Параметр | Описание |
|---|---|
delivery_variant[type] обязательныйстрока |
DeliveryVariant::External. |
delivery_variant[title] обязательныйстрока |
Название способа. |
delivery_variant[url] обязательныйстрока |
Адрес расчёта, http или https, не локальный. |
delivery_variant[api_version] необязательныйстрока |
v2 — вторая версия расчёта. Пусто — первая. В ответ ключ не входит. |
delivery_variant[javascript] необязательныйстрока |
Скрипт способа. Возвращается как передан. |
delivery_variant[customer_pickup] необязательныйлогический |
Самовывоз. Есть в ответе внешней доставки. |
POST /admin/delivery_variants.json
Authorization: Basic API_KEY:API_PASSWORD
Accept: application/json
Content-Type: application/json
{
"delivery_variant": {
"type": "DeliveryVariant::External",
"title": "docs-api внешняя",
"url": "https://example.com/delivery",
"api_version": "v2",
"javascript": "console.log('docs-api')",
"customer_pickup": false
}
}
Ответ · 201
{
"id": 65,
"type": "DeliveryVariant::External",
"title": "docs-api внешняя",
"url": "https://example.com/delivery",
"javascript": "console.log('docs-api')",
"customer_pickup": false,
"payment_delivery_variants": [],
"delivery_locations": [],
"delivery_time_intervals": []
}
Первая версия: магазин делает GET на url и дописывает query. В запросе есть price (сумма товаров со скидкой), weight, region, city, zip, kladr_code, вложенный location и callback=external. Дополнительные поля заказа и адреса в этот запрос не входят. Ответ сервиса — текст, внутри которого есть external(...). В скобках JSON с delivery_price. Ошибка — error или массив errors. Строка Current carrier is not available! помечает способ недоступным. Ожидание ответа — 4 секунды.
Вторая версия: магазин делает POST на url, заголовок Content-Type: application/json; charset=UTF-8, тело {"order": ...}. В order есть account_id, currency_iso_code, delivery_variant_id, warehouse_id, items_price, total_weight, shipping_address (full_locality_name, delivery_address, location) и order_lines (product_id, variant_id, quantity, weight, dimensions). Дополнительных полей заказа в этом теле нет. Ответ — JSON-массив объектов. У тарифа магазин читает price, tariff_id, title, description, delivery_interval, shipping_company, shipping_company_handle, warnings, errors, fields_values, outlet. Пустой массив, не-JSON и HTTP-ошибка означают, что расчёт стоимости не получен. Ожидание ответа — 6 секунд. Если тарифов несколько, при повторном расчёте магазин ищет элемент с тем же tariff_id.
outlet в тарифе второй версии — пункт выдачи: адрес и описание приходят из ответа сервиса. Пустые address и description в заказе означают, что сервис прислал их пустыми.
Выбранный тариф записывается в заказ ключом order[delivery_info_attributes]. В объекте сохраняются tariff_id, price, title, shipping_company_handle, delivery_interval и outlet. В заказе они возвращаются в delivery_info. Создание заказа описано в заказах.
От локации
DeliveryVariant::LocationDepend требует хотя бы одну зону. Пустой способ отвечает 422, тело {"delivery_zones":["не может быть пустым"]}.
У зоны есть title, массив tariffs_attributes и массив locations_attributes. У тарифа обязательны max_weight, price, step и step_price. Без step и step_price ответ 422, у обоих полей текст «Не заполнены обязательные поля» и «не является числом». step и step_price задают доплату за каждый следующий шаг веса после max_weight.
| Параметр | Описание |
|---|---|
delivery_variant[type] обязательныйстрока |
DeliveryVariant::LocationDepend. |
delivery_variant[title] обязательныйстрока |
Название способа. |
delivery_variant[delivery_zones_attributes] обязательныймассив |
Хотя бы одна зона с тарифом и локацией. |
delivery_zones_attributes[][title] необязательныйстрока |
Название зоны. |
delivery_zones_attributes[][tariffs_attributes] необязательныймассив |
Тарифы: max_weight, price, step, step_price. |
delivery_zones_attributes[][locations_attributes] необязательныймассив |
Локации зоны: country, region, city. |
POST /admin/delivery_variants.json
Authorization: Basic API_KEY:API_PASSWORD
Accept: application/json
Content-Type: application/json
{
"delivery_variant": {
"type": "DeliveryVariant::LocationDepend",
"title": "docs-api от локации",
"delivery_zones_attributes": [
{
"title": "Москва",
"tariffs_attributes": [
{"max_weight": 5, "price": 300, "step": 1, "step_price": 50}
],
"locations_attributes": [
{"country": "RU", "region": "Москва", "city": "Москва"}
]
}
]
}
}
Ответ · 201
{
"id": 57,
"type": "DeliveryVariant::LocationDepend",
"title": "docs-api от локации",
"delivery_zones": [
{
"id": 1,
"title": "Москва",
"tariffs": [
{
"id": 1,
"max_weight": "5.0",
"price": "300.0",
"step": "1.0",
"step_price": "50.0"
}
],
"locations": [
{"id": 9, "country": "RU", "region": "Москва", "city": "Москва"}
]
}
]
}
От суммы
DeliveryVariant::PriceDepend требует min_price — цену, когда ни одно правило не подошло. Правила — rules_attributes. У правила order_price строго больше нуля. Ноль отвечает 422, тело {"rules.order_price":["Стоимость заказа должна быть больше нуля"]}. order_weight можно передать вместе с порогом суммы. price правила — цена доставки при достижении порога.
| Параметр | Описание |
|---|---|
delivery_variant[type] обязательныйстрока |
DeliveryVariant::PriceDepend. |
delivery_variant[title] обязательныйстрока |
Название способа. |
delivery_variant[min_price] обязательныйчисло |
Цена, если правило не подошло. Ноль допустим. |
delivery_variant[rules_attributes] необязательныймассив |
Пороги. order_price больше нуля, price — цена доставки, order_weight необязателен. |
POST /admin/delivery_variants.json
Authorization: Basic API_KEY:API_PASSWORD
Accept: application/json
Content-Type: application/json
{
"delivery_variant": {
"type": "DeliveryVariant::PriceDepend",
"title": "docs-api от суммы",
"min_price": 200,
"rules_attributes": [
{"order_price": 1, "price": 500},
{"order_price": 3000, "price": 0}
]
}
}
Ответ · 201
{
"id": 81,
"type": "DeliveryVariant::PriceDepend",
"title": "docs-api от суммы",
"min_price": "200.0",
"rules": [
{"id": 1, "order_price": "1.0", "price": "500.0", "order_weight": null},
{"id": 9, "order_price": "3000.0", "price": "0.0", "order_weight": null}
]
}
Правило с весом возвращает order_weight строкой, например "2.0".
Самовывоз
DeliveryVariant::PickUp создаёт способ точек самовывоза и, если такого ещё нет, способ оплаты PaymentGateway::PickUp с названием «Оплата в точке самовывоза». В теле ответа создания этой оплаты может ещё не быть: её показывает следующий GET того же способа. Точки этого способа читаются и создаются через точки самовывоза.
Точка внешнего пункта передаётся в заказ тем же order[delivery_info_attributes], внутри outlet. Для этого типа у точки нужны latitude и longitude. Без координат ответ 422:
{
"delivery_variant_id": ["Не выбрана точка самовывоза"],
"base": ["Не выбрана точка самовывоза"]
}
С координатами заказ сохраняется, а delivery_info.outlet содержит id, external_id, latitude, longitude, title, address и source_id.
В объекте есть пустые массивы pick_up_sources и pick_up_source_delivery_variants. Источник внешних пунктов — отдельный ресурс приложения.
| Параметр | Описание |
|---|---|
delivery_variant[type] обязательныйстрока |
DeliveryVariant::PickUp. |
delivery_variant[title] обязательныйстрока |
Название способа. |
delivery_variant[add_payment_gateways] необязательныйлогический |
Привязать текущие способы оплаты. |
POST /admin/delivery_variants.json
Authorization: Basic API_KEY:API_PASSWORD
Accept: application/json
Content-Type: application/json
{
"delivery_variant": {
"type": "DeliveryVariant::PickUp",
"title": "docs-api самовывоз",
"add_payment_gateways": true
}
}
Ответ · 201
{
"id": 73,
"type": "DeliveryVariant::PickUp",
"title": "docs-api самовывоз",
"payment_delivery_variants": [
{"id": 5, "payment_gateway_id": 6101770}
],
"pick_up_sources": [],
"pick_up_source_delivery_variants": [],
"delivery_locations": [],
"delivery_time_intervals": []
}
POST /admin/pick_up_sources.json создаёт источник пунктов текущего приложения. После сохранения источник появляется в pick_up_sources способа самовывоза: там есть id, url, point_info_url, method, created_at. В ответе самого создания method нет. Если способа DeliveryVariant::PickUp ещё нет, создание источника добавляет его с названием «Доставка в точки самовывоза» и привязывает уже существующие способы оплаты. Удаление источника этот способ не удаляет.
| Параметр | Описание |
|---|---|
pick_up_source[title] необязательныйстрока |
Название источника. |
pick_up_source[url] необязательныйстрока |
Адрес списка пунктов. Уникален в магазине. http или https, не локальный. |
pick_up_source[point_info_url] необязательныйстрока |
Адрес карточки одного пункта. |
pick_up_source[http_method] необязательныйстрока |
Метод запроса. В способе самовывоза приходит как method. По умолчанию POST. |
POST /admin/pick_up_sources.json
Authorization: Basic API_KEY:API_PASSWORD
Accept: application/json
Content-Type: application/json
{
"pick_up_source": {
"title": "docs-api источник",
"url": "https://example.com/points",
"point_info_url": "https://example.com/point",
"http_method": "POST"
}
}
Ответ · 201
{
"id": 1,
"title": "docs-api источник",
"url": "https://example.com/points",
"point_info_url": "https://example.com/point",
"created_at": "2026-10-21T22:22:12.805+03:00",
"updated_at": "2026-10-21T22:22:12.805+03:00"
}
GET /admin/pick_up_sources.json — источники текущего приложения. DELETE /admin/pick_up_sources/:id.json отвечает 200, тело пустое.
Точки самовывоза
GET /admin/outlets.json отдаёт точки способов DeliveryVariant::PickUp. Пустой список — []. Создание точки само ищет такой способ, а если его нет — создаёт способ с названием «Доставка в точки самовывоза».
Обязательны title, address, price и пара координат latitude, longitude. Цена ноль допустима.
| Параметр | Описание |
|---|---|
outlet[title] обязательныйстрока |
Название точки. |
outlet[address] обязательныйстрока |
Адрес. |
outlet[description] необязательныйстрока |
Как найти точку. |
outlet[price] обязательныйчисло |
Цена выдачи. Ноль допустим. |
outlet[latitude] обязательныйчисло |
Широта. |
outlet[longitude] обязательныйчисло |
Долгота. |
POST /admin/outlets.json
Authorization: Basic API_KEY:API_PASSWORD
Accept: application/json
Content-Type: application/json
{
"outlet": {
"title": "docs-api пункт",
"address": "Москва, ул. Пример, 1",
"description": "Вход со двора",
"price": 0,
"latitude": "55.755800",
"longitude": "37.617300"
}
}
Ответ · 201
{
"id": 1,
"account_id": 233686,
"delivery_variant_id": 73,
"title": "docs-api пункт",
"description": "Вход со двора",
"address": "Москва, ул. Пример, 1",
"price": "0.0",
"latitude": "55.7558",
"longitude": "37.6173",
"title_translations": ["docs-api пункт"],
"description_translations": ["Вход со двора"]
}
Название и описание приходят и строкой, и массивом переводов. PUT с price и description отвечает 200 и обновлённым объектом.
Без адреса и координат ответ 422:
{
"address": ["Не заполнены обязательные поля"],
"coordinates": ["Не валидный формат координаты точки"]
}
Адрес без координат отвечает 422 только по coordinates.
DELETE /admin/outlets/1.json отвечает 200, тело пустое.