FS-DOC-05Technical
Integration and API Spec
Vendor and Subscription Renewal Tracking
[YourCompany.com] · IT Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative technical reference for every integration point in the Vendor and Subscription Renewal Tracking automation. It covers tool inventory, exact OAuth scopes, webhook and trigger configuration, field mappings between agents, credential store layout, orchestration design, and error handling behaviour. The FullSpec team uses this specification to build and verify every connection. No integration credential should be hardcoded in workflow logic; all secrets are stored in the shared credential store referenced in Section 04.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agent(s)
Google Sheets
Live subscription register: read all contract rows daily, write decision outcomes
OAuth 2.0 (service account recommended)
Google Workspace Business Starter (or free personal for single-user)
Agent 1 (Renewal Triage), Agent 3 (Finance Record)
Notion
Vendor contract document store: read vendor page metadata, write updated renewal date and decision
OAuth 2.0 (internal integration token)
Notion Free or above
Agent 1 (Renewal Triage), post-decision update step
Slack
Contract owner renewal alerts: post structured messages to designated channel or DM
OAuth 2.0 (bot token, xoxb)
Slack Free or Pro
Agent 2 (Stakeholder Notification)
Gmail
Finance team charge summary emails: compose and send formatted email via authenticated account
OAuth 2.0 (Gmail API, delegated access)
Google Workspace (any tier) or personal Gmail
Agent 2 (Stakeholder Notification)
Xero
Draft bill creation for confirmed renewals, pre-populated with vendor, amount, and due date
OAuth 2.0 (authorization code flow)
Xero Starter or above
Agent 3 (Finance Record)
Automation platform
Orchestration and scheduling layer: runs daily schedule trigger, routes data between agents, manages credential store and retry logic
Platform-native credential store
Vendor-dependent; must support cron scheduling and webhook ingestion
All agents (orchestration layer)
Before you connect anything: provision sandbox or test credentials for every tool in this table and validate each connection end to end before switching to production credentials. For Google Sheets and Gmail use a dedicated service account or test OAuth client. For Xero use the Xero Demo Company. For Notion use a duplicate test workspace. For Slack use a private test channel. Production credentials must not be used during build or QA.
02Per-tool integration detail
Google Sheets
Google Sheets is the master subscription register. The Renewal Triage Agent reads all active rows on each scheduled run. The Finance Record Agent writes the confirmed decision, decision date, and acting owner back to the corresponding row. A service account with domain-wide delegation is the preferred auth pattern to avoid token expiry disrupting the daily 7:00 AM trigger.
Auth method
OAuth 2.0 via Google service account (JSON key file). Alternatively, OAuth 2.0 authorization code flow with offline access and refresh token stored in credential store.
Required scopes
https://www.googleapis.com/auth/spreadsheets (read and write); https://www.googleapis.com/auth/drive.readonly (to locate the sheet by name if Spreadsheet ID is not hardcoded — store the ID in the credential store instead)
Trigger / webhook
Poll-based. The orchestration layer fires a cron schedule at 07:00 daily (timezone: as configured per [YourCompany.com] locale). No push webhook from Sheets is used. The sheet is read in full on each run.
Required configuration
Spreadsheet ID stored in credential store (key: GSHEETS_REGISTER_ID). Sheet tab name stored in credential store (key: GSHEETS_TAB_NAME). Column header row must be row 1. Required columns: vendor_name, renewal_date, annual_cost, contract_owner_slack_id, notice_period_days, auto_renew_flag, decision, decision_date, decision_owner. No formula-based derived columns may be in the range read by the automation — store computed values only in columns outside the read range.
Rate limits
Google Sheets API v4: 300 read requests/minute/project, 60 write requests/minute/user. At current volume (~35 rows read once daily, up to 35 row writes per run), throttling is not required. If row count exceeds 500, implement batch read using ranges rather than row-by-row calls.
Constraints
Spreadsheet must not be in View-only mode for the service account. Merged cells in the data range will break row parsing — enforce flat tabular structure. Date values must be stored as ISO 8601 (YYYY-MM-DD) or Google Sheets serial numbers; string-formatted dates require a parse step.
// Read input
Spreadsheet ID: ${GSHEETS_REGISTER_ID}
Range: ${GSHEETS_TAB_NAME}!A1:Z500
// Read output
Array of row objects keyed by column header
// Write input (decision update)
Row index, decision, decision_date, decision_owner
// Write output
Updated row confirmation (HTTP 200)Notion
Notion stores the canonical vendor contract page for each supplier. The Renewal Triage Agent queries the vendor database to enrich triage output with contract metadata. After a decision is confirmed, the automation updates the relevant vendor page with the new renewal date, confirmed cost, and decision taken.
Auth method
OAuth 2.0 internal integration token (Bearer token). Token must be granted access to the specific Notion database used as the vendor contract store. Do not use a user-level token tied to a personal account.
Required scopes
read_content (read database entries and page properties); update_content (write renewal date, cost, and decision to vendor page properties); no insert_content required if pages are pre-existing per vendor.
Trigger / webhook
No inbound webhook from Notion. Notion is queried on demand by Agent 1 after Google Sheets rows are read. Updates are pushed by the orchestration layer after decision confirmation.
Required configuration
Notion database ID stored in credential store (key: NOTION_VENDOR_DB_ID). Notion integration token stored in credential store (key: NOTION_INTEGRATION_TOKEN). Vendor database must have the following properties: Vendor Name (title), Renewal Date (date), Annual Cost (number, currency), Last Decision (select: Renew, Cancel, Pending), Last Updated (date). The Vendor Name property is the join key between Notion and Google Sheets — values must match exactly (case-sensitive).
Rate limits
Notion API: 3 requests/second average, burst up to 90 requests/30 seconds. At current volume (up to 35 vendor page reads + up to 35 writes per daily run), rate limits are not a concern. Add a 350 ms inter-request delay if batch processing more than 20 pages in sequence.
Constraints
Notion API does not support querying by fuzzy match on title — vendor names must be exact string matches between Sheets and Notion. Archived pages are excluded from database queries by default; do not archive active vendor pages. Notion API v1 only; do not use unofficial endpoints.
// Query input
Database ID: ${NOTION_VENDOR_DB_ID}
Filter: { property: 'Vendor Name', title: { equals: vendor_name } }
// Query output
Page ID, Renewal Date, Annual Cost, Last Decision
// Update input
Page ID, Renewal Date (new), Annual Cost (confirmed), Last Decision, Last Updated
// Update output
Updated page object (HTTP 200)Slack
Slack is used by Agent 2 (Stakeholder Notification Agent) to post a structured renewal alert to each contract owner. Messages are sent as direct messages or to a designated channel using the contract_owner_slack_id field from the Google Sheets register. Each message contains vendor name, renewal date, annual cost, notice deadline, and a prompt to log a decision.
Auth method
OAuth 2.0 bot token (xoxb-...). The Slack app must be installed into the workspace with bot scopes. Store the bot token in the credential store — do not use a legacy incoming webhook URL as it cannot target specific users.
Required scopes
chat:write (post messages to channels the bot is a member of); im:write (open and post to direct message conversations); users:read (resolve user ID from email if Slack ID is not stored in the register); users:read.email (required alongside users:read for email-to-ID lookup)
Webhook / trigger setup
Outbound only. The automation calls the Slack Web API chat.postMessage endpoint. No inbound event subscription is required for this automation. If future builds require reading message reactions (e.g. approve via emoji), add the reactions:read scope and configure an Event Subscription URL in the Slack app manifest.
Required configuration
Slack bot token stored in credential store (key: SLACK_BOT_TOKEN). Fallback alert channel stored in credential store (key: SLACK_FALLBACK_CHANNEL, format: C0XXXXXXXXX). The contract_owner_slack_id column in Google Sheets must contain the Slack member ID (format: U0XXXXXXXXX), not a display name or email. A lookup step using users.lookupByEmail can populate this field during register setup if only email addresses are available.
Rate limits
Slack Web API Tier 3 (chat.postMessage): 50 requests/minute. At current volume (up to 35 messages per daily run), no throttling is needed. Add a 1.2-second delay between messages if more than 40 messages are sent in a single run to stay within limits.
Constraints
The bot must be invited to any private channel it posts to. Direct message conversations must be opened using conversations.open before posting if the bot has not previously messaged the user. Message blocks must use Slack Block Kit JSON — plain text fallback is required in the text field for notification previews.
// Input per contract owner
channel: U0XXXXXXXXX (Slack member ID from register)
blocks: [ Header, Section (vendor, date, cost, notice deadline), Actions (link to register) ]
text: 'Renewal alert: {vendor_name} renews on {renewal_date}'
// Output
{ ok: true, ts: '...', channel: '...' } on success
{ ok: false, error: 'channel_not_found' } on failure -> route to fallback channelIntegration and API SpecPage 1 of 3
FS-DOC-05Technical
Gmail
Gmail is used by Agent 2 (Stakeholder Notification Agent) to send a consolidated upcoming-charges summary to the finance contact. The email lists all renewals due within 30 days, with vendor names, amounts, and due dates, plus a link to the live Google Sheets register. The sender identity must be the IT manager or a shared mailbox — not the automation platform's own email address.
Auth method
OAuth 2.0 authorization code flow with offline access. The refresh token must be stored in the credential store. Delegated access via a Google Workspace service account is preferred for shared mailbox sending (requires domain-wide delegation and Gmail API enabled in Google Cloud project).
Required scopes
https://www.googleapis.com/auth/gmail.send (compose and send email only — do not request broader gmail.modify or gmail.readonly unless required by future steps); https://www.googleapis.com/auth/gmail.compose (alternative minimal scope for drafting before send)
Trigger / webhook
Outbound only. Gmail is called after Agent 2 has assembled the finance summary from triage output. No inbound Gmail trigger is used in this automation. The finance email fires once per daily run if any renewals fall within the 30-day window.
Required configuration
Gmail OAuth refresh token stored in credential store (key: GMAIL_REFRESH_TOKEN). Sender address stored in credential store (key: GMAIL_SENDER_ADDRESS). Finance recipient address stored in credential store (key: GMAIL_FINANCE_RECIPIENT). Google Cloud project client ID and client secret stored in credential store (keys: GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET). Email template uses placeholders: {{renewal_table}}, {{register_link}}, {{run_date}} — these are resolved at runtime from triage output.
Rate limits
Gmail API: 250 quota units/second/user; gmail.send costs 100 units per call. At one consolidated email per daily run, rate limits are not a concern. If the automation is extended to send individual per-contract emails, monitor daily send limits (2,000 emails/day for Workspace, 500/day for personal Gmail).
Constraints
The Gmail API does not support sending from an alias unless the alias is configured in Gmail settings for the authenticated account. HTML email body must be base64url-encoded in the raw message field. Attachments are not used in this automation — the register link replaces any attachment.
// Input
to: ${GMAIL_FINANCE_RECIPIENT}
from: ${GMAIL_SENDER_ADDRESS}
subject: 'Upcoming Renewals Summary - {run_date}'
body_html: rendered HTML from template with {{renewal_table}}, {{register_link}}
// Output
{ id: 'message_id', threadId: '...', labelIds: ['SENT'] } on success
HTTP 403 invalid_grant -> refresh token expired -> alert via fallback Slack channelXero
Xero is used by Agent 3 (Finance Record Agent) to create a draft bill for each confirmed renewal. The bill is pre-populated with the vendor name (matched to an existing supplier contact), the renewal amount, and the due date. The bill is created in draft status only — the finance officer must approve and post it manually. Xero is not used for any read operations in this automation.
Auth method
OAuth 2.0 authorization code flow (PKCE recommended). Xero access tokens expire after 30 minutes; refresh tokens are valid for 60 days and must be rotated on each use. Store the refresh token and tenant ID in the credential store — never hardcode.
Required scopes
accounting.transactions (create and read invoices and bills); accounting.contacts.read (verify supplier contact exists before creating bill — prevents orphaned draft bills). Do not request accounting.settings, payroll, or files scopes.
Trigger / webhook
No inbound Xero webhook is used. Agent 3 is triggered by the decision_confirmed event written to Google Sheets. If future builds require reconciliation confirmation from Xero, configure a Xero webhook endpoint on the automation platform to receive invoice.updated events (requires HTTPS endpoint with TLS 1.2+).
Required configuration
Xero tenant ID stored in credential store (key: XERO_TENANT_ID). Xero OAuth refresh token stored in credential store (key: XERO_REFRESH_TOKEN). Xero OAuth client ID and client secret stored in credential store (keys: XERO_CLIENT_ID, XERO_CLIENT_SECRET). Default account code for subscription expenses stored in credential store (key: XERO_EXPENSE_ACCOUNT_CODE, e.g. 429). Tax type stored in credential store (key: XERO_TAX_TYPE, e.g. NONE or applicable local code). All vendor names in the Google Sheets register must correspond to an existing supplier contact in Xero — this is a hard prerequisite; new vendors must be created manually in Xero before the automation can raise a bill.
Rate limits
Xero API: 60 calls/minute per organisation, 5,000 calls/day. At current volume (up to 35 draft bills per run, plus up to 35 contact lookups), peak usage is 70 calls per run — well within limits. No throttling required. If daily run volume exceeds 100 bills, implement a 1-second delay between bill creation calls.
Constraints
Xero draft bills cannot be posted or approved via API — the Status field must remain DRAFT on creation. Duplicate bill prevention: before creating a bill, query existing drafts for the same contact and due date; if a match exists, skip creation and log a warning. Xero does not allow a bill to reference a contact by name alone — the ContactID (UUID) must be resolved first via a contacts lookup using the vendor name. The Xero Demo Company must be used for all QA and sandbox testing; never create bills in the production organisation during the build phase.
// Contact lookup input
GET /api.xro/2.0/Contacts?where=Name=="{vendor_name}"
Headers: Authorization: Bearer {access_token}, Xero-tenant-id: ${XERO_TENANT_ID}
// Contact lookup output
ContactID (UUID) for matched supplier
// Bill creation input
POST /api.xro/2.0/Invoices
{ Type: 'ACCPAY', Contact: { ContactID }, Date: run_date, DueDate: renewal_date,
Status: 'DRAFT', LineItems: [{ Description: vendor_name + ' renewal',
UnitAmount: annual_cost, AccountCode: ${XERO_EXPENSE_ACCOUNT_CODE},
TaxType: ${XERO_TAX_TYPE} }] }
// Bill creation output
{ Invoices: [{ InvoiceID, Status: 'DRAFT', InvoiceNumber }] }03Field mappings between tools
The tables below define the exact field-level mappings for each agent handoff in the automation. Field names use monospace notation to match the exact keys expected in API payloads and the Google Sheets column headers. Any mismatch in field names will cause a silent null value or a mapping error at runtime.
Handoff 1: Google Sheets to Renewal Triage Agent (Agent 1)
Source tool
Source field
Destination tool
Destination field
Google Sheets
`vendor_name`
Triage Agent output
`vendor_name`
Google Sheets
`renewal_date`
Triage Agent output
`renewal_date` (ISO 8601)
Google Sheets
`annual_cost`
Triage Agent output
`annual_cost` (numeric, USD)
Google Sheets
`contract_owner_slack_id`
Triage Agent output
`owner_slack_id`
Google Sheets
`notice_period_days`
Triage Agent output
`notice_period_days` (integer)
Google Sheets
`auto_renew_flag`
Triage Agent output
`auto_renew` (boolean)
Google Sheets
`decision`
Triage Agent output
`existing_decision` (string or null)
Google Sheets
`row_index`
Triage Agent output
`sheet_row_index` (integer, for write-back)
Handoff 2: Renewal Triage Agent (Agent 1) enrichment from Notion
Source tool
Source field
Destination tool
Destination field
Triage Agent output
`vendor_name`
Notion (query filter)
`Vendor Name` (title property)
Notion
`page_id`
Triage Agent output
`notion_page_id`
Notion
`Renewal Date`
Triage Agent output
`notion_renewal_date`
Notion
`Annual Cost`
Triage Agent output
`notion_annual_cost`
Notion
`Last Decision`
Triage Agent output
`notion_last_decision`
Handoff 3: Renewal Triage Agent (Agent 1) to Stakeholder Notification Agent (Agent 2)
Source tool
Source field
Destination tool
Destination field
Triage Agent output
`vendor_name`
Slack message block
`vendor_name` (Section text)
Triage Agent output
`renewal_date`
Slack message block
`renewal_date` (formatted display string)
Triage Agent output
`annual_cost`
Slack message block
`annual_cost` (formatted as $X,XXX.XX)
Triage Agent output
`notice_period_days`
Slack message block
`notice_deadline` (computed: renewal_date minus notice_period_days)
Triage Agent output
`owner_slack_id`
Slack API
`channel` (chat.postMessage)
Triage Agent output
`urgency_level`
Slack message block
`header_text` (e.g. URGENT, ACTION REQUIRED)
Triage Agent output (all 30-day items)
`vendor_name`, `annual_cost`, `renewal_date`
Gmail body
`{{renewal_table}}` row (HTML table row)
Handoff 4: Google Sheets decision write-back and Notion update (post human decision)
Source tool
Source field
Destination tool
Destination field
Human input (register row)
`decision` (Renew or Cancel)
Google Sheets
`decision` (column, same row)
Human input (register row)
`decision_date`
Google Sheets
`decision_date` (ISO 8601)
Human input (register row)
`decision_owner`
Google Sheets
`decision_owner` (name string)
Google Sheets
`vendor_name`
Notion (page lookup)
`Vendor Name` (title property, join key)
Google Sheets
`renewal_date`
Notion page property
`Renewal Date`
Google Sheets
`annual_cost`
Notion page property
`Annual Cost`
Google Sheets
`decision`
Notion page property
`Last Decision` (select)
Google Sheets
`decision_date`
Notion page property
`Last Updated` (date)
Handoff 5: Google Sheets confirmed renewal to Finance Record Agent (Agent 3) and Xero
Source tool
Source field
Destination tool
Destination field
Google Sheets
`vendor_name`
Xero (contact lookup)
`Name` filter in GET /Contacts
Xero (contact lookup)
`ContactID`
Xero (bill creation)
`Contact.ContactID`
Google Sheets
`annual_cost`
Xero bill
`LineItems[0].UnitAmount`
Google Sheets
`renewal_date`
Xero bill
`DueDate`
Static value
`ACCPAY`
Xero bill
`Type`
Static value
`DRAFT`
Xero bill
`Status`
Credential store
`XERO_EXPENSE_ACCOUNT_CODE`
Xero bill
`LineItems[0].AccountCode`
Credential store
`XERO_TAX_TYPE`
Xero bill
`LineItems[0].TaxType`
Integration and API SpecPage 2 of 3
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
Three discrete workflows, one per agent, running within a single automation platform project. Workflows share a single credential store. Agent 1 (Renewal Triage) is the entry point and the only scheduled workflow. Agents 2 and 3 are triggered by data events produced by Agent 1 and the human decision step respectively, not by independent schedules.
Agent 1 trigger: Renewal Triage Agent
Cron schedule (poll-based). Fires daily at 07:00 in the organisation's configured timezone. No inbound webhook. The workflow reads Google Sheets, evaluates all rows, and produces the triage output array. If zero rows are in an alert window, the workflow exits cleanly with a log entry and does not invoke Agent 2.
Agent 2 trigger: Stakeholder Notification Agent
Data event (internal). Triggered by Agent 1 passing the filtered alert array downstream. Not an HTTP webhook. Executes synchronously within the same run session as Agent 1 if the platform supports chained execution; otherwise implemented as a webhook call from Agent 1 to Agent 2's inbound trigger URL (HMAC-SHA256 signature validation required on the receiving workflow using a shared secret stored as WEBHOOK_SHARED_SECRET in the credential store).
Agent 3 trigger: Finance Record Agent
Poll-based with conditional filter. Agent 3 polls the Google Sheets register every 15 minutes for rows where decision equals 'Renew' and xero_bill_created equals blank or false. When a qualifying row is found, it initiates the Xero contact lookup and bill creation sequence. This poll interval ensures bills are created promptly after a human logs a decision without requiring a push webhook from Google Sheets.
Credential store structure
All secrets are stored in the automation platform's native encrypted credential store, referenced by environment variable key names. No credential value is written into workflow node configuration fields directly. See code block below for the full credential key list.
Signature validation (Agent 2 webhook path)
If Agent 2 is invoked via HTTP webhook from Agent 1, the request must include an X-FullSpec-Signature header containing HMAC-SHA256(payload, WEBHOOK_SHARED_SECRET). Agent 2's inbound trigger verifies this header before processing. Requests failing signature validation are rejected with HTTP 401 and logged.
Logging and audit
Every workflow run must write a structured log entry on completion: run timestamp, agent name, number of rows processed, number of alerts sent, number of bills created, and any errors encountered. Logs are retained for a minimum of 90 days within the platform or forwarded to an external log destination if the platform supports it.
All values are resolved at runtime from the credential store. No value is hardcoded in workflow logic.
// Credential store: full key inventory
// Google Sheets
GSHEETS_REGISTER_ID = '<spreadsheet_id>'
GSHEETS_TAB_NAME = '<sheet_tab_name>'
GSHEETS_SERVICE_ACCOUNT_KEY = '<base64_encoded_service_account_json>'
// Gmail
GMAIL_CLIENT_ID = '<google_oauth_client_id>'
GMAIL_CLIENT_SECRET = '<google_oauth_client_secret>'
GMAIL_REFRESH_TOKEN = '<offline_refresh_token>'
GMAIL_SENDER_ADDRESS = '<sender@yourcompany.com>'
GMAIL_FINANCE_RECIPIENT = '<finance@yourcompany.com>'
// Slack
SLACK_BOT_TOKEN = 'xoxb-...'
SLACK_FALLBACK_CHANNEL = '<channel_id_C0XXXXXXXXX>'
// Xero
XERO_CLIENT_ID = '<xero_oauth_client_id>'
XERO_CLIENT_SECRET = '<xero_oauth_client_secret>'
XERO_REFRESH_TOKEN = '<xero_refresh_token>'
XERO_TENANT_ID = '<xero_organisation_tenant_id>'
XERO_EXPENSE_ACCOUNT_CODE = '429'
XERO_TAX_TYPE = 'NONE'
// Notion
NOTION_INTEGRATION_TOKEN = 'secret_...'
NOTION_VENDOR_DB_ID = '<notion_database_id>'
// Orchestration
WEBHOOK_SHARED_SECRET = '<random_32_byte_hex_string>'
AUTOMATION_TIMEZONE = 'America/New_York'
REGISTER_LINK = '<public_or_shared_link_to_google_sheet>'
Xero refresh tokens expire after 60 days of inactivity. The orchestration layer must rotate the Xero refresh token on every successful authentication call and update XERO_REFRESH_TOKEN in the credential store immediately. If the token is not rotated, the next run will fail with a 401 invalid_grant error and Agent 3 will be unable to create bills until the token is manually re-authorised.
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 run timestamp, agent name, integration name, error code, and the row or record being processed at the time of failure. Where a retry is defined, exponential backoff is used with the intervals noted below.
Integration
Scenario
Required behaviour
Google Sheets (read)
HTTP 429 quota exceeded on read
Retry 3 times with exponential backoff: 10s, 30s, 90s. If all retries fail, abort the run and post an alert to SLACK_FALLBACK_CHANNEL with error details. Do not proceed to Agent 2 without a successful read.
Google Sheets (read)
Spreadsheet not found (HTTP 404) or permission denied (HTTP 403)
Do not retry. Post an alert to SLACK_FALLBACK_CHANNEL immediately. Log the error. Abort the run. This indicates a credential or sharing configuration issue requiring manual intervention.
Google Sheets (write)
Write fails for a specific row (HTTP 400 or 500)
Retry the write for that row 2 times with 15s delay. If still failing, log the failed row index and continue processing remaining rows. Send a summary of failed writes to SLACK_FALLBACK_CHANNEL at end of run.
Notion (query)
Vendor name not found in Notion database
Log a warning for the row. Continue processing with the data available from Google Sheets (do not block the alert). Flag the row in the triage output as notion_match: false. Post a weekly digest of unmatched vendors to SLACK_FALLBACK_CHANNEL.
Notion (update)
Page update fails (HTTP 400 or 409 conflict)
Retry once after 20s. If still failing, log the error with the page ID and continue. Do not block the Xero bill creation on a Notion update failure. Alert SLACK_FALLBACK_CHANNEL with the failed update detail.
Slack (chat.postMessage)
channel_not_found or user_not_found error for a specific owner
Fall back immediately to posting the alert to SLACK_FALLBACK_CHANNEL with the intended recipient's Slack ID included in the message. Log the failed delivery. Do not retry the original DM. Continue sending to other recipients.
Slack (chat.postMessage)
HTTP 429 rate limit exceeded
Read the Retry-After header value from the Slack response and wait the specified number of seconds before retrying. Retry up to 3 times. If rate limit persists, batch remaining messages and space them 2 seconds apart.
Gmail (send)
OAuth refresh token invalid or expired (HTTP 401 invalid_grant)
Do not retry. Post an alert to SLACK_FALLBACK_CHANNEL that the Gmail token requires re-authorisation. Log the error. Skip the finance email for this run and note it in the run log. A human must re-authorise the Gmail OAuth credential before the next run.
Gmail (send)
Recipient address rejected (HTTP 400 invalid To address)
Log the error with the recipient address. Do not retry. Post an alert to SLACK_FALLBACK_CHANNEL. The finance recipient address stored in GMAIL_FINANCE_RECIPIENT must be verified manually.
Xero (contact lookup)
Vendor name returns no matching contact
Do not create a bill. Log a warning with the vendor name and renewal date. Post an alert to SLACK_FALLBACK_CHANNEL: 'Xero supplier not found for {vendor_name} - bill not created, manual entry required.' Continue processing other confirmed renewals.
Xero (bill creation)
HTTP 429 or 503 from Xero API
Retry 3 times with exponential backoff: 15s, 45s, 135s. If all retries fail, log the failure with the vendor name and amount. Post an alert to SLACK_FALLBACK_CHANNEL. The finance officer must be notified to create the bill manually.
Xero (bill creation)
Duplicate draft bill detected (same contact and due date already exists)
Skip bill creation. Log a duplicate warning with the vendor name, ContactID, and due date. Update the Google Sheets row with xero_bill_created: duplicate_skipped to prevent repeated detection on the next poll cycle.
All error alerts posted to SLACK_FALLBACK_CHANNEL must follow a consistent structure: [ERROR] Agent name, Integration, Timestamp, Error code, Affected record (vendor name or row index), and a plain-English description of the required manual action. Generic or empty error messages are not acceptable. If an error cannot be classified, it must be treated as critical and posted immediately rather than batched.
Integration and API SpecPage 3 of 3