FS-DOC-05Technical
Integration and API Spec
Compliance & Certification Tracking
[YourCompany.com] · Operations Department · Prepared by FullSpec · [Today's Date]
This document defines the exact integration configuration required to connect every tool in the Compliance and Certification Tracking automation. It covers authentication methods, required OAuth scopes, webhook setup, field mappings between agents, orchestration layout, credential store contents, and failure handling. The FullSpec team uses this specification as the authoritative reference during build and testing. No credentials, IDs, or environment-specific values should be hardcoded in workflow logic; all such values belong in the credential store as documented in Section 04.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agents
Automation platform
Orchestration layer: schedules, routes data between agents, manages retries and credential store
Internal (platform account)
Any paid tier supporting scheduled triggers and multi-step workflows
All agents
Google Sheets
Master compliance register: source of expiry data, owner lookup, and status write-back
OAuth 2.0 (Google Identity Platform)
Google Workspace (any tier) or personal Google account with Sheets API enabled
Agent 1, Agent 2, Agent 3
Gmail
Send personalised reminder emails, escalation emails, and weekly compliance reports
OAuth 2.0 (Google Identity Platform)
Google Workspace (any tier); sending limits apply on free accounts
Agent 1, Agent 2
Slack
Post channel alerts and direct escalation messages to owners and managers
OAuth 2.0 (Slack app installation) plus Bot Token
Free tier supports incoming webhooks; paid tier required for DM to non-admins in some configurations
Agent 1, Agent 2
Notion
Certification document library: receive filed renewal documents and link back to register
OAuth 2.0 (Notion integration token) or internal integration secret
Free tier supports API access; Plus plan recommended for team permissions and file size limits
Agent 3
Xero
Route renewal invoices to finance for payment processing
OAuth 2.0 (Xero app, PKCE flow)
Xero Starter or above with bills and contacts API access
Agent 3
Before you connect anything: all six integrations must be tested against sandbox or development environments before production credentials are stored. For Google, use a test Google Workspace account or a personal account with isolated Sheets. For Xero, use the Xero Demo Company. For Notion, use a duplicate workspace. For Slack, use a dedicated test workspace. Only promote to production credentials after all test cases in the QA Plan pass.
Integration and API SpecPage 1 of 5
FS-DOC-05Technical
02Per-tool integration detail
Google Sheets
Acts as the master compliance register. Agent 1 reads rows to classify expiring items. Agent 2 reads status fields to detect unacknowledged items. Agent 3 writes updated expiry dates, certificate references, status flags, and Notion document links back to the register. All read and write operations target a single named spreadsheet identified by its Spreadsheet ID stored in the credential store.
Auth method
OAuth 2.0 via Google Identity Platform. A service account with domain-wide delegation is preferred for server-side polling without user interaction. Store the service account JSON key in the credential store; do not embed it in workflow config.
Required scopes
https://www.googleapis.com/auth/spreadsheets (read and write to spreadsheets); https://www.googleapis.com/auth/drive.readonly (resolve spreadsheet ID from Drive if needed). Do not request broader Drive write access.
Webhook / trigger setup
Google Sheets does not natively push events to external systems. Agent 1 uses a scheduled poll (daily at 07:00 local time, configurable). Agent 2 uses a scheduled poll (every business day at 07:00 and 12:00). Agent 3 is triggered by an inbound webhook from the owner's renewal confirmation action, not by polling the sheet directly.
Required configuration
Spreadsheet ID stored in credential store (key: SHEETS_REGISTER_ID). Fixed column schema must be locked before go-live: column A = item_name, B = owner_email, C = expiry_date (ISO 8601), D = renewal_status, E = urgency_tier, F = doc_link, G = last_reminder_sent, H = escalated_flag, I = certificate_ref. Header row must be row 1; data starts row 2. Sheet tab name stored in credential store (key: SHEETS_TAB_NAME).
Rate limits
Google Sheets API v4: 300 read requests per minute per project; 300 write requests per minute per project. At a register size of 30 to 80 rows polled twice daily, peak usage is well under 10 requests per poll cycle. No throttling required at current volume. If the register grows beyond 500 rows, implement batch read using batchGet.
Constraints
Date values must be stored as ISO 8601 strings (YYYY-MM-DD) or as Google Sheets serial numbers; mixed formats will cause the urgency classifier to produce incorrect tier assignments. Owner email addresses in column B must be valid SMTP addresses; blank or malformed values must trigger an alert rather than silently skip the row.
// Read (Agents 1 and 2)
GET /v4/spreadsheets/{SHEETS_REGISTER_ID}/values/{SHEETS_TAB_NAME}!A2:I
// Returns: array of row arrays, positional columns A-I as above
// Write (Agent 3)
PUT /v4/spreadsheets/{SHEETS_REGISTER_ID}/values/{SHEETS_TAB_NAME}!C{row}:I{row}
Body: { range, majorDimension: 'ROWS', values: [[expiry_date, renewal_status, urgency_tier, doc_link, last_reminder_sent, escalated_flag, certificate_ref]] }Gmail
Used by Agent 1 to send tiered reminder emails and by Agent 2 to send escalation emails to owners and their managers. A weekly compliance report is also distributed via Gmail on a scheduled basis. All outbound emails use pre-approved templates stored in the credential store by template ID; no email body is assembled from free text at runtime.
Auth method
OAuth 2.0 via Google Identity Platform. Use the same service account as Google Sheets with domain-wide delegation, impersonating the sending mailbox (e.g. compliance-automation@[YourCompany.com]). Store the impersonation address in the credential store (key: GMAIL_SENDER_ADDRESS).
Required scopes
https://www.googleapis.com/auth/gmail.send (send only; do not request gmail.readonly or gmail.modify for this integration). If bounce detection is needed in a future iteration, add https://www.googleapis.com/auth/gmail.readonly at that point.
Webhook / trigger setup
Gmail is outbound only in this automation. No inbound webhook is configured. Owner reply detection (for renewal confirmation) is handled by the owner submitting a status update to the register, not by parsing Gmail replies.
Required configuration
Sending address stored in credential store (key: GMAIL_SENDER_ADDRESS). Three email templates are pre-configured and their IDs stored (keys: GMAIL_TEMPLATE_REMINDER_90, GMAIL_TEMPLATE_REMINDER_60, GMAIL_TEMPLATE_REMINDER_30, GMAIL_TEMPLATE_ESCALATION, GMAIL_TEMPLATE_REPORT). Templates use placeholders: {{item_name}}, {{owner_name}}, {{expiry_date}}, {{renewal_instructions_url}}, {{manager_name}}. Placeholder substitution occurs at runtime using register row data.
Rate limits
Gmail API: 250 quota units per second per user; sending via the API counts as 100 units per message. At 30 to 80 items with daily scans and weekly reports, peak send volume is under 100 messages per day. No throttling required at current volume. Google Workspace sending limits (2,000 messages per day for most tiers) are not approached at this scale.
Constraints
Emails must include an unsubscribe or opt-out note if the recipient is external to the organisation, to comply with CAN-SPAM and similar regulations. Internal-only sends (staff and managers within the same domain) are exempt. The sender domain must have SPF and DKIM records configured to avoid deliverability issues.
// Send reminder or escalation email
POST /gmail/v1/users/{GMAIL_SENDER_ADDRESS}/messages/send
Body: { raw: base64url_encoded_MIME_message }
// MIME headers required: From, To, Subject, Content-Type: text/html; charset=utf-8
// Template placeholder substitution (performed by orchestration layer before API call)
template_body.replace('{{item_name}}', row.item_name)
template_body.replace('{{owner_name}}', row.owner_name)
template_body.replace('{{expiry_date}}', row.expiry_date)Slack
Used by Agent 1 to post structured expiry alerts to the designated compliance channel and by Agent 2 to send direct messages to the item owner and their manager when escalation is triggered. The Slack app is installed once at the workspace level; the bot token and channel IDs are stored in the credential store.
Auth method
OAuth 2.0 app installation flow generating a Bot Token (xoxb-...). The bot must be installed to the workspace by a Slack admin and granted the required scopes. Store the bot token in the credential store (key: SLACK_BOT_TOKEN). Do not use webhook-only apps; the bot.users.lookupByEmail method requires a full bot token.
Required scopes
chat:write (post messages to channels the bot is a member of); chat:write.public (post to public channels without joining); im:write (open and send direct messages); users:read (look up user IDs); users:read.email (resolve owner email address to Slack user ID for DM targeting). Bot must be invited to the compliance channel manually before go-live.
Webhook / trigger setup
The automation posts to Slack; it does not receive webhooks from Slack. Outbound posts use the chat.postMessage API method. If future iterations require slash commands or interactive buttons (e.g. an owner clicking Acknowledge in Slack), an incoming webhook endpoint and request URL must be registered in the Slack app manifest at that time.
Required configuration
Compliance channel ID stored in credential store (key: SLACK_COMPLIANCE_CHANNEL_ID). Channel ID is not the channel name; retrieve it from the Slack app settings or by calling conversations.list. Manager lookup table stored in credential store or in a named range in the register (owner_email -> manager_slack_user_id). Slack message block templates for urgency tiers (90, 60, 30 days) and escalation stored as JSON in the credential store (keys: SLACK_BLOCK_REMINDER, SLACK_BLOCK_ESCALATION).
Rate limits
Slack Web API Tier 3: 50 requests per minute for chat.postMessage. At 30 to 80 items with daily channel posts and per-item escalation DMs, peak usage is under 10 requests per run cycle. No throttling required at current volume. If the register grows beyond 200 active items, implement a 1.5-second delay between consecutive DM sends.
Constraints
The bot must be a member of the compliance channel before posting; channel membership is not granted automatically by scopes. Slack user ID resolution from email address requires users:read.email scope and will fail silently if the owner email is not associated with an active Slack account. Implement a fallback to channel mention (using @display-name) when user ID resolution fails, and log the resolution failure.
// Post channel alert
POST https://slack.com/api/chat.postMessage
Headers: Authorization: Bearer {SLACK_BOT_TOKEN}, Content-Type: application/json
Body: { channel: SLACK_COMPLIANCE_CHANNEL_ID, blocks: SLACK_BLOCK_REMINDER, text: fallback_text }
// Send direct escalation message
POST https://slack.com/api/chat.postMessage
Body: { channel: {resolved_slack_user_id}, blocks: SLACK_BLOCK_ESCALATION, text: fallback_text }
// Resolve owner Slack user ID from email
GET https://slack.com/api/users.lookupByEmail?email={owner_email}
Returns: { user: { id: 'U0XXXXXXX' } }Integration and API SpecPage 2 of 5
FS-DOC-05Technical
Notion
Used by Agent 3 to file renewed certification documents in the organisation's certification library database. Each filed document creates or updates a Notion page within the designated database, using a standardised naming convention and linking the page URL back to the Google Sheets register. The Notion integration must have access to the specific database; access is not granted workspace-wide by default.
Auth method
Internal integration token (starts with secret_...) generated from the Notion integration settings at notion.so/my-integrations. The token is scoped to pages and databases that the integration has been explicitly shared with. Store the token in the credential store (key: NOTION_INTEGRATION_TOKEN). OAuth 2.0 is available for public-facing integrations; internal token is sufficient here.
Required scopes
Notion internal integrations do not use named OAuth scopes. The integration must be granted: Read content (read existing database entries); Insert content (create new pages); Update content (update existing pages with new expiry data and doc links). These capabilities are set in the integration configuration panel, not via scope strings. Share the target Notion database with the integration explicitly.
Webhook / trigger setup
Notion does not provide outbound webhooks in the current public API (as of API version 2022-06-28). Agent 3 is triggered by the owner's renewal confirmation action (external trigger), not by Notion events. The automation writes to Notion; it does not listen for changes from Notion.
Required configuration
Certification library database ID stored in credential store (key: NOTION_DATABASE_ID). Database ID is the 32-character string in the database URL. Database must have the following properties pre-created: Name (title), Item_Name (text), Owner_Email (email), Expiry_Date (date), Certificate_Ref (text), Status (select: Active, Expired, Pending Review), Renewal_Date (date), Document_Link (url). Naming convention for pages: [ITEM_NAME]_[OWNER_INITIALS]_[EXPIRY_YYYY-MM-DD]. This convention is stored in credential store (key: NOTION_NAMING_CONVENTION).
Rate limits
Notion API: 3 requests per second average; burst of up to 10 requests per second. Agent 3 processes one renewal at a time, triggered by individual owner actions. At 30 to 80 active items with renewals spread across weeks, peak API usage is well under 1 request per second. No throttling required at current volume.
Constraints
File attachments (the actual certificate PDFs) cannot be uploaded directly via the Notion API in the current version; only URLs to externally hosted files can be stored as document links. If the certificate document is emailed to the automation, it must be stored in Google Drive or another accessible location first, and the Drive link stored in Notion. This is a known Notion API limitation and must be communicated to the operations manager during handoff.
// Create new certification page in database
POST https://api.notion.com/v1/pages
Headers: Authorization: Bearer {NOTION_INTEGRATION_TOKEN}, Notion-Version: 2022-06-28
Body: { parent: { database_id: NOTION_DATABASE_ID }, properties: { Name: {title: [{text: {content: page_name}}]}, Expiry_Date: {date: {start: expiry_date}}, Owner_Email: {email: owner_email}, Certificate_Ref: {rich_text: [{text: {content: cert_ref}}]}, Status: {select: {name: 'Active'}} } }
// Update existing page
PATCH https://api.notion.com/v1/pages/{page_id}
Body: { properties: { Expiry_Date: {date: {start: new_expiry_date}}, Status: {select: {name: 'Active'}}, Renewal_Date: {date: {start: today}} } }Xero
Used by Agent 3 to route renewal invoices to finance for payment processing. When a renewal event includes an invoice attachment or reference number, the automation creates a draft bill in Xero and assigns it to the appropriate contact. Finance staff review and approve bills in Xero; the automation does not approve or pay invoices automatically.
Auth method
OAuth 2.0 with PKCE flow (Xero's standard server-side app flow). Register a Xero app at developer.xero.com to obtain client_id and client_secret. Store both in the credential store (keys: XERO_CLIENT_ID, XERO_CLIENT_SECRET). Store the refresh token after first authorisation (key: XERO_REFRESH_TOKEN). Store the tenant (organisation) ID (key: XERO_TENANT_ID). Access tokens expire after 30 minutes; the orchestration layer must refresh using the refresh token before each API call.
Required scopes
accounting.transactions (create and read invoices and bills); accounting.contacts.read (look up or verify supplier contact records); offline_access (required to obtain a refresh token for unattended operation). Do not request accounting.settings or payroll scopes.
Webhook / trigger setup
Xero webhooks can be configured to notify the automation when a bill is approved or paid, enabling a future-state confirmation flow. For the current build, Xero is write-only: Agent 3 creates draft bills and does not receive events from Xero. Webhook registration is documented for future use: register the endpoint URL in the Xero developer portal under the app's Webhooks section and validate with the Xero-delivered HMAC-SHA256 signature header (x-xero-signature).
Required configuration
Tenant ID stored in credential store (key: XERO_TENANT_ID). Finance contact name and Xero contact ID for the default AP approver stored in credential store (key: XERO_DEFAULT_AP_CONTACT_ID). Bill account code for compliance renewals stored in credential store (key: XERO_RENEWAL_ACCOUNT_CODE, e.g. '420' for compliance expenditure). All monetary values passed in USD with currency code 'USD'.
Rate limits
Xero API: 60 calls per minute per app per organisation; 5,000 calls per day. Agent 3 creates at most one bill per renewal event. At 30 to 80 items with renewals spread across weeks, daily API usage is under 10 calls. No throttling required at current volume.
Constraints
Xero OAuth tokens are tenant-specific; if [YourCompany.com] has multiple Xero organisations, the XERO_TENANT_ID must match the correct entity. The refresh token must be rotated after each use (Xero uses refresh token rotation); the orchestration layer must persist the new refresh token to the credential store immediately after each refresh or subsequent calls will fail with a 401.
// Refresh access token
POST https://identity.xero.com/connect/token
Body: grant_type=refresh_token&refresh_token={XERO_REFRESH_TOKEN}&client_id={XERO_CLIENT_ID}&client_secret={XERO_CLIENT_SECRET}
Response: { access_token, refresh_token, expires_in }
// Store new refresh_token to credential store immediately
// Create draft bill
POST https://api.xero.com/api.xro/2.0/Invoices
Headers: Authorization: Bearer {access_token}, Xero-tenant-id: {XERO_TENANT_ID}, Content-Type: application/json
Body: { Type: 'ACCPAY', Contact: { ContactID: XERO_DEFAULT_AP_CONTACT_ID }, DueDate: invoice_due_date, LineItems: [{ Description: item_name + ' renewal', AccountCode: XERO_RENEWAL_ACCOUNT_CODE, UnitAmount: invoice_amount, Quantity: 1 }], Status: 'DRAFT', Reference: certificate_ref }Integration and API SpecPage 3 of 5
FS-DOC-05Technical
03Field mappings between tools
The following tables define the exact field mappings at each agent handoff point. Source and destination field names are given in monospace format as they appear in the API responses and request bodies.
Handoff 1: Google Sheets to Gmail and Slack (Compliance Monitoring Agent, Agent 1)
Source tool
Source field
Destination tool
Destination field
Google Sheets
`values[n][0]` (col A)
Gmail / Slack
`{{item_name}}` in template; `item_name` in Slack block
Google Sheets
`values[n][1]` (col B)
Gmail
`To:` header (MIME); `{{owner_email}}`
Google Sheets
`values[n][1]` (col B)
Slack
Resolved to `slack_user_id` via `users.lookupByEmail`
Google Sheets
`values[n][2]` (col C)
Gmail / Slack
`{{expiry_date}}` formatted as DD MMM YYYY for display
Google Sheets
`values[n][4]` (col E)
Gmail
Selects template key: `GMAIL_TEMPLATE_REMINDER_90`, `_60`, or `_30`
Google Sheets
`values[n][4]` (col E)
Slack
Selects block template key: `SLACK_BLOCK_REMINDER` with urgency colour field
Google Sheets
Row index (derived)
Sheets write-back
`G{row}` (`last_reminder_sent`) set to ISO 8601 timestamp of send
Handoff 2: Google Sheets to Gmail and Slack (Escalation Agent, Agent 2)
Source tool
Source field
Destination tool
Destination field
Google Sheets
`values[n][0]` (col A)
Gmail / Slack
`{{item_name}}` in escalation template and Slack block
Google Sheets
`values[n][1]` (col B)
Gmail
`To:` header (owner); `{{owner_email}}`
Google Sheets
`values[n][1]` (col B)
Slack
Resolved to owner `slack_user_id` via `users.lookupByEmail`
Google Sheets
`values[n][1]` (col B)
Slack
Manager `slack_user_id` resolved via manager lookup table in credential store
Google Sheets
`values[n][2]` (col C)
Gmail / Slack
`{{expiry_date}}` formatted as DD MMM YYYY
Google Sheets
`values[n][6]` (col G)
Gmail
`{{original_reminder_date}}` in escalation template body
Google Sheets
Row index (derived)
Sheets write-back
`H{row}` (`escalated_flag`) set to `TRUE`; timestamp appended to col G
Handoff 3: Owner renewal confirmation to Google Sheets, Notion, and Xero (Renewal Processing Agent, Agent 3)
Source tool
Source field
Destination tool
Destination field
Inbound webhook payload
`payload.item_name`
Google Sheets
`A{row}` matched for row lookup; not overwritten
Inbound webhook payload
`payload.new_expiry_date`
Google Sheets
`C{row}` (`expiry_date`) updated to new ISO 8601 date
Inbound webhook payload
`payload.new_expiry_date`
Notion
`Expiry_Date` property (date type, `start` field)
Inbound webhook payload
`payload.certificate_ref`
Google Sheets
`I{row}` (`certificate_ref`) updated
Inbound webhook payload
`payload.certificate_ref`
Notion
`Certificate_Ref` property (rich_text)
Inbound webhook payload
`payload.certificate_ref`
Xero
`Reference` field on draft bill
Inbound webhook payload
`payload.doc_url`
Google Sheets
`F{row}` (`doc_link`) updated with Notion page URL after creation
Inbound webhook payload
`payload.doc_url`
Notion
`Document_Link` property (url type)
Inbound webhook payload
`payload.invoice_amount`
Xero
`LineItems[0].UnitAmount`
Inbound webhook payload
`payload.invoice_due_date`
Xero
`DueDate` field on draft bill
Notion API response
`response.id` (page ID)
Google Sheets
`F{row}` (`doc_link`) set to `https://notion.so/{page_id}`
Google Sheets
`values[n][3]` (col D)
Sheets write-back
`D{row}` (`renewal_status`) set to `Renewed - Pending Review`
04Build stack and orchestration
Orchestration layout
One workflow per agent: Workflow 1 (Compliance Monitoring Agent), Workflow 2 (Escalation Agent), Workflow 3 (Renewal Processing Agent). A fourth workflow handles weekly report generation and distribution. All four workflows share a single credential store; no credentials are duplicated across workflows. Workflow 4 (Report) is triggered on a weekly schedule independent of the three agent workflows.
Agent 1 trigger mechanism
Scheduled poll. Workflow 1 runs every day at 07:00 (organisation local timezone, configurable). The orchestration layer calls the Google Sheets API, retrieves all register rows, filters for rows where expiry_date is within 90, 60, or 30 days of today, and passes matched rows to the reminder dispatch steps. No webhook is used for this trigger.
Agent 2 trigger mechanism
Scheduled poll with conditional logic. Workflow 2 runs every business day at 07:30 (offset from Workflow 1 to avoid concurrent Sheets API calls). It reads all register rows where last_reminder_sent is populated, escalated_flag is FALSE or empty, and renewal_status does not contain 'Renewed'. For each matched row, it calculates business days elapsed since last_reminder_sent. If elapsed days is greater than or equal to 5, escalation is triggered.
Agent 3 trigger mechanism
Inbound webhook (event-driven). Workflow 3 is triggered by an HTTP POST to a unique webhook URL generated by the orchestration layer. The webhook URL is shared with the owner submission form or integration point. Webhook signature validation uses HMAC-SHA256: the request must include an X-FullSpec-Signature header containing HMAC-SHA256(secret=WEBHOOK_SECRET, data=raw_request_body). Requests failing signature validation are rejected with HTTP 401 and logged.
Workflow 4 trigger mechanism
Scheduled poll. Weekly on Monday at 08:00 local time. Reads all register rows, compiles current status summary, and dispatches the compliance report via Gmail to the operations manager and any addresses listed in REPORT_RECIPIENT_LIST in the credential store.
Credential store type
The orchestration platform's native encrypted credential/secret store. No credentials are stored in workflow node configuration fields, environment variable files, or source code. All keys listed below must be created in the credential store before any workflow is activated.
Credential store: all keys must be populated before activating any workflow. Values shown in angle brackets are environment-specific.
// Credential store contents (all keys required before go-live)
// Google (shared by Sheets and Gmail integrations)
GOOGLE_SERVICE_ACCOUNT_JSON = <service account JSON key, base64-encoded>
GOOGLE_IMPERSONATION_ADDRESS = compliance-automation@[YourCompany.com]
// Google Sheets
SHEETS_REGISTER_ID = <32-char spreadsheet ID from URL>
SHEETS_TAB_NAME = ComplianceRegister
// Gmail templates (IDs reference pre-built template objects in the orchestration layer)
GMAIL_SENDER_ADDRESS = compliance-automation@[YourCompany.com]
GMAIL_TEMPLATE_REMINDER_90 = tmpl_reminder_90d
GMAIL_TEMPLATE_REMINDER_60 = tmpl_reminder_60d
GMAIL_TEMPLATE_REMINDER_30 = tmpl_reminder_30d
GMAIL_TEMPLATE_ESCALATION = tmpl_escalation
GMAIL_TEMPLATE_REPORT = tmpl_weekly_report
// Slack
SLACK_BOT_TOKEN = xoxb-<workspace-bot-token>
SLACK_COMPLIANCE_CHANNEL_ID = C<11-char channel ID>
SLACK_BLOCK_REMINDER = <JSON string of Slack Block Kit template>
SLACK_BLOCK_ESCALATION = <JSON string of Slack Block Kit escalation template>
SLACK_MANAGER_LOOKUP = <JSON map: { owner_email: manager_slack_user_id, ... }>
// Notion
NOTION_INTEGRATION_TOKEN = secret_<43-char internal integration token>
NOTION_DATABASE_ID = <32-char database ID from URL>
NOTION_NAMING_CONVENTION = {ITEM_NAME}_{OWNER_INITIALS}_{EXPIRY_YYYY-MM-DD}
// Xero
XERO_CLIENT_ID = <Xero app client ID>
XERO_CLIENT_SECRET = <Xero app client secret>
XERO_REFRESH_TOKEN = <current refresh token; updated after each use>
XERO_TENANT_ID = <Xero organisation/tenant ID>
XERO_DEFAULT_AP_CONTACT_ID = <Xero ContactID for AP approver>
XERO_RENEWAL_ACCOUNT_CODE = 420
// Webhook security
WEBHOOK_SECRET = <min 32-char random string, used for HMAC-SHA256 validation>
// Reporting
REPORT_RECIPIENT_LIST = ops-manager@[YourCompany.com], finance-lead@[YourCompany.com]Integration and API SpecPage 4 of 5
FS-DOC-05Technical
05Error handling and retry logic
Unhandled exceptions must never fail silently. Every integration failure must produce a logged error entry and, where the failure affects a compliance-critical action (missed reminder, failed register update, failed Notion filing, failed Xero bill), an alert must be dispatched to the operations manager via email or a fallback Slack message to the compliance channel.
Integration
Scenario
Required behaviour
Google Sheets (read)
API returns 429 rate limit error during daily scan poll
Retry with exponential backoff: wait 5 s, 15 s, 45 s (3 attempts). If all retries fail, log the error with timestamp and send alert email to GMAIL_SENDER_ADDRESS -> REPORT_RECIPIENT_LIST. Do not skip the daily scan silently; reschedule for 30 minutes later and log the delay.
Google Sheets (read)
Owner email field (col B) is blank or malformed for a flagged row
Skip reminder dispatch for that row. Log the row number and item_name. Post a warning to SLACK_COMPLIANCE_CHANNEL_ID: 'Reminder skipped for [item_name]: missing or invalid owner email. Manual action required.' Do not halt the workflow for other rows.
Google Sheets (write)
Write-back of last_reminder_sent or escalated_flag fails with 5xx error
Retry 3 times with 10 s linear backoff. If all retries fail, send alert to REPORT_RECIPIENT_LIST with the item_name, intended update values, and timestamp so the operations manager can update the register manually. Log failure with full API response.
Gmail (send)
Message send returns 500 or 503 error
Retry up to 3 times with exponential backoff (10 s, 30 s, 90 s). If all retries fail, log the failure with recipient address and item_name. Post fallback alert to SLACK_COMPLIANCE_CHANNEL_ID: 'Email reminder failed for [item_name] -> [owner_email]. Manual email required.' Do not mark last_reminder_sent in the register until send is confirmed successful.
Gmail (send)
Recipient address bounces (550 User unknown) on reminder send
Do not retry (permanent failure). Log the bounce. Post alert to SLACK_COMPLIANCE_CHANNEL_ID and send error report to REPORT_RECIPIENT_LIST. Mark col G in register with 'EMAIL_BOUNCE' so the escalation agent does not attempt further email sends to that address.
Slack (channel post)
Bot is not a member of SLACK_COMPLIANCE_CHANNEL_ID, returns channel_not_found or not_in_channel
Log the error. Send fallback email to REPORT_RECIPIENT_LIST with the channel alert content. Do not retry the Slack post; this requires a human to re-invite the bot to the channel. Alert the operations manager with the specific error and remediation steps.
Slack (DM escalation)
users.lookupByEmail returns users_not_found for owner email
Fall back to posting the escalation message in SLACK_COMPLIANCE_CHANNEL_ID with the owner's display name (from register) mentioned as plain text rather than a user mention. Log the resolution failure. Ensure Gmail escalation email is still sent to the owner's email address regardless of Slack resolution failure.
Notion (page creation)
API returns 409 conflict (page already exists with matching name)
Retrieve the existing page ID using a database query filtered by Item_Name and Expiry_Date. Update the existing page via PATCH rather than creating a duplicate. Log that an update was performed instead of a creation. If the retrieve-and-update also fails, log the full error and alert REPORT_RECIPIENT_LIST; document filing must not be silently skipped.
Notion (page creation)
API returns 500 or 503 during document filing
Retry 3 times with 15 s linear backoff. If all retries fail, store the pending Notion payload in the orchestration layer's error queue. Alert REPORT_RECIPIENT_LIST with the item_name and doc_url so the filing can be completed manually. Do not mark the register row as complete until Notion filing is confirmed.
Xero (bill creation)
Access token expired (401 Unauthorized)
Refresh the access token using XERO_REFRESH_TOKEN before retrying the bill creation. Store the new refresh token to the credential store immediately after refresh. If the refresh call itself fails (invalid_grant), log the error and alert REPORT_RECIPIENT_LIST: 'Xero token refresh failed. Re-authorise the Xero connection at [developer.xero.com].' Do not attempt the bill creation after a failed refresh.
Xero (bill creation)
Duplicate bill detected (Xero returns validation error referencing matching reference number)
Do not create a duplicate bill. Log the duplicate detection with the existing Xero invoice ID if returned. Alert REPORT_RECIPIENT_LIST with the certificate_ref and amount so finance can verify whether the existing bill is the correct one. Mark the Xero step as skipped (not failed) in the workflow run log.
Inbound webhook (Agent 3 trigger)
Webhook payload fails HMAC-SHA256 signature validation
Reject the request with HTTP 401. Log the source IP, timestamp, and raw payload hash. Do not process the payload. If more than 3 invalid signature attempts are received within 10 minutes from the same IP, log a security alert to REPORT_RECIPIENT_LIST. Do not alert the submitting client (avoid information disclosure).
All error log entries must include: workflow name, run ID, timestamp (UTC), integration name, HTTP status code (where applicable), error message, item_name (where applicable), and the action taken (retried, fallback triggered, or manual alert sent). Logs must be retained for a minimum of 90 days to support audit trail requirements.
Integration and API SpecPage 5 of 5