Пожалуй, самый быстрый способ полюбить автоматизацию — вручную раскидать пять шортсов по шести соцсетям. И ладно, если весь контент уже подготовили… Чаще всего приходится монтировать каждый ролик, придумывать подписи и подгонять формат под нужную соцсеть. Желание наращивать объемы быстро испарится даже у самого отчаянного энтузиаста.
Хорошая новость в том, что большую часть рутины можно переложить на цифровой конвейер n8n.
В материале разложим создание рабочей системы в n8n по узлам и соберем схему, которая сама нарезает и раскидывает контент по площадкам.
Создаем workflow и подключаем Telegram
Переходим на сайт n8n, регистрируемся и создаем новый workflow через Create New Workflow. Откроется пустой канвас — дальше на нем шаг за шагом соберем всю схему автоматизации.

Для удобства сразу разделим канвас на четыре части. Нажимаем Shift + S — n8n добавит на поле Sticky Note. Растягиваем заметку мышкой, дважды кликаем по ней и вписываем название Get YouTube Video. Таким же способом создаем еще три области:
- YouTube to Short Videos;
- Schedule Videos;
- Publish Videos.

Цвет заметки можно поменять через ее настройки. Сами Sticky Note на работу workflow не влияют — они просто делят большую схему на понятные участки. Если такая разметка не нужна, заметки можно вообще не добавлять.
Начнем с Telegram — кому-то же нужно принимать наши заказы на шортсы. Жмем Add first step или плюс на канвасе, через поиск находим Telegram Trigger и выбираем событие On Message. Этот узел будет ловить сообщения от бота и запускать весь workflow, когда боту прилетит новая ссылка.

Для работы Telegram Trigger нужен и сам бот, поэтому идем в BotFather, жмем Start и отправляем команду /newbot. Дальше BotFather попросит придумать название, а затем username. Username должен заканчиваться на bot, например, shorts_factory_bot.
BotFather создаст бота и пришлет ссылку на него вместе с токеном примерно такого вида:
1234567890:AAExampleToken

Копируем токен целиком и возвращаемся в n8n. Открываем настройки Telegram Trigger, в Credential to connect with жмем Create New Credential и вставляем токен в поле Access Token.
Сохраняем подключение. Зеленый credential означает, что n8n достучался до Telegram и наш бот официально принят в конвейер.

Открываем бота по ссылке из BotFather и жмем Start. С этого момента он работает как пульт управления конвейером: отправляем ссылку на YouTube, остальное забирает n8n.

Запускаем workflow через Execute Workflow и отправляем боту тестовую YouTube-ссылку. Возвращаемся в Telegram Trigger и смотрим OUTPUT: там должны появиться текст сообщения, данные отправителя и chat.id. Если видите значения, значит связь между Telegram и n8n уже налажена.

После Telegram Trigger жмем плюс и добавляем Code. Сразу переименуем его в Split, чтобы потом не искать нужный блок среди десятка одинаковых Code. Для этого открываем узел, кликаем по названию в левом верхнем углу, пишем Split и сохраняем. Именно здесь сообщение из Telegram разбивается на отдельные значения.

В узле Code выбираем JavaScript и вставляем код:
const message = $input.first().json.message.text;
const chatId = $input.first().json.message.chat.id;
const parts = message.trim().split(/\s+/);
const urlPart = parts.find(part =>
part.includes(‘youtube.com’) || part.includes(‘youtu.be’)
);
const numberPart = parts.find(part => /^\d+$/.test(part));
if (urlPart && numberPart) {
const number = parseInt(numberPart, 10);
if (number >= 1 && number <= 10) {
return [{
json: {
action: ‘process’,
‘YouTube URL’: urlPart,
‘How many shorts to generate?’: number,
chat_id: chatId
}
}];
}
return [{
json: {
action: ‘invalid_number’,
chat_id: chatId,
message: ‘Please use a number between 1 and 10’
}
}];
}
return [{
json: {
action: ‘invalid_format’,
chat_id: chatId,
message: ‘Send: YouTube_URL Number\nExample: https://youtu.be/abc123 5’
}
}];
Из сообщения Split вытаскивает три значения: ссылку на YouTube, количество шортсов и chat_id.
Теперь сообщение боту отправляем уже в таком формате:
https://youtu.be/abc123 5
Цифра после ссылки задает количество шортсов. Сейчас Split принимает значения от 1 до 10 и отсекает все, что выходит за этот диапазон. При необходимости лимит можно поменять прямо в коде.
Запускаем Split через Execute step и смотрим OUTPUT. Если все сработало как надо, справа появятся action: process, YouTube URL, How many shorts to generate? и chat_id. Значит, Split нормально разобрал сообщение и подготовил данные для следующих узлов.

Подключаем Klap и отправляем ролик на анализ
Klap — ИИ-сервис для нарезки длинных видео на шортсы. Он будет разбирать YouTube-ролик и искать в нем подходящие фрагменты для будущих коротких видео. Регистрируемся, открываем раздел API и создаем API key.
Возвращаемся в n8n и после Split добавляем HTTP Request. Готового узла Klap здесь нет, поэтому подключаем сервис через API. Узел называем Analyze Video — через него отправим ролик в Klap на анализ.

Дальше прокручиваем настройки HTTP Request вниз и жмем Import cURL. Вставляем готовую команду для Klap и подтверждаем импорт. n8n сам раскидает метод, URL, заголовки и тело запроса по нужным полям.
Сама команда:
curl —request POST \
—url https://api.klap.app/v2/tasks/video-to-shorts \
—header ‘Content-Type: application/json’ \
—header ‘Authorization: Bearer YOUR_API_KEY’ \
—data ‘{
«source_video_url»: «YOUTUBE_VIDEO_URL»,
«language»: «en»,
«target_clip_count»: 3,
«max_clip_count»: 3,
«editing_options»: {
«captions»: true,
«reframe»: true,
«emojis»: true,
«remove_silences»: true,
«intro_title»: true
},
«min_duration»: 15,
«max_duration»: 60,
«target_duration»: 30
}’
После импорта меняем YOUR_API_KEY на свой ключ Klap.

Затем прокручиваем настройки до Body, открываем JSON и переключаем поле с Fixed на Expression. После этого жмем маленькую стрелку в правом нижнем углу — откроется расширенный редактор.

В редакторе слева открываем данные из Split. YouTube URL перетаскиваем на место YOUTUBE_VIDEO_URL, а How many shorts to generate? подставляем в target_clip_count и max_clip_count. Справа в Result сразу проверяем результат: должна появиться настоящая ссылка на ролик и нужное количество шортсов в обоих полях.

Остальные параметры в JSON отвечают за настройки будущих роликов. Их можно менять под себя, а в самом Klap можно отдельно настраивать разные параметры обработки. Пока оставляем текущие значения как есть и жмем Execute step.
Если видим status: processing, все идет по плану. Klap принял видео и уже ищет внутри него будущие шортсы. Сохраняем Analyze Video и двигаемся дальше.
Проверяем статус анализа
Следом добавляем еще один HTTP Request и называем его Check Status. Он будет проверять, закончил Klap разбор ролика.

В Check Status жмем Import cURL и вставляем команду проверки статуса:
curl —request GET \
—url https://api.klap.app/v2/tasks/TASK_ID \
—header ‘Authorization: Bearer YOUR_API_KEY’
После импорта в Check Status вверху появится URL с заглушкой TASK_ID. Слева открываем данные узла Analyze Video, находим поле id — это номер задачи, которую Klap создал на прошлом шаге. Перетаскиваем id прямо в URL вместо TASK_ID.
Обычно после подстановки значения URL уже стоит в режиме Expression. Если остался Fixed, меняем вручную. И сразу проверяем, чтобы вокруг значения не было пробелов.

Жмем Execute step. Пока Klap разбирает ролик, в OUTPUT вы увидите статус processing. Когда анализ закончится, он сменится на ready.
После Check Status добавляем IF и задаем условие: status должен быть равен ready.

У IF появятся два выхода: true и false. Ветку true оставляем для продолжения workflow, а к false подключаем Wait. В Wait ставим 30 секунд и соединяем его обратно с Check Status.

Сохраняем workflow и закрепляем результат Analyze Video через Pin data. Для этого открываем Analyze Video и в OUTPUT нажимаем иконку с изображением скрепки. n8n сохранит уже полученный ответ Klap, поэтому во время дальнейшей сборки не придется снова отправлять ролик на анализ и оплачивать работу API.

Дальше работаем с веткой true. Ставим еще один HTTP Request и называем его Get Ideas. Как только анализ закончится, он заберет у Klap все найденные куски для будущих шортсов.
В Get Ideas нажимаем Import cURL и вставляем команду:
curl —request GET \
—url https://api.klap.app/v2/projects/OUTPUT_ID \
—header ‘Authorization: Bearer YOUR_API_KEY’
После импорта подставляем тот же API key Klap. Как только анализ завершится и Check Status возвращает ready, workflow уходит по ветке true. В Get Ideas открываем IF → True Branch, находим output_id и заменяем им OUTPUT_ID в URL.

После подстановки output_id быстро проверяем, не затесался ли рядом лишний пробел, и запускаем Get Ideas через Execute step.
Get Ideas должен вернуть отдельный item для каждого будущего шортса. В нашем примере их пять, поэтому в Table увидим пять значений. Результат Get Ideas закрепляем через Pin data (как мы делали выше).

Точно так же закрепляем готовый результат Check Status, чтобы дальше не запускать уже пройденные шаги по новой.
После Get Ideas добавляем еще один HTTP Request и называем его Make HD Short. Подходящие куски Klap уже нашел, теперь отправляем их на нормальный рендер. Открываем Import cURL и вставляем готовую команду для запуска HD-версий.
Команда:
curl —request POST \
—url https://api.klap.app/v2/projects/OUTPUT_ID/SHORT_ID/exports \
—header ‘Authorization: Bearer YOUR_API_KEY’
После импорта подставляем данные из уже пройденных узлов: output_id берем из Check Status, short id — из Get Ideas, а в Authorization снова вставляем API key Klap.

Жмем Execute step. Make HD Short запустится для каждого item из Get Ideas, поэтому при пяти найденных фрагментах Klap отправит в рендер сразу пять роликов. В OUTPUT мы пока увидим processing — то есть фабрика заработала, но готовых файлов еще нет.

Сразу после Make HD Short ставим еще один HTTP Request и называем его Get Final Shorts.

Через него будем проверять каждый экспорт и забирать готовые файлы, как только они появятся. Открываем Import cURL и вставляем команду:
curl —request GET \
—url https://api.klap.app/v2/projects/OUTPUT_ID/SHORT_ID/exports/EXPORT_ID \
—header ‘Authorization: Bearer YOUR_API_KEY’
После импорта собираем URL из данных предыдущих узлов: output_id берем из Check Status, short id — из Get Ideas, export id — из Make HD Short. Если output_id никак не хочет нормально вставляться в нужное место, просто копируем значение и подставляем вручную.

В Authorization снова вставляем API key Klap и жмем Execute step. Если рендер уже завершился, Get Final Shorts вернет готовые ролики. Если часть файлов еще обрабатывается, мы увидим статус processing.
После Get Final Shorts ставим Code и называем его Check Ready. Он посчитает, сколько роликов уже дорендерилось, и поймет, можно ли двигать всю пачку дальше. В Code выбираем JavaScript и вставляем готовую функцию.

Сам код:
const allVideos = $input.all();
const totalExpected = $(‘Get Ideas’).all().length;
const readyCount = allVideos.filter(v => v.json.status ===
‘ready’).length;
const allReady = readyCount === totalExpected;
// Add a field that says if everything is ready or not
return allVideos.map(video => ({
json: {
…video.json,
is_everything_ready: allReady ? true : false
}
}));
Жмем Execute step. Check Ready пересчитает все ролики со статусом ready и сверит их с количеством шортсов из Get Ideas. Если никто по дороге не потерялся и вся пачка готова, в OUTPUT появится is_everything_ready: true.

После Check Ready добавляем IF и задаем проверку is_everything_ready → Boolean → is true. Если значение true, значит все ролики уже на месте и можно продолжать.

К false цепляем Wait на 30 секунд, а дальше снова ведем в Get Final Shorts. Пока в пачке остаются недорендеренные ролики, n8n будет ждать и перепроверять их каждые полминуты. Когда подтянется последний, цикл закончится.

К ветке true подключаем HTTP Request и называем его Get YT Title. Далее открываем Import cURL и вставляем команду:
curl —request GET \
—url ‘https://www.youtube.com/oembed?url=<YOUTUBE_VIDEO_URL>&format=json’
После импорта переключаем URL в Expression. Слева открываем Split, находим YouTube URL и ставим его на место YOUTUBE_VIDEO_URL.

Жмем Execute step — в OUTPUT появятся данные исходного ролика, включая поле title с его названием.

Настраиваем расписание публикаций
С роликами разобрались, теперь нужно распределить их по времени. Для этого создаем обычную таблицу Google Sheets и добавляем лист Schedule. Его оформляем по образцу ниже.

В нашем примере публикуем по одному ролику в день с 09:00 до 21:00. Здесь же задаем смещение часового пояса: для UTC+2 в Time zone offset ставим 2, для UTC−4 — −4. Таблица сама переведет локальное время в UTC, а n8n уже по нему соберет расписание.
Возвращаемся в n8n и в блоке Schedule Videos добавляем Google Sheets. Подключаем Google-аккаунт, выбираем операцию Get Row(s), нашу таблицу и лист Schedule.

Жмем Execute step — в OUTPUT должны появиться строки с настройками. Узел называем Get Schedule.

Следом ставим Code с названием Schedule Settings. Здесь собираем из таблицы три значения, которые дальше будут рулить расписанием: число постов в день, время старта и время окончания.

Вставляем готовую функцию:
let postsPerDay;
let startHour;
let endHour;
for (const item of items) {
const setting = item.json[«Publishing Settings»];
const value = item.json[«col_2»];
if (setting === «Post per Day») {
postsPerDay = parseInt(value, 10);
} else if (setting === «Start Time») {
startHour = parseInt(value.split(«:»)[0], 10);
} else if (setting === «End Time») {
endHour = parseInt(value.split(«:»)[0], 10);
}
}
return [
{
json: {
postsPerDay,
startHour,
endHour
}
}
];
Жмем Execute step. Если все сработало, в OUTPUT появятся postsPerDay, startHour и endHour. Значит, n8n получил настройки из таблицы и теперь знает, сколько публиковать в день и в какое время.

В той же Google Sheets, где уже есть лист Schedule, создаем второй лист и называем его Content. В нем готовим колонки для данных о шортсах: Video Title, Video URL, Video Clip Idea, Clip Virality Score, Clip Caption, Clip URL, Schedule Time и Schedule Time (local). Структуру делаем как на скрине ниже, а в дальнейшем эти поля будет заполнять n8n.

После Schedule Settings добавляем Google Sheets. В Operation выбираем Get Row(s), в Document — ту же таблицу, а в Sheet — созданный нами лист Content. Узел называем Get Existing Schedule.
В Get Existing Schedule открываем Settings и включаем Always Output Data. Пока лист Content пустой, эта настройка не даст workflow остановиться на пустой таблице.

Следом ставим Code с названием Get Last Time. Узел находит самую позднюю дату в Schedule Time и от нее строит очередь для новой пачки. Если таблица еще пустая, отправной точкой будет текущее время.

В Get Last Time выбираем JavaScript и вставляем готовую функцию:
// Get existing scheduled posts
const existingPosts = $input.all();
// Safe fallback: if the sheet is empty, use an empty object
const scheduleSettings = ($(‘Get Existing Schedule’).first() || {
json: {}
}).json;
// Find the latest scheduled time
let lastScheduledTime = new Date();
if (existingPosts.length > 0) {
// Get all scheduled times and find the latest
const scheduledTimes = existingPosts
.map(post => new Date(post.json[‘Schedule Time’]))
.filter(date => !isNaN(date))
.sort((a, b) => b — a); // Sort descending
if (scheduledTimes.length > 0) {
lastScheduledTime = scheduledTimes[0];
}
}
// Pass this to the next node
return [{
json: {
lastScheduledTime: lastScheduledTime.toISOString(),
postsPerDay: scheduleSettings.postsPerDay,
startHour: scheduleSettings.startHour,
endHour: scheduleSettings.endHour
}
}];
Жмем Execute step. Если все сработало как надо, в OUTPUT появится lastScheduledTime. Значит, Get Last Time нашел точку, от которой дальше будет строиться новая очередь.

Дальше добавляем Code с названием Schedule Videos. Здесь ролики расходятся по календарю: кто сегодня, кто завтра, кто подождет своей очереди. Лимиты и нужные часы узел учитывает сам.
Вставляем функцию:
const postsPerDay = parseInt($(‘Schedule Settings’).first().json.postsPerDay);
const startHour = parseInt($(‘Schedule Settings’).first().json.startHour);
const endHour = parseInt($(‘Schedule Settings’).first().json.endHour);
const lastScheduledTime = new Date($input.first().json.lastScheduledTime);
// Get video data
const videos = $(‘Get Ideas’).all();
const finalShorts = $(‘Get Final Shorts’).all();
const now = new Date();
const totalHours = endHour — startHour;
const intervalMs = (totalHours * 60 * 60 * 1000) / postsPerDay;
// Start from the day after the last scheduled post
let startDate = new Date(lastScheduledTime);
startDate.setDate(startDate.getDate() + 1);
startDate.setHours(startHour, 0, 0, 0);
// If starting date is in the past, use tomorrow
if (startDate < now) {
startDate = new Date();
startDate.setDate(startDate.getDate() + 1);
startDate.setHours(startHour, 0, 0, 0);
}
const result = [];
let videoIndex = 0;
let currentDate = new Date(startDate);
let dailyPostCount = 0;
while (videoIndex < videos.length) {
// Calculate time slot
const baseTime = new Date(currentDate);
baseTime.setHours(
startHour + (dailyPostCount * (totalHours / postsPerDay)),
0,
0,
0
);
// Add randomness ±30 min
const offset = (Math.random() — 0.5) * 60 * 60 * 1000;
const scheduledTime = new Date(baseTime.getTime() + offset);
// Get video data
const video = videos[videoIndex].json;
const finalShort = finalShorts[videoIndex].json;
result.push({
json: {
scheduled_at: scheduledTime.toISOString(),
video_name: video.name,
video_virality_score: video.virality_score,
video_caption: video.publication_captions.youtube,
video_tiktok_caption: video.publication_captions.tiktok,
video_url: finalShort.src_url,
video_id: video.id
}
});
videoIndex++;
dailyPostCount++;
// Move to next day if daily limit reached
if (dailyPostCount >= postsPerDay) {
currentDate.setDate(currentDate.getDate() + 1);
dailyPostCount = 0;
}
}
return result;
Жмем Execute step и открываем Table. Здесь уже видно, кто куда и когда поедет: у каждого ролика есть scheduled_at, название фрагмента, virality score, подпись и URL готового файла. Пять шортсов — пять строк с готовыми датами публикации.

Следом добавляем еще один Code и называем его Convert Times. Сейчас scheduled_at хранится в UTC, поэтому этим узлом мы переводим даты в нужный локальный часовой пояс.
Вставляем функцию:
return $input.all().map(item => {
const utc = item.json.scheduled_at;
const local = new Date(utc).toLocaleString(«en-US», {
timeZone: «INSERT_YOUR_TIMEZONE»,
timeZoneName: «short»,
hour12: false
});
return {
json: {
…item.json,
local_time: local
}
};
});
Вместо INSERT_YOUR_TIMEZONE указываем часовой пояс в формате IANA, например Europe/Berlin или America/New_York. Если нужный идентификатор сходу не вспоминается, его проще спросить у ChatGPT:
«Мне нужна корректная строка часового пояса IANA для следующего города или места: «нужный город». Укажи только ID часового пояса, например America/New_York или Europe/Berlin, без дополнительных пояснений».
Жмем Execute step. К каждому ролику добавится local_time — уже нормальная дата публикации в выбранном часовом поясе.

Жмем Execute step. К каждому ролику добавится local_time — уже нормальная дата публикации в выбранном часовом поясе.

Сохраняем готовые шортсы в таблицу
Расписание готово, теперь сохраняем данные по каждому шортсу в лист Content.
После Convert Times добавляем узел Google Sheets. В Resource выбираем Sheet Within Document, в Operation — Append Row, затем указываем нашу таблицу и лист Content. В Mapping Column Mode включаем Map Each Column Manually.
В каждую строку отправляем полный набор данных по ролику: название исходника, YouTube-ссылку, идею фрагмента, virality score, подпись, ссылку на готовый шортс и обе даты публикации — UTC и локальную.
Для этого в Values to Send у каждого поля переключаем значение из Fixed в Expression. Затем подставляем данные из уже пройденных узлов:
- Video Title — title из Get YT Title;
- Video URL — YouTube URL из Split;
- Video Clip Idea — video_name;
- Clip Virality Score — video_virality_score;
- Clip Caption — video_caption;
- Clip URL — video_url;
- Schedule Time — scheduled_at;
- Schedule Time (local) — local_time.

Узел называем Save Info и жмем Execute step.

Если Klap подготовил пять шортсов, в OUTPUT получим пять items, а в листе Content появятся пять новых строк — по одной на каждый ролик.

Возвращаемся в Google Sheets и смотрим, что получилось. Заодно открываем Clip URL и проверяем готовый шортс.
Публикуем шортсы через Blotato
Шортсы готовы, даты расставлены — теперь вся эта красота должна еще добраться до соцсетей. Для этого подключаем Blotato: n8n будет загружать туда готовые ролики и ставить их в очередь.
Регаемся в Blotato и привязываем нужные соцсети в ЛК. Для нашей схемы это TikTok, Instagram*, Facebook*, LinkedIn, YouTube и X. Когда соцсети подключены, забираем в настройках API key и возвращаемся в n8n.

После Save Info добавляем HTTP Request и называем его Upload Video. Сюда отправляем ссылку на готовый шортс. Blotato подтягивает файл к себе и возвращает URL, с которым дальше можно спокойно разносить ролик по площадкам.
В Upload Video жмем Import cURL и вставляем команду:
curl —request POST \
—url https://backend.blotato.com/v2/media \
—header ‘blotato-api-key: <YOUR API KEY>’ \
—data ‘{
«url»: «<CLIP_URL>»
}’
После импорта в blotato-api-key меняем YOUR API KEY на свой ключ Blotato. Затем спускаемся до Body: в Body Content Type оставляем JSON, а в Specify Body выбираем Using Fields Below.
В Body Parameters добавляем параметр url. Value переключаем в Expression и подставляем Clip URL из Save Info. Жмем Execute step и смотрим OUTPUT — Upload Video должен пройтись по всей пачке шортсов.

Запускаем узел и смотрим OUTPUT. Если вся пачка загрузилась без ошибок, можно переходить к публикациям.
После Upload Video добавляем еще один HTTP Request.

Открываем Import cURL и вставляем команду:
curl —request POST \
—url https://backend.blotato.com/v2/posts \
—header ‘Content-Type: application/json’ \
—header ‘blotato-api-key: <YOUR API KEY>’ \
—data ‘{
«post»: {
«accountId»: «<YOUR_ACCOUNT_ID>»,
«content»: {
«text»: «<CLIP_CAPTION>»,
«mediaUrls»: [
«<MEDIA_URL>»
],
«platform»: «tiktok»
},
«target»: {
«targetType»: «tiktok»,
«privacyLevel»: «PUBLIC_TO_EVERYONE»,
«disabledComments»: false,
«disabledDuet»: false,
«disabledStitch»: false,
«isBrandedContent»: false,
«isYourBrand»: true,
«isAiGenerated»: false
}
},
«scheduledTime»: «<SCHEDULED_TIMESTAMP>»
}’
После импорта снова вставляем API key Blotato и переключаем JSON в Expression.

Дальше нужен Account ID от акка TikTok. Копируем значение прямо из кабинета Blotato и вставляем его в accountId.
Следом заполняем Clip Caption. Его берем из Save Info — поле Clip Caption.

Дальше в mediaUrls подставляем url из Upload Video. Последним подставляем Schedule Timestamp — берем его из Save Info, поле Schedule Time.
Узел называем TikTok и запускаем через Execute step. Затем открываем Calendar в Blotato и проверяем, что ролики появились в расписании на нужные даты. Если все на месте, TikTok подключен и можно двигаться дальше.

Остальные площадки подключаем отдельными ветками от Upload Video. Дальше все по накатанной: HTTP Request, Import cURL, API key Blotato, JSON в Expression. Clip Caption берем из Save Info, media URL — из Upload Video, Schedule Timestamp — оттуда же. Для каждой площадки дальше подставляем свой Account ID и заполняем ее персональный набор полей.

Начинаем с Instagram. Добавляем еще один HTTP Request от Upload Video, жмем Import cURL и вставляем команду:
curl —request POST \
—url https://backend.blotato.com/v2/posts \
—header ‘Content-Type: application/json’ \
—header ‘blotato-api-key: <YOUR API KEY>’ \
—data ‘{
«post»: {
«accountId»: «<YOUR_ACCOUNT_ID>»,
«content»: {
«text»: «<CLIP_CAPTION>»,
«mediaUrls»: [
«<MEDIA_URL>»
],
«platform»: «instagram»
},
«target»: {
«targetType»: «instagram»,
«privacyLevel»: «PUBLIC_TO_EVERYONE»,
«disabledComments»: false,
«disabledDuet»: false,
«disabledStitch»: false,
«isBrandedContent»: false,
«isYourBrand»: true,
«isAiGenerated»: false
}
},
«scheduledTime»: «<SCHEDULED_TIMESTAMP>»
}’

После импорта вставляем API key Blotato и переключаем JSON в Expression. В Blotato открываем подключенный Instagram-аккаунт, копируем Account ID и вставляем его в accountId. Clip Caption берем из Save Info, в mediaUrls подставляем url из Upload Video, а в scheduledTime — Schedule Time из Save Info.
Узел называем Instagram и жмем Execute step.
Следом подключаем Facebook. От Upload Video добавляем еще один HTTP Request, открываем Import cURL и вставляем:
curl —request POST \
—url https://backend.blotato.com/v2/posts \
—header ‘Content-Type: application/json’ \
—header ‘blotato-api-key: <YOUR API KEY>’ \
—data ‘{
«post»: {
«accountId»: «<YOUR_ACCOUNT_ID>»,
«content»: {
«text»: «<CLIP_CAPTION>»,
«mediaUrls»: [
«<MEDIA_URL>»
],
«platform»: «facebook»
},
«target»: {
«targetType»: «facebook»,
«pageId»: «<YOUR_PAGE_ID>»,
«privacyLevel»: «PUBLIC_TO_EVERYONE»,
«disabledComments»: false,
«disabledDuet»: false,
«disabledStitch»: false,
«isBrandedContent»: false,
«isYourBrand»: true,
«isAiGenerated»: false
}
},
«scheduledTime»: «<SCHEDULED_TIMESTAMP>»
}’

Здесь дополнительно понадобится Page ID. В Blotato открываем подключенный Facebook-аккаунт и копируем Account ID и Page ID. Вставляем их в accountId и pageId. Clip Caption, media URL и Schedule Timestamp подставляем так же, как в предыдущих узлах.
Узел называем Facebook и запускаем через Execute step.
Дальше добавляем LinkedIn. Создаем еще одну ветку от Upload Video и импортируем cURL:
curl —request POST \
—url https://backend.blotato.com/v2/posts \
—header ‘Content-Type: application/json’ \
—header ‘blotato-api-key: <YOUR API KEY>’ \
—data ‘{
«post»: {
«accountId»: «<YOUR_ACCOUNT_ID>»,
«content»: {
«text»: «<CLIP_CAPTION>»,
«mediaUrls»: [
«<MEDIA_URL>»
],
«platform»: «linkedin»
},
«target»: {
«targetType»: «linkedin»,
«privacyLevel»: «PUBLIC_TO_EVERYONE»,
«disabledComments»: false,
«disabledDuet»: false,
«disabledStitch»: false,
«isBrandedContent»: false,
«isYourBrand»: true,
«isAiGenerated»: false
}
},
«scheduledTime»: «<SCHEDULED_TIMESTAMP>»
}’
Вставляем API key, переключаем JSON в Expression и копируем Account ID подключенного LinkedIn-аккаунта из Blotato. Затем подставляем Clip Caption из Save Info, url из Upload Video и Schedule Time из Save Info.

Узел называем LinkedIn и жмем Execute step.
Следующая площадка — YouTube. Добавляем HTTP Request от Upload Video и импортируем:
curl —request POST \
—url https://backend.blotato.com/v2/posts \
—header ‘Content-Type: application/json’ \
—header ‘blotato-api-key: <YOUR API KEY>’ \
—data ‘{
«post»: {
«accountId»: «<YOUR_ACCOUNT_ID>»,
«content»: {
«text»: «<CLIP_CAPTION>»,
«mediaUrls»: [
«<MEDIA_URL>»
],
«platform»: «youtube»
},
«target»: {
«targetType»: «youtube»,
«title»: «<VIDEO_TITLE>»,
«privacyStatus»: «public»,
«shouldNotifySubscribers»: true,
«isMadeForKids»: false
}
},
«scheduledTime»: «<SCHEDULED_TIMESTAMP>»
}’
Вставляем API key и переключаем JSON в Expression. В Blotato открываем подключенный YouTube-аккаунт, копируем Account ID и подставляем его в нужное поле. Clip Caption и Schedule Timestamp берем из Save Info, media URL — из Upload Video.

Здесь появляется еще одно поле — VIDEO_TITLE. Подставляем в него Video Clip Idea из Save Info, называем узел YouTube и запускаем через Execute step.
Последней подключаем Twitter/X. Добавляем еще один HTTP Request от Upload Video и вставляем cURL:
curl —request POST \
—url https://backend.blotato.com/v2/posts \
—header ‘Content-Type: application/json’ \
—header ‘blotato-api-key: <YOUR API KEY>’ \
—data ‘{
«post»: {
«accountId»: «<YOUR_ACCOUNT_ID>»,
«content»: {
«text»: «<CLIP_CAPTION>»,
«mediaUrls»: [
«<MEDIA_URL>»
],
«platform»: «twitter»
},
«target»: {
«targetType»: «twitter»
}
},
«scheduledTime»: «<SCHEDULED_TIMESTAMP>»
}’

Вставляем API key Blotato и переключаем JSON в Expression. Затем открываем подключенный Twitter-аккаунт в Blotato и копируем Account ID. Clip Caption и Schedule Time берем из Save Info, url — из Upload Video.
Узел называем Twitter и жмем Execute step.
В итоге от Upload Video у нас должно отходить шесть параллельных веток: TikTok, Instagram, Facebook, LinkedIn, YouTube и Twitter. Открываем Calendar в Blotato и проверяем, что публикации появились для всех подключенных площадок.
Отправляем отчет в Telegram
После Twitter возвращаемся в Telegram и добавляем действие Send a Text Message. В Chat ID подставляем chat.id из Telegram Trigger, чтобы итоговый отчет прилетел именно в тот чат, откуда запускали всю эту историю.
Дальше в поле Text вставляем шаблон сообщения:
Your YouTube video «{{ $(‘Get YT Title’).first().json.title }}»
was converted into {{ $(‘Split’).first().json[‘How many shorts to generate?’] }} shorts and scheduled for publication on TikTok, Instagram, Facebook, LinkedIn, Twitter and YouTube at these times:
{{ $(‘Convert Times’).all().map(item => item.json.local_time).join(‘\n’) }}
Выражения внутри {{ }} n8n заполнит автоматически. Из Get YT Title он возьмет название исходного ролика, из Split — количество созданных шортсов, из Convert Times — время публикации каждого видео.
В Settings включаем Execute Once. После завершения всей пачки бот отправит одно сообщение с названием ролика, количеством шортсов и расписанием публикаций.
Что по ценам
Главные расходы в этой связке приходятся на n8n, Klap и Blotato. Google Sheets можно использовать с обычным Google-аккаунтом, Telegram Bot API бесплатный.
n8n Cloud на тарифе Starter стоит от €20 в месяц при оплате за год и включает 2500 запусков workflow. Есть и возможность локальной установки через Community Edition, поэтому при наличии сервера расходы можно свести к стоимости самого хостинга.
У Klap API оплата идет за операции. Обработка исходного видео стоит $0,44, генерация одного шортса — $0,32, экспорт готового ролика — еще $0,48. Пачка из пяти шортсов из одного длинного видео обойдется примерно в $4,44.
Цены на Blotato начинаются от $29 в месяц. За эти деньги можно подключить несколько соцсетей и публиковаться через API без доплаты за каждый отдельный пост.
В итоге вся инфраструктура обходится примерно от €20 + $29 в месяц, сверху добавляются расходы Klap по фактическому объему нарезки. Чем больше исходников и шортсов, тем ощутимее расходы на Klap.
И один совет
Если по ходу сборки что-то отвалится, зовите на помощь ИИ. Чат-боту можно скинуть текст ошибки, скрин узла или кусок JSON.
А еще лучше подключить агента с доступом к браузеру. Он сможет сам открыть n8n, пройтись по workflow, OUTPUT и настройкам узлов, чтобы быстрее разобраться, на каком шаге все поехало.
