publishing.md
166 lines · small-business/skills/social-content-engine/reference/publishing.md at 2574e29
Publishing and staging
Two paths, in order of preference. Whichever one runs, the post is staged, never published — the owner controls go-live and can still cancel or edit.
| Path | When | What the owner gets |
|---|---|---|
| HubSpot social | Marketing Hub Professional or higher | A scheduled post in the HubSpot campaign queue |
| Scheduling CSV | No HubSpot, or a non-Professional HubSpot plan | A CSV to import into Buffer, Later, or their tool |
Raw lines 1 to 12
Before staging anything
- Every scheduled time must be in the future. Calendars built weeks ago go stale quietly. Compare each time against now and surface any that have passed: "The Jul 8 post date has already passed. Skip it or move it?"
- Every asset URL must be a permanent export URL, not an autofill response thumbnail. Those expire in minutes and attach as broken images.
- The caption must be the approved one, not a later edit that skipped Checkpoint 3.
Raw lines 13 to 24
HubSpot social staging
Requires: HubSpot Marketing Hub Professional (or higher). Base URL: https://api.hubapi.com Auth: Bearer token (OAuth 2.0 or private app token).
Raw lines 25 to 32
Tier check
If you're unsure whether the user has Marketing Hub Professional:
GET /crm/v3/objects/companies?limit=1Check the subscriptionType on the account. If the API returns a 403 on social endpoints, tell the user: "HubSpot campaign staging requires Marketing Hub Professional. Your current plan doesn't include it. I can export a scheduling CSV instead — would that work?"
Raw lines 33 to 47
Create a campaign
POST /marketing/v3/campaigns
{
"name": "Summer Sale 2026 — Social",
"startDate": "2026-06-01",
"endDate": "2026-06-30",
"currencyCode": "<Currency from the Business context block, e.g. AUD>",
"utm": {
"source": "social",
"medium": "owned",
"campaign": "summer-sale-2026"
}
}Response: { "id": "<campaign_id>", ... } — save this for social post association.
Raw lines 48 to 68
Create a social post
One call per calendar row. Stage as SCHEDULED, never PUBLISHED.
POST /marketing/v3/social/posts
{
"campaignId": "<campaign_id>",
"channelId": "<hubspot_channel_id>",
"content": {
"body": "<approved caption text>"
},
"scheduledAt": "2026-06-03T10:00:00Z",
"attachments": [
{
"url": "<canva_export_png_url>"
}
],
"status": "SCHEDULED"
}**channelId** — the HubSpot Social account ID (not the platform name). Retrieve connected accounts:
GET /marketing/v3/social/channelsMatch by type (INSTAGRAM, FACEBOOK, TWITTER, LINKEDIN) and use the id field.
**scheduledAt** — must be in the future (relative to the time of the API call). Use noon local time for the owner's timezone unless they specify otherwise.
Raw lines 69 to 103
Verify the scheduled queue
After staging all posts:
GET /marketing/v3/social/posts?status=SCHEDULED&campaignId=<campaign_id>Surface results in a table: date, channel, first 60 chars of caption, status. Provide the direct HubSpot campaign URL: https://app.hubspot.com/content/{portalId}/social/campaigns/{campaign_id}
Tell the user: "Posts are scheduled and will publish automatically. You can cancel or edit any post in HubSpot before the send time."
Raw lines 104 to 120
CSV fallback (non-Professional users)
When HubSpot staging is not available, export a CSV the user can import into Buffer, Later, or their scheduler of choice.
Column headers:
Date,Time,Channel,Caption,ImageURL,StatusExample row:
2026-06-03,10:00 AM CDT,Instagram,"Summer Sale: 30% off candles ☀️ ...",https://canva.com/export/...,ScheduledTell the user: "I've prepared a scheduling CSV since your HubSpot plan doesn't include social staging. You can import this into Buffer or Later. [file path]"
Raw lines 121 to 140
Field reference
| Field | Type | Notes |
|---|---|---|
campaignId | string | UUID from campaign creation |
channelId | string | From GET /social/channels |
content.body | string | Caption text; max 2,000 chars |
scheduledAt | ISO 8601 | Must be future time; include timezone offset |
attachments[].url | string | Public URL (Canva export URL works) |
status | enum | Always "SCHEDULED" — never "PUBLISHED" |
Raw lines 141 to 153
Email handoff
Email copy is never staged by this skill, on any path. Present the approved subject, preheader, and body inline, grouped by send date, for the owner to paste into whatever they send from:
Jul 11 — "Your service plan renews Aug 1"
Aug 5 — "Two tune-up slots left this month"That is the whole handoff. Do not offer to send it, and do not build a Canva design for it.
Raw lines 154 to 166