Hootsuite Publishing — Schedule Posts with Media
Overview
Schedule social media posts with images and videos using the Hootsuite REST API. The publishing workflow involves: uploading media to get a media ID, then scheduling a message referencing that media.
Prerequisites
- Completed
hootsuite-install-authsetup - Social profiles connected in Hootsuite
- Media files (images/videos) for upload
Instructions
Step 1: Upload Media
// publishing.ts
import 'dotenv/config';
import fs from 'fs';
const TOKEN = process.env.HOOTSUITE_ACCESS_TOKEN!;
const BASE = 'https://platform.hootsuite.com/v1';
// Step 1a: Create upload URL
async function createMediaUpload(sizeBytes: number, mimeType: string) {
const response = await fetch(`${BASE}/media`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ sizeBytes, mimeType }),
});
const { data } = await response.json();
console.log('Upload URL:', data.uploadUrl);
console.log('Media ID:', data.id);
return data; // { id, uploadUrl, uploadUrlDurationSeconds }
}
// Step 1b: Upload file to the S3 URL
async function uploadFile(uploadUrl: string, filePath: string, mimeType: string) {
const fileBuffer = fs.readFileSync(filePath);
const response = await fetch(uploadUrl, {
method: 'PUT',
headers: { 'Content-Type': mimeType },
body: fileBuffer,
});
if (response.status !== 200) throw new Error(`Upload failed: ${response.status}`);
console.log('File uploaded successfully');
}
// Step 1c: Check media status
async function getMediaStatus(mediaId: string) {
const response = await fetch(`${BASE}/media/${mediaId}`, {
headers: { 'Authorization': `Bearer ${TOKEN}` },
});
const { data } = await response.json();
console.log('Media state:', data.state); // PENDING, READY, REJECTED
return data;
}
Step 2: Schedule Post with Media
async function scheduleWithMedia(config: {
profileIds: string[];
text: string;
mediaIds: string[];
scheduledAt: Date;
}) {
const response = await fetch(`${BASE}/messages`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: config.text,
socialProfileIds: config.profileIds,
scheduledSendTime: config.scheduledAt.toISOString(),
mediaUrls: config.mediaIds.map(id => ({ id })),
emailNotification: false,
}),
});
const result = await response.json();
for (const msg of result.data) {
console.log(`Message ${msg.id}: ${msg.state} → ${msg.scheduledSendTime}`);
}
return result;
}
Step 3: Complete Publishing Flow
async function publishPostWithImage(
profileId: string,
text: string,
imagePath: string,
scheduledAt: Date
) {
// 1. Create upload URL
const stats = fs.statSync(imagePath);
const mimeType = imagePath.endsWith('.png') ? 'image/png' : 'image/jpeg';
const media = await createMediaUpload(stats.size, mimeType);
// 2. Upload file to S3
await uploadFile(media.uploadUrl, imagePath, mimeType);
// 3. Wait for media processing
let status = await getMediaStatus(media.id);
while (status.state === 'PENDING') {
await new Promise(r => setTimeout(r, 2000));
status = await getMediaStatus(media.id);
}
if (status.state !== 'READY') {
throw new Error(`Media rejected: ${status.state}`);
}
// 4. Schedule post with media
return scheduleWithMedia({
profileIds: [profileId],
text,
mediaIds: [media.id],
scheduledAt,
});
}
Step 4: Bulk Scheduling
interface ScheduledPost {
text: string;
profileIds: string[];
scheduledAt: Date;
imagePath?: string;
}
async function bulkSchedule(posts: ScheduledPost[]) {
const results = [];
for (const post of posts) {
if (post.imagePath) {
results.push(await publishPostWithImage(post.profileIds[0], post.text, post.imagePath, post.scheduledAt));
} else {
results.push(await scheduleWithMedia({ profileIds: post.profileIds, text: post.text, mediaIds: [], scheduledAt: post.scheduledAt }));
}
await new Promise(r => setTimeout(r, 500)); // Rate limit buffer
}
return results;
}
Output
- Media uploaded and processed
- Posts scheduled with images across social profiles
- Bulk scheduling support
Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Media REJECTED | File too large or wrong format | Check size limits per network |
| 422 scheduledSendTime | Date in the past | Must be future date |
| 413 Payload Too Large | Image exceeds limit | Compress or resize image |
| Missing profile | Profile disconnected | Reconnect in Hootsuite dashboard |
Resources
Next Steps
For analytics, see hootsuite-core-workflow-b.