Hootsuite Publishing — Schedule Posts with Media
SkillDev tools'Execute Hootsuite primary workflow: Core Workflow A.
Use Hootsuite Publishing — Schedule Posts with Media in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Hootsuite Publishing — Schedule Posts with Media and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Hootsuite Publishing skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
What this skill tells your AI
The instructions your AI receives, as published by jeremylongshore/tons-of-skills-marketplace in skills/.curated/hootsuite-core-workflow-a/SKILL.md and read by Ahel’s review.
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 |
Examples
Run a fictional content-plan fixture and record fixture=calendar-basic-v2; profile=sandbox-brand; drafts=4; approval=required; audience=validated; public_posts=0; cleanup=complete.
Resources
Next Steps
For analytics, see hootsuite-core-workflow-b.
Signals
- GitHub stars
- 3k
- Forks
- 415
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
hootsuite-core-workflow-a- Source
- github.com/jeremylongshore/tons-of-skills-marketplace