API ответственного хранения (Storage API v1)
Storage API — это способ подключить ваш склад на FULEX к внешней WMS или ERP, чтобы не вести топологию, остатки и заявки вручную в кабинете. По смыслу это как API-токен продавца в Wildberries или Ozon: вы выпускаете ключ и даёте внешней системе доступ к данным своего склада.
Интеграция работает сразу на production (https://fulex.pro) — отдельной тестовой среды для интеграторов нет. Чтобы проверить подключение, ничего не сломав, используйте ключ «только чтение» и dry_run=true на изменяющих запросах (см. «Как протестировать, ничего не сломав»). Оплата, оферта, сделка и подпись актов остаются в кабинете FULEX — API их не заменяет.
- Для всех
- Что это и кому нужно
- Что можно и чего нельзя через API
- Быстрый старт за 5 шагов
- Ключ доступа: как получить и хранить
- Как протестировать, ничего не сломав
- Словарь терминов
- Справочник для разработчика
- Базовый URL
- Авторизация и заголовки
- Права (scopes)
- Общие типы данных
- Склад
- Топология (зоны и ячейки)
- Занятость
- Заявки
- Приёмка и отгрузка
- События: polling и webhooks
- Ошибки и повторы
- Полный чек-лист запуска
- Справка
- Частые вопросы (FAQ)
Что это и кому нужно
Небольшой склад удобно вести прямо в кабинете: план, загрузка в %, брони, приёмка и отгрузка — без всякого API. Ничего подключать не нужно.
API нужен, если топология и остатки уже живут во внешней WMS / ERP, и вы хотите, чтобы они автоматически синхронизировались с FULEX. Тогда сотрудник не переносит данные руками, а внешняя система общается с платформой напрямую.
Кому что делать:
- Менеджеру / владельцу склада — прочитать «Что можно и чего нельзя», «Быстрый старт» и «Ключ доступа»: этого хватает, чтобы завести доступ и понять границы.
- Разработчику — «Справочник для разработчика»: базовый URL, заголовки, endpoints, webhooks и ошибки.
Что можно и чего нельзя через API
| Через API можно | Только в кабинете FULEX |
|---|---|
| Импортировать топологию (зоны и ячейки) из WMS | Оплата и оферта |
| Синхронизировать занятость ячеек | Создание сделки и подпись актов |
| Получать адресные заявки, confirm / reject | Отклик на заявку из общего каталога |
| Фиксировать приёмку и отгрузку | Управление ключами и webhook (нужна сессия исполнителя) |
| Получать события (polling и webhooks) | Оспаривание, санкции, поддержка |
API — это про данные склада, а не про деньги. Денежных операций в нём нет, и он не заменяет юридические шаги сделки в кабинете.
Быстрый старт за 5 шагов
Всё выполняется сразу на production. Чтобы пройти путь безопасно, начните с ключа «только чтение» и проверяйте каждый изменяющий запрос через dry_run:
- Кабинет исполнителя → «API и интеграции» → выберите склад → создайте ключ и сразу сохраните его (показывается один раз).
GET /api/v1/storage/facility— убедитесь, что ключ привязан к нужному складу.- Импортируйте небольшую топологию:
topology/import?dry_run=true→ исправьте ошибки → повторите сdry_run=falseи новым Idempotency-Key. - Загрузите занятость (
PUT .../cells/occupancy) и включите чтение событий (polling). - Прогоните тестовую заявку: событие → confirm/reject → приёмка/отгрузка. Сверьте кабинет и WMS.
Когда маппинг сверён на dry_run без ошибок — переключите ключ на полный доступ (или выпустите боевой) и включайте запись. Подробный чек-лист — в разделе «Полный чек-лист запуска».
Ключ доступа: как получить и хранить
Ключ создаётся в кабинете для конкретного склада и определяет, что этому подключению разрешено (см. права/scopes). Один ключ = один склад; на разные склады нужны разные ключи.
- Полное значение ключа показывается один раз при создании. Потерянный ключ посмотреть нельзя — создайте новый (rotate) и отзовите старый (revoke).
- Не кладите ключ в Git, переписку или Postman-коллекцию — только в secret / переменную окружения.
- Ключ даёт доступ только к своему складу. Другие склады исполнителя этим ключом недоступны.
Управление ключами и webhook выполняется под сессией исполнителя (не под Bearer API-ключом):
POST /api/executor/storage/onboarding/api— API-черновик склада + первый ключPOST /api/executor/storage/facilities/{facility_id}/api-keys— новый ключ к существующему складуPOST .../api-keys/{key_id}/rotate·POST .../api-keys/{key_id}/revoke— перевыпуск / отзыв
Как протестировать, ничего не сломав
Отдельной тестовой среды для интеграторов нет — все запросы идут сразу на production. Но проверить подключение можно безопасно, не затрагивая реальные данные склада:
- Ключ «только чтение» — выпустите read-only ключ и прогоните все GET-запросы (склад, ячейки, заявки, события): проверите авторизацию, scopes, маппинг полей и пагинацию без единой записи.
dry_run=true— на изменяющих запросах (импорт топологии, занятость) сервер валидирует тело и возвращает счётчики, но ничего не пишет. Так вы сверяете формат и маппинг ячеек перед реальной загрузкой.- Тест вебхука — из кабинета отправьте событие
storage.webhook.testна ваш URL и проверьте приём и HMAC-подпись. - Локальный симулятор —
scripts/storage_api_simulator.pyповторяет поведение API: стройте и отлаживайте интеграцию у себя локально, затем переключитесь на боевой ключ.
Полный цикл с записью (реальная приёмка/отгрузка) прогоняйте на одной небольшой настоящей операции — уже после того, как read-only и dry_run прошли без ошибок.
Словарь терминов
| Термин | Простыми словами |
|---|---|
| Ключ (Bearer) | Пароль подключения. Передаётся в заголовке Authorization в каждом запросе. |
| Scope (право) | Что конкретному ключу разрешено: только читать или ещё и записывать. |
| external_id | Ваш постоянный ID объекта из WMS (зоны, ячейки). По нему FULEX узнаёт объект при повторной синхронизации. |
| Idempotency-Key | Метка одной операции. Позволяет безопасно повторить запрос, не создав дубль. |
| dry_run | «Проверка вхолостую»: запрос проверяется, но ничего не меняет. Удобно перед реальной записью. |
| Топология | Структура склада: зоны (стеллажи/ряды) и ячейки внутри них. |
| Занятость | Какие ячейки заняты и чьим товаром. |
| Polling | Периодический опрос событий: вы сами запрашиваете «что нового». |
| Webhook | Наоборот: FULEX сам присылает событие на ваш HTTPS-адрес. |
Базовый URL
| Параметр | Значение |
|---|---|
| Base URL | https://fulex.pro |
| Префикс API | /api/v1/storage |
Полный адрес endpoint — это Base URL + префикс + путь, например: https://fulex.pro/api/v1/storage/facility. Отдельного тестового домена/песочницы нет — все запросы идут на production; как проверять безопасно, см. «Как протестировать, ничего не сломав».
Авторизация и заголовки
Во всех внешних запросах к API:
Authorization: Bearer fulex_storage_...
Accept: application/json
Content-Type: application/json
Idempotency-Key: 01JXYZ... ← для всех POST/PUT/PATCH
Передавать executor_id в теле не нужно — склад берётся из ключа. Денежных операций в этом API нет.
Права (scopes)
При создании ключа выбирается набор прав. Пресет «Полный» даёт все scopes ниже; «Только чтение» — без write.
| Scope | Что открывает |
|---|---|
facility:read | Карточка склада |
topology:write | Импорт зон и ячеек |
cells:read | Чтение ячеек и занятости |
cells:write | Синхронизация занятости |
applications:read | Список адресных заявок |
applications:write | Confirm / reject заявки |
events:read | Polling событий |
operations:write | Приёмка и завершение отгрузки |
Общие типы данных
| Тип / правило | Как принимаем |
|---|---|
id (UUID FULEX) | Строка UUID, стабильный идентификатор на стороне платформы |
external_id | Ваш постоянный ID из WMS, строка до 200 символов, непустая. При retry тот же ID — не генерируйте новый UUID на каждый повтор |
| Время | RFC 3339 UTC, например 2026-07-31T09:00:00Z |
| Количества | Целые неотрицательные числа |
| Объём | Число (decimal) в м³ |
| Размеры / вес | Числа в метрах и кг (width_m, depth_m, height_m, max_weight_kg) |
| Списки | Query: limit 1–200, cursor; в ответе next_cursor или null |
dry_run | Query boolean. true — только проверка, без записи. После успеха повторите то же тело с false и новым Idempotency-Key |
Idempotency-Key | Обязателен на изменяющих запросах. Один ключ = одна логическая операция. Тот же ключ + то же тело → безопасный повтор. Тот же ключ + другое тело → 409 |
Склад
GET /api/v1/storage/facility · scope facility:read
Возвращает профиль склада, привязанного к ключу (включая layout и статус витрины). Другие склады исполнителя этим ключом недоступны.
Топология (зоны и ячейки)
Прочитать текущее
GET /api/v1/storage/cells · scope cells:read — зоны с ячейками.
Импорт
POST /api/v1/storage/topology/import?dry_run=true|false · scope topology:write · нужен Idempotency-Key
Режим — replace draft: вы присылаете желаемый полный список. Объект, которого нет в импорте, при применении архивируется, только если он не занят, не в резерве и не в открытой операции — иначе 409 topology_in_use.
Тело запроса (TopologyImport):
{
"zones": [
{
"external_id": "ZONE-A",
"name": "Зона A",
"aisle": "A",
"cells": [
{
"external_id": "A-01-01",
"code": "A-01-01",
"level": "1",
"volume_m3": 1.7,
"width_m": 1.2,
"depth_m": 0.8,
"height_m": 1.8,
"max_weight_kg": 800
}
]
}
]
}
| Поле | Обяз. | Тип | Смысл |
|---|---|---|---|
zones | да | массив | Список зон (стеллажей/рядов) |
zones[].external_id | да | string ≤200 | ID зоны в вашей WMS |
zones[].name | нет | string | Человекочитаемое имя |
zones[].aisle | нет | string | Проход / ряд |
zones[].cells | да | массив | Ячейки зоны |
cells[].external_id | да | string ≤200 | ID ячейки в WMS (уникален в рамках склада) |
cells[].code | нет | string | Код на плане / этикетке |
cells[].level | нет | string | Ярус |
cells[].volume_m3 | нет | number | Полезный объём, м³ |
cells[].width_m / depth_m / height_m | нет | number | Габариты, м (если объём не задан явно) |
cells[].max_weight_kg | нет | number | Макс. вес, кг |
Ответ (и dry run, и apply): счётчики created_zones, created_cells, updated_cells, archived_cells, protected_cells, флаг dry_run, актуальный layout.
Занятость
Полная начальная загрузка из WMS
PUT /api/v1/storage/cells/occupancy?dry_run=true|false · scope cells:write · Idempotency-Key
Заменяет начальную занятость, пришедшую из WMS. Ячейки, которых нет в entries, становятся свободными для WMS-загрузки. Размещения активных сделок FULEX не снимаются и защищены.
{
"entries": [
{
"external_id": "A-01-01",
"owner_label": "ООО Пример",
"pallet_number": "PAL-900"
}
]
}
| Поле | Обяз. | Тип | Смысл |
|---|---|---|---|
entries | да | массив | Список занятых ячеек |
external_id | да | string | Ячейка из топологии |
owner_label | нет | string | Владелец партии / компания |
pallet_number | нет | string | Номер паллеты / партии |
Одна ячейка
PATCH /api/v1/storage/cells/{external_id}/occupancy · scope cells:write · Idempotency-Key
{
"status": "occupied",
"owner_label": "ООО Пример",
"pallet_number": "PAL-901"
}
status — только free или occupied. Ячейки активных сделок FULEX менять нельзя.
Также: GET /api/v1/storage/cells/occupancy — текущая эффективная загрузка.
Заявки
GET /api/v1/storage/applications?cursor=&limit=50 · scope applications:read
Список адресных заявок на этот склад. В карточке: id FULEX, объём, срок, тип товара, дедлайн подтверждения, статус. Контакты выдаются только в объёме, разрешённом процессом сделки.
| Метод | Путь | Тело | Смысл |
|---|---|---|---|
POST |
/applications/{application_id}/confirm |
— | Подтвердить операционную возможность принять товар. Это не оплата и не hard hold |
POST |
/applications/{application_id}/reject |
опционально reason, note, stale_capacity (boolean) |
Отклонить. stale_capacity=true — сигнал, что вместимость в витрине устарела |
Обе операции требуют Idempotency-Key и scope applications:write.
Отдельно событие storage.request.published — новая заявка в общем каталоге хранения; отклик на неё делается в кабинете, не через confirm.
Приёмка и отгрузка
Scope: operations:write. Фиксация факта в WMS ≠ подпись акта заказчиком.
Приёмка
POST /api/v1/storage/deals/{deal_id}/receipt · Idempotency-Key
{
"pallets": 10
}
или явное размещение:
{
"external_cell_ids": ["A-01-01", "A-01-02"]
}
Передайте либо pallets (целое ≥ 1, авторазмещение), либо external_cell_ids (массив ваших ID ячеек).
Отгрузка
POST /api/v1/storage/shipment-requests/{shipment_id}/complete · Idempotency-Key
{
"external_cell_ids": ["A-01-01", "A-01-02"]
}
external_cell_ids обязателен — ячейки, которые освобождаются после фактической отгрузки. Запрос на отгрузку сначала создаётся в FULEX.
События: polling и webhooks
Polling
GET /api/v1/storage/events?cursor=&limit=100 · scope events:read
Доставка at-least-once: дедуплицируйте по event.id. Сохраняйте next_cursor только после обработки всей страницы. При первом запуске cursor можно не передавать.
Поля события: id (UUID), type, facility_id, resource_id, payload (объект), occurred_at, опционально cursor.
| type | Когда |
|---|---|
storage.application.created | Адресная заявка на ваш склад |
storage.request.published | Заявка в общем каталоге хранения |
storage.application.confirmed / rejected | После confirm/reject |
storage.deal.paid | Сделка оплачена в FULEX |
storage.topology.imported | Применён импорт топологии |
storage.occupancy.updated | Обновлена занятость |
storage.receipt.completed | Зафиксирована приёмка |
storage.shipment.requested / completed | Запрос/завершение отгрузки |
storage.webhook.test | Тестовая доставка из кабинета |
Webhooks
Настройка в кабинете: POST /api/executor/storage/facilities/{facility_id}/webhooks — URL только HTTPS, список event_types. Secret подписи показывается один раз.
Заголовки доставки:
X-Fulex-Webhook-IdX-Fulex-Event-TypeX-Fulex-Webhook-Timestamp(unix seconds)X-Fulex-Webhook-Signature—v1=+ hex HMAC-SHA256
Строка подписи: <timestamp>.<сырое тело>. Проверяйте HMAC по raw body до парсинга JSON, constant-time compare, окно времени ~5 минут. Ответьте 2xx за 10 секунд. Иначе до 8 повторов с растущей задержкой (10 с → до 1 ч). Polling остаётся резервом.
Ошибки и повторы
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"request_id": "req_01JXYZ",
"details": [{ "field": "cells[0].status", "reason": "unsupported value" }]
}
}
| HTTP | Когда | Повтор |
|---|---|---|
| 400 / 422 | Формат или бизнес-валидация | Нет — исправьте данные |
| 401 / 403 | Ключ, scope, склад | Нет |
| 404 | Ресурс вне области ключа | Нет |
| 409 | Idempotency / topology_in_use / защищённая ячейка | Перечитайте состояние |
| 429 | Лимит (~120 req/min на ключ) | Да, после Retry-After |
| 5xx | Временная ошибка | Да, backoff + jitter |
Изменяющий запрос повторяйте только с тем же Idempotency-Key и тем же телом. В логах сохраняйте request_id, не ключ API.
Полный чек-лист запуска
- Кабинет → «API и интеграции» → склад или API-черновик → выпустите ключ (для проверки — «только чтение»).
GET /api/v1/storage/facility— сверьте склад.- Маленький
topology/import?dry_run=true, исправьте ошибки, затемdry_run=falseс новым Idempotency-Key. - То же для
PUT .../cells/occupancy. - Включите polling, сохраните cursor; при необходимости настройте webhook и проверьте HMAC.
- Прогоните заявку на небольшой реальной операции: событие → confirm/reject → receipt/shipment.
- Сверьте кабинет и WMS. После успешной проверки переключите ключ на полный доступ (или выпустите боевой) для операций с записью.
Частые вопросы (FAQ)
Нужен ли программист, чтобы пользоваться API?
Чтобы подключить API — да: настройку запросов, polling и webhooks делает разработчик или интегратор вашей WMS. Но выпустить ключ, выбрать права и понять границы можно и без кода — это разделы «Быстрый старт» и «Ключ доступа». Если склад небольшой, API вообще не обязателен — всё делается в кабинете.
Я потерял ключ. Можно его восстановить?
Нет. Полное значение ключа показывается только один раз при создании. Создайте новый ключ (rotate) и отзовите старый (revoke) в разделе «API и интеграции».
Заменяет ли API кабинет FULEX?
Нет. Оплата, оферта, создание сделки и подпись актов остаются в кабинете. API синхронизирует данные склада (топология, занятость, заявки, приёмка/отгрузка), но не проводит деньги и юридические шаги.
Чем confirm заявки отличается от оплаты?
confirm подтверждает только операционную возможность принять товар. Это не оплата и не жёсткий резерв — деньги и сделка идут своим порядком в кабинете.
Сколько ключей можно создать и на сколько складов?
Ключ выпускается на один склад. На несколько складов — несколько ключей. К одному складу можно держать несколько ключей (например, отдельный «только чтение»).
Обязателен ли webhook?
Нет. Можно работать только на polling (GET /events). Webhook — это доставка событий вам на HTTPS-адрес; polling при этом остаётся резервом.
Как проверить интеграцию, ничего не сломав?
Отдельной тестовой среды нет — всё на production, но есть безопасные способы: ключ «только чтение» (все GET без записи), dry_run=true на изменяющих запросах (валидация без записи), тест вебхука из кабинета и локальный симулятор scripts/storage_api_simulator.py. Полный цикл с записью — на небольшой реальной операции. Подробнее — раздел «Как протестировать, ничего не сломав».
Машинный контракт: OpenAPI docs/openapi/storage-api-v1.yaml, ТЗ docs/SPEC_STORAGE_API_v1.md, Postman docs/postman/, симулятор scripts/storage_api_simulator.py. Старый Partner API (capacity/holds) — отдельный контур, не алиас v1.
См. также: ответственное хранение в кабинете.