Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Execute Hootsuite primary workflow: Core Workflow A. Use when implementing primary use case, building main features, or core integration tasks. Trigger with phrases like "hootsuite main workflow", "primary task with hootsuite".
.claude/skills/jeremylongshore-hootsuite-core-workflow-a/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 44% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 43% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 30% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 54% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 111% | 0% |
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.
hootsuite-install-auth setuptypescript// 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; }
typescriptasync 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; }
typescriptasync 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, }); }
typescriptinterface 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; }
| 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 |
For analytics, see hootsuite-core-workflow-b.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 22,114 | 20,570 | -7% | 1 | 1 | 0% | 3,561 | 5,111 | +44% | 0 | 0 | — |
case-02 | fail→pass | 24,401 | 24,501 | +0% | 1 | 1 | 0% | 4,129 | 5,902 | +43% | 0 | 0 | — |
case-03 | fail→pass | 17,081 | 20,954 | +23% | 1 | 1 | 0% | 3,614 | 4,710 | +30% | 0 | 0 | — |
case-04 | fail→fail | 15,012 | 14,046 | -6% | 1 | 1 | 0% | 2,686 | 4,289 | +60% | 0 | 0 | — |
case-05 | fail→pass | 14,449 | 18,046 | +25% | 1 | 1 | 0% | 2,760 | 4,264 | +54% | 0 | 0 | — |
case-06 | pass→pass | 11,690 | 13,064 | +12% | 1 | 1 | 0% | 2,155 | 2,871 | +33% | 0 | 0 | — |
case-07 | pass→pass | 13,127 | 9,054 | -31% | 1 | 1 | 0% | 1,638 | 2,289 | +40% | 0 | 0 | — |
case-08 | pass→pass | 13,550 | 5,998 | -56% | 1 | 1 | 0% | 1,536 | 2,504 | +63% | 0 | 0 | — |
case-09 | pass→pass | 13,742 | 4,526 | -67% | 1 | 1 | 0% | 1,580 | 2,310 | +46% | 0 | 0 | — |
case-10 | pass→pass | 10,954 | 9,263 | -15% | 1 | 1 | 0% | 1,183 | 2,238 | +89% | 0 | 0 | — |
case-11 | pass→pass | 11,069 | 8,621 | -22% | 1 | 1 | 0% | 1,157 | 2,068 | +79% | 0 | 0 | — |
case-12 | fail→pass | 10,762 | 8,970 | -17% | 1 | 1 | 0% | 1,071 | 2,255 | +111% | 0 | 0 | — |
case-13 | fail→pass | 12,427 | 5,046 | -59% | 1 | 1 | 0% | 1,296 | 2,331 | +80% | 0 | 0 | — |
case-14 | pass→pass | 13,146 | 14,682 | +12% | 1 | 1 | 0% | 2,419 | 3,252 | +34% | 0 | 0 | — |
case-15 | pass→pass | 12,918 | 14,576 | +13% | 1 | 1 | 0% | 2,183 | 3,217 | +47% | 0 | 0 | — |
case-16 | fail→pass | 37,526 | 12,301 | -67% | 1 | 1 | 0% | 1,290 | 3,895 | +202% | 0 | 0 | — |
case-17 | fail→pass | 10,955 | 8,132 | -26% | 1 | 1 | 0% | 1,653 | 1,980 | +20% | 0 | 0 | — |
case-18 | pass→pass | 17,509 | 13,622 | -22% | 1 | 1 | 0% | 2,039 | 2,961 | +45% | 0 | 0 | — |
case-19 | fail→fail | 13,867 | 10,868 | -22% | 1 | 1 | 0% | 1,686 | 2,635 | +56% | 0 | 0 | — |
case-20 | fail→pass | 13,020 | 5,462 | -58% | 1 | 1 | 0% | 1,322 | 2,217 | +68% | 0 | 0 | — |
case-21 | pass→pass | 4,800 | 7,277 | +52% | 1 | 1 | 0% | 798 | 1,820 | +128% | 0 | 0 | — |
case-22 | pass→pass | 4,726 | 2,371 | -50% | 1 | 1 | 0% | 812 | 1,856 | +129% | 0 | 0 | — |
case-23 | fail→pass | 14,798 | 12,567 | -15% | 1 | 1 | 0% | 1,647 | 2,765 | +68% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 22 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +43 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.