Back to SEO Content Production Workflow

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

SEO Content Production Workflow

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

This document is the authoritative technical reference for every integration used in the SEO Content Production Workflow automation. It covers authentication methods, required API scopes, webhook and trigger configuration, field mappings between tools, credential store contents, orchestration layout, and error handling behaviour for all three agents: the Brief Generation Agent, the SEO Audit Agent, and the CMS Publishing Agent. The FullSpec team uses this spec to build, configure, and test every connection. No external platform names are prescribed for the orchestration layer; all references to the build environment use the generic term 'automation platform'.

01Tool inventory

Tool
Role in automation
Auth method
Min plan required
Used by agent(s)
Automation platform
Orchestration layer: runs all three agent workflows, manages triggers, credential store, and retry logic
N/A (internal)
Any tier supporting webhooks, polling, and HTTP requests
All agents
Notion
Content calendar and status tracker: triggers all three agents on status-field changes; receives write-backs of brief links, SEO scores, and live URLs
OAuth 2.0 / Internal integration token
Notion Plus (API access available on all paid plans; Free plan supported but rate-limited)
Agent 1, Agent 2, Agent 3
Ahrefs
Keyword metrics and SERP data source for brief generation
API key (Bearer token)
Ahrefs Standard or Advanced (API access not included on Lite plan)
Agent 1
Google Docs (Google Drive API)
Brief document creation and storage; draft document reading for SEO audit and CMS publish
OAuth 2.0 (service account recommended for server-side access)
Google Workspace (any tier) or personal Google account with Drive API enabled
Agent 1, Agent 2, Agent 3
Surfer SEO
SEO content scoring and section-level recommendation retrieval for the SEO Audit Agent
API key (Bearer token)
Surfer SEO Scale or above (Content Audit API endpoint required)
Agent 2
Slack
Writer and editor notifications: brief-ready DM and SEO scorecard alert
OAuth 2.0 / Bot token (xoxb-)
Slack Free or above (incoming webhooks and bot tokens available on all tiers)
Agent 2
WordPress
CMS publishing via REST API: post creation, metadata population, scheduling, and canonical URL retrieval
Application password (Basic Auth over HTTPS) or JWT plugin token
WordPress.org self-hosted (REST API enabled by default); WordPress.com Business plan minimum for REST API access
Agent 3
Before you connect anything: provision sandbox or staging credentials for every tool listed above and validate each connection in a non-production environment before any production credentials are entered into the automation platform. For Notion, use a duplicate test database. For WordPress, use a staging site clone. For Ahrefs and Surfer SEO, confirm API quota availability in the staging key before running volume tests.
Integration and API SpecPage 1 of 4
FS-DOC-05Technical

02Per-tool integration detail

Notion

Notion is the central status trigger and write-back destination for all three agents. The automation platform polls the target database for status-field changes or receives webhook events via the Notion API. All Notion database property names must match the agreed schema exactly; any rename at the Notion level will break trigger conditions.

Auth method
Internal integration token (Bearer) stored in the credential store as NOTION_API_TOKEN. Scoped to a single shared integration linked to the content calendar database only.
Required scopes
Read content (read_database, read_page), Update content (update_page), Insert content (insert_content) — granted via the Notion integration settings panel under 'Capabilities'.
Webhook / trigger setup
Notion does not natively push webhooks. The automation platform polls the content calendar database using the POST /v1/databases/{database_id}/query endpoint, filtering for rows where the 'Status' property has changed within the last polling interval. Recommended poll interval: 60 seconds. Database ID stored as NOTION_DATABASE_ID in the credential store.
Required configuration
Content calendar database must contain the following properties with these exact names: 'Status' (select), 'Article Title' (title), 'Target Keyword' (rich_text), 'Assigned Writer' (person or rich_text), 'Target Publish Date' (date), 'Brief Link' (url), 'SEO Score' (number), 'Live URL' (url), 'Publish Date Actual' (date). All names are case-sensitive.
Rate limits
Notion API rate limit: 3 requests per second per integration token. At 12 to 20 articles/month with 60-second polling and batched query calls, peak load is well under 1 request/second. No throttling layer is required at current volume, but polling should batch all changed rows in a single query rather than issuing per-row requests.
Constraints
The Notion API returns a maximum of 100 results per query page. Pagination via the 'start_cursor' parameter must be implemented if the database exceeds 100 rows. Rich text fields are returned as arrays; the automation platform must extract the plain_text value from the first element.
// Trigger poll — POST /v1/databases/{NOTION_DATABASE_ID}/query
Filter: { property: 'Status', select: { equals: 'Approved' } }
// Write-back — PATCH /v1/pages/{page_id}
Body: { properties: { 'Brief Link': { url: '<google_docs_url>' }, 'Status': { select: { name: 'Brief Ready' } } } }
Ahrefs

Ahrefs provides keyword volume, keyword difficulty, SERP competitor URLs, and related keyword suggestions for the Brief Generation Agent. All API calls use the v3 REST API. The integration is read-only; no data is written back to Ahrefs.

Auth method
API key passed as a Bearer token in the Authorization header. Stored in the credential store as AHREFS_API_KEY. Never hardcoded in workflow logic.
Required scopes
Ahrefs API keys are not scope-segmented on Standard/Advanced plans. The key grants full read access. Access is controlled at the plan level. Confirm the key belongs to a Standard or Advanced seat before provisioning.
Webhook / trigger setup
No webhook. Agent 1 calls Ahrefs synchronously at runtime. Two endpoints are called per article: GET /v3/keywords-explorer/overview (keyword volume, difficulty, CPC, global volume) and GET /v3/serp-overview (top 10 SERP URLs for competitor reference). Target keyword is passed as the 'keyword' query parameter. Country code stored as AHREFS_DEFAULT_COUNTRY (default: 'us').
Required configuration
AHREFS_API_KEY stored in credential store. AHREFS_DEFAULT_COUNTRY stored in credential store. A monthly API row-unit budget cap must be set in the automation platform config to prevent overages; recommended cap: 500 rows/month for 20 briefs/month with headroom.
Rate limits
Ahrefs Standard plan: 500 API row units included per month. Each keywords-overview call consumes 1 row unit; each serp-overview call consuming up to 10 row units per request. At 20 articles/month, estimated consumption is approximately 220 row units/month. No in-workflow throttling is required, but the platform must log row-unit usage per run and alert if the monthly cap is approached within 20% of the limit.
Constraints
API access is not available on the Ahrefs Lite plan. If the account is downgraded, all Agent 1 runs will fail with a 403 response. The automation platform must handle 403 distinctly from transient errors and alert immediately rather than retrying.
// GET https://apiv3.ahrefs.com/v3/keywords-explorer/overview
Headers: { Authorization: 'Bearer {AHREFS_API_KEY}' }
Params: { keyword: '<target_keyword>', country: '{AHREFS_DEFAULT_COUNTRY}', select: 'volume,difficulty,cpc,keyword_difficulty' }
// Response extract
{ volume: 2400, difficulty: 34, cpc: 1.20, keyword_difficulty: 34 }
// GET https://apiv3.ahrefs.com/v3/serp-overview
Params: { keyword: '<target_keyword>', country: '{AHREFS_DEFAULT_COUNTRY}', select: 'url,title,traffic' }
// Response extract — top 5 competitor URLs passed to brief template
Google Docs (Google Drive API + Google Docs API)

Google Docs serves three functions: Agent 1 creates a new brief document from a master template; Agent 2 reads the draft document content to send to Surfer SEO; Agent 3 reads the approved draft content to construct the WordPress post body. A service account with domain-wide delegation is the recommended auth approach to avoid per-user OAuth token management.

Auth method
OAuth 2.0 service account. Service account JSON key stored in the credential store as GOOGLE_SERVICE_ACCOUNT_JSON. The service account must be granted Editor access on the shared 'Content Briefs' and 'Article Drafts' Google Drive folders.
Required scopes
https://www.googleapis.com/auth/drive (create, read, update files); https://www.googleapis.com/auth/documents (read and write document content); https://www.googleapis.com/auth/drive.file (scoped file access — use if minimising permissions).
Webhook / trigger setup
No webhook from Google Docs triggers the automation. Status changes come from Notion. The automation platform calls the Docs API on demand when an agent run begins.
Required configuration
GOOGLE_BRIEF_TEMPLATE_ID: the Drive file ID of the master brief template (stored in credential store, not hardcoded). GOOGLE_BRIEFS_FOLDER_ID: the Drive folder ID where new brief documents are created. GOOGLE_DRAFTS_FOLDER_ID: the Drive folder ID where writer drafts are stored. Template must contain named placeholders: {{target_keyword}}, {{secondary_keywords}}, {{intent_summary}}, {{recommended_headings}}, {{suggested_word_count}}, {{competitor_urls}}. These are replaced via the Docs API batchUpdate replaceAllText method.
Rate limits
Google Docs API: 300 read requests per minute per project; 60 write requests per minute per project. At current volume (20 articles/month, 3 API calls per article), peak load is negligible. No throttling required.
Constraints
The service account must have explicit access to the template file and the target folders; it cannot access files outside its granted scope. Do not store the service account JSON key anywhere other than the credential store. The Docs API replaceAllText is case-sensitive; placeholder strings in the template must match exactly.
// Step 1: Copy template to Briefs folder
POST https://www.googleapis.com/drive/v3/files/{GOOGLE_BRIEF_TEMPLATE_ID}/copy
Body: { name: '<Article Title> — Brief', parents: ['{GOOGLE_BRIEFS_FOLDER_ID}'] }
// Step 2: Replace placeholders
POST https://docs.googleapis.com/v1/documents/{new_doc_id}:batchUpdate
Body: { requests: [ { replaceAllText: { containsText: { text: '{{target_keyword}}' }, replaceText: '<keyword>' } }, ... ] }
// Step 3 (Agent 2 / Agent 3): Read document body
GET https://docs.googleapis.com/v1/documents/{doc_id}
Extract: body.content[].paragraph.elements[].textRun.content — concatenate all runs
Surfer SEO

The SEO Audit Agent submits the article draft text to the Surfer SEO Content Editor API, retrieves the overall content score and section-level recommendations, and packages these into a scorecard delivered via Slack. This is a synchronous request-response integration; the agent waits for the score before proceeding.

Auth method
API key passed as a Bearer token in the Authorization header. Stored in the credential store as SURFER_API_KEY.
Required scopes
Surfer SEO API keys are not scope-segmented. The key grants access to all API endpoints available on the account plan. A Scale plan or above is required for the Content Audit endpoint.
Webhook / trigger setup
No webhook. The SEO Audit Agent calls POST /api/v1/audits synchronously, passing the article text and target keyword. The API returns the score and recommendations within the same response (typical latency: 3 to 8 seconds).
Required configuration
SURFER_API_KEY in credential store. SURFER_SCORE_THRESHOLD stored as a platform config variable (default: 70). Any section scoring below this threshold is flagged in the Slack scorecard. The threshold is adjustable without a code change.
Rate limits
Surfer SEO API: 60 requests per minute on Scale plan. At 20 articles/month with one audit call each, monthly volume is 20 requests. No throttling required at current volume.
Constraints
The Content Audit endpoint has a maximum request body size of 50,000 characters. Articles exceeding this length must be truncated to the first 50,000 characters for scoring purposes; the automation platform must log a warning when truncation occurs. The API returns HTTP 429 when the rate limit is hit; the retry handler must honour the Retry-After header.
// POST https://api.surferseo.com/api/v1/audits
Headers: { Authorization: 'Bearer {SURFER_API_KEY}', Content-Type: 'application/json' }
Body: { keyword: '<target_keyword>', content: '<article_plain_text>', language: 'en' }
// Response
{ score: 74, sections: [ { heading: 'Introduction', score: 68, recommendation: 'Add 2 uses of secondary keyword' }, ... ] }
// Sections where score < SURFER_SCORE_THRESHOLD are extracted and included in the Slack scorecard
Slack

Slack delivers two distinct notifications: a brief-ready DM to the assigned writer after Agent 1 completes, and a SEO scorecard alert to the content manager after Agent 2 completes. Both messages are sent by a dedicated Slack bot. Incoming webhooks are used for channel messages; the chat.postMessage API method is used for direct messages.

Auth method
OAuth 2.0 Bot token (xoxb- prefix). Stored in the credential store as SLACK_BOT_TOKEN. The bot must be installed to the workspace and invited to any channels it posts in.
Required scopes
chat:write (post messages to channels and DMs); users:read.email (look up a Slack user by email address to resolve the 'Assigned Writer' Notion field to a Slack user ID); channels:read (verify channel membership before posting).
Webhook / trigger setup
Slack is not a trigger source in this workflow. The automation platform sends outbound messages only. No incoming webhook listener is required. Optionally, a Slack App webhook URL can be stored as SLACK_WEBHOOK_URL_CONTENT_TEAM for channel-level alerts as a simpler alternative to the bot token for channel posts.
Required configuration
SLACK_BOT_TOKEN in credential store. SLACK_CONTENT_MANAGER_USER_ID: the Slack user ID of the content manager (stored in credential store). SLACK_WRITER_CHANNEL_ID: the channel or DM target for writer notifications. SLACK_EDITOR_CHANNEL_ID: the channel for SEO scorecard alerts. Message templates are stored as platform config variables and are not hardcoded in workflow logic.
Rate limits
Slack API tier 3 rate limit: 50 requests per minute for chat.postMessage. At 20 articles/month, peak load is two messages per article: well within limits. No throttling required.
Constraints
Slack user ID lookup via email requires the users:read.email scope and returns a 404 if the writer's email in Notion does not match a Slack workspace member. The automation platform must handle this case by falling back to posting to the SLACK_WRITER_CHANNEL_ID with the writer's name in the message body rather than failing the run.
// Brief-ready DM — POST https://slack.com/api/chat.postMessage
Headers: { Authorization: 'Bearer {SLACK_BOT_TOKEN}' }
Body: { channel: '<writer_slack_user_id>', text: 'Your brief for "<Article Title>" is ready.', blocks: [ { type: 'section', text: { type: 'mrkdwn', text: '*Brief:* <{brief_url}|View in Google Docs>\n*Keyword:* <target_keyword>\n*Due:* <publish_date>' } } ] }
// SEO scorecard alert — POST https://slack.com/api/chat.postMessage
Body: { channel: '{SLACK_EDITOR_CHANNEL_ID}', text: 'SEO Audit complete for "<Article Title>". Score: <score>/100.', blocks: [ { type: 'section', text: { type: 'mrkdwn', text: '*Flagged sections:*\n<formatted_flag_list>' } } ] }
WordPress

The CMS Publishing Agent creates a new WordPress post via the WordPress REST API, populating all metadata fields from the Notion article record, and retrieves the canonical URL of the scheduled or published post to write back to Notion. The REST API must be enabled on the WordPress installation (it is enabled by default on self-hosted WordPress.org installs).

Auth method
WordPress Application Password (introduced in WordPress 5.6). The application password is generated from the WordPress admin user profile and stored in the credential store as WP_APP_PASSWORD. Combined with WP_APP_USERNAME, these are Base64-encoded and passed as a Basic Auth header over HTTPS. A JWT authentication plugin (e.g. JWT Authentication for WP-API) may be used as an alternative; store the token as WP_JWT_TOKEN if so.
Required scopes
WordPress Application Passwords do not use OAuth scopes. The application password inherits the permissions of the WordPress user account it is generated from. The account must have the 'Editor' or 'Administrator' role to create posts, assign categories, assign tags, and set featured images. A dedicated automation user account with the Editor role is strongly recommended over using a personal admin account.
Webhook / trigger setup
No webhook from WordPress triggers the automation. Agent 3 calls the REST API on demand when the Notion 'Approved for Publish' status change is detected. The WordPress site base URL is stored as WP_SITE_URL in the credential store.
Required configuration
WP_SITE_URL, WP_APP_USERNAME, WP_APP_PASSWORD in credential store. Category IDs and tag IDs are numeric in the WordPress REST API and must be stored as platform config variables (WP_DEFAULT_CATEGORY_ID, WP_TAG_MAP) rather than hardcoded. The featured image must be uploaded to the WordPress media library before the post is created; the returned attachment ID is then set as the featured_media field. Slug is derived from the Notion 'Slug' field; if absent, fallback to slugifying the Article Title.
Rate limits
WordPress REST API rate limits are server-dependent. Standard self-hosted WordPress installations do not impose API rate limits by default. Hosting providers may enforce request-per-minute limits at the server level. At 20 articles/month with 2 to 3 API calls per publish run (media upload, post create, URL retrieve), peak load is negligible. If the host enforces limits, add a 2-second delay between media upload and post creation calls.
Constraints
HTTPS is mandatory; Application Passwords must not be used over plain HTTP. The REST API endpoint must not be blocked by a security plugin (e.g. Wordfence, iThemes Security). Confirm that /wp-json/wp/v2/ returns a 200 response before build begins. Featured images must be provided as a publicly accessible URL for the media upload step; a 401 or 403 on the media upload endpoint indicates the automation user lacks sufficient permissions.
// Step 1: Upload featured image
POST {WP_SITE_URL}/wp-json/wp/v2/media
Headers: { Authorization: 'Basic {base64(WP_APP_USERNAME:WP_APP_PASSWORD)}', Content-Disposition: 'attachment; filename="<image_filename>"', Content-Type: 'image/jpeg' }
Body: <binary image data fetched from Notion 'Featured Image URL' field>
Response: { id: 4821 } — store as featured_media_id
// Step 2: Create post
POST {WP_SITE_URL}/wp-json/wp/v2/posts
Body: { title: '<meta_title>', content: '<html_body>', excerpt: '<meta_description>', slug: '<slug>', status: 'future', date: '<iso8601_publish_date>', categories: [<category_id>], tags: [<tag_ids>], featured_media: <featured_media_id>, meta: { _yoast_wpseo_title: '<meta_title>', _yoast_wpseo_metadesc: '<meta_description>' } }
Response: { id: 9103, link: 'https://example.com/<slug>/' } — write link back to Notion
Integration and API SpecPage 2 of 4
FS-DOC-05Technical

03Field mappings between tools

The tables below define every field handoff between tools for each agent. All field names are shown in monospace format as they appear in the source and destination systems. These mappings must be implemented exactly; any deviation in property naming will cause silent data loss or workflow failures.

Agent 1 handoff: Notion to Ahrefs, and Ahrefs plus Notion to Google Docs

Source tool
Source field
Destination tool
Destination field
Notion
`properties.Target Keyword.rich_text[0].plain_text`
Ahrefs API
`keyword` (query param)
Notion
`properties.Article Title.title[0].plain_text`
Google Docs (file name)
`name` (Drive copy request body)
Notion
`properties.Target Publish Date.date.start`
Google Docs placeholder
`{{publish_date}}` (replaceAllText)
Notion
`properties.Assigned Writer.rich_text[0].plain_text`
Google Docs placeholder
`{{assigned_writer}}` (replaceAllText)
Ahrefs response
`data.volume`
Google Docs placeholder
`{{search_volume}}` (replaceAllText)
Ahrefs response
`data.difficulty`
Google Docs placeholder
`{{keyword_difficulty}}` (replaceAllText)
Ahrefs response
`data.cpc`
Google Docs placeholder
`{{cpc}}` (replaceAllText)
Ahrefs response
`serp[0..4].url`
Google Docs placeholder
`{{competitor_urls}}` (replaceAllText, newline-joined)
Google Docs (new file)
`id` (Drive file ID)
Notion write-back
`properties.Brief Link.url`
Automation platform
Static value: `'Brief Ready'`
Notion write-back
`properties.Status.select.name`

Agent 2 handoff: Notion and Google Docs to Surfer SEO, and Surfer SEO to Slack and Notion

Source tool
Source field
Destination tool
Destination field
Notion
`properties.Brief Link.url`
Google Docs API
`documentId` (extracted from URL path segment)
Notion
`properties.Target Keyword.rich_text[0].plain_text`
Surfer SEO API
`keyword` (request body)
Google Docs
`body.content[].paragraph.elements[].textRun.content`
Surfer SEO API
`content` (request body, concatenated plain text)
Surfer SEO response
`score`
Notion write-back
`properties.SEO Score.number`
Surfer SEO response
`score`
Slack message
`text` (interpolated into scorecard message body)
Surfer SEO response
`sections[where score < SURFER_SCORE_THRESHOLD].heading`
Slack message
`blocks[].text.text` (formatted flag list)
Surfer SEO response
`sections[where score < SURFER_SCORE_THRESHOLD].recommendation`
Slack message
`blocks[].text.text` (appended to flag list)
Notion
`properties.Article Title.title[0].plain_text`
Slack message
`text` (article identifier in message)

Agent 3 handoff: Notion and Google Docs to WordPress, and WordPress to Notion

Source tool
Source field
Destination tool
Destination field
Notion
`properties.Brief Link.url`
Google Docs API
`documentId` (draft doc URL path segment)
Google Docs
`body.content[].paragraph.elements[].textRun.content`
WordPress API
`content` (HTML-converted post body)
Notion
`properties.Article Title.title[0].plain_text`
WordPress API
`title`
Notion
`properties.Meta Title.rich_text[0].plain_text`
WordPress API
`meta._yoast_wpseo_title`
Notion
`properties.Meta Description.rich_text[0].plain_text`
WordPress API
`excerpt` and `meta._yoast_wpseo_metadesc`
Notion
`properties.Slug.rich_text[0].plain_text`
WordPress API
`slug`
Notion
`properties.Category.select.name`
WordPress API
`categories` (resolved to numeric ID via WP_TAG_MAP config)
Notion
`properties.Tags.multi_select[].name`
WordPress API
`tags` (each resolved to numeric ID via WP_TAG_MAP config)
Notion
`properties.Featured Image URL.url`
WordPress media upload
Request body binary (fetched from URL, returned `id` used as `featured_media`)
Notion
`properties.Target Publish Date.date.start`
WordPress API
`date` (ISO 8601 format)
WordPress response
`link`
Notion write-back
`properties.Live URL.url`
Automation platform
Static value: `'Published'`
Notion write-back
`properties.Status.select.name`
WordPress response
`date` (actual scheduled date)
Notion write-back
`properties.Publish Date Actual.date.start`
Integration and API SpecPage 3 of 4
FS-DOC-05Technical

04Build stack and orchestration

Orchestration layout
Three discrete workflows in the automation platform, one per agent. Each workflow is independently deployable, versioned, and testable. Shared configuration (credential store entries, config variables) is centralised and referenced by all three workflows; no values are duplicated across workflows.
Shared credential store
A single credential store (environment-scoped: staging and production environments are isolated) holds all API keys, tokens, IDs, and config variables. Workflows reference credentials by key name only; raw values are never written into workflow logic, condition blocks, or log outputs.
Agent 1 trigger mechanism
Poll-based. The automation platform polls the Notion database every 60 seconds using POST /v1/databases/{NOTION_DATABASE_ID}/query filtered to Status = 'Approved'. Each matching row that has not previously been processed (tracked via a platform-managed execution log keyed on the Notion page ID) initiates one Agent 1 workflow run.
Agent 2 trigger mechanism
Poll-based. Same Notion polling pattern as Agent 1, filtered to Status = 'Draft Ready'. The Notion page ID is used to prevent duplicate runs. Poll interval: 60 seconds.
Agent 3 trigger mechanism
Poll-based. Filtered to Status = 'Approved for Publish'. The Notion page ID deduplication key is checked before the workflow proceeds. Poll interval: 60 seconds.
Webhook signature validation
Not applicable for the current trigger mechanism (Notion polling). If the orchestration layer is later reconfigured to receive Notion webhook events (when natively available), all incoming payloads must be validated using the Notion-Signature header and a shared HMAC-SHA256 secret before any workflow logic executes.
Execution log and deduplication
The automation platform maintains an execution log table keyed on Notion page ID and agent number. Before each workflow run, the platform checks whether a successful run has already been recorded for that page ID and agent. Duplicate runs are skipped and logged with a 'duplicate_skipped' status. This prevents re-processing if the poll catches the same status change twice.
Staging vs production environments
All three workflows are built and tested in a staging environment pointing to the staging credential store (Notion test database, WordPress staging site, Surfer SEO staging key). Promotion to production requires a deliberate environment switch and re-validation of all credential store entries. No staging credentials may be used in production workflows.
Credential store and config variable manifest — staging and production stores must be provisioned separately
// CREDENTIAL STORE — all entries required before any workflow runs
// Notion
NOTION_API_TOKEN          = 'secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
NOTION_DATABASE_ID        = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

// Google (service account JSON stored as a single escaped string)
GOOGLE_SERVICE_ACCOUNT_JSON   = '{"type":"service_account","project_id":"...", ...}'
GOOGLE_BRIEF_TEMPLATE_ID      = '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms'
GOOGLE_BRIEFS_FOLDER_ID       = '1a2b3c4d5e6f7g8h9i0jKLMNOPQRSTUVWX'
GOOGLE_DRAFTS_FOLDER_ID       = '9z8y7x6w5v4u3t2s1r0qPONMLKJIHGFEDC'

// Ahrefs
AHREFS_API_KEY            = 'ahrefs_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
AHREFS_DEFAULT_COUNTRY    = 'us'
AHREFS_MONTHLY_ROW_CAP    = 500

// Surfer SEO
SURFER_API_KEY            = 'surfer_xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
SURFER_SCORE_THRESHOLD    = 70

// Slack
SLACK_BOT_TOKEN               = 'xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
SLACK_CONTENT_MANAGER_USER_ID = 'U01ABCDEFGH'
SLACK_WRITER_CHANNEL_ID       = 'C02ABCDEFGH'
SLACK_EDITOR_CHANNEL_ID       = 'C03ABCDEFGH'

// WordPress
WP_SITE_URL               = 'https://yourcompany.com'
WP_APP_USERNAME           = 'wp-automation-user'
WP_APP_PASSWORD           = 'xxxx xxxx xxxx xxxx xxxx xxxx'
WP_DEFAULT_CATEGORY_ID    = 5
WP_TAG_MAP                = '{"seo":12,"content-marketing":18,"blogging":24}'

// Platform config (non-secret, stored as config variables)
POLL_INTERVAL_SECONDS     = 60
MAX_ARTICLE_CHARS_SURFER  = 50000
RETRY_MAX_ATTEMPTS        = 3
RETRY_BACKOFF_SECONDS     = [30, 120, 300]

05Error handling and retry logic

Unhandled exceptions must never fail silently. Every integration point has a defined failure behaviour. If a scenario is not covered by the table below, the automation platform must log the full error payload, set the affected Notion row status to 'Automation Error', and send a Slack alert to the content manager before halting the run.
Integration
Scenario
Required behaviour
Notion (poll / trigger)
Notion API returns 503 or network timeout during polling
Retry the poll request up to 3 times with exponential backoff (30s, 120s, 300s). If all retries fail, log the error and suppress the alert for one cycle; alert the content manager via Slack if polling fails for more than 10 consecutive minutes.
Notion (write-back)
PATCH /v1/pages/{page_id} returns 409 (conflict) or 404 (page deleted)
Retry once after 30 seconds. If the 404 persists, the article row has been deleted; log the event with the page ID, do not retry further, and send a Slack alert to SLACK_CONTENT_MANAGER_USER_ID identifying the lost record.
Ahrefs API
403 response (plan downgrade or key revoked)
Do not retry. Set Notion row Status to 'Automation Error', append 'Ahrefs API access denied — check plan or key' to a Notion 'Error Notes' field, and send an immediate Slack alert. Agent 1 run halts.
Ahrefs API
429 response (row-unit quota exhausted for the month)
Do not retry. Halt Agent 1 for the remainder of the calendar month. Set all queued 'Approved' Notion rows to 'Automation Error' with the note 'Ahrefs quota exceeded'. Alert the content manager with the earliest date the quota resets.
Ahrefs API
500 or 502 transient server error
Retry up to 3 times with backoff (30s, 120s, 300s). If all retries fail, log the full response, set Notion row to 'Automation Error', and alert the content manager. Agent 1 run halts; the row can be manually reset to 'Approved' to trigger a re-run.
Google Docs (create brief)
Drive API returns 403 (service account lacks folder access)
Do not retry. Log the error and send an immediate Slack alert with the missing folder ID. The run halts. This indicates a permissions misconfiguration that must be resolved before any further Agent 1 runs are attempted.
Google Docs (read draft)
Document not found (404) when Agent 2 or Agent 3 reads the draft
Retry once after 60 seconds (the writer may be mid-save). If the document is still not accessible, set the Notion row to 'Automation Error' with the note 'Draft document not found — verify Brief Link field'. Alert the content manager.
Surfer SEO API
429 response — rate limit hit or account quota exhausted
Honour the Retry-After header value. If Retry-After exceeds 600 seconds, halt the run and queue it for retry at the next poll cycle. Log the queued state. Alert the content manager if any article audit is delayed by more than 30 minutes.
Surfer SEO API
Request body exceeds 50,000 character limit
Truncate content to 50,000 characters before the API call. Log a warning including the article title, original character count, and the fact that truncation occurred. Proceed with the truncated audit; do not halt the run. Include a truncation notice in the Slack scorecard message.
Slack (message delivery)
chat.postMessage returns invalid_channel or channel_not_found
Retry once after 10 seconds. If the error persists, log the full message payload to the platform execution log so it can be manually reviewed. Do not halt the parent agent run; Slack notification failure is non-blocking. Alert via the fallback SLACK_EDITOR_CHANNEL_ID if the primary channel is the one that failed.
WordPress (media upload)
Media upload returns 401 or 403
Do not retry. Halt Agent 3 run. Set Notion row to 'Automation Error' with note 'WordPress media upload failed — check automation user permissions'. Send Slack alert. This is a permissions issue requiring manual resolution before the row can be re-triggered.
WordPress (post create)
POST /wp-json/wp/v2/posts returns 500 or times out
Retry up to 3 times with backoff (30s, 120s, 300s). Before each retry, verify no duplicate post was created by querying GET /wp-json/wp/v2/posts?slug={slug}. If a duplicate is found, retrieve its link, write it back to Notion, set status to 'Published', and halt without creating a second post. If all retries fail with no post created, set Notion row to 'Automation Error' and alert the content manager.
Integration and API SpecPage 4 of 4

More documents for this process

Every document generated for SEO Content Production Workflow.

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