FS-DOC-05Technical
Integration and API Spec
Project Milestone Tracking
[YourCompany.com] · Operations Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative technical reference for every API connection, OAuth scope, field mapping, credential entry, and error-handling rule required to build and operate the Project Milestone Tracking automation. It covers all five named tools (Asana, Google Sheets, Gmail, Slack, and Notion) plus the orchestration layer. The FullSpec team uses this specification during build, QA, and post-launch maintenance. It assumes the reader is comfortable with REST APIs, OAuth 2.0 flows, and webhook configuration. Nothing in this document should be skipped or deferred: every section represents a decision that must be resolved before a connection is made to a production environment.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agents
Asana
Source of milestone and task data; trigger for daily overdue scan
OAuth 2.0 (Authorization Code flow)
Asana Premium ($10.99/user/month) for advanced search and custom fields
Agent 1 (Milestone Monitor), Agent 2 (Owner Reminder)
Google Sheets
Central milestone tracking log; read/write for all three agents
OAuth 2.0 via Google Identity Platform
Google Workspace (any tier) or personal Google account with Sheets API enabled
Agent 1 (Milestone Monitor), Agent 3 (Report Builder)
Gmail
Outbound personalised reminder emails to milestone owners
OAuth 2.0 via Google Identity Platform (same credentials as Sheets)
Google Workspace Business Starter or personal Gmail with Gmail API enabled
Agent 2 (Owner Reminder)
Slack
Escalation alert messages and weekly report digest to leadership channel
OAuth 2.0 (Slack App installation, Bot Token)
Slack Pro or higher for message history and channel management API access
Agent 2 (Owner Reminder), Agent 3 (Report Builder)
Notion
Weekly milestone status report page; written by Report Builder Agent
OAuth 2.0 (Notion public integration)
Notion Plus ($8/user/month) for full API write access and shared pages
Agent 3 (Report Builder)
Orchestration layer
Hosts and executes all three agent workflows; manages scheduling, credential store, retry logic, and inter-agent sequencing
Platform-level service account or API key per connected tool
Paid tier required to support scheduled triggers and multi-step workflows ($49/month per template JSON)
All agents (system-level)
Before you connect anything: provision a sandbox or test environment for every tool listed above and validate each OAuth connection end-to-end before introducing production credentials. For Asana, create a dedicated test project with dummy milestones. For Google Sheets and Gmail, use a non-production Google Workspace account. For Slack, use a private test channel. For Notion, use a duplicate of the target database. Only promote to production credentials after all test cases in the QA plan pass.
02Per-tool integration detail
Asana
Asana is the trigger source and the authoritative record for all milestone data. The orchestration layer polls the Asana API on a scheduled basis each morning at 08:00 and also listens for task update webhooks to capture owner status changes in near-real-time. All project IDs in scope must be stored in the credential store, not hardcoded in the workflow.
Auth method
OAuth 2.0 Authorization Code flow. Redirect URI must be registered in the Asana developer app settings. Access token expires in 3600 seconds; refresh token is long-lived and stored in the credential store.
Required scopes
default (grants access to tasks, projects, workspaces, users, and webhooks under the authorised user's workspace). The Asana API does not expose granular scope strings beyond 'default' for its standard OAuth app; all permission control is via workspace membership and project sharing.
Webhook / trigger setup
Register a webhook on each in-scope project using POST /webhooks with resource set to the project GID and target set to the orchestration layer's inbound webhook URL. Asana sends an X-Hook-Signature header (HMAC-SHA256) on every delivery; the orchestration layer must validate this signature before processing any payload. Subscribe to the 'task' resource type with filters for 'due_on' and 'completed' field changes.
Required configuration
ASANA_WORKSPACE_GID: stored in credential store. ASANA_PROJECT_GIDS: comma-separated list of all in-scope project GIDs, stored in credential store. ASANA_CUSTOM_FIELD_GID_MILESTONE_TYPE: GID of the custom field used to identify milestone tasks (as opposed to standard tasks), stored in credential store. Escalation threshold (days overdue before flag): configured as a workflow variable, default value 3.
Rate limits
150 requests/minute per user token (Asana Premium). At ~40 milestones/week across all projects, the daily scan generates at most 80-120 API calls (one search + one task GET per item). Well within limit; no throttling required at current volume. Implement a 200 ms inter-request delay as a precaution during the bulk scan loop.
Constraints
Asana free tier does not support advanced search or custom fields; Premium is the minimum required plan. All milestone tasks must use a consistent naming convention and have the custom milestone field populated before the agent can distinguish them from regular tasks. Project GIDs must not be hardcoded; they are managed via the credential store so new projects can be added without rebuilding the workflow.
// Trigger payload (webhook, task due_on change)
{ "events": [{ "resource": { "gid": "<task_gid>", "resource_type": "task" }, "type": "changed", "change": { "field": "due_on", "new_value": "<YYYY-MM-DD>" }, "user": { "gid": "<user_gid>" } }] }
// Scheduled poll: GET /tasks/search
?workspace=<ASANA_WORKSPACE_GID>&due_on.before=<today>&completed=false&custom_fields.<MILESTONE_FIELD_GID>.is_set=true
// Task GET response fields used downstream
task.gid | task.name | task.due_on | task.assignee.gid | task.assignee.name | task.assignee.email | task.projects[0].name | task.permalink_url | task.completedGoogle Sheets
Google Sheets acts as the central milestone tracking log. The Milestone Monitor Agent writes overdue items to the sheet each morning; the Owner Reminder Agent reads from it to determine which items require escalation; and the Report Builder Agent reads the full sheet every Monday to generate the Notion status report. A fixed sheet schema must be in place before any agent connects.
Auth method
OAuth 2.0 via Google Identity Platform. A single OAuth 2.0 credential covers both the Sheets API and the Gmail API (same Google account). Stored as GOOGLE_OAUTH_CREDENTIALS in the credential store.
Required scopes
https://www.googleapis.com/auth/spreadsheets (read and write to all spreadsheets); https://www.googleapis.com/auth/drive.file (required to create new sheets if the weekly archive sheet does not exist). Do not request https://www.googleapis.com/auth/drive (full drive access) — use drive.file only.
Webhook / trigger setup
Google Sheets does not natively push webhooks. The orchestration layer reads the sheet on a scheduled poll. No inbound webhook registration is required for this tool.
Required configuration
SHEETS_TRACKING_SPREADSHEET_ID: ID of the live milestone tracking Google Sheet, stored in credential store. SHEETS_TRACKING_TAB_NAME: name of the active tracking worksheet tab, default 'MilestoneTracker'. SHEETS_HEADER_ROW: row number of the header row, default 1. Column order is fixed: A=task_gid, B=task_name, C=project_name, D=assignee_name, E=assignee_email, F=due_date, G=days_overdue, H=status, I=escalation_flag, J=last_reminder_sent, K=owner_response, L=last_updated_timestamp.
Rate limits
Google Sheets API: 300 requests/minute per project, 60 requests/minute per user. The automation performs at most 2 batch write operations and 3 read operations per daily run across ~40 rows. Well within limits; no throttling required. Use batchUpdate (spreadsheets.values.batchUpdate) for all writes to minimise API call count.
Constraints
The tracking sheet must not be reformatted or have columns reordered by users, as the agents reference columns by fixed letter index. Add a note to the sheet header row warning users not to modify the column structure. New rows are always appended; rows are never deleted by automation (archival is a separate manual step).
// Write: Milestone Monitor Agent appends/updates a row
spreadsheets.values.batchUpdate -> range: 'MilestoneTracker!A:L'
values: [ task_gid, task_name, project_name, assignee_name, assignee_email, due_date, days_overdue, status, escalation_flag, last_reminder_sent, owner_response, last_updated_timestamp ]
// Read: Owner Reminder Agent reads escalation_flag column
spreadsheets.values.get -> range: 'MilestoneTracker!A:L' -> filter rows where I (escalation_flag) == 'YES'
// Read: Report Builder Agent reads full sheet every Monday
spreadsheets.values.get -> range: 'MilestoneTracker!A:L' -> group by C (project_name), H (status)
Gmail
Gmail is used exclusively for outbound reminder emails sent by the Owner Reminder Agent to milestone task owners. Emails are personalised using a stored template with placeholders resolved at send time from the tracking sheet data. The automation sends on behalf of the authenticated Google account; the from address must be a recognised internal address to avoid spam filtering.
Auth method
OAuth 2.0 via Google Identity Platform, same credential set as Google Sheets (GOOGLE_OAUTH_CREDENTIALS). No additional app registration is required if Sheets is already connected under the same Google account.
Required scopes
https://www.googleapis.com/auth/gmail.send (send email only; does not grant read or inbox access). Do not request https://www.googleapis.com/auth/gmail.modify or https://mail.google.com/ — the minimum send-only scope is sufficient and reduces the attack surface.
Webhook / trigger setup
No inbound webhook required. Gmail is outbound-only in this automation. Owner replies are not parsed via the Gmail API at this stage; owners are directed to update the Asana task directly. If future builds require reply parsing, the Gmail push notification API (users.watch) would be added at that point.
Required configuration
GMAIL_SENDER_ADDRESS: the From address for all reminder emails, stored in credential store. GMAIL_REMINDER_TEMPLATE_ID: reference key for the reminder email template stored in the orchestration layer's template store. Template placeholders: {{task_name}}, {{project_name}}, {{due_date}}, {{days_overdue}}, {{asana_task_url}}, {{owner_first_name}}. GMAIL_REPLY_TO_ADDRESS: optional reply-to address if different from sender, stored in credential store.
Rate limits
Gmail API send quota: 100 send requests/second per user; 25,000 messages/day for Google Workspace accounts, 500/day for personal Gmail. At ~40 reminder emails/week (under 10/day average), this is far below any limit. No throttling required. If the project scope expands significantly, monitor daily send volume against the account type's quota.
Constraints
The authenticated sending account must be a member of the same Google Workspace domain as recipients to avoid deliverability issues. Do not use a shared/alias address as the OAuth principal — authenticate as the primary account and set a Reply-To if needed. All email content must be plain text or basic HTML only; do not use external image assets hosted outside the organisation's domain.
// Outbound: users.messages.send
POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
Authorization: Bearer <access_token>
{
"raw": "<base64url-encoded RFC 2822 message>",
// Resolved template fields injected before encoding:
// To: {{assignee_email}}
// From: {{GMAIL_SENDER_ADDRESS}}
// Subject: 'Milestone overdue: {{task_name}} ({{days_overdue}} days)'
// Body: resolved GMAIL_REMINDER_TEMPLATE_ID with all placeholders substituted
}Integration and API SpecPage 1 of 3
FS-DOC-05Technical
Slack
Slack is used by both the Owner Reminder Agent (escalation alerts to a project channel) and the Report Builder Agent (weekly digest to the leadership channel). Both use the Slack Web API via a Bot Token installed into the target workspace. Channel IDs must be stored in the credential store; channel names must never be hardcoded because they can be renamed without notice.
Auth method
OAuth 2.0 Slack App installation flow. Install a custom Slack app into the target workspace and obtain a Bot User OAuth Token (xoxb-). Store as SLACK_BOT_TOKEN in the credential store. The app must be invited to each target channel before it can post.
Required scopes
chat:write (post messages to channels the bot is a member of); chat:write.public (post to public channels without being a member, use only if required); channels:read (list channels and resolve names to IDs); users:read (resolve Slack user IDs from email addresses for @mentions in escalation alerts); users:read.email (required alongside users:read to look up users by email).
Webhook / trigger setup
No inbound webhook is used for Slack in this automation. Slack is outbound-only. If future builds add Slack slash commands or interactive buttons (e.g. 'Mark resolved' directly from Slack), the Slack Events API and Request URL would be configured at that point. For this build, configure an Incoming Webhook URL as a fallback delivery method only, stored as SLACK_FALLBACK_WEBHOOK_URL in the credential store.
Required configuration
SLACK_BOT_TOKEN: Bot User OAuth Token, stored in credential store. SLACK_ESCALATION_CHANNEL_ID: channel ID (not name) of the project escalation channel, stored in credential store. SLACK_LEADERSHIP_CHANNEL_ID: channel ID of the leadership digest channel, stored in credential store. SLACK_FALLBACK_WEBHOOK_URL: Incoming Webhook URL as a fallback, stored in credential store. Escalation message template: stored in orchestration layer template store with placeholders {{task_name}}, {{project_name}}, {{days_overdue}}, {{assignee_name}}, {{asana_task_url}}, {{stakeholder_slack_id}}.
Rate limits
Slack Web API tier 3: 50 requests/minute for chat.postMessage. The automation posts at most 40 escalation messages/week (under 10/day) plus 1 digest/week. Well within limits; no throttling required. If the escalation volume grows beyond 200 messages/week, introduce a 1.5-second inter-message delay.
Constraints
The bot must be manually invited to SLACK_ESCALATION_CHANNEL_ID and SLACK_LEADERSHIP_CHANNEL_ID before go-live. Channel IDs are immutable even if the channel is renamed; always store the ID not the name. @mentions in escalation alerts require the user's Slack member ID (not display name); resolve this via users.lookupByEmail using the assignee email from Asana.
// Escalation alert: chat.postMessage
POST https://slack.com/api/chat.postMessage
Authorization: Bearer {{SLACK_BOT_TOKEN}}
{
"channel": "{{SLACK_ESCALATION_CHANNEL_ID}}",
"text": "Escalation: *{{task_name}}* is {{days_overdue}} days overdue",
"blocks": [
{ "type": "section", "text": { "type": "mrkdwn", "text": ":red_circle: *{{task_name}}* — {{project_name}}\n*Owner:* <@{{stakeholder_slack_id}}>\n*Overdue by:* {{days_overdue}} days\n<{{asana_task_url}}|View in Asana>" } }
]
}
// Weekly digest: chat.postMessage to leadership channel
{
"channel": "{{SLACK_LEADERSHIP_CHANNEL_ID}}",
"text": "Weekly Milestone Report — {{report_date}}",
"blocks": [
{ "type": "section", "text": { "type": "mrkdwn", "text": "*On track:* {{count_on_track}} | *At risk:* {{count_at_risk}} | *Overdue:* {{count_overdue}}\n<{{notion_report_url}}|View full report in Notion>" } }
]
}Notion
Notion is the destination for the weekly milestone status report, written by the Report Builder Agent every Monday morning. The agent writes to a pre-created Notion database page, appending a dated child page for each weekly report. The parent database ID must be stored in the credential store. The Notion integration must be connected to the target workspace and granted access to the parent page.
Auth method
OAuth 2.0 via Notion public integration. Create a Notion integration at developers.notion.com, obtain the Internal Integration Secret, and store as NOTION_API_KEY in the credential store. Share the target parent database page with the integration from the Notion UI (Share > Invite > select integration name).
Required scopes
Notion's integration model uses capability checkboxes rather than OAuth scope strings. Enable: Read content (required to read the parent database structure); Insert content (required to create new child pages); Update content (required to update existing report pages if a report for the current week already exists). Do not enable User information capabilities unless required for future builds.
Webhook / trigger setup
Notion does not provide outbound webhooks in the current API version. The Report Builder Agent writes to Notion on a schedule (Monday 07:30) triggered by the orchestration layer; no inbound webhook registration is needed.
Required configuration
NOTION_API_KEY: Internal Integration Secret, stored in credential store. NOTION_PARENT_DATABASE_ID: ID of the Notion database that holds all weekly milestone reports, stored in credential store. Page title template: 'Milestone Status Report — Week of {{report_week_start_date}}'. Page properties required in the parent database schema: Title (title type), Week (date type), Status (select type with options: Draft, Published), On Track Count (number), At Risk Count (number), Overdue Count (number). Content blocks within the page are grouped by project name as toggle headings with nested milestone rows.
Rate limits
Notion API: 3 requests/second average; burst allowed. The Report Builder Agent makes 1 page create call plus up to 50 block append calls (one per project section and milestone row) per Monday run. This completes in under 30 seconds at the rate limit. No throttling required at current volume.
Constraints
The parent database must be created and shared with the integration before the first run. The database schema (property names and types listed above) must match exactly — the agent references property names by string, so renaming a property in Notion will break the write operation. If the integration loses access to the page (e.g. the page is moved or the share is revoked), the agent will return a 404 and trigger the error-handling path.
// Create weekly report page
POST https://api.notion.com/v1/pages
Authorization: Bearer {{NOTION_API_KEY}}
Notion-Version: 2022-06-28
{
"parent": { "database_id": "{{NOTION_PARENT_DATABASE_ID}}" },
"properties": {
"Title": { "title": [{ "text": { "content": "Milestone Status Report — Week of {{report_week_start_date}}" } }] },
"Week": { "date": { "start": "{{report_week_start_date}}" } },
"Status": { "select": { "name": "Published" } },
"On Track Count": { "number": {{count_on_track}} },
"At Risk Count": { "number": {{count_at_risk}} },
"Overdue Count": { "number": {{count_overdue}} }
}
}
// Append content blocks
POST https://api.notion.com/v1/blocks/{{new_page_id}}/children
// One toggle block per project, with milestone rows as bulleted list children03Field mappings between tools
The following tables cover every agent handoff where data moves from one tool to another. Field names are shown in exact monospace format as they appear in the API response or sheet column. All mappings are one-directional unless noted.
Handoff 1: Asana to Google Sheets (Milestone Monitor Agent — daily scan output)
Source tool
Source field
Destination tool
Destination field
Asana
`task.gid`
Google Sheets
`A: task_gid`
Asana
`task.name`
Google Sheets
`B: task_name`
Asana
`task.projects[0].name`
Google Sheets
`C: project_name`
Asana
`task.assignee.name`
Google Sheets
`D: assignee_name`
Asana
`task.assignee.email`
Google Sheets
`E: assignee_email`
Asana
`task.due_on`
Google Sheets
`F: due_date`
Computed
`TODAY() - task.due_on` (days)
Google Sheets
`G: days_overdue`
Asana
`task.completed` (false = Open)
Google Sheets
`H: status`
Computed
days_overdue >= 3 OR no response in 24 hrs
Google Sheets
`I: escalation_flag` (YES/NO)
Orchestration
ISO 8601 timestamp at write time
Google Sheets
`L: last_updated_timestamp`
Handoff 2: Google Sheets to Gmail (Owner Reminder Agent — reminder email composition)
Source tool
Source field
Destination tool
Destination field
Google Sheets
`E: assignee_email`
Gmail
`To` header
Google Sheets
`D: assignee_name` (first token)
Gmail
`{{owner_first_name}}` template placeholder
Google Sheets
`B: task_name`
Gmail
`{{task_name}}` template placeholder
Google Sheets
`C: project_name`
Gmail
`{{project_name}}` template placeholder
Google Sheets
`F: due_date`
Gmail
`{{due_date}}` template placeholder
Google Sheets
`G: days_overdue`
Gmail
`{{days_overdue}}` template placeholder
Asana (re-fetched)
`task.permalink_url`
Gmail
`{{asana_task_url}}` template placeholder
Handoff 3: Google Sheets to Slack (Owner Reminder Agent — escalation alert)
Source tool
Source field
Destination tool
Destination field
Google Sheets
`B: task_name`
Slack
`{{task_name}}` in block kit text
Google Sheets
`C: project_name`
Slack
`{{project_name}}` in block kit text
Google Sheets
`G: days_overdue`
Slack
`{{days_overdue}}` in block kit text
Google Sheets
`D: assignee_name`
Slack
`{{assignee_name}}` in block kit text
Google Sheets
`E: assignee_email`
Slack
Resolved via `users.lookupByEmail` to `{{stakeholder_slack_id}}` for @mention
Asana (re-fetched)
`task.permalink_url`
Slack
`{{asana_task_url}}` in block kit action link
Handoff 4: Google Sheets to Notion (Report Builder Agent — Monday weekly report)
Source tool
Source field
Destination tool
Destination field
Google Sheets
`C: project_name` (grouped)
Notion
Toggle heading block label
Google Sheets
`B: task_name`
Notion
Bulleted list item text within project toggle
Google Sheets
`H: status`
Notion
Status label appended to bulleted list item
Google Sheets
`F: due_date`
Notion
Date appended to bulleted list item
Google Sheets
`G: days_overdue`
Notion
Appended as '(X days overdue)' string if > 0
Google Sheets
COUNT rows where H='On Track'
Notion
`On Track Count` database property
Google Sheets
COUNT rows where H='At Risk'
Notion
`At Risk Count` database property
Google Sheets
COUNT rows where H='Overdue'
Notion
`Overdue Count` database property
Handoff 5: Notion to Slack (Report Builder Agent — leadership digest)
Source tool
Source field
Destination tool
Destination field
Notion
`new_page_id` (resolved to public URL)
Slack
`{{notion_report_url}}` in digest block
Google Sheets
COUNT rows where H='On Track'
Slack
`{{count_on_track}}` in digest text
Google Sheets
COUNT rows where H='At Risk'
Slack
`{{count_at_risk}}` in digest text
Google Sheets
COUNT rows where H='Overdue'
Slack
`{{count_overdue}}` in digest text
Orchestration
Monday date of current week
Slack
`{{report_date}}` in digest header
Integration and API SpecPage 2 of 3
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
Three separate workflows, one per agent (Milestone Monitor Agent, Owner Reminder Agent, Report Builder Agent). Each workflow is independently versioned and can be paused or redeployed without affecting the others. All three workflows share a single credential store within the orchestration platform; no credentials are duplicated across workflows.
Agent 1 trigger: Milestone Monitor Agent
Scheduled poll trigger firing at 08:00 every day (platform-native cron scheduler). Additionally, the Asana webhook (registered per project GID) fires an inbound HTTP trigger when a task due_on or completed field changes. Webhook signature validation (HMAC-SHA256 using the Asana webhook secret) is performed as the first step of the workflow before any processing begins. If signature validation fails, the workflow terminates and logs an error without processing the payload.
Agent 2 trigger: Owner Reminder Agent
Triggered by an internal event emitted by Agent 1 on completion of its daily run. This is implemented as a webhook call from Agent 1 to Agent 2's inbound trigger URL within the orchestration platform, passing a JSON payload containing the list of flagged milestone row IDs from the tracking sheet. Agent 2 does not run independently of Agent 1; if Agent 1 fails, Agent 2 does not fire.
Agent 3 trigger: Report Builder Agent
Scheduled poll trigger firing every Monday at 07:30 (platform-native cron scheduler, day-of-week = Monday). This fires before Agent 1's 08:00 run so the report reflects the previous week's final state. Agent 3 reads directly from the Google Sheet and does not depend on Agent 1 completing first on Monday morning.
Credential store structure
All secrets stored in the orchestration platform's native encrypted credential store. No credentials appear in workflow node configurations, code blocks, or template strings. See code block below for the full credential store manifest.
Inter-agent data transfer
Agent 1 to Agent 2: internal webhook with JSON body containing flagged row IDs. Agent 1 to Agent 3: no direct transfer; Agent 3 reads the shared Google Sheet independently. Agent 3 to Slack: Notion page URL returned by the Notion API create call is passed directly within the Agent 3 workflow to the Slack postMessage step.
Logging and observability
Every workflow execution is logged with a run ID, start time, end time, step count, and outcome (success/partial/failed). Logs are retained for 30 days in the orchestration platform. A daily summary of run outcomes is emailed to support@gofullspec.com and the nominated process owner.
Credential store manifest — all entries required before any workflow is activated
// Credential store manifest
// All entries stored in the orchestration platform's encrypted credential store
// Environment: PRODUCTION (duplicate all entries under SANDBOX prefix for test environment)
ASANA_CLIENT_ID = "<Asana OAuth app client ID>"
ASANA_CLIENT_SECRET = "<Asana OAuth app client secret>"
ASANA_ACCESS_TOKEN = "<OAuth access token — rotated automatically>"
ASANA_REFRESH_TOKEN = "<OAuth refresh token — long-lived>"
ASANA_WORKSPACE_GID = "<Asana workspace GID>"
ASANA_PROJECT_GIDS = "<gid1>,<gid2>,<gid3>,..." // comma-separated, all in-scope projects
ASANA_CUSTOM_FIELD_GID_MILESTONE = "<GID of milestone-type custom field>"
ASANA_WEBHOOK_SECRET = "<HMAC-SHA256 secret provided by Asana on webhook registration>"
GOOGLE_OAUTH_CLIENT_ID = "<Google Cloud project OAuth client ID>"
GOOGLE_OAUTH_CLIENT_SECRET = "<Google Cloud project OAuth client secret>"
GOOGLE_OAUTH_ACCESS_TOKEN = "<access token — expires 3600s, auto-refreshed>"
GOOGLE_OAUTH_REFRESH_TOKEN = "<refresh token — long-lived>"
SHEETS_TRACKING_SPREADSHEET_ID = "<Google Sheets document ID from URL>"
SHEETS_TRACKING_TAB_NAME = "MilestoneTracker"
SHEETS_HEADER_ROW = "1"
GMAIL_SENDER_ADDRESS = "<authenticated sender email address>"
GMAIL_REPLY_TO_ADDRESS = "<optional reply-to address>"
GMAIL_REMINDER_TEMPLATE_ID = "reminder_v1" // key into orchestration template store
SLACK_BOT_TOKEN = "xoxb-<workspace bot token>"
SLACK_ESCALATION_CHANNEL_ID = "<channel ID — not channel name>"
SLACK_LEADERSHIP_CHANNEL_ID = "<channel ID — not channel name>"
SLACK_FALLBACK_WEBHOOK_URL = "https://hooks.slack.com/services/<path>"
NOTION_API_KEY = "secret_<Notion internal integration secret>"
NOTION_PARENT_DATABASE_ID = "<Notion database page ID from URL>"
ESCALATION_THRESHOLD_DAYS = "3" // days overdue before escalation flag is set
ESCALATION_REMINDER_WAIT_HOURS = "24" // hours after first reminder before escalation fires
AGENT2_INBOUND_TRIGGER_URL = "<orchestration platform internal webhook URL for Agent 2>"
// Note: rotate ASANA_ACCESS_TOKEN and GOOGLE_OAUTH_ACCESS_TOKEN automatically via OAuth refresh flow
// Note: ASANA_WEBHOOK_SECRET must be re-registered if the webhook is deleted and recreated
05Error handling and retry logic
Unhandled exceptions must never fail silently. Every integration point has a defined failure path. If an error does not match a listed scenario below, the orchestration layer must log the full error payload, halt the affected workflow run, and send an alert to support@gofullspec.com. Do not swallow exceptions with empty catch blocks.
Integration
Scenario
Required behaviour
Asana / OAuth token
Access token expired (401 Unauthorized returned by Asana API)
Immediately attempt token refresh using ASANA_REFRESH_TOKEN via POST https://app.asana.com/-/oauth_token. On success, update ASANA_ACCESS_TOKEN in credential store and retry the failed request once. If refresh also fails (400 or 401), halt the workflow run, log the error with full response body, and send alert to support@gofullspec.com.
Asana / webhook signature
Inbound webhook payload fails HMAC-SHA256 signature validation
Reject the payload immediately with HTTP 400. Do not process any data from an unverified payload. Log the rejection with the raw X-Hook-Signature header value and the computed expected value. Send alert to support@gofullspec.com if more than 3 consecutive rejections occur within 5 minutes.
Asana / task search
Asana returns 429 Too Many Requests during the daily scan
Pause the scan loop for 60 seconds (respect Retry-After header if present), then resume from the last successfully processed task GID. Do not restart the full scan from the beginning. Log the rate limit hit and the resume point.
Google Sheets / write
batchUpdate call returns 429 or 503 (quota exceeded or service unavailable)
Retry with exponential backoff: wait 10 seconds, then 30 seconds, then 120 seconds. Maximum 3 retries. If all retries fail, log the unwritten row data to the orchestration platform's error log and send an alert. The row must not be silently dropped; it must be re-queued for the next scheduled run.
Google Sheets / read
Report Builder Agent reads an empty sheet (zero data rows) on Monday morning
Treat as a valid state only if the sheet header row is present. If the header row is also absent, halt the workflow and alert support@gofullspec.com as this indicates the sheet has been deleted or the spreadsheet ID is wrong. If the header is present but there are no data rows, post a Slack digest message stating 'No milestones found in tracking sheet this week' and create a minimal Notion page with zero counts.
Gmail / send
users.messages.send returns 429 (rate limit) or 500 (server error)
Retry after 30 seconds, then after 90 seconds. Maximum 2 retries per individual email. If all retries fail, log the intended recipient email address and the resolved template content to the orchestration error log. Do not skip the recipient silently. Alert support@gofullspec.com with a list of failed sends after the daily run completes.
Gmail / send
users.messages.send returns 400 with 'Invalid To header' (malformed email address from Asana)
Skip this specific send, log the malformed address and the source task GID, and continue processing remaining recipients. Flag the row in Google Sheets column K (owner_response) as 'EMAIL_INVALID' and set escalation_flag to 'YES' so the item appears in the Slack escalation channel for manual review.
Slack / chat.postMessage
Bot token is revoked or returns 401 invalid_auth
Halt all Slack posting steps in the affected workflow run. Attempt to post a message via SLACK_FALLBACK_WEBHOOK_URL (Incoming Webhook, which uses a separate authentication mechanism). Log the token failure and send an email alert to support@gofullspec.com. Do not retry the Bot Token until it has been reissued and updated in the credential store.
Slack / chat.postMessage
channel_not_found error returned (channel ID is wrong or bot not invited)
Halt posting to the affected channel. Log the channel ID that was attempted. Do not fall back to a different channel silently. Alert support@gofullspec.com with the channel ID so the credential store entry can be corrected. The workflow must not re-attempt until the credential store is updated.
Notion / page create
API returns 404 object_not_found (parent database ID is wrong or integration access revoked)
Halt the Notion write step. Do not attempt to create a new parent database. Log the NOTION_PARENT_DATABASE_ID value that was used. Alert support@gofullspec.com. As a manual fallback, export the tracking sheet data as a CSV and attach it to the Slack leadership digest message with a note that the Notion report failed this week.
Notion / page create
API returns 409 conflict (a report page for the current week already exists)
Switch to an update operation: retrieve the existing page ID by querying the parent database filtered by the current week's date property, then use PATCH /blocks/{page_id}/children to append new content blocks. Do not create a duplicate page.
Agent 2 / inter-agent trigger
Agent 1 fails to complete its run and does not emit the trigger event to Agent 2
Agent 2 does not fire. This is correct and expected behaviour — Agent 2 must not run without a valid input payload from Agent 1. The failure of Agent 1 is already logged and alerted per its own error handling path. If Agent 1 is re-run manually after a fix, Agent 2 will be re-triggered by the corrected Agent 1 run. Document this dependency explicitly in the SOP so the operations team knows that fixing Agent 1 is the recovery path.
For all error alerts sent to support@gofullspec.com, the alert email must include: the workflow name and run ID, the step that failed, the HTTP status code and response body (truncated to 500 characters), the timestamp of the failure, and the next scheduled retry time if applicable. Generic 'workflow failed' alerts without this context will not be actionable.
Integration and API SpecPage 3 of 3