Back to Client Reporting Automation

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

Client Reporting Automation

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

This document defines every integration point in the Client Reporting Automation, covering authentication methods, required scopes, field mappings, orchestration layout, credential storage, and failure behaviour. It is written for the FullSpec engineering team responsible for building, testing, and maintaining the automation. Everything in this spec is derived from the confirmed process template and must be treated as the authoritative reference during build. Where implementation choices are left open (for example, the specific orchestration tool), the spec describes required behaviour rather than tool-specific syntax.

01Tool inventory

Tool
Role in automation
Auth method
Min plan required
Used by agent(s)
Automation platform (orchestration layer)
Schedules triggers, routes data between agents, manages retries and error handling
Internal service account / API key per connected tool
Any tier supporting scheduled triggers and HTTP requests
Both agents
Google Sheets
Master schedule config and client staging tab for aggregated metrics
OAuth 2.0 (Google identity)
Google Workspace Business Starter or personal Google account
Agent 1 (Report Data Collector)
HubSpot
CRM data source for account activity, deal stage, and contact metrics; activity log destination post-delivery
OAuth 2.0 (HubSpot app) or Private App token
HubSpot Starter (CRM API access required)
Agent 1 (read); Agent 2 (write)
Google Looker Studio
Branded report template; refreshes from Google Sheets data source on each reporting cycle
Google OAuth 2.0 via data source connector; PDF export via Apps Script or third-party connector
Free (Looker Studio) plus Google Workspace for Apps Script execution
Agent 2 (Report Builder and Distributor)
Google Drive
Archive destination for exported PDF reports organised by client folder
OAuth 2.0 (Google identity, shared with Sheets scope grant)
Google Workspace Business Starter or personal Google account
Agent 2
Gmail
Automated delivery of report email with PDF attachment to confirmed client contact
OAuth 2.0 (Google identity, shared with Drive scope grant)
Google Workspace Business Starter
Agent 2
Slack
Internal delivery notification to designated channel after report is sent
Slack Bot Token (OAuth 2.0, Slack app)
Free tier sufficient; Slack Pro required if message retention or audit logging is needed
Agent 2
Before you connect anything: create sandbox or test environments for every tool and validate each connection with non-production credentials before touching live client data. For Google Workspace, use a dedicated service account in a test Workspace org. For HubSpot, use a developer sandbox account. For Slack, use a private test channel in a non-production workspace. Production credentials must not be entered into any workflow until the Test and QA phase is complete.

02Per-tool integration detail

Google Sheets

Serves two roles: (1) master schedule configuration table listing each client account, reporting frequency, and report template variant; (2) per-client staging tabs where the Report Data Collector writes aggregated metrics before Looker Studio refreshes.

Auth method
OAuth 2.0 using a Google service account with domain-wide delegation, or a user-level OAuth grant. Credentials stored as a JSON key file in the credential store, never hardcoded.
Required scopes
https://www.googleapis.com/auth/spreadsheets | https://www.googleapis.com/auth/drive.readonly (for sheet discovery only)
Trigger / webhook setup
No inbound webhook. The orchestration layer polls the master schedule tab on a configurable cron schedule (default: daily at 06:00 UTC). The trigger reads the NEXT_REPORT_DATE column and fires the agent workflow for each account where the date matches today.
Required configuration
Master schedule tab must contain columns: CLIENT_ID, CLIENT_NAME, REPORT_TEMPLATE_ID, NEXT_REPORT_DATE, REVIEW_REQUIRED (boolean), RECIPIENT_EMAIL. Each client staging tab must be named using the pattern CLIENT_ID_staging (e.g. ACC001_staging). Staging tab column headers must exactly match the field mapping table in section 03. Spreadsheet ID stored in credential store as GSHEETS_SCHEDULE_SPREADSHEET_ID.
Rate limits
Google Sheets API v4: 300 read requests/minute per project, 60 write requests/minute per user. At 40 reports/month (approx. 2 per business day), peak load is well within limits. No throttling required at current volume.
Constraints
Staging tab must be pre-created for every client before the agent runs. Missing tabs cause a hard error and must trigger the fallback alert (see section 05). Do not use merged cells in the staging tab; the API treats merged cells inconsistently.
// Read (Agent 1 trigger poll)
GET /v4/spreadsheets/{GSHEETS_SCHEDULE_SPREADSHEET_ID}/values/Schedule!A2:H
// Returns rows where NEXT_REPORT_DATE == today

// Write (Agent 1 metrics output)
PUT /v4/spreadsheets/{GSHEETS_SCHEDULE_SPREADSHEET_ID}/values/{CLIENT_ID}_staging!A1
// Body: { range, majorDimension: 'ROWS', values: [[metric_name, value, period, timestamp]] }
HubSpot

Two distinct integration roles: Agent 1 queries HubSpot as a read-only data source to pull account activity, deal stage, and contact metrics for the reporting period. Agent 2 writes a delivery activity record to the client contact record after the report is sent.

Auth method
HubSpot Private App token (preferred over OAuth for server-side automations). Token stored in credential store as HUBSPOT_PRIVATE_APP_TOKEN. Passed as Authorization: Bearer {token} header on every request.
Required scopes
crm.objects.contacts.read | crm.objects.deals.read | crm.objects.companies.read | crm.objects.contacts.write (for activity log) | timeline.events | crm.objects.notes.write
Trigger / webhook setup
No inbound webhook from HubSpot in this automation. All reads are pull-based, initiated by Agent 1 after the schedule trigger fires. HubSpot webhook subscriptions are not required.
Required configuration
Field mapping session required before go-live to confirm exact HubSpot property names per account configuration. The following property name placeholders must be confirmed and stored in the credential store: HUBSPOT_DEAL_STAGE_PROP, HUBSPOT_ACTIVITY_COUNT_PROP, HUBSPOT_CONTACT_EMAIL_PROP, HUBSPOT_CONTACT_NAME_PROP. Client HubSpot Company ID for each account must be stored in the master schedule tab (column HUBSPOT_COMPANY_ID). Activity log template string stored as HUBSPOT_ACTIVITY_TEMPLATE in the credential store.
Rate limits
HubSpot API v3: 100 requests/10 seconds (Starter), burst to 150. At 40 reports/month across business hours the average rate is well below the limit. If batch runs occur (multiple reports on same date), introduce a 200ms delay between account requests. No dedicated throttling layer required at current volume.
Constraints
HubSpot property names are case-sensitive and vary by portal configuration. All property names must be resolved at build time against the target HubSpot portal, not assumed from documentation examples. The Private App token does not expire but must be rotated if compromised; store rotation date in the credential store.
// Read: fetch company properties (Agent 1)
GET /crm/v3/objects/companies/{HUBSPOT_COMPANY_ID}?properties={HUBSPOT_DEAL_STAGE_PROP},{HUBSPOT_ACTIVITY_COUNT_PROP}
// Read: fetch contact email for delivery (Agent 1)
GET /crm/v3/objects/contacts?filterGroups=[{filters:[{propertyName:'associatedcompanyid',operator:'EQ',value:'{HUBSPOT_COMPANY_ID}'}]}]&properties={HUBSPOT_CONTACT_EMAIL_PROP},{HUBSPOT_CONTACT_NAME_PROP}

// Write: log delivery activity (Agent 2)
POST /crm/v3/objects/notes
// Body: { properties: { hs_note_body: '{HUBSPOT_ACTIVITY_TEMPLATE}', hs_timestamp: '{ISO8601_TIMESTAMP}' } }
// Associate note to company:
PUT /crm/v3/objects/notes/{note_id}/associations/companies/{HUBSPOT_COMPANY_ID}/note_to_company
Google Looker Studio

Hosts the branded report template connected to the Google Sheets staging tab as its data source. When the staging tab is updated by Agent 1, Looker Studio refreshes automatically on its next connector poll (default 15 minutes). PDF export is triggered by Agent 2 using a Google Apps Script bound to the report, invoked via the Apps Script API, or a third-party connector (e.g. a headless browser export service).

Auth method
Looker Studio data source connector authenticated via the same Google OAuth 2.0 service account used for Sheets. Apps Script execution authenticated via Apps Script API using OAuth 2.0 with the service account. Apps Script API credentials stored as GAPPS_SCRIPT_OAUTH_TOKEN in the credential store.
Required scopes
https://www.googleapis.com/auth/spreadsheets (data source read) | https://www.googleapis.com/auth/script.projects | https://www.googleapis.com/auth/script.runrequest | https://www.googleapis.com/auth/drive.file (for PDF save)
Trigger / webhook setup
No inbound webhook. Agent 2 calls the Apps Script API to execute the export function after confirming the staging tab has been populated. The script function name must be stored as LOOKER_EXPORT_FUNCTION_NAME in the credential store.
Required configuration
One Looker Studio report per template variant. Each report's data source must be pointed at the correct staging tab before go-live. Report ID for each template variant stored in the master schedule tab (column REPORT_TEMPLATE_ID). Apps Script project ID stored as GAPPS_SCRIPT_PROJECT_ID. PDF output folder ID in Google Drive stored as GDRIVE_REPORTS_FOLDER_ID. File naming convention: {CLIENT_ID}_{PERIOD_YYYYMM}_Report.pdf
Rate limits
Apps Script API: 100 requests/100 seconds per user. Looker Studio connector data refresh: every 15 minutes (platform-controlled, cannot be forced faster). At 40 reports/month there is no rate concern. If same-day batch runs exceed 10 concurrent exports, add a 30-second stagger between export calls.
Constraints
Looker Studio does not expose a native PDF export API. The Apps Script or third-party approach adds an estimated $10-$20/month tool cost for a headless export connector if Apps Script is insufficient. This must be resolved during the Discovery stage. The export script must handle the case where Looker Studio has not yet refreshed its data source and must wait up to 20 minutes before retrying.
// Invoke Apps Script export function (Agent 2)
POST https://script.googleapis.com/v1/scripts/{GAPPS_SCRIPT_PROJECT_ID}:run
// Body: { function: '{LOOKER_EXPORT_FUNCTION_NAME}', parameters: ['{CLIENT_ID}', '{PERIOD_YYYYMM}', '{REPORT_TEMPLATE_ID}'] }
// Returns: { done: true, response: { result: { drive_file_id: '...', pdf_url: '...' } } }
Integration and API SpecPage 1 of 3
FS-DOC-05Technical
Google Drive

Archive destination for all exported PDF reports. Reports are saved to a per-client subfolder within a root reports folder. The Drive file ID returned by the export step is passed to both the Gmail send step (as attachment) and the HubSpot activity log step (as a shareable link).

Auth method
OAuth 2.0 using the same Google service account as Sheets and Looker Studio. No additional credential entry required if the combined OAuth grant includes Drive scopes.
Required scopes
https://www.googleapis.com/auth/drive.file | https://www.googleapis.com/auth/drive.metadata.readonly
Trigger / webhook setup
No inbound webhook. Drive is a write destination only. The orchestration layer calls the Drive API to confirm the file exists before proceeding to the Gmail send step.
Required configuration
Root reports folder ID stored as GDRIVE_REPORTS_FOLDER_ID. Per-client subfolder IDs stored in the master schedule tab (column GDRIVE_CLIENT_FOLDER_ID). If a client folder does not exist, Agent 2 must create it automatically using the client name as the folder title and log a warning. File naming convention enforced: {CLIENT_ID}_{PERIOD_YYYYMM}_Report.pdf
Rate limits
Google Drive API v3: 1,000 requests/100 seconds. At current volume this is not a concern. No throttling required.
Constraints
The exported PDF must not exceed 25 MB for Gmail attachment compatibility. Looker Studio reports at standard complexity are well below this limit. The Drive file must be set to 'anyone with link can view' only if the HubSpot activity log link is intended to be accessible externally; otherwise restrict to domain only.
// Confirm file exists after export (Agent 2)
GET /drive/v3/files/{drive_file_id}?fields=id,name,size,webViewLink
// Upload fallback if Apps Script export writes locally first
POST /upload/drive/v3/files?uploadType=multipart
// Headers: Content-Type: multipart/related
// Body part 1: { name: '{CLIENT_ID}_{PERIOD_YYYYMM}_Report.pdf', parents: ['{GDRIVE_CLIENT_FOLDER_ID}'] }
// Body part 2: PDF binary stream
Gmail

Sends the formatted client report email with the PDF attached. The email uses a pre-approved template with placeholder substitution for client name, reporting period, and AI-drafted commentary body. For accounts flagged as REVIEW_REQUIRED, the send step is gated behind a human approval step; the email is not dispatched until the reviewer confirms.

Auth method
OAuth 2.0 using a dedicated sending Google account (not the service account). OAuth refresh token stored as GMAIL_OAUTH_REFRESH_TOKEN. Access tokens refreshed automatically by the orchestration layer before each send batch.
Required scopes
https://www.googleapis.com/auth/gmail.send | https://www.googleapis.com/auth/gmail.compose
Trigger / webhook setup
No inbound webhook. Gmail is a send-only integration triggered by Agent 2 after the Drive file is confirmed and (where applicable) the human review gate is passed.
Required configuration
Sending email address stored as GMAIL_SENDER_ADDRESS. Email subject line template stored as GMAIL_SUBJECT_TEMPLATE (e.g. '{CLIENT_NAME} | Monthly Report | {PERIOD_MONTHYEAR}'). Email body HTML template stored as GMAIL_BODY_TEMPLATE with placeholders: {{client_name}}, {{period}}, {{commentary_text}}, {{drive_link}}. Recipient address sourced from HUBSPOT_CONTACT_EMAIL_PROP resolved at runtime, not hardcoded.
Rate limits
Gmail API: 250 quota units/user/second; sending limit 2,000 emails/day (Google Workspace). At 40 reports/month the daily send rate is negligible. No throttling required.
Constraints
PDF attachment must be base64-encoded in the MIME message. Total message size must not exceed 25 MB (Gmail limit). The send step must log the Gmail message ID returned by the API for audit purposes. If the recipient email is null or malformed (resolved from HubSpot), the send step must abort and alert, not send to a fallback address.
// Compose and send (Agent 2)
POST /gmail/v1/users/{GMAIL_SENDER_ADDRESS}/messages/send
// Body: { raw: base64url(MIME_MESSAGE) }
// MIME structure:
//   From: {GMAIL_SENDER_ADDRESS}
//   To: {recipient_email}
//   Subject: {resolved_subject}
//   Content-Type: multipart/mixed
//     Part 1: text/html  -- rendered GMAIL_BODY_TEMPLATE
//     Part 2: application/pdf; name='{CLIENT_ID}_{PERIOD_YYYYMM}_Report.pdf'
// Response: { id: '{gmail_message_id}', threadId: '...', labelIds: ['SENT'] }
Slack

Posts a structured delivery notice to the designated internal Slack channel after the Gmail send is confirmed. The message includes client name, reporting period, delivery timestamp, and a link to the archived Drive file. This step provides the account team with immediate visibility without requiring them to check HubSpot or Gmail.

Auth method
Slack Bot Token (xoxb- prefix) issued via a dedicated Slack App installed in the workspace. Token stored as SLACK_BOT_TOKEN in the credential store. The Slack App must be installed by a workspace admin before build begins.
Required scopes
chat:write | chat:write.public (if the target channel is not in the bot's default channel list)
Trigger / webhook setup
Outbound only via the Slack Web API chat.postMessage method. Alternatively, an Incoming Webhook URL may be used for simplicity; if so, the webhook URL is stored as SLACK_WEBHOOK_URL in the credential store. The two approaches are mutually exclusive; confirm which to use during Discovery.
Required configuration
Target channel ID stored as SLACK_DELIVERY_CHANNEL_ID (use channel ID, not name, to avoid breakage if the channel is renamed). Message template stored as SLACK_MESSAGE_TEMPLATE with Block Kit JSON structure. Placeholders: {{client_name}}, {{period}}, {{timestamp}}, {{drive_link}}, {{gmail_message_id}}.
Rate limits
Slack Web API: tier 3 rate limit, 50+ requests/minute for chat.postMessage. At 40 reports/month this is not a concern. No throttling required.
Constraints
The Slack App must remain installed and the bot token must not be revoked. If the bot is removed from the channel, postMessage returns a not_in_channel error; this must trigger a warning alert, not a silent failure. Do not post client-sensitive report content in the Slack message body; the Drive link provides access.
// Post delivery notice (Agent 2)
POST https://slack.com/api/chat.postMessage
// Headers: Authorization: Bearer {SLACK_BOT_TOKEN}, Content-Type: application/json
// Body:
{
  channel: '{SLACK_DELIVERY_CHANNEL_ID}',
  blocks: [
    { type: 'section', text: { type: 'mrkdwn', text: ':white_check_mark: *Report sent:* {{client_name}} | {{period}}' } },
    { type: 'section', fields: [
      { type: 'mrkdwn', text: '*Sent at:* {{timestamp}}' },
      { type: 'mrkdwn', text: '*Archive:* <{{drive_link}}|View PDF>' }
    ]}
  ]
}

03Field mappings between tools

The tables below define field mappings at each agent handoff point. All source and destination field names are written in monospace using exact API property names or tab column headers. These names must be validated against the live HubSpot portal and the actual Google Sheets tab structure during the Discovery stage before build proceeds.

Handoff 1: HubSpot to Google Sheets (Report Data Collector, Agent 1)

Source tool
Source field
Destination tool
Destination field
HubSpot
`properties.{HUBSPOT_DEAL_STAGE_PROP}`
Google Sheets
`deal_stage`
HubSpot
`properties.{HUBSPOT_ACTIVITY_COUNT_PROP}`
Google Sheets
`activity_count`
HubSpot
`properties.{HUBSPOT_CONTACT_EMAIL_PROP}`
Google Sheets
`recipient_email`
HubSpot
`properties.{HUBSPOT_CONTACT_NAME_PROP}`
Google Sheets
`recipient_name`
HubSpot
`properties.hs_object_id` (company)
Google Sheets
`hubspot_company_id`
HubSpot
`properties.createdate` (filtered to period)
Google Sheets
`period_start_date`
HubSpot
`associations.deals[].properties.closedate`
Google Sheets
`deals_closed_count`
Google Sheets (schedule)
`CLIENT_ID`
Google Sheets (staging)
`client_id`
Google Sheets (schedule)
`REPORT_TEMPLATE_ID`
Google Sheets (staging)
`template_id`
Google Sheets (schedule)
`NEXT_REPORT_DATE`
Google Sheets (staging)
`report_period`
Google Sheets (schedule)
`REVIEW_REQUIRED`
Google Sheets (staging)
`review_required`
Google Sheets (schedule)
`GDRIVE_CLIENT_FOLDER_ID`
Google Sheets (staging)
`drive_folder_id`

Handoff 2: Google Sheets staging to Looker Studio (via data source connector, Agent 2)

Source tool
Source field
Destination tool
Destination field
Google Sheets
`deal_stage`
Google Looker Studio
`Deal Stage` (dimension)
Google Sheets
`activity_count`
Google Looker Studio
`Activity Count` (metric)
Google Sheets
`deals_closed_count`
Google Looker Studio
`Deals Closed` (metric)
Google Sheets
`report_period`
Google Looker Studio
`Reporting Period` (dimension)
Google Sheets
`client_id`
Google Looker Studio
`Client ID` (filter parameter)
Google Sheets
`period_start_date`
Google Looker Studio
`Period Start` (date dimension)

Handoff 3: Google Drive and AI commentary to Gmail (Agent 2)

Source tool
Source field
Destination tool
Destination field
Google Drive
`webViewLink`
Gmail
`{{drive_link}}` in body template
Google Drive
`id` (file ID)
Gmail
MIME attachment reference
Google Sheets (staging)
`recipient_email`
Gmail
MIME `To:` header
Google Sheets (staging)
`recipient_name`
Gmail
`{{client_name}}` in subject and body
Google Sheets (staging)
`report_period`
Gmail
`{{period}}` in subject and body
AI agent output
`commentary_text`
Gmail
`{{commentary_text}}` in body template

Handoff 4: Gmail confirmation and Google Drive to HubSpot activity log (Agent 2)

Source tool
Source field
Destination tool
Destination field
Gmail API response
`id` (message ID)
HubSpot
`hs_note_body` (included in note text)
Google Drive
`webViewLink`
HubSpot
`hs_note_body` (Drive link in note body)
Orchestration layer
`ISO8601_TIMESTAMP` (send time)
HubSpot
`hs_timestamp`
Google Sheets (staging)
`hubspot_company_id`
HubSpot
Association target for note

Handoff 5: Gmail confirmation and Google Drive to Slack (Agent 2)

Source tool
Source field
Destination tool
Destination field
Google Sheets (staging)
`recipient_name`
Slack
`{{client_name}}` in Block Kit message
Google Sheets (staging)
`report_period`
Slack
`{{period}}` in Block Kit message
Orchestration layer
`ISO8601_TIMESTAMP`
Slack
`{{timestamp}}` in Block Kit message
Google Drive
`webViewLink`
Slack
`{{drive_link}}` in Block Kit message
Gmail API response
`id`
Slack
`{{gmail_message_id}}` in Block Kit message
Integration and API SpecPage 2 of 3
FS-DOC-05Technical

04Build stack and orchestration

Orchestration layout
Two discrete workflows, one per agent. Agent 1 (Report Data Collector) and Agent 2 (Report Builder and Distributor) are separate workflow definitions in the automation platform. They share a single credential store. Agent 2 is triggered by a completion event or status flag written by Agent 1 to the Google Sheets staging tab, not by a direct workflow chain, so the two workflows remain independently restartable.
Agent 1 trigger mechanism
Scheduled poll. The orchestration layer runs a cron job daily at 06:00 UTC. It reads the master schedule tab in Google Sheets and creates one workflow execution per client account where NEXT_REPORT_DATE matches today's date. Poll-based, no inbound webhook required.
Agent 2 trigger mechanism
Conditional poll or event flag. Agent 2 polls the staging tab every 5 minutes for a STAGING_STATUS column value of 'ready'. Alternatively, Agent 1 writes a timestamp to STAGING_CONFIRMED_AT on completion, which Agent 2 monitors. The REVIEW_REQUIRED flag is evaluated at the start of Agent 2's execution; if true, the workflow pauses at the commentary step and sends an internal approval prompt before proceeding to the Gmail send step.
Human review gate mechanism
When REVIEW_REQUIRED is true, Agent 2 writes the AI-drafted commentary to a review holding cell in the staging tab and sends an internal notification (via Slack or email to the designated reviewer). The Gmail send step is gated behind a REVIEW_APPROVED flag in the staging tab. The workflow polls every 10 minutes for up to 4 hours before escalating.
Credential store
All secrets stored in the automation platform's built-in encrypted credential store or an external secrets manager (e.g. environment variable store). No credentials hardcoded in workflow definitions or code blocks. See code block below for full contents.
Workflow naming convention
wf_agent1_report_data_collector | wf_agent2_report_builder_distributor
Execution logging
Every workflow execution logs: CLIENT_ID, execution start time, each step outcome (success/failure), Gmail message ID on send, HubSpot note ID on log, and any error payloads. Logs retained for minimum 90 days.
Credential store contents. All values injected at runtime by the orchestration layer. None are written into workflow logic.
// CREDENTIAL STORE CONTENTS
// All values are environment variables or encrypted secrets.
// Never commit these values to version control.

GSHEETS_SCHEDULE_SPREADSHEET_ID   = '<google_sheets_file_id>'
GSHEETS_SERVICE_ACCOUNT_KEY_JSON  = '<path_to_service_account_json_or_inline_secret>'
GSHEETS_OAUTH_REFRESH_TOKEN       = '<refresh_token_for_user_oauth_if_not_service_account>'

HUBSPOT_PRIVATE_APP_TOKEN         = '<hubspot_private_app_token>'
HUBSPOT_DEAL_STAGE_PROP           = '<confirmed_hs_property_name>'
HUBSPOT_ACTIVITY_COUNT_PROP       = '<confirmed_hs_property_name>'
HUBSPOT_CONTACT_EMAIL_PROP        = '<confirmed_hs_property_name>'
HUBSPOT_CONTACT_NAME_PROP         = '<confirmed_hs_property_name>'
HUBSPOT_ACTIVITY_TEMPLATE         = 'Report sent for period {period}. Archive: {drive_link}'

GAPPS_SCRIPT_PROJECT_ID           = '<apps_script_project_id>'
GAPPS_SCRIPT_OAUTH_TOKEN          = '<oauth_token_for_apps_script_api>'
LOOKER_EXPORT_FUNCTION_NAME       = 'exportReportToDrive'

GDRIVE_REPORTS_FOLDER_ID          = '<root_reports_folder_id_in_drive>'
GDRIVE_OAUTH_REFRESH_TOKEN        = '<shared_with_sheets_if_same_grant>'

GMAIL_OAUTH_REFRESH_TOKEN         = '<refresh_token_for_sending_account>'
GMAIL_SENDER_ADDRESS              = '<sending_email_address>'
GMAIL_SUBJECT_TEMPLATE            = '{CLIENT_NAME} | Monthly Report | {PERIOD_MONTHYEAR}'
GMAIL_BODY_TEMPLATE               = '<path_to_html_template_or_inline_string>'

SLACK_BOT_TOKEN                   = '<xoxb-slack-bot-token>'
SLACK_DELIVERY_CHANNEL_ID         = '<slack_channel_id>'
SLACK_MESSAGE_TEMPLATE            = '<block_kit_json_template_string>'
// SLACK_WEBHOOK_URL              = '<incoming_webhook_url_if_using_webhook_method>'
// Note: use either SLACK_BOT_TOKEN or SLACK_WEBHOOK_URL, not both.

// Rotation policy:
// HUBSPOT_PRIVATE_APP_TOKEN      - rotate every 12 months or on personnel change
// GMAIL_OAUTH_REFRESH_TOKEN      - reissue if sending account password changes
// SLACK_BOT_TOKEN                - rotate if Slack App is reinstalled or token revoked
// GSHEETS_SERVICE_ACCOUNT_KEY_JSON - rotate every 12 months
OAuth refresh tokens for Google integrations (Sheets, Drive, Gmail, Apps Script) are long-lived but will be revoked if the authorising user changes their Google account password, enables 2FA for the first time, or revokes access via their Google Account security settings. The FullSpec team will configure monitoring to detect 401 errors from any Google API and alert via support@gofullspec.com before a reporting cycle is affected.

05Error handling and retry logic

Every integration point has a defined failure behaviour. Unhandled exceptions must never fail silently. All errors must be logged with the full error payload, the CLIENT_ID, and the workflow execution ID. Where a retry is defined, exponential backoff is used: wait 30 seconds before retry 1, 2 minutes before retry 2, 10 minutes before retry 3. After three failed retries, the step is marked as failed and an alert is dispatched to support@gofullspec.com and the designated process owner.

Integration
Scenario
Required behaviour
Google Sheets (schedule poll)
GSHEETS_SCHEDULE_SPREADSHEET_ID not found or 403 forbidden
Abort all executions for that run. Log error with timestamp. Send alert to support@gofullspec.com. Do not retry without credential remediation. No silent failure.
Google Sheets (schedule poll)
NEXT_REPORT_DATE column contains malformed date or blank value for a row
Skip that row. Log a warning with the row index and CLIENT_ID. Continue processing other rows. Alert process owner via Slack at end of run with a list of skipped rows.
HubSpot (data read)
401 Unauthorized (token invalid or revoked)
Abort Agent 1 execution for all accounts. Log error. Alert immediately via support@gofullspec.com. Do not retry until HUBSPOT_PRIVATE_APP_TOKEN is rotated and re-entered in the credential store.
HubSpot (data read)
404 Company not found for HUBSPOT_COMPANY_ID
Mark staging tab STATUS column as 'hubspot_not_found' for that client. Skip to next account. Log warning. Alert process owner. Do not attempt Agent 2 for that account.
HubSpot (data read)
429 Rate limit exceeded
Pause execution for 10 seconds. Retry up to 3 times with exponential backoff. If still failing after 3 retries, mark account as deferred, log error, and alert. Resume on next scheduled run.
Google Sheets (staging write)
Client staging tab missing for CLIENT_ID
Do not create the tab automatically. Log error with CLIENT_ID. Alert process owner immediately. Mark account as 'staging_missing' in the schedule tab. No silent failure.
Looker Studio / Apps Script (PDF export)
Apps Script execution timeout or export function returns error
Retry up to 3 times with 2-minute gaps to allow Looker Studio data source to refresh. If still failing after 3 retries, halt Agent 2 for that account. Log full error payload. Alert support@gofullspec.com. Do not send report without confirmed PDF.
Google Drive (file save)
403 Forbidden or GDRIVE_CLIENT_FOLDER_ID invalid
Attempt to create the client subfolder under GDRIVE_REPORTS_FOLDER_ID using the client name. If folder creation also fails, abort and alert. Log the Drive API error response. Do not proceed to Gmail send step.
Gmail (send)
Recipient email is null or fails RFC 5322 validation
Abort send for that account. Log the invalid address value and CLIENT_ID. Alert process owner with the client name and the null/invalid field. Do not substitute a fallback address. Never send to an unintended recipient.
Gmail (send)
550 or 5xx SMTP rejection from recipient mail server
Log the bounce with Gmail message ID and error code. Alert process owner and support@gofullspec.com. Do not auto-retry a rejected send. Mark account delivery status as 'send_failed' in staging tab for manual follow-up.
HubSpot (activity log write)
POST to /crm/v3/objects/notes fails with 5xx error
Retry up to 3 times with exponential backoff. If all retries fail, log the failure and continue to the Slack notification step. The report has already been sent; the activity log failure is non-blocking but must be flagged for manual entry by the process owner.
Slack (delivery notice)
not_in_channel or channel_not_found error
Log the error with full Slack API response. Do not retry. Send a fallback email notification to GMAIL_SENDER_ADDRESS listing the client name, period, and Drive link so the team is not left without visibility. Alert support@gofullspec.com to remediate the Slack App channel membership.
Human review gate
REVIEW_APPROVED flag not set within 4 hours of commentary being written to staging tab
Escalate via Slack and email to the designated reviewer and the process owner. Pause workflow execution for that account. Do not time out and auto-send. Log the escalation timestamp. Require explicit approval or explicit rejection before proceeding.
Global rule: unhandled exceptions at any step in either workflow must never fail silently. If the orchestration platform catches an exception not covered by the scenarios above, it must log the full stack trace with CLIENT_ID and execution ID, and dispatch an alert to support@gofullspec.com within 60 seconds. The process owner must be notified of any account that did not complete its reporting cycle within 30 minutes of the scheduled trigger firing.
Integration and API SpecPage 3 of 3

More documents for this process

Every document generated for Client Reporting Automation.

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