architect/_archive/2025-11-09-marketplace-old/marketplace-mvp/MP1-API-GUIDE.md

🔌 MP1 API GUIDE - Справочник API интеграций

Проект: Marketplace MVP (mp1)
Версия: 2.4.0
Последнее обновление: 2025-11-08


📑 СОДЕРЖАНИЕ

  1. Обзор
  2. Ozon API
  3. СДЭК API
  4. Почта России API
  5. Best Practices
  6. Тестирование API

🎯 ОБЗОР {#обзор}

Интегрированные маркетплейсы

Интегрированные службы доставки


📦 OZON API {#ozon-api}

Документация: https://docs.ozon.ru/api/seller/
Модуль: modules/api/ozon.py
Класс: OzonAPI

Аутентификация

from modules.api.ozon import OzonAPI

api = OzonAPI(
    client_id="123456",
    api_key="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
)

# Тест подключения
if api.test_connection():
    print("✅ Подключено к Ozon API")

Методы продавца

test_connection() → bool

Проверка подключения к API

result = api.test_connection()
# Returns: True/False

get_seller_info() → dict

Получить информацию о продавце

seller = api.get_seller_info()

# Response:
{
    "name": "ООО Рога и Копыта",
    "inn": "1234567890",
    "status": "ENABLED"
}

Методы заказов

fetch_orders(date_from, date_to) → List[dict]

Получить заказы за период

orders = api.fetch_orders(
    date_from="2025-11-01T00:00:00Z",
    date_to="2025-11-08T23:59:59Z"
)

# Response:
[
    {
        "posting_number": "12345-0001-1",
        "order_date": "2025-11-08T10:30:00Z",
        "status": "awaiting_packaging",
        "fulfillment_scheme": "fbs",  # fbo/fbs/realfbs
        "delivery_type": "pvz",  # pvz/courier/kts/post
        "customer": {
            "name": "Иванов Иван",
            "phone": "+79991234567"
        },
        "items": [
            {
                "sku": "SKU-001",
                "name": "Товар",
                "quantity": 2,
                "price": 1500.00
            }
        ],
        "total_amount": 3000.00
    }
]

get_order_details(posting_number) → dict

Получить детали заказа

details = api.get_order_details("12345-0001-1")

start_packing(posting_numbers: List[str]) → dict

Перевести заказы в сборку (для FBS)

result = api.start_packing([
    "12345-0001-1",
    "12345-0002-1"
])

# Response:
{
    "result": [
        {"posting_number": "12345-0001-1", "status": "success"},
        {"posting_number": "12345-0002-1", "status": "success"}
    ]
}

ship_posting(posting_number, tracking_number) → dict

Отметить отгрузку заказа

# Для FBS (трек-номер Ozon)
api.ship_posting(
    posting_number="12345-0001-1",
    tracking_number="OZON-12345678"
)

# Для realFBS (свой трек-номер СДЭК/Почты)
api.ship_posting(
    posting_number="12345-0001-1",
    tracking_number="1234567890123"  # Трек СДЭК
)

cancel_posting(posting_number, cancel_reason_id) → dict

Отменить заказ

api.cancel_posting(
    posting_number="12345-0001-1",
    cancel_reason_id=352  # Нет в наличии
)

Методы документов (FBS)

get_posting_labels(posting_numbers: List[str]) → bytes

Получить этикетки для заказов (PDF)

# Получить этикетки Ozon для FBS
pdf_bytes = api.get_posting_labels([
    "12345-0001-1",
    "12345-0002-1"
])

# Сохранить PDF
with open("labels.pdf", "wb") as f:
    f.write(pdf_bytes)

get_posting_act(date, delivery_service) → bytes

Получить акт приёма-передачи для курьера Ozon (PDF)

pdf_bytes = api.get_posting_act(
    date="2025-11-08",
    delivery_service="ozon"
)

with open("act.pdf", "wb") as f:
    f.write(pdf_bytes)

get_package_list(posting_numbers: List[str]) → bytes

Получить упаковочный лист (PDF)

pdf_bytes = api.get_package_list([
    "12345-0001-1"
])

Методы трекинга (realFBS)

upload_tracking_numbers(data: List[dict]) → dict

Загрузить трек-номера на Ozon (для realFBS)

result = api.upload_tracking_numbers([
    {
        "posting_number": "12345-0001-1",
        "tracking_number": "1234567890123",  # Ваш трек СДЭК
        "delivery_company": "cdek"
    }
])

Методы справочников

get_warehouses() → List[dict]

Получить список складов

warehouses = api.get_warehouses()

# Response:
[
    {
        "id": "12345",
        "name": "Склад Москва",
        "city": "Москва"
    }
]

get_delivery_services() → List[dict]

Получить список служб доставки

services = api.get_delivery_services()

# Response:
[
    {
        "id": "ozon",
        "name": "Ozon Логистика"
    },
    {
        "id": "cdek",
        "name": "СДЭК"
    }
]

get_posting_statuses() → List[dict]

Получить список статусов заказов

statuses = api.get_posting_statuses()

get_categories(language="RU") → List[dict]

Получить список категорий товаров

categories = api.get_categories(language="RU")

Лимиты API


🚚 СДЭК API {#cdek-api}

Документация: https://api-docs.cdek.ru/
Модуль: modules/delivery/cdek.py (в разработке)
Класс: CdekAPI

Планируемая реализация

from modules.delivery.cdek import CdekAPI

api = CdekAPI(
    account="xxxxx",
    secure_password="yyyyy"
)

Методы (планируется)

create_order(order_data: dict) → dict

Создать заказ в СДЭК

order = api.create_order({
    "tariff_code": 136,  # Посылка дверь-дверь
    "sender": {
        "name": "ООО Компания",
        "phones": [{"number": "+79991234567"}]
    },
    "recipient": {
        "name": "Иванов Иван",
        "phones": [{"number": "+79997654321"}]
    },
    "from_location": {
        "code": 44,  # Москва
        "address": "ул. Примерная, д.1"
    },
    "to_location": {
        "code": 270,  # Новосибирск
        "address": "ул. Ленина, д.10, кв.5"
    },
    "packages": [
        {
            "number": "1",
            "weight": 500,  # граммы
            "length": 10,  # см
            "width": 10,
            "height": 10
        }
    ]
})

# Response:
{
    "entity": {
        "uuid": "72753031-a593-11ea-...",
        "cdek_number": "1234567890"
    }
}

get_label(order_uuid: str) → bytes

Получить этикетку СДЭК (PDF)

pdf_bytes = api.get_label(order_uuid="72753031-...")

with open("cdek_label.pdf", "wb") as f:
    f.write(pdf_bytes)

track(order_uuid: str) → dict

Трекинг заказа

status = api.track(order_uuid="72753031-...")

# Response:
{
    "entity": {
        "uuid": "72753031-...",
        "cdek_number": "1234567890",
        "statuses": [
            {
                "code": "CREATED",
                "name": "Создан",
                "date_time": "2025-11-08T10:00:00+03:00"
            }
        ]
    }
}

Тарифы СДЭК (2025)

Код Название Тип
136 Посылка дверь-дверь Курьерская доставка
137 Посылка дверь-склад До ПВЗ
138 Посылка склад-дверь Из ПВЗ курьером
139 Посылка склад-склад ПВЗ → ПВЗ

Sandbox СДЭК

URL: https://api.edu.cdek.ru/v2
Test account: z9GRRu7FxmO53CQ9cFfI6qiy32wpfTkd
Test password: w24JTCv4MnAcuRTx0oHjHLDtyt3I6IBq


📫 ПОЧТА РОССИИ API {#pochta-api}

Документация: https://otpravka.pochta.ru/specification
Модуль: modules/delivery/pochta.py (в разработке)
Класс: PochtaAPI

Планируемая реализация

from modules.delivery.pochta import PochtaAPI

api = PochtaAPI(
    token="your_access_token",
    key="your_api_key"
)

Методы (планируется)

create_order(order_data: dict) → dict

Создать отправление

order = api.create_order({
    "mail-category": "ORDINARY",  # Простое
    "mail-type": "POSTAL_PARCEL",  # Посылка
    "mass": 500,  # граммы
    "recipient": {
        "given-name": "Иван",
        "surname": "Иванов",
        "tel-address": "+79997654321"
    },
    "recipient-address": {
        "index": "630000",
        "region": "Новосибирская обл",
        "place": "Новосибирск",
        "street": "Ленина",
        "house": "10",
        "room": "5"
    }
})

# Response:
{
    "barcode": "12345678901234",
    "tracking-number": "12345678901234"
}

get_label(barcode: str) → bytes

Получить печатную форму (этикетку)

pdf_bytes = api.get_label(barcode="12345678901234")

track(barcode: str) → dict

Трекинг отправления

status = api.track(barcode="12345678901234")

# Response:
{
    "barcode": "12345678901234",
    "operations": [
        {
            "operation-type": "Прием",
            "date": "2025-11-08T10:00:00"
        }
    ]
}

Тарифы Почта России

Тип Код Описание
POSTAL_PARCEL посылка Стандартная посылка
EMS ems Ускоренная доставка
LETTER письмо Письмо

🛠️ BEST PRACTICES {#best-practices}

Обработка ошибок

from modules.api.ozon import OzonAPIError, OzonAuthError

try:
    orders = api.fetch_orders(...)
except OzonAuthError as e:
    logger.error(f"Ошибка авторизации Ozon: {e}")
    # Проверить Client ID и API Key
except OzonAPIError as e:
    logger.error(f"Ошибка Ozon API: {e}")
    # Повторить запрос или сообщить пользователю
except Exception as e:
    logger.error(f"Неожиданная ошибка: {e}")

Retry Logic

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=4, max=10)
)
def fetch_with_retry():
    return api.fetch_orders(...)

Rate Limiting

import time

# Не больше 100 запросов/минуту (Ozon)
requests_per_minute = 100
delay = 60 / requests_per_minute  # 0.6 секунд

for batch in batches:
    api.process(batch)
    time.sleep(delay)

Timeout

# ВСЕГДА указывать timeout
api = OzonAPI(
    client_id=client_id,
    api_key=api_key,
    timeout=10  # 10 секунд
)

Батчинг

# НЕ делать 100 запросов
for posting_number in posting_numbers:
    api.get_order_details(posting_number)  # ❌ Плохо

# Делать 1 запрос с батчем
orders = api.fetch_orders(...)  # ✅ Хорошо (до 100 заказов за раз)

🧪 ТЕСТИРОВАНИЕ API {#тестирование-api}

Sandbox окружения

Ozon Sandbox:
- URL: https://api-seller.ozon.ru/sandbox (если доступно)
- Тестовые ключи: получить в личном кабинете

СДЭК Sandbox:
- URL: https://api.edu.cdek.ru/v2
- Account: z9GRRu7FxmO53CQ9cFfI6qiy32wpfTkd
- Password: w24JTCv4MnAcuRTx0oHjHLDtyt3I6IBq

Почта России Sandbox:
- URL: https://otpravka-api.pochta.ru (sandbox нет, используйте dev ключи)

Юнит-тесты

# tests/test_ozon_api.py
import pytest
from modules.api.ozon import OzonAPI

def test_ozon_connection():
    api = OzonAPI(
        client_id="test_client",
        api_key="test_key"
    )
    result = api.test_connection()
    assert result == True  # или False если тестовые ключи невалидны

def test_fetch_orders():
    api = OzonAPI(...)
    orders = api.fetch_orders(
        date_from="2025-11-01T00:00:00Z",
        date_to="2025-11-08T23:59:59Z"
    )
    assert isinstance(orders, list)
    if orders:
        assert "posting_number" in orders[0]

E2E тесты (Playwright)

// tests/test_realfbs_workflow.js
test('realFBS полный цикл', async ({ page }) => {
  // 1. Загрузка заказов
  await page.goto('http://localhost:8502');
  await page.click('text=Заказы');
  await page.click('text=Загрузить заказы');

  // 2. Проверка что заказ realFBS появился
  await expect(page.locator('text=realfbs')).toBeVisible();

  // 3. Создание заказа в СДЭК
  await page.click('text=Создать в СДЭК');
  await expect(page.locator('text=✅ Заказ создан')).toBeVisible();

  // 4. Загрузка трек-номера
  await expect(page.locator('text=Трек:')).toBeVisible();
});

Последнее обновление: 2025-11-08
Автор: MP1 Team