Scheduling & Auto-Publishing
Why Automate Publishing?
Generating videos is only half the battle. If you produce 10 videos per day but upload them manually, the publishing step becomes the bottleneck. Auto-publishing closes the loop — your pipeline generates content and delivers it to audiences without human intervention.
Automated publishing ensures videos go live at the optimal time for each platform, with complete metadata (titles, descriptions, tags, thumbnails), correct privacy settings, and cross-platform distribution — all without you touching a single upload form.
Content Calendar Automation
A content calendar defines what gets published, when, and where. In an automated system, the calendar is a data source (Google Sheet, Notion database, or JSON file) that the pipeline reads to determine today's production queue.
// Google Sheet columns:
// | Date | Topic | Platform | Time | Status |
// | 2025-07-14 | AI Video Trends | YouTube | 14:00 | pending |
// | 2025-07-14 | Quick AI Tips #42 | TikTok | 09:00 | pending |
// | 2025-07-14 | AI Video Trends | Instagram | 18:00 | pending |
const getCalendarEntries = async (date) => {
const sheets = google.sheets({ version: 'v4', auth });
const response = await sheets.spreadsheets.values.get({
spreadsheetId: SHEET_ID,
range: 'Calendar!A:E'
});
const rows = response.data.values;
return rows
.filter(row => row[0] === date && row[4] === 'pending')
.map(row => ({
date: row[0],
topic: row[1],
platform: row[2],
time: row[3],
status: row[4]
}));
};YouTube API Auto-Upload
YouTube's Data API v3 allows full programmatic control over video uploads, including setting titles, descriptions, tags, thumbnails, playlists, scheduling, and monetization settings.
const uploadToYouTube = async (videoFile, thumbnailFile, metadata) => {
const youtube = google.youtube({ version: 'v3', auth: oauthClient });
// Step 1: Upload the video
const videoResponse = await youtube.videos.insert({
part: 'snippet,status,contentDetails',
requestBody: {
snippet: {
title: metadata.title, // Max 100 characters
description: metadata.description, // Max 5000 characters
tags: metadata.tags, // Array of strings
categoryId: '28', // Science & Technology
defaultLanguage: 'en',
defaultAudioLanguage: 'en'
},
status: {
privacyStatus: 'private', // Start private, schedule public
publishAt: metadata.publishAt, // ISO 8601: '2025-07-14T14:00:00Z'
selfDeclaredMadeForKids: false,
license: 'youtube',
embeddable: true
}
},
media: {
body: fs.createReadStream(videoFile)
}
});
const videoId = videoResponse.data.id;
// Step 2: Set custom thumbnail
await youtube.thumbnails.set({
videoId: videoId,
media: {
body: fs.createReadStream(thumbnailFile)
}
});
// Step 3: Add to playlist
await youtube.playlistItems.insert({
part: 'snippet',
requestBody: {
snippet: {
playlistId: metadata.playlistId,
resourceId: {
kind: 'youtube#video',
videoId: videoId
}
}
}
});
return { videoId, url: `https://youtube.com/watch?v=${videoId}` };
};Social Media Scheduling
Each social media platform has different API capabilities, posting requirements, and optimal formats. A multi-platform publishing system must adapt the same content for each destination.
| Platform | API Available | Max Video Length | Optimal Dimensions | Best Post Time (General) |
|---|---|---|---|---|
| YouTube | Yes (Data API v3) | 12 hours | 1920x1080 (16:9) | 2-4 PM weekdays |
| TikTok | Yes (Content Posting API) | 10 minutes | 1080x1920 (9:16) | 7-9 PM daily |
| Instagram Reels | Yes (Graph API) | 90 seconds | 1080x1920 (9:16) | 11 AM - 1 PM, 7-9 PM |
| X (Twitter) | Yes (v2 API) | 2 min 20 sec | 1920x1080 or 1080x1920 | 9 AM, 12 PM weekdays |
| Yes (Marketing API) | 10 minutes | 1920x1080 (16:9) | 8-10 AM Tue-Thu | |
| Yes (Graph API) | 240 minutes | 1920x1080 (16:9) | 1-4 PM weekdays |
TikTok Auto-Publishing
TikTok's Content Posting API allows direct video uploads from your server. The process involves initializing an upload, sending the video file, and then publishing with metadata.
// Step 1: Initialize the upload
const initResponse = await fetch('https://open.tiktokapis.com/v2/post/publish/video/init/', {
method: 'POST',
headers: {
'Authorization': `Bearer ${TIKTOK_ACCESS_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
post_info: {
title: 'AI Video Generation Tips #aitools #ai',
privacy_level: 'PUBLIC_TO_EVERYONE',
disable_duet: false,
disable_comment: false,
disable_stitch: false
},
source_info: {
source: 'FILE_UPLOAD',
video_size: fileSize,
chunk_size: fileSize,
total_chunk_count: 1
}
})
});
const { upload_url, publish_id } = await initResponse.json();
// Step 2: Upload the video file
await fetch(upload_url, {
method: 'PUT',
headers: {
'Content-Range': `bytes 0-${fileSize - 1}/${fileSize}`,
'Content-Type': 'video/mp4'
},
body: fs.createReadStream(videoPath)
});
// Step 3: Check publish status
const statusResponse = await fetch(
`https://open.tiktokapis.com/v2/post/publish/status/fetch/`,
{
method: 'POST',
headers: { 'Authorization': `Bearer ${TIKTOK_ACCESS_TOKEN}` },
body: JSON.stringify({ publish_id })
}
);Instagram Reels Publishing
Instagram requires videos to be hosted at a publicly accessible URL before publishing via the Graph API. The flow is: host the video, create a media container, then publish it.
// Step 1: Create media container (video must be at a public URL)
const containerResponse = await fetch(
`https://graph.facebook.com/v18.0/${IG_USER_ID}/media`,
{
method: 'POST',
body: new URLSearchParams({
media_type: 'REELS',
video_url: publicVideoUrl,
caption: 'How AI generates videos automatically! #aiautomation #aivideo',
share_to_feed: 'true',
access_token: IG_ACCESS_TOKEN
})
}
);
const { id: containerId } = await containerResponse.json();
// Step 2: Wait for processing (poll status)
let status = 'IN_PROGRESS';
while (status === 'IN_PROGRESS') {
await sleep(10000);
const check = await fetch(
`https://graph.facebook.com/v18.0/${containerId}?fields=status_code&access_token=${IG_ACCESS_TOKEN}`
);
status = (await check.json()).status_code;
}
// Step 3: Publish
await fetch(
`https://graph.facebook.com/v18.0/${IG_USER_ID}/media_publish`,
{
method: 'POST',
body: new URLSearchParams({
creation_id: containerId,
access_token: IG_ACCESS_TOKEN
})
}
);Optimal Posting Times by Platform
Posting at the right time can increase initial engagement by 30-50%. While optimal times vary by audience, research-backed general guidelines exist for each platform.
| Platform | Best Days | Best Times (EST) | Worst Times | Why |
|---|---|---|---|---|
| YouTube | Thu-Sat | 2-4 PM | Mon morning | Viewers browse after work/school |
| TikTok | Tue-Thu | 7-9 PM | Early morning | Peak scroll time in evenings |
| Tue, Wed, Fri | 11 AM - 1 PM | Late night (11PM-5AM) | Lunch break browsing + evening wind-down | |
| X (Twitter) | Mon-Fri | 9 AM, 12 PM | Weekends | Work-hours engagement pattern |
| Tue-Thu | 8-10 AM | Fri afternoon, weekends | Professional morning routine | |
| Wed-Fri | 1-4 PM | Early morning, late night | Afternoon break browsing |
Metadata Automation
Metadata — titles, descriptions, tags, and hashtags — is critical for discoverability. AI can generate optimized metadata automatically based on the video's script and target audience.
const generateMetadata = async (script, platform) => {
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [
{
role: 'system',
content: `You are a ${platform} SEO expert. Generate optimized metadata for a video based on its script. Return JSON with: title (max 100 chars), description (max 5000 chars for YouTube, 2200 for Instagram), tags (array of 15-30 keywords), hashtags (array of 5-10 for social platforms).`
},
{
role: 'user',
content: `Platform: ${platform}\nScript:\n${script}`
}
],
response_format: { type: 'json_object' }
});
return JSON.parse(response.choices[0].message.content);
};
// Generate platform-specific metadata
const ytMeta = await generateMetadata(script, 'YouTube');
const tiktokMeta = await generateMetadata(script, 'TikTok');
const igMeta = await generateMetadata(script, 'Instagram Reels');Thumbnail Generation Automation
Thumbnails are the single biggest factor in click-through rate. Automated thumbnail generation uses AI image generation combined with text overlay to produce eye-catching thumbnails at scale.
const generateThumbnail = async (videoTitle, keyFrame) => {
// Step 1: Generate background image with DALL-E
const bgImage = await openai.images.generate({
model: 'dall-e-3',
prompt: `YouTube thumbnail background: ${videoTitle}. Bold, vibrant, high-contrast, eye-catching, no text. 16:9 aspect ratio.`,
size: '1792x1024',
quality: 'hd'
});
// Step 2: Generate click-worthy title text with GPT
const titleText = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{
role: 'user',
content: `Write a SHORT (3-5 words max) YouTube thumbnail text for: "${videoTitle}". Make it dramatic, curiosity-driven. Examples: "This Changes Everything", "AI Can Do WHAT?", "You Won't Believe This"`
}]
});
// Step 3: Overlay text on image using Sharp + Canvas
const thumbnail = await sharp(bgImageBuffer)
.composite([
{ input: textOverlayBuffer, gravity: 'center' },
{ input: brandLogoBuffer, gravity: 'southeast' }
])
.resize(1280, 720) // YouTube thumbnail standard size
.jpeg({ quality: 95 })
.toBuffer();
return thumbnail;
};