AploDelivery Partner API — портал разработчика · v2 (2026-10-16)

Быстрый старт

Работает

Цель этой страницы — первый успешный подписанный запрос за 10 минут. Полная механика подписи и тестовый вектор для сверки своей реализации — в Аутентификации.

Что у вас должно быть заранее

Ключ и секрет храните только на своём backend — см. Безопасность.

Шаг 1. Проверить, что сервис жив

curl -s https://api.aplosnab.ru/health/live
# {"status":"ok"}

Этот запрос не подписывается.

Шаг 2. Первый подписанный запрос — список точек

BASE_URL='https://api.aplosnab.ru'
APLO_API_KEY='ваш-api-key'
APLO_API_SECRET='ваш-hmac-секрет'
TIMESTAMP="$(date +%s)"
REQUEST_ID="$(uuidgen)"
METHOD='GET'
REQ_PATH='/v1/points'
BODY_HASH="$(printf '' | sha256sum | awk '{print $1}')"
CANONICAL="$(printf '%s\n%s\n%s\n%s\n%s' \
  "$TIMESTAMP" "$REQUEST_ID" "$METHOD" "$REQ_PATH" "$BODY_HASH")"
SIGNATURE="$(printf '%s' "$CANONICAL" |
  openssl dgst -sha256 -hmac "$APLO_API_SECRET" -hex |
  sed 's/^.* //')"

curl --fail-with-body "$BASE_URL$REQ_PATH" \
  -H "X-API-Key: $APLO_API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Request-ID: $REQUEST_ID" \
  -H "X-Signature: $SIGNATURE"

Ожидаемый ответ:

{"points":[{"externalPointId":"3","active":true,"streetsSyncedAt":null}]}

streetsSyncedAt: null означает, что словарь улиц для точки ещё ни разу не загружался плагином — это не ошибка, см. Быстрый старт → проверка адреса и order-lifecycle.

Шаг 3. Отфильтровать блюда по точке

Показывайте на сайте только блюда, которые реально смаппены на точку — это снижает риск 422 product_mapping_not_found при создании заказа:

METHOD='GET'
REQ_PATH='/v1/points/3/products'
BODY_HASH="$(printf '' | sha256sum | awk '{print $1}')"
TIMESTAMP="$(date +%s)"
REQUEST_ID="$(uuidgen)"
CANONICAL="$(printf '%s\n%s\n%s\n%s\n%s' \
  "$TIMESTAMP" "$REQUEST_ID" "$METHOD" "$REQ_PATH" "$BODY_HASH")"
SIGNATURE="$(printf '%s' "$CANONICAL" |
  openssl dgst -sha256 -hmac "$APLO_API_SECRET" -hex |
  sed 's/^.* //')"

curl --fail-with-body "$BASE_URL$REQ_PATH" \
  -H "X-API-Key: $APLO_API_KEY" -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Request-ID: $REQUEST_ID" -H "X-Signature: $SIGNATURE"
{"externalPointId":"3","products":[{"externalProductId":"321","kind":"product"}]}

Шаг 4. Проверить адрес до оформления

METHOD='POST'
REQ_PATH='/v1/addresses/check'
BODY='{"externalPointId":"3","street":"Ленина","house":"5","city":"Балашиха"}'
BODY_HASH="$(printf '%s' "$BODY" | sha256sum | awk '{print $1}')"
TIMESTAMP="$(date +%s)"
REQUEST_ID="$(uuidgen)"
CANONICAL="$(printf '%s\n%s\n%s\n%s\n%s' \
  "$TIMESTAMP" "$REQUEST_ID" "$METHOD" "$REQ_PATH" "$BODY_HASH")"
SIGNATURE="$(printf '%s' "$CANONICAL" |
  openssl dgst -sha256 -hmac "$APLO_API_SECRET" -hex |
  sed 's/^.* //')"

curl --fail-with-body "$BASE_URL$REQ_PATH" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $APLO_API_KEY" -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Request-ID: $REQUEST_ID" -H "X-Signature: $SIGNATURE" \
  -d "$BODY"

status в ответе: ok (можно продолжать), ambiguous (попросите гостя выбрать улицу из candidates), not_found (адрес не найден), unknown (словарь ещё не загружен — это НЕ блокирует оформление заказа). Поле house возвращается эхом и не проверяется — в POS-словаре домов нет.

Шаг 5. Создать заказ

Минимальный валидный CanonicalOrder (совпадает с полями openapi.yaml, total обязан точно равняться сумме price.amountMinor × quantity по всем позициям — если это не так, сервер отвечает 400 invalid_request):

{
  "schemaVersion": 1,
  "source": "website",
  "externalOrderId": "site-1042",
  "externalPointId": "3",
  "createdAt": "2026-09-23T10:15:00+03:00",
  "deliverAt": "2026-09-23T11:00:00+03:00",
  "guest": {
    "externalClientId": "c-9001",
    "name": "Иван",
    "phone": "+79261234567"
  },
  "address": {
    "street": "Ленина",
    "house": "5"
  },
  "items": [
    {
      "externalProductId": "321",
      "quantity": "2",
      "price": {"amountMinor": 42000, "currency": "RUB"}
    }
  ],
  "total": {"amountMinor": 84000, "currency": "RUB"}
}

Подпишите POST /v1/orders этим телом (метод и путь в canonical-строке — POST и /v1/orders, без query). Ответ:

Тело ответа (JSON, независимо от языка клиента) — полная проекция заказа с полями id, iikoOrderId, iikoDeliveryId (стабильные UUID, выданные AploDelivery сразу при создании — они существуют с первой секунды, ещё до того, как плагин на кассе увидел заказ), state, deliveryStatus, cancelState и другими — см. Жизненный цикл заказа.

Гоча для Go SDK: Client.Submit (единственный метод для POST /v1/orders) декодирует ответ в узкий тип ingressclient.OrderStatus — там только ID, ExternalOrderID, Source, State, IIKOOrderID, IIKODeliveryID. Поля deliveryStatus/cancelState в JSON присутствуют, но Submit их отбрасывает. Если сразу после создания заказа вам нужны эти поля — прочитайте заказ повторно через Client.GetOrder/Client.FindByExternalOrderID, которые декодируют в полный ingressclient.OrderView.

comment поля в схеме есть у заказа и у каждой позиции, но modifiers для source: website сейчас не используются (сайт ВиЛ не продаёт модификаторы) — не заполняйте это поле в v1.

Шаг 6. Отследить заказ

Основной канал — вебхуки. Резервный — поллинг:

METHOD='GET'
REQ_PATH='/v1/orders'
QUERY='externalOrderId=site-1042'
BODY_HASH="$(printf '' | sha256sum | awk '{print $1}')"
TIMESTAMP="$(date +%s)"
REQUEST_ID="$(uuidgen)"
CANONICAL="$(printf '%s\n%s\n%s\n%s\n%s' \
  "$TIMESTAMP" "$REQUEST_ID" "$METHOD" "$REQ_PATH" "$BODY_HASH")"
SIGNATURE="$(printf '%s' "$CANONICAL" |
  openssl dgst -sha256 -hmac "$APLO_API_SECRET" -hex |
  sed 's/^.* //')"

# подпись строится ТОЛЬКО от REQ_PATH (без query-строки), см. authentication.md;
# query добавляется только к URL самого curl-запроса:
curl --fail-with-body "$BASE_URL$REQ_PATH?$QUERY" \
  -H "X-API-Key: $APLO_API_KEY" -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Request-ID: $REQUEST_ID" -H "X-Signature: $SIGNATURE"

Пример на Go (пакет pkg/ingressclient)

SDK сам строит canonical-строку, подписывает запрос и не следует HTTP-редиректам:

package main

import (
	"context"
	"log"
	"os"

	"aplo-loyalty-delivery/pkg/ingressclient"
)

func main() {
	client, err := ingressclient.New(ingressclient.Options{
		BaseURL: os.Getenv("APLODELIVERY_URL"),
		APIKey:  os.Getenv("APLODELIVERY_API_KEY"),
		Secret:  os.Getenv("APLODELIVERY_API_SECRET"),
		Source:  ingressclient.SourceWebsite,
	})
	if err != nil {
		log.Fatal(err)
	}

	points, err := client.ListPoints(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("available points: %d", len(points))
}

ingressclient.New требует абсолютный HTTPS origin без пути/query/фрагмента (HTTP разрешён только для loopback в тестах), API-ключ ≥ 8 символов и секрет ≥ 32 символов — иначе конструктор вернёт ошибку сразу, без сетевого запроса.

Дальше