На первый взгляд миграция из Kaiten в Bitrix24 выглядит как обычная интеграционная задача: прочитать данные из одного REST API и записать в другой REST API.
Но это впечатление быстро проходит, когда начинаешь переносить не демо-доску, а живую проектную систему.
В Kaiten уже накоплены пользователи, пространства, карточки, комментарии, файлы, ссылки внутри описаний, пользовательские поля, стадии, архивные задачи, связи между карточками и исторический контекст работы команды. Если перенести только названия карточек, формально миграция состоится. Но для бизнеса это будет потеря памяти.
В нашем случае нужно было перенести данные из облачного Kaiten в коробочный Bitrix24 так, чтобы команда смогла продолжить работу уже в новом контуре: с группами, задачами, файлами, комментариями, правами доступа и понятной структурой.
В этой статье расскажу, как мы построили мигратор на Python, где помог асинхронный подход, почему маппинг ID оказался центральной частью архитектуры, какие ограничения обнаружились в коробочном Bitrix24 и почему часть задач пришлось решать не только через REST API, но и через отдельные серверные скрипты.
Репозиторий с кодом: https://github.com/vlikhobabin/kaiten-to-bitrix
Контекст: нужно было перенести не доску, а рабочую историю
В нашей компании большая часть операционной работы велась в Kaiten. Там были пространства, карточки, файлы, обсуждения, пользовательские поля, связи между задачами и привычная структура доступа.
Однако через год активной работы в Kaiten мы решили переходить на коробочный Bitrix24. Причины решений, я полагаю, будут похожими у разных компаний: единый корпоративный портал, требования к размещению данных, интеграция с внутренними процессами, контроль над инфраструктурой, желание собрать коммуникации и задачи в одном контуре и т.д.
На уровне бизнеса задача звучала просто:
перенести все проектные данные из Kaiten в Bitrix24.
На уровне реализации это быстро превратилось в другой список:
перенести пользователей и сопоставить их по email;
перенести пространства Kaiten в группы Bitrix24;
сохранить участников и права доступа;
перенести карточки в задачи;
разложить задачи по стадиям;
перенести комментарии;
сохранить исторические даты комментариев;
скачать файлы из Kaiten;
загрузить файлы в Bitrix24;
заменить ссылки в описаниях карточек;
обработать пользовательские поля;
учесть архивные и завершённые карточки;
перенести связи Parent/Child и связанные карточки;
сделать так, чтобы миграцию можно было повторять, тестировать и докатывать частями.
В этот момент становится понятно: это не «экспорт-импорт». Это восстановление рабочей памяти команды в другой системе.
Что именно переносили
В итоговой версии мигратор поддерживал несколько основных типов объектов.
Kaiten Bitrix24 ------ -------- Пользователи → Пользователи Пространства → Группы / проекты Карточки → Задачи Комментарии → Комментарии / сообщения задачи Файлы → Файлы Bitrix24.Диск Пользовательские поля → UF-поля и/или блок в описании Parent/Child связи → Подзадачи / зависимости / связанные задачи Группы доступа Kaiten → Участники групп Bitrix24
Сразу оговорюсь: это не универсальный коробочный продукт «нажмите кнопку и мигрируйте любой Kaiten в любой Bitrix24». Это миграционный инструмент, написанный под реальный переход, с учётом конкретной структуры данных, конкретного Bitrix24 и конкретных компромиссов.
Но именно поэтому в нём много практических деталей, которые редко видны в красивых интеграционных схемах.
Архитектура мигратора
Проект получился достаточно простым по идее, но модульным по устройству.
kaiten-to-bitrix/ ├── config/ # настройки и конфигурация ├── connectors/ # API-клиенты Kaiten и Bitrix24 ├── migrators/ # миграторы по типам объектов ├── models/ # Pydantic-модели данных ├── transformers/ # преобразование форматов ├── utils/ # вспомогательные модули ├── scripts/ # сценарии запуска миграции │ └── vps/ # серверные скрипты для коробочного Bitrix24 ├── mappings/ # соответствия ID, создаются во время миграции └── logs/ # журналы выполнения
Стек достаточно обычный:
Python;
httpxдля HTTP-запросов;asyncioдля параллельной обработки;pydantic/pydantic-settingsдля моделей и настроек;.envдля токенов и параметров подключения;отдельные Python-скрипты для операций на сервере Bitrix24;
Ключевая идея была в разделении ответственности:
Connector → знает, как говорить с API конкретной системы Model → описывает структуру данных Transformer → преобразует Kaiten-объект в Bitrix24-формат Migrator → управляет процессом переноса конкретного типа данных Script → даёт понятную точку запуска Mapping → хранит соответствие старых и новых ID
Такой подход оказался особенно важен из-за зависимостей между объектами. Нельзя нормально перенести карточку, пока не понятно, в какую группу Bitrix24 она должна попасть. Нельзя корректно перенести комментарий, пока не создана задача. Нельзя восстановить связи между карточками, пока не создан маппинг карточек.
Главный артефакт миграции — не задача, а mapping
Почти во всех миграциях самым скучным файлом оказывается самый важный файл.
В нашем случае это были JSON-маппинги:
mappings/user_mapping.json mappings/space_mapping.json mappings/card_mapping.json mappings/custom_fields_mapping.json mappings/custom_properties_cache.json mappings/groups_cache.json
Без этих файлов миграция превращается в одноразовый скрипт, который страшно запускать повторно. С ними появляется возможность:
не создавать дубли;
докатывать миграцию частями;
повторно запускать обработку конкретного пространства;
восстанавливать связи между карточками после массового переноса;
переносить комментарии и файлы уже к существующим задачам;
анализировать ошибки после выполнения;
делать инкрементальную миграцию.
На практике маппинг - это не техническая мелочь, а позвоночник всего процесса.
Я не буду подробно описывать все этапы переноса, а остановлюсь лишь на интересных, с технической точки, моментах.
Пространства Kaiten → группы Bitrix24
В Kaiten рабочая область организована через пространства, доски, колонки, карточки и права доступа. В Bitrix24 ближайшим контейнером для проектной работы являются группы или проекты.
Мы приняли решение переносить не каждую доску, а именно пространства.
Логика была примерно такой:
конечные пространства без дочерних пространств превращались в группы Bitrix24;
пространства второго уровня тоже могли превращаться в группы;
технические или ненужные пространства исключались через конфигурацию;
участники пространства переносились в участники группы;
дополнительно учитывались пользователи через группы доступа Kaiten;
для новых групп включались нужные возможности: задачи, файлы, календарь, чат, база знаний, поиск.
Именно здесь обнаружился первый важный организационный момент: структура Kaiten и структура Bitrix24 не совпадают один к одному.
Можно было бы попытаться перенести всё максимально буквально: пространство в пространство, доску в доску, колонку в колонку. Но в реальности миграция между системами почти всегда требует не буквального копирования, а адаптации модели данных.
В нашем случае пространство Kaiten становилось группой Bitrix24, а карточки внутри пространства - задачами этой группы.
Карточки Kaiten → задачи Bitrix24
Карточки - самая объёмная и самая «грязная» часть миграции. Для карточек нужно было решить сразу несколько задач.
Во-первых, определить, в какую группу Bitrix24 попадёт задача. Для этого использовался space_mapping.json. Во-вторых, сопоставить стадии. В Kaiten у колонок есть типы. В нашем сценарии они маппились так:
type: 1 → стадия «Новые» type: 2 и другие → стадия «Выполняются» type: 3 → по умолчанию пропускаем архивные карточки → по умолчанию пропускаем
Позже появилась опция --include-archived. С ней завершённые и архивные карточки можно было переносить в стадию «Сделаны» и выставлять статус завершённой задачи.
В-третьих, нужно было перенести не только заголовок и описание, но и всё окружение карточки:
автора;
ответственного;
участников;
сроки;
описание;
комментарии;
файлы;
пользовательские поля;
связи с другими карточками;
информацию об исходном пространстве.
И вот здесь обычная миграция превращается в серию маленьких компромиссов.
Грабля 1. Производительность API и контроль конкурентности
Если переносить карточки последовательно, миграция получается слишком медленной. Особенно когда у карточек есть файлы, комментарии и пользовательские поля. Но если просто запустить сотни запросов параллельно, можно получить другую проблему: таймауты, ошибки API, нестабильные ответы, превышение лимитов и сложную отладку. Поэтому мы использовали асинхронный подход с ограничением конкурентности.
Упрощённо логика выглядела так:
semaphore = asyncio.Semaphore(10) async def process_card(card): async with semaphore: for attempt in range(3): try: return await migrate_single_card(card) except Exception: if attempt == 2: raise await asyncio.sleep(1) await asyncio.gather(*(process_card(card) for card in cards))
По фактическому опыту асинхронная обработка дала существенный прирост скорости, но я бы осторожно относился к любым универсальным цифрам. Время миграции сильно зависит от файлов, комментариев, скорости Bitrix24, сетевых задержек и количества дополнительных операций.
В нашем проекте ориентировочные показатели были такими:
Тип данных |
Количество |
Ориентировочное время |
|---|---|---|
Пользователи |
89 |
около 25 секунд |
Пространства |
34 |
около 3 минут |
Карточки |
около 1200 |
около 30 минут |
Файлы |
около 450 |
около 15 минут |
Комментарии |
около 3800 |
около 20 минут |
Это не бенчмарк библиотеки. Это практический замер на конкретной миграции.
Грабля 2. Файлы — это не только вложения
Файлы в карточках оказались отдельной проблемой.
Наивная логика выглядит так:
Получить список файлов карточки.
Скачать файл из Kaiten.
Загрузить файл в Bitrix24.
Прикрепить к задаче.
Но в реальной карточке файлы могут быть не только во вложениях. Они могут быть ссылками внутри описания:
Смотрите документ: [ТЗ.pdf](https://.../files/...)
Если просто загрузить файл в Bitrix24, ссылка в описании всё равно будет вести в Kaiten. После отключения Kaiten или ограничения доступа такая ссылка станет бесполезной.
Поэтому пришлось делать обработку описания:
Найти Markdown-ссылки на файлы Kaiten.
Скачать исходный файл.
Загрузить его в Bitrix24.
Получить новую ссылку.
Заменить старую ссылку в описании задачи.
Упрощённый фрагмент:
file_pattern = r'\[([^\]]+)\]\((https?://[^)]+/files/[^)]+)\)' for match in re.finditer(file_pattern, description): file_name = match.group(1) old_url = match.group(2) content = await kaiten.download_file(old_url) bitrix_file_id = await bitrix.upload_task_file(task_id, file_name, content) new_url = await bitrix.get_file_url(bitrix_file_id) description = description.replace(match.group(0), f'[{file_name}]({new_url})')
Именно такие детали отличают «перенесли данные» от «пользователи действительно могут продолжить работу».
Грабля 3. Пользовательские поля Kaiten богаче, чем кажется
Пользовательские поля - один из самых неприятных участков миграции.
В Kaiten набор типов достаточно широкий: строки, числа, даты, email, телефон, checkbox, select, multi-select, URL, formula, user, attachment, голосования, каталоги и другие варианты.
В Bitrix24 у задач тоже есть пользовательские поля, но модель другая. На момент актуализации статьи официальный REST Bitrix24 умеет создавать пользовательские поля задач через task.item.userfield.add, но поддерживаемые типы для задач ограничены: строка, число, дата/время и boolean.
То есть формально API для пользовательских полей есть. Но для полной миграции Kaiten-полей этого недостаточно.
Например, если в Kaiten есть select или multi_select с набором значений, цветами, сортировкой и исторически накопленными значениями, то простое создание строкового поля в Bitrix24 теряет часть смысла. А если поле вообще специфическое - вроде голосования, каталога или вложения - прямого соответствия может не быть.
В проекте использовались два подхода.
Подход 1. Создавать пользовательские поля в Bitrix24
Для части полей мы пытались создавать соответствующие UF-поля в Bitrix24. В коробочной версии для этого использовались серверные скрипты с прямым доступом к базе, так как у Битрикса для этого нет штатных API методов. В облачной версии все пользовательские поля придется создать заранее.
Логика двухэтапная:
Локально получить поля Kaiten и их значения.
На сервере Bitrix24 создать нужные записи и маппинги.
Это позволяло точнее контролировать создаваемую структуру, но добавляло риск: мы уже не просто используем публичный REST API, а вмешиваемся в внутреннюю структуру коробочного продукта.
Подход 2. Дублировать пользовательские поля в описании задачи
Для сохранения читаемого контекста часть пользовательских полей форматировалась прямо в описание задачи:
<hr> <h3>Пользовательские поля</h3> <table> <tr><th>Название</th><th>Значение</th></tr> <tr><td>Приоритет клиента</td><td>Высокий</td></tr> <tr><td>Плановая дата</td><td>15.04.2025</td></tr> </table>
Это не всегда красиво как модель данных, зато надёжно как миграция смысла.
Пользователь открывает задачу в Bitrix24 и видит важные поля, даже если они не идеально легли в нативную модель Bitrix24.
Сейчас я бы рассматривал такую стратегию как нормальный компромисс:
критичные поля переносить в нативные UF-поля;
сложные или плохо сопоставимые поля сохранять в описании;
обязательно формировать отчёт о том, какие поля были перенесены нативно, а какие - как информационный блок.
Грабля 4. Комментарии и историческая хронология
Перенести комментарии - не значит просто добавить новые сообщения к задаче.
Для пользователя важно, чтобы обсуждение выглядело как история, а не как пачка комментариев, созданных в день миграции.
В старой логике Bitrix24 для комментариев использовались методы семейства task.commentitem.*. В новых версиях Bitrix24 комментарии в новой карточке задачи переехали в чат задачи, а старые методы для комментариев помечены как deprecated, хотя часть операций ещё может работать в целях совместимости.
Это важный момент для тех, кто будет повторять такую миграцию сейчас: обязательно проверьте версию модуля задач Bitrix24 и актуальную документацию REST перед реализацией.
В нашем реальном проекте потребовался отдельный серверный слой для сохранения исторических дат комментариев. После создания комментариев мы обновляли дату в базе Bitrix24 через серверный скрипт:
python3 /root/kaiten-vps-scripts/update_comment_dates.py '{"601": "2025-07-08 14:22:00"}'
Скрипт обновлял POST_DATE в таблице b_forum_message.
В облачном Битрикс24, я полагаю, сохранить исходную дату комментария нет никакой технической возможности.
В идеальном мире такие вещи должны делаться только через официальный API. Но миграции в коробочные системы часто живут не в идеальном мире. Там есть исторические данные, внутренние таблицы, отличия версий, ограничения методов и требование бизнеса: «после перехода пользователи должны видеть нормальную историю».
Поэтому главное правило здесь такое: если вы идёте в базу напрямую, это должно быть осознанное миграционное действие, а не постоянная архитектура интеграции.
Грабля 5. Дочерние пространства и связи между карточками
В первом приближении кажется, что можно мигрировать пространство за пространством.
Но в Kaiten структура может быть глубже:
есть дочерние пространства;
карточки могут жить в подчинённой структуре;
доступ может наследоваться или задаваться через группы;
карточки могут быть связаны друг с другом;
у карточек могут быть родительские и дочерние отношения.
В репозитории для этого появилась отдельная логика:
автоматический поиск карточек из дочерних пространств;
рекурсивный поиск родительского пространства, для которого уже есть группа Bitrix24;
обработка
parent_entity_uid, если нет прямогоparent_id;сохранение информации об исходном пространстве в описании задачи;
перенос Parent/Child отношений;
перенос связанных карточек;
установка связей уже после создания карточек, когда
card_mapping.jsonзаполнен.
Почему связи лучше устанавливать в конце?
Потому что при переносе карточки A связанная карточка B может ещё не существовать в Bitrix24. Если пытаться установить связь сразу, часть связей неизбежно потеряется. Поэтому надёжнее сначала создать все задачи, сохранить mapping, а затем вторым проходом восстановить отношения.
Это хороший пример общей закономерности миграций: некоторые данные нельзя корректно перенести за один проход.
Результаты миграции
В результате удалось перенести рабочие данные из Kaiten в коробочный Bitrix24 с сохранением основной структуры и контекста.
Что получилось хорошо:
пользователи сопоставлены по email;
пространства перенесены в группы Bitrix24;
участники добавлены в группы;
карточки перенесены в задачи;
активные карточки попали в рабочие стадии;
архивные и завершённые можно было переносить отдельным режимом;
комментарии перенесены;
файлы скачаны из Kaiten и загружены в Bitrix24;
ссылки в описаниях заменены;
пользовательские поля сохранены как данные и/или как читаемый блок;
связи между карточками восстановлены отдельным проходом;
миграцию можно было тестировать и запускать частями.
Ориентировочная статистика проекта:
Объект |
Объём |
|---|---|
Пользователи |
89 |
Пространства |
34 |
Карточки |
около 1200 |
Файлы |
около 450 |
Комментарии |
около 3800 |
Пользовательские поля |
десятки полей и значений |
Главный результат даже не в том, что объекты появились в Bitrix24. Главный результат - команда не потеряла рабочий контекст.
Что я бы сделал иначе сейчас
Этот проект был написан под конкретный переход. Если бы я делал такую миграцию заново, я бы усилил несколько вещей.
1. Сначала сделал бы reconciliation report
То есть отдельный отчёт сверки:
В Kaiten найдено пользователей: 100 Перенесено в Bitrix24: 89 Пропущено: 11 Причины: нет email, архивный пользователь, дубль В Kaiten найдено карточек: 1400 Перенесено: 1200 Пропущено: 200 Причины: архивные, финальная колонка, нет маппинга пространства Файлов найдено: 470 Файлов перенесено: 450 Ошибок скачивания: 20
Логи полезны разработчику, но бизнесу и проектной команде нужен понятный итоговый отчёт: что было, что стало, что не перенеслось и почему.
2. Жёстче разделил бы «данные» и «историю»
Перенести текущие карточки - одна задача.
Перенести историю обсуждений, даты, авторов, файлы, ссылки и связи - другая.
Для бизнеса часто критичны именно текущие активные задачи. Исторический слой можно переносить отдельным этапом, с другими требованиями к точности и срокам.
Я бы прямо разделил режимы:
current-only # только активная рабочая структура with-history # комментарии, даты, файлы archive-full # полная историческая миграция
3. Перепроверил бы актуальные методы Bitrix24 REST
За время жизни проекта API меняется.
На момент актуализации статьи у Bitrix24 есть REST-методы для пользовательских полей задач, но они ограничены по типам. Также изменилась модель комментариев в новой карточке задач: обсуждения уезжают в чат задачи, а старые методы комментариев помечаются как устаревшие.
Поэтому старое утверждение «это невозможно через API» сегодня лучше заменить на более точное:
часть операций можно делать через официальный REST, но не вся семантика Kaiten-полей и исторических комментариев переносится один к одному. Для конкретной коробочной версии и конкретного набора данных нужно отдельно проверять, какие операции делать через REST, какие через D7/Bitrix Framework, а какие остаются миграционными SQL-операциями.
4. Добавил бы слой D7 вместо части прямого SQL
Для коробочного Bitrix24 прямой SQL работает, но это самый жёсткий вариант.
Более аккуратный путь - писать серверные скрипты не только на уровне SQL, а по возможности использовать внутренние API Bitrix Framework / D7. Это всё равно требует доступа к серверу, но меньше завязывает мигратор на конкретные таблицы.
Прямой SQL я бы оставил как последний уровень: когда REST и D7 не дают нужного результата.
5. Сделал бы мигратор более продуктовым
Сейчас это инженерный инструмент. Для повторного использования я бы добавил:
веб-интерфейс настройки миграции;
экран предварительного анализа;
отчёт расхождений;
прогресс по этапам;
очередь заданий;
безопасный restart;
режим rollback для созданных объектов;
настройку правил маппинга через UI;
экспорт итогового отчёта в PDF/Excel;
уведомления в Telegram или Bitrix24.
Но это уже превращает одноразовый мигратор в отдельный продукт.
Выводы
Миграция проектной системы - это не перенос таблицы задач.
Это перенос рабочей памяти команды.
В карточках живут не только заголовки и описания. Там есть обсуждения, решения, вложения, ссылки, пользовательские поля, связи, статусы, исторические даты и неявная логика работы.
REST API решает только часть задачи. Вторая часть - это проектирование соответствий, контроль целостности, повторяемость, проверка результата и честное принятие компромиссов.
В нашем случае Python-мигратор позволил перенести данные из Kaiten в коробочный Bitrix24 и сохранить основной рабочий контекст. Но главный урок был не в выборе httpx, asyncio или Pydantic.
Главный урок такой: если система целый год была местом, где команда думала, спорила, принимала решения и прикладывала файлы, то миграция такой системы должна относиться к этим данным не как к «записям в API», а как к памяти организации.
Именно её и нужно переносить.
Полезные ссылки
Репозиторий мигратора: https://github.com/vlikhobabin/kaiten-to-bitrix
Документация Kaiten API: https://developers.kaiten.ru/
Документация Bitrix24 REST API: https://apidocs.bitrix24.com/