Back to Content Calendar Management

Integration and API Spec

The reference during the build: every tool connection, auth scope, field mapping, and error-handling rule.

4 pagesPDF · Technical
FS-DOC-05Technical

Integration and API Spec

Content Calendar Management

[YourCompany.com] · Marketing Department · Prepared by FullSpec · [Today's Date]

This document defines every integration point, API credential, field mapping, and error behaviour required to build and operate the Content Calendar Management automation. It is written for the FullSpec build team and covers Notion, Slack, Buffer, HubSpot, and the orchestration layer. Each section provides the exact configuration detail needed to connect tools, map data, and ensure the system degrades gracefully under failure conditions. No credential values should appear in code or workflow nodes; all secrets are stored in the shared credential store as described in Section 04.

01Tool inventory

Tool
Role in automation
Auth method
Min plan required
Used by agents
Notion
Content calendar database: trigger source, status read/write, draft link detection, metric write-back
OAuth 2.0 (internal integration token)
Free (Plus recommended for API rate headroom)
Agent 1, Agent 2, Agent 3
Slack
Brief delivery, overdue reminders, reviewer notifications, revision messages
OAuth 2.0 (bot token via Slack App)
Free (Pro for audit logs)
Agent 1, Agent 2
Buffer
Scheduled post creation on approval; weekly engagement metrics source
OAuth 2.0 (Buffer API v1)
Essentials ($6/month per channel) or Team
Agent 2, Agent 3
HubSpot
Campaign record creation and update on publish; contact activity logging
Private App token (Bearer)
Free CRM (Marketing Hub Starter for campaign objects)
Agent 2
Google Docs
Draft link source: URL stored in Notion record and passed to reviewer via Slack
No direct API call; URL string passed between Notion and Slack
Free (Google Workspace)
Agent 2 (URL read only)
Orchestration layer
Workflow host: one workflow per agent, shared credential store, trigger management, retry logic
Platform-native credential manager
Paid tier with webhook support and scheduled triggers
All agents
Before you connect anything: all OAuth flows and webhook registrations must be validated against sandbox or test environments before production credentials are entered. For Notion, use a duplicate test database. For Slack, use a dedicated test workspace or a private channel. For Buffer, use a test profile with no live channels attached. For HubSpot, use a developer sandbox account. Confirm each connection returns a valid response before switching to production.

02Per-tool integration detail

Notion

Primary data store and trigger source for all three agents. The Notion integration token must be issued as an internal integration from the workspace settings and shared with the specific database pages used in this automation.

Auth method
OAuth 2.0 internal integration token. Navigate to notion.so/my-integrations, create an integration scoped to the content calendar database, and copy the Internal Integration Token. Store as NOTION_API_TOKEN in the credential store.
Required scopes
Read content: databases:read, pages:read. Write content: pages:write. Read database schema: databases:read. All three are granted by default for internal integrations; confirm in the integration capabilities panel.
Webhook / trigger setup
Notion does not natively push webhooks. The orchestration layer must poll the content database on a schedule (recommended: every 5 minutes for new row detection; every 15 minutes for deadline-date checks). Filter poll results by last_edited_time > last_checked_timestamp to avoid reprocessing. Store last_checked_timestamp in a persistent workflow variable per agent.
Required configuration
Database ID stored as NOTION_DATABASE_ID in the credential store. Required property names (exact, case-sensitive): Assignee (person), Deadline (date), Draft Link (url), Status (select: Brief Sent, Draft Submitted, Approved, Needs Revision, Scheduled, Published), Channel (multi-select), Topic (title), Format (select), Target Audience (rich text), Engagement Likes (number), Engagement Comments (number), Engagement Shares (number), Engagement Reach (number), Campaign ID (text). A Notion database audit must confirm these field names before build starts.
Rate limits
3 requests/second per integration token. At 20 to 40 content pieces/month, polling generates approximately 288 requests/hour (every 5 minutes across 3 agents). This is well within the limit. No throttling is required at current volume, but exponential backoff must be implemented on 429 responses.
Constraints
Notion rich text fields return an array of text objects; the build must flatten these to a plain string before passing to Slack. Person fields return a user ID, not a display name; a secondary lookup to users:read is needed to resolve the Slack-compatible display name or email for routing.
// Notion poll response (simplified)
GET /v1/databases/{NOTION_DATABASE_ID}/query
Authorization: Bearer {NOTION_API_TOKEN}
Content-Type: application/json

// Filter: new rows or rows where Deadline = today
{
  "filter": {
    "or": [
      { "property": "Status", "select": { "equals": "Brief Sent" } },
      { "property": "Deadline", "date": { "equals": "today" } }
  ]}
}

// Page update (status write-back)
PATCH /v1/pages/{page_id}
{ "properties": { "Status": { "select": { "name": "Scheduled" } } } }
Slack

Outbound messaging tool used by the Calendar Orchestrator Agent (brief dispatch, overdue reminders) and the Review and Publish Agent (reviewer notification, revision routing). A dedicated Slack App must be created in the target workspace.

Auth method
OAuth 2.0 bot token. Create a Slack App at api.slack.com/apps, add it to the target workspace, and install it under Bot Token Scopes. Store the Bot User OAuth Token as SLACK_BOT_TOKEN in the credential store.
Required scopes
chat:write (send messages as the bot), chat:write.public (post to public channels without joining), users:read (resolve user IDs to names/emails), users:read.email (look up users by email address for routing), channels:read (list channels for validation), im:write (open direct message channels to users).
Webhook / trigger setup
Slack is outbound only in this automation. No incoming webhooks or event subscriptions are required. All messages are sent via the chat.postMessage API method. Direct messages to individual writers and reviewers use im.open to retrieve the DM channel ID before posting.
Required configuration
SLACK_MARKETING_MANAGER_USER_ID and SLACK_DEFAULT_REVIEW_CHANNEL stored in the credential store. Writer user IDs are resolved at runtime by looking up the Notion Assignee email against Slack users:read.email. Bot must be invited to any private channels used for review notifications.
Rate limits
Tier 3 methods (chat.postMessage): 1 request/second. At 20 to 40 pieces/month the automation sends at most 200 messages/month across all agents, averaging fewer than 7 per day. No throttling is required. Implement a 1-second delay between consecutive postMessage calls within a single workflow run to stay within tier limits.
Constraints
Slack message blocks have a 3,000-character limit per block. Brief content must be truncated and linked to the Notion record if it exceeds this. User lookup by email will fail if the writer's email in Notion does not match their Slack account email; implement a fallback to post to the marketing manager DM with an alert.
// Send brief to writer (direct message)
POST https://slack.com/api/chat.postMessage
Authorization: Bearer {SLACK_BOT_TOKEN}
{
  "channel": "{writer_dm_channel_id}",
  "blocks": [
    { "type": "header", "text": { "type": "plain_text", "text": "New Content Brief" } },
    { "type": "section", "text": { "type": "mrkdwn",
      "text": "*Topic:* {notion.Topic}\n*Format:* {notion.Format}\n*Deadline:* {notion.Deadline}\n*Channel:* {notion.Channel}\n*Audience:* {notion.Target Audience}" } },
    { "type": "actions", "elements": [
      { "type": "button", "text": { "type": "plain_text", "text": "Open in Notion" },
        "url": "https://notion.so/{page_id}" }
    ]}
  ]
}
Integration and API SpecPage 1 of 3
FS-DOC-05Technical
Buffer

Used by the Review and Publish Agent to create scheduled posts on approval, and by the Performance Sync Agent to retrieve weekly engagement metrics for all published pieces.

Auth method
OAuth 2.0. Register an application at buffer.com/developers/apps to obtain a client ID and secret. Complete the authorization code flow to retrieve an access token and refresh token. Store as BUFFER_ACCESS_TOKEN and BUFFER_REFRESH_TOKEN in the credential store. Implement token refresh on 401 responses using BUFFER_CLIENT_ID and BUFFER_CLIENT_SECRET.
Required scopes
publish (create and manage scheduled posts), analytics (read post-level engagement metrics). Both scopes must be requested during the OAuth authorization flow. Confirm scope grant in the Buffer app dashboard after authorization.
Webhook / trigger setup
Buffer does not trigger the automation. The Review and Publish Agent calls Buffer outbound when a Notion record reaches Approved status. The Performance Sync Agent calls Buffer on a weekly schedule (Monday 07:00 in the workspace timezone). No incoming webhooks from Buffer are required.
Required configuration
BUFFER_PROFILE_IDS stored as a JSON map in the credential store, keyed by Notion Channel value (e.g. {"LinkedIn": "abc123", "Twitter": "def456", "Instagram": "ghi789"}). This map is the sole place channel-to-profile-ID resolution occurs. Post scheduled_at must be read from the Notion Deadline field and converted to ISO 8601 UTC before sending. Note: Buffer analytics do not return granular data for LinkedIn or TikTok via the v1 API; those channels may require a manual pull at launch.
Rate limits
60 requests/minute per access token. The Performance Sync Agent fetches metrics for up to 40 published records per week, requiring at most 40 GET requests per run. The Review and Publish Agent creates at most one post per content piece. Total monthly API calls are well below 2,400. No throttling is required at current volume.
Constraints
Buffer's create post endpoint accepts text and media_urls only. Image or video assets referenced in the Google Doc must be separately hosted (e.g. as public Google Drive links or uploaded to a CDN) before the URL can be passed. The automation does not handle media upload in this build; the writer must provide a public media URL in the Notion record Media URL field.
// Create scheduled post
POST https://api.bufferapp.com/1/updates/create.json
Authorization: Bearer {BUFFER_ACCESS_TOKEN}
{
  "profile_ids": ["{BUFFER_PROFILE_IDS[notion.Channel]}"],
  "text": "{notion.approved_copy}",
  "media": { "link": "{notion.Media URL}" },
  "scheduled_at": "{notion.Deadline | toISO8601UTC}"
}

// Fetch post metrics (Performance Sync)
GET https://api.bufferapp.com/1/updates/{update_id}/interactions.json
  ?event=likes&access_token={BUFFER_ACCESS_TOKEN}

// Response fields used:
// interactions.likes, interactions.comments, interactions.shares, statistics.reach
HubSpot

Receives campaign record creation and update calls from the Review and Publish Agent when a piece is scheduled or published. Uses the Private App authentication model rather than OAuth to avoid user-linked token expiry.

Auth method
Private App token (Bearer). In HubSpot, navigate to Settings > Integrations > Private Apps, create a new app named 'Content Calendar Automation', and copy the generated token. Store as HUBSPOT_PRIVATE_APP_TOKEN in the credential store. No refresh logic is required; private app tokens do not expire unless rotated manually.
Required scopes
crm.objects.contacts.read, crm.objects.contacts.write, crm.objects.deals.read, crm.objects.deals.write, content (for campaign objects if Marketing Hub Starter is active). Assign these under the Private App's scope configuration before saving. If campaign objects are not available on the current HubSpot plan, the automation falls back to logging a note on the associated contact record.
Webhook / trigger setup
HubSpot is outbound only in this automation. The Review and Publish Agent calls the HubSpot CRM API after Buffer scheduling is confirmed. No HubSpot webhooks or workflow triggers are involved.
Required configuration
HUBSPOT_CAMPAIGN_OWNER_ID (the HubSpot user ID of the marketing manager, stored in the credential store). Custom contact property hs_content_last_published_date (date) and hs_content_channel (single-line text) must be created in HubSpot before build. The Campaign ID from the Notion record is stored in the Notion Campaign ID field and passed as the association ID. If no Campaign ID is present, the agent creates a new campaign object and writes the returned ID back to the Notion record.
Rate limits
Free and Starter tiers: 110 requests/10 seconds and 100 requests/day for some endpoints (note: CRM API is 100 requests/10 seconds for v3). At 20 to 40 pieces/month the automation makes at most 80 CRM calls/month. No throttling is required. Implement exponential backoff on 429 responses.
Constraints
HubSpot campaign objects require Marketing Hub Starter or above. If the account is on Free CRM, campaign logging falls back to contact note creation via the engagements API. This fallback must be configured and tested separately. Association between a campaign object and a content piece is one-to-one in this build; multi-campaign associations are out of scope.
// Create or update campaign record
POST https://api.hubapi.com/crm/v3/objects/marketing_events
Authorization: Bearer {HUBSPOT_PRIVATE_APP_TOKEN}
{
  "properties": {
    "hs_event_name": "{notion.Topic}",
    "hs_event_type": "CONTENT_PUBLISH",
    "hs_start_date": "{notion.Deadline | toISO8601UTC}",
    "hs_external_account_id": "{HUBSPOT_CAMPAIGN_OWNER_ID}",
    "hs_external_event_id": "{notion.page_id}"
  }
}

// Fallback: log note on contact
POST https://api.hubapi.com/crm/v3/objects/notes
{
  "properties": {
    "hs_note_body": "Content published: {notion.Topic} on {notion.Channel} at {notion.Deadline}",
    "hs_timestamp": "{now | toISO8601UTC}"
  }
}

03Field mappings between tools

The tables below document the exact field-level data flow at each agent handoff. Monospace field names are used throughout. Where a transformation is applied, it is noted in the destination field column.

Agent 1 handoff: Notion to Slack (brief dispatch and overdue reminder)

Source tool
Source field
Destination tool
Destination field
Notion
`properties.Title` (title array, flattened)
Slack
`blocks[1].text` Topic line
Notion
`properties.Format` (select.name)
Slack
`blocks[1].text` Format line
Notion
`properties.Deadline` (date.start)
Slack
`blocks[1].text` Deadline line (formatted YYYY-MM-DD)
Notion
`properties.Channel` (multi_select[].name, joined)
Slack
`blocks[1].text` Channel line
Notion
`properties.Target Audience` (rich_text, flattened)
Slack
`blocks[1].text` Audience line
Notion
`properties.Assignee` (people[0].id -> email lookup)
Slack
`channel` (resolved to DM channel via `im.open`)
Notion
`id` (page ID)
Slack
`blocks[2].elements[0].url` (Notion deep link)

Agent 2 handoff: Notion to Slack (reviewer notification) and Notion to Buffer (post scheduling)

Source tool
Source field
Destination tool
Destination field
Notion
`properties.Draft Link` (url)
Slack
`blocks[1].text` Draft link line
Notion
`properties.Assignee` (people[0].id -> email)
Slack
`channel` (reviewer DM or review channel)
Notion
`properties.Title` (flattened)
Slack
`blocks[0].text` message header
Notion
`properties.Title` (flattened)
Buffer
`text` (post body, truncated to 280 chars for Twitter profiles)
Notion
`properties.Channel` (multi_select[].name)
Buffer
`profile_ids[]` (resolved via BUFFER_PROFILE_IDS map)
Notion
`properties.Deadline` (date.start)
Buffer
`scheduled_at` (converted to ISO 8601 UTC)
Notion
`properties.Media URL` (url)
Buffer
`media.link`
Buffer
`id` (update ID, returned on create)
Notion
`properties.Buffer Update ID` (text, written back)

Agent 2 handoff: Notion and Buffer to HubSpot (campaign logging)

Source tool
Source field
Destination tool
Destination field
Notion
`properties.Title` (flattened)
HubSpot
`properties.hs_event_name`
Notion
`properties.Deadline` (ISO 8601 UTC)
HubSpot
`properties.hs_start_date`
Notion
`properties.Channel` (first value)
HubSpot
`properties.hs_content_channel` (custom)
Notion
`id` (page ID)
HubSpot
`properties.hs_external_event_id`
HubSpot
`id` (campaign object ID, returned on create)
Notion
`properties.Campaign ID` (written back if new)

Agent 3 handoff: Buffer to Notion (weekly performance sync)

Source tool
Source field
Destination tool
Destination field
Notion
`properties.Buffer Update ID` (text, read)
Buffer
GET `/1/updates/{update_id}/interactions.json` (lookup key)
Buffer
`statistics.likes`
Notion
`properties.Engagement Likes` (number)
Buffer
`statistics.comments`
Notion
`properties.Engagement Comments` (number)
Buffer
`statistics.shares`
Notion
`properties.Engagement Shares` (number)
Buffer
`statistics.reach`
Notion
`properties.Engagement Reach` (number)
If a Notion content record does not have a Buffer Update ID populated, the Performance Sync Agent skips that record and logs the omission to the workflow run log. Do not attempt a fuzzy match on title or date. A missing Buffer Update ID indicates the post was scheduled outside the automation or the Agent 2 write-back failed; both cases require a manual check.
Integration and API SpecPage 2 of 3
FS-DOC-05Technical

04Build stack and orchestration

Orchestration layout
Three separate workflows, one per agent. Each workflow owns its own trigger, credential references, and error branch. Workflows share a single credential store; no credentials are duplicated across workflows. Inter-workflow communication is via Notion record state (the Status field acts as the shared state machine).
Agent 1 trigger mechanism
Poll-based. The orchestration layer queries the Notion content database every 5 minutes. Two conditions are evaluated independently: (1) a new page exists where Status = 'Brief Sent' has not been set and a Title is present; (2) an existing page has a Deadline equal to today and Status is still 'Draft Submitted' or earlier. Each condition produces a separate execution branch within the same workflow.
Agent 2 trigger mechanism
Poll-based with state change detection. The orchestration layer queries the Notion database every 5 minutes. Trigger condition: a page where Draft Link is not empty AND Status was previously 'Brief Sent' (detected by Status transition). Second condition: Status field changes to 'Approved'. The last_seen_status for each page_id is stored in a persistent workflow variable (key: notion_last_status_{page_id}) to detect the transition without relying on Notion webhooks. No signature validation is required as the trigger is an outbound poll.
Agent 3 trigger mechanism
Time-based schedule. Runs every Monday at 07:00 in the configured workspace timezone (store WORKSPACE_TIMEZONE in the credential store, e.g. America/New_York). The workflow iterates over all Notion records where Status = 'Published' and Buffer Update ID is not empty, fetching metrics for each.
Credential store scope
All secrets are stored in the orchestration platform's native encrypted credential store. No credential value appears in any workflow node, environment variable file, or log output. Credentials are referenced by key name only within workflow expressions.
Token rotation policy
BUFFER_ACCESS_TOKEN is refreshed automatically on 401 using BUFFER_REFRESH_TOKEN. HUBSPOT_PRIVATE_APP_TOKEN and NOTION_API_TOKEN do not auto-expire but must be rotated every 90 days manually. SLACK_BOT_TOKEN does not expire unless the Slack App is reinstalled. A calendar reminder for 90-day rotation should be set for all tokens at launch.
All keys above must be present in the credential store before any workflow is activated. Values are entered once by the FullSpec team during the Connect phase and never appear in workflow logic or logs.
// Credential store contents (key names only — no values stored here)

NOTION_API_TOKEN              // Notion internal integration token
NOTION_DATABASE_ID            // Content calendar database ID (e.g. 'a1b2c3d4e5f6...')

SLACK_BOT_TOKEN               // Bot User OAuth Token (xoxb-...)
SLACK_MARKETING_MANAGER_USER_ID  // HubSpot user ID for routing fallback
SLACK_DEFAULT_REVIEW_CHANNEL  // Channel ID for reviewer notifications (e.g. 'C0123ABCDE')

BUFFER_ACCESS_TOKEN           // OAuth 2.0 access token
BUFFER_REFRESH_TOKEN          // OAuth 2.0 refresh token
BUFFER_CLIENT_ID              // App client ID
BUFFER_CLIENT_SECRET          // App client secret
BUFFER_PROFILE_IDS            // JSON map: { "LinkedIn": "abc", "Twitter": "def", "Instagram": "ghi" }

HUBSPOT_PRIVATE_APP_TOKEN     // Private App Bearer token
HUBSPOT_CAMPAIGN_OWNER_ID     // HubSpot user ID of the marketing manager

WORKSPACE_TIMEZONE            // e.g. 'America/New_York' — used by Agent 3 schedule trigger

05Error handling and retry logic

Every integration point must have a defined failure behaviour. Unhandled exceptions must never fail silently. Where a step fails after all retries, the orchestration layer must write a structured error event to the workflow run log and send an alert to SLACK_MARKETING_MANAGER_USER_ID.

Integration
Scenario
Required behaviour
Notion poll (all agents)
API returns 429 (rate limit)
Pause execution. Apply exponential backoff: wait 5 s, 10 s, 20 s across 3 retries. If all retries fail, log the error and skip the current poll cycle. Resume on the next scheduled poll interval. Do not alert the marketing manager for a single missed poll cycle.
Notion poll (all agents)
API returns 500 or 503
Retry 3 times with 30-second intervals. If all retries fail, send a Slack alert to SLACK_MARKETING_MANAGER_USER_ID: 'Notion connection error — workflow paused. Contact support@gofullspec.com.' Log full error payload to the run log.
Notion write-back (Agent 2, Agent 3)
PATCH request fails after retries (status field or metric field update)
Log the failed write with page_id, intended field, and intended value. Send a Slack alert to the marketing manager listing the affected content record. The record must be manually updated by the team. Do not retry indefinitely; cap at 3 attempts.
Slack message send (Agent 1, Agent 2)
chat.postMessage returns error: channel_not_found or user_not_found
Attempt fallback: resolve the writer email from Notion Assignee field and look up via users:read.email. If still unresolved, post the brief or notification to SLACK_DEFAULT_REVIEW_CHANNEL with a note that the intended recipient could not be reached. Log the routing failure.
Slack message send (Agent 1, Agent 2)
API returns 429 (rate limit)
Wait 1 second and retry up to 3 times. If still throttled, queue the message for the next execution cycle (within 5 minutes). Do not drop the message silently.
Buffer post create (Agent 2)
API returns 400 (invalid profile ID or malformed request)
Do not retry. Log the error with the full request body and the Notion page_id. Send a Slack alert to the marketing manager: 'Buffer scheduling failed for [Topic]. The post must be scheduled manually.' Mark the Notion record Status as 'Scheduling Failed' (add this select option to the database before build).
Buffer post create (Agent 2)
API returns 401 (token expired)
Trigger the token refresh flow using BUFFER_REFRESH_TOKEN. Retry the original request once with the new access token. If the refresh itself fails (400 or 401), send a Slack alert and halt the workflow. Do not retry post creation with an invalid token.
Buffer metrics fetch (Agent 3)
Update ID not found (404) for a Notion record
Skip the record. Log the page_id and Buffer Update ID value that returned 404. After completing the full run, send a single consolidated Slack summary listing all skipped records. Do not send one alert per skipped record.
Buffer metrics fetch (Agent 3)
Analytics endpoint returns empty or null statistics
Write zero values to the Notion fields (do not leave fields blank). Add a note in the Notion record's Engagement Reach field description or via a separate Notes field: 'Metrics unavailable — may indicate LinkedIn or TikTok channel (manual pull required).' Log the channel name.
HubSpot campaign create (Agent 2)
API returns 403 (insufficient scope or plan limit)
Fall back immediately to the contact note endpoint (POST /crm/v3/objects/notes). Log that the campaign object endpoint was unavailable and the fallback was used. Send a single alert to the marketing manager explaining the plan limitation. Do not retry the campaign endpoint in this run.
HubSpot campaign create (Agent 2)
API returns 429 (rate limit)
Apply exponential backoff: wait 10 s, 30 s, 60 s across 3 retries. If all fail, log the error with the Notion page_id and intended HubSpot payload. Queue for retry on the next Agent 2 execution cycle triggered by the same Notion record (Status remains 'Approved' until the write succeeds).
Any integration
Unhandled exception (unexpected status code, network timeout, malformed response)
Catch at the workflow level using a global error handler. Log the full exception including integration name, endpoint, request payload, response body, and timestamp. Send an immediate Slack alert to SLACK_MARKETING_MANAGER_USER_ID. Do not swallow the exception or allow the workflow to continue past the failed step. Notify support@gofullspec.com if the error recurs more than 3 times in a 24-hour window.
Unhandled exceptions must never fail silently. Every workflow must implement a global catch branch that fires on any uncaught error, writes to the run log, and sends a Slack alert. Silence in the workflow output is not evidence of success.
Integration and API SpecPage 3 of 3

More documents for this process

Every document generated for Content Calendar Management.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Developer Handover Pack
Technical · Developer
View
Test and QA Plan
Quality · Developer
View