Back to Cash Flow Forecasting

Developer Handover Pack

Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.

4 pagesPDF · Technical
FS-DOC-04Technical

Developer Handover Pack

Cash Flow Forecasting Automation

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

This document is the primary technical reference for the FullSpec team building the Cash Flow Forecasting automation. It covers the current-state process map, all agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. The process owner and finance lead are responsible for confirming API access, chart-of-accounts rules, and the Google Sheets model structure before build begins. Everything else described here is owned and executed by FullSpec. Questions should be directed to support@gofullspec.com.

01Process snapshot

Step
Name
Description
1
Export Bank Transactions from Xero
Bookkeeper logs into Xero and exports a CSV of all cleared transactions for the past 7 days across each bank account. (15 min)
2
Download AR Ageing Report
A separate AR ageing report is pulled from Xero showing outstanding invoices by overdue bucket, saved and renamed locally. (10 min)
3
Pull Upcoming Bills and Scheduled Payments
Bookkeeper opens the bills section of Xero and manually notes every payable due in the next 8 weeks with amounts and due dates. (20 min)
4
Check Stripe for Pending Payouts
Stripe is checked separately to confirm which payouts are scheduled and when they will land. Figures are noted by hand. (10 min)
5
Paste All Data into the Forecast Spreadsheet
All exported data is pasted into the master Google Sheet, with figures placed into correct weekly columns by hand. Formulas sometimes break. (35 min)
6
Categorise Irregular or Uncoded Transactions
Transactions without a known category rule are reviewed manually and assigned to the correct forecast line. Time varies with volume. (30 min)
7
Apply Known Assumptions and Adjustments
Finance lead applies adjustments for expected large payments, seasonal corrections, or pipeline deals. Business judgement required. (20 min)
8
Sense-Check Closing Balance Figures
Finance lead reads through the 8-week closing balance row and flags any negative or unexpectedly low week. (15 min)
9
Format Report and Export to PDF
Summary tab is manually updated with headline numbers, charts refreshed, and the sheet exported to PDF. (15 min)
10
Email the Forecast to Stakeholders
PDF and a short commentary note are emailed to the business owner and leadership team members via Gmail. (10 min)
11
Post Forecast Summary in Team Channel
A brief summary of opening balance, 4-week projection, and cash risk flags is pasted into the Slack finance channel. (5 min)
12
File and Archive Previous Week's Forecast
Last week's spreadsheet version is saved to a named folder in Google Drive for audit purposes. (5 min)
Time cost summary: Total manual time per cycle is 190 minutes (3 hours 10 minutes). At 4 cycles per month this is approximately 760 minutes (12.7 hours) per month and 160 hours per year at a loaded cost of $10,400/year. Steps 1 through 6 and steps 9 through 12 are fully replaced by automation. Steps 7 and 8 remain with the finance lead by design. Steps 5 and 6 are the primary bottlenecks, accounting for 65 minutes per cycle and the highest error risk.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Data Collection Agent

Connects to Xero and Stripe on a fixed Monday morning schedule and pulls all financial data required to populate the forecast. The agent authenticates via OAuth 2.0 (Xero) and a restricted API key (Stripe), handles pagination across multiple bank accounts, and retries failed API calls up to three times before triggering a failure alert. The bookkeeper does not need to log in to either system. All retrieved data is timestamped at the moment of retrieval and assembled into a single structured payload passed to the Categorisation Agent. Complexity: Moderate.

Trigger
Monday schedule, fires at 06:00 local time via the orchestration layer's built-in cron scheduler.
Tools
Xero, Stripe
Replaces steps
1, 2, 3, 4
Estimated build
7 hours, Moderate
// Input
trigger: { type: 'schedule', day: 'Monday', time: '06:00', timezone: 'business_tz' }

// Xero API calls (paginated, per bank account)
xero.bankTransactions.list({ dateFrom: now-7d, dateTo: now, status: 'AUTHORISED' })
xero.reports.getAgedReceivables({ reportDate: now, agingPeriod: 30 })
xero.bills.list({ dueDateTo: now+56d, status: ['AUTHORISED','DRAFT'] })

// Stripe API call
stripe.payouts.list({ arrival_date.lte: now+56d, status: 'pending' })

// Output
payload: {
  run_id: string (uuid),
  run_timestamp: ISO8601,
  bank_transactions: [ { id, date, amount, description, account_id, xero_contact_name } ],
  ar_ageing: { current: number, days_30: number, days_60: number, days_90_plus: number },
  upcoming_bills: [ { id, due_date, amount, contact_name, status } ],
  stripe_payouts: [ { id, amount, currency, arrival_date, status } ]
}
  • Xero requires OAuth 2.0 with the following scopes: accounting.transactions.read, accounting.reports.read, accounting.contacts.read. The tenant_id must be stored as a named credential in the orchestration layer's credential store, not hardcoded.
  • Stripe connection must use a restricted API key scoped to payouts:read only. The live key must be stored encrypted; never use the full secret key.
  • The Xero bank transaction pull must paginate using the page parameter (max 100 records per page) to handle high-volume accounts. Page limit must be capped at 50 pages; if exceeded, raise a PARTIAL_DATA_WARNING flag in the payload.
  • If any single API call fails after three retries, the run must halt and post a failure alert to the Slack finance channel with the run_id and failed endpoint name. Do not pass a partial payload downstream.
  • Confirm before build: number of Xero bank accounts and whether multiple Xero tenants (legal entities) are in scope. Multi-tenant support is not included in the Standard build and requires the Enterprise tier.
  • Confirm before build: Xero plan must be Established or higher for the aged receivables report endpoint to be available via API.
  • All monetary values must be normalised to the business's base currency (stored as a config variable: BASE_CURRENCY) before the payload is assembled.
Categorisation Agent

Receives the structured data payload from the Data Collection Agent and classifies each bank transaction against the business's chart-of-accounts coding rules. Rules are stored as a configuration table (category_rules) mapping keyword patterns and Xero account codes to forecast line labels. Transactions matching a rule with confidence above the threshold are assigned automatically. Transactions below the threshold, or with no rule match, are written to a flagged_items list rather than guessed. The fully categorised dataset and flagged list are then written to the master Google Sheet. Complexity: Moderate.

Trigger
Receives the structured payload object from the Data Collection Agent on successful completion.
Tools
Google Sheets
Replaces steps
5, 6
Estimated build
8 hours, Moderate
// Input
payload: { run_id, run_timestamp, bank_transactions[], ar_ageing{}, upcoming_bills[], stripe_payouts[] }
config: { category_rules[], confidence_threshold: 0.80, sheet_id: string, data_tab: string, flags_tab: string }

// Processing
for each transaction in bank_transactions:
  match against category_rules by xero_account_code, then description keyword
  if match_confidence >= 0.80: assign forecast_line_label, set flagged = false
  else: set forecast_line_label = 'UNMATCHED', set flagged = true, append to flagged_items[]

// Output (written to Google Sheets via Sheets API v4)
data_tab write: {
  run_id, run_timestamp,
  weekly_columns: { week_start_date: { inflows: number, outflows: number, net: number } },
  ar_ageing: { current, days_30, days_60, days_90_plus },
  upcoming_bills: [ { due_date, amount, contact_name } ],
  stripe_payouts: [ { arrival_date, amount } ]
}
flags_tab write: {
  flagged_items: [ { transaction_id, date, amount, description, reason: string } ]
}
approval_cell: cleared to '' (empty) to reset any prior approval state
  • Google Sheets API v4 requires OAuth 2.0 with scopes: https://www.googleapis.com/auth/spreadsheets. The sheet_id and tab names (data_tab, flags_tab, approval_tab) must be stored as named config variables, not hardcoded.
  • The category_rules configuration table must be agreed and documented with the finance lead before build begins. Rules should map: xero_account_code (string), description_keywords (array of strings), and forecast_line_label (string). A minimum viable ruleset of 20 rules is expected for go-live.
  • The write step must use batchUpdate (not individual cell writes) to avoid exceeding the Sheets API rate limit of 300 write requests per minute. Write the full weekly column set in a single batch per run.
  • Existing sheet formulas and named ranges must not be overwritten. The build must map the exact cell ranges for data write zones and must leave formula rows untouched. Confirm the Google Sheet column structure is stable before writing any integration.
  • The approval_cell reference (e.g. Sheet1!B2) must be confirmed with the finance lead and stored as a config variable: APPROVAL_CELL_REF.
  • Deduplicate transactions by transaction_id before writing; if the same run_id is replayed (e.g. after a failed write), existing rows must be overwritten, not appended.
  • If the Sheets write fails, the agent must retry up to two times then post a Slack alert with the run_id and the error message. Do not leave the sheet in a partially written state.
Distribution Agent

Monitors the designated approval cell in the Google Sheet for the finance lead's approval signal. When the cell value is set to 'Approved', the agent triggers downstream delivery: it exports the summary tab as a PDF via the Google Sheets export endpoint, emails the PDF to the stakeholder distribution list via Gmail, and posts a formatted cash summary message to the Slack finance channel. The agent does not fire until the approval signal is received; if no approval is set within 48 hours of the data write, a reminder is posted to Slack. Complexity: Simple.

Trigger
Polling the Google Sheet approval cell (APPROVAL_CELL_REF) every 5 minutes. Fires when cell value equals 'Approved' (case-insensitive match).
Tools
Google Sheets, Gmail, Slack
Replaces steps
9, 10, 11, 12
Estimated build
5 hours, Simple
// Input
trigger: approval_cell value == 'Approved'
config: {
  sheet_id: string,
  summary_tab: string,
  export_format: 'application/pdf',
  gmail_from: string (authenticated sender address),
  gmail_to: [ string ] (stakeholder distribution list),
  gmail_subject_template: 'Cash Flow Forecast - Week of {{week_start_date}}',
  gmail_body_template: string (references run_timestamp, flagged_item_count),
  slack_channel: string (finance channel ID),
  slack_message_template: string (references opening_balance, projected_close_4w, flagged_item_count),
  no_approval_reminder_hours: 48
}

// Processing
step_1: export summary_tab to PDF via Sheets export URL with access token
step_2: attach PDF to Gmail draft; populate subject and body from templates; send
step_3: compose Slack message from template fields; post to slack_channel
step_4: write archive entry to Google Sheets archive_tab: { run_id, approved_at, pdf_url, sent_to[] }

// Output
gmail_send: { message_id, sent_at, recipients[], attachment_filename }
slack_post: { ts, channel, message_preview }
archive_row: { run_id, week_start_date, approved_at, sent_at, pdf_filename }

// On approval
approval_cell is not cleared after send; archive_tab entry acts as the audit record.
If no approval detected within 48 hours: post Slack reminder to finance channel with run_id and sheet link.
  • Gmail send requires OAuth 2.0 with scope: https://www.googleapis.com/auth/gmail.send. The authenticated sender address must match the Google Workspace account used for finance communications.
  • The stakeholder distribution list (gmail_to array) must be confirmed with the process owner before build and stored as a config variable: DISTRIBUTION_LIST. The list must not be hardcoded.
  • Slack integration requires a bot token with scopes: chat:write, chat:write.public. The bot must be invited to the finance channel before testing.
  • The PDF export uses the Google Sheets export endpoint: https://docs.google.com/spreadsheets/d/{sheet_id}/export?format=pdf&gid={summary_tab_gid}. The summary_tab_gid must be retrieved and stored during build setup.
  • Polling the approval cell every 5 minutes is the recommended approach. If the orchestration layer supports webhook-based Sheets triggers, switch to that method to reduce API call volume.
  • The 48-hour no-approval reminder must reference the run_id and include a direct link to the Google Sheet so the finance lead can act immediately from Slack.
  • The archive_tab write serves as the permanent audit trail replacing the manual file-save step (step 12). Confirm the archive tab name and column structure with the finance lead before build.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow with field names at each agent handoff
// ─────────────────────────────────────────────────────────────────────
// TRIGGER
// ─────────────────────────────────────────────────────────────────────
orchestration_scheduler fires at 06:00 Monday
  -> event: { type: 'schedule', day: 'Monday', time: '06:00', timezone: BUSINESS_TZ }
  -> initialise run_id (uuid), run_timestamp (ISO8601)

// ─────────────────────────────────────────────────────────────────────
// AGENT 1: DATA COLLECTION AGENT
// ─────────────────────────────────────────────────────────────────────

// Xero OAuth 2.0 (tenant_id from credential store)
GET /api.xero.com/api.xro/2.0/BankTransactions
  params: { DateFrom: now-7d, DateTo: now, Status: 'AUTHORISED' }
  -> bank_transactions[]: { BankTransactionID, Date, Total, Description,
                            BankAccount.AccountID, Contact.Name }
  [paginated: page=1..N, max 100/page; halt + SLACK_ALERT if pages > 50]

GET /api.xero.com/api.xro/2.0/Reports/AgedReceivablesByContact
  params: { reportDate: now, fromDate: now-90d }
  -> ar_ageing: { current, days_30, days_60, days_90_plus } (all: number, base currency)

GET /api.xero.com/api.xro/2.0/Invoices
  params: { Type: 'ACCPAY', DueDateTo: now+56d, Status: ['AUTHORISED','DRAFT'] }
  -> upcoming_bills[]: { InvoiceID, DueDate, AmountDue, Contact.Name, Status }

// Stripe restricted API key (payouts:read)
GET /v1/payouts
  params: { 'arrival_date[lte]': now+56d, status: 'pending', limit: 100 }
  -> stripe_payouts[]: { id, amount, currency, arrival_date, status }

// Retry logic: up to 3 attempts per endpoint, 5s backoff
// On failure after 3 retries: halt run, POST Slack alert { run_id, failed_endpoint }

// ── HANDOFF: Data Collection Agent -> Categorisation Agent ──────────
payload_v1: {
  run_id: string,
  run_timestamp: ISO8601,
  bank_transactions: [ { id, date, amount, description, account_id, contact_name } ],
  ar_ageing: { current, days_30, days_60, days_90_plus },
  upcoming_bills: [ { id, due_date, amount, contact_name, status } ],
  stripe_payouts: [ { id, amount, currency, arrival_date, status } ]
}

// ─────────────────────────────────────────────────────────────────────
// AGENT 2: CATEGORISATION AGENT
// ─────────────────────────────────────────────────────────────────────

load config: { category_rules[], confidence_threshold: 0.80,
               sheet_id, data_tab, flags_tab, approval_tab, APPROVAL_CELL_REF }

for each tx in bank_transactions[]:
  attempt match: category_rules[].xero_account_code == tx.account_id
               OR category_rules[].description_keywords intersect tx.description
  if match_confidence >= 0.80:
    tx.forecast_line_label = matched_rule.forecast_line_label
    tx.flagged = false
  else:
    tx.forecast_line_label = 'UNMATCHED'
    tx.flagged = true
    append to flagged_items[]: { transaction_id, date, amount, description, reason }

deduplicate by transaction_id (overwrite on replay)

// Google Sheets API v4 batchUpdate
WRITE data_tab:
  range: DATA_WRITE_RANGE (config variable)
  values: [ run_id, run_timestamp, weekly columns by week_start_date,
            ar_ageing fields, upcoming_bills rows, stripe_payouts rows ]

WRITE flags_tab:
  range: FLAGS_WRITE_RANGE (config variable)
  values: flagged_items[] rows

CLEAR approval_cell: APPROVAL_CELL_REF -> '' (reset state)

// On Sheets write failure: retry x2, then POST Slack alert { run_id, error_message }

// ── HANDOFF: Categorisation Agent -> Distribution Agent ─────────────
// Distribution Agent begins polling APPROVAL_CELL_REF every 5 minutes
// Human step: Finance Lead reviews data_tab and flags_tab,
//             applies manual adjustments (steps 7-8, retained),
//             sets APPROVAL_CELL_REF = 'Approved'

// ─────────────────────────────────────────────────────────────────────
// AGENT 3: DISTRIBUTION AGENT
// ─────────────────────────────────────────────────────────────────────

poll trigger: APPROVAL_CELL_REF value == 'Approved' (case-insensitive)

// Step 1: PDF export
GET https://docs.google.com/spreadsheets/d/{sheet_id}/export
  params: { format: 'pdf', gid: SUMMARY_TAB_GID }
  -> pdf_blob (binary), pdf_filename: 'CashForecast_{{week_start_date}}.pdf'

// Step 2: Gmail send (OAuth 2.0, scope: gmail.send)
POST /gmail/v1/users/me/messages/send
  to: DISTRIBUTION_LIST[]
  from: GMAIL_SENDER_ADDRESS
  subject: 'Cash Flow Forecast - Week of {{week_start_date}}'
  body: gmail_body_template { run_timestamp, flagged_item_count }
  attachment: pdf_blob
  -> { message_id, sent_at }

// Step 3: Slack post (bot token, scope: chat:write)
POST /api.slack.com/api/chat.postMessage
  channel: SLACK_FINANCE_CHANNEL_ID
  text: slack_message_template {
    opening_balance: bank_transactions[0].balance,
    projected_close_4w: weekly_columns[week+4].closing_balance,
    flagged_item_count: flagged_items.length
  }
  -> { ts, channel }

// Step 4: Archive write
APPEND archive_tab row:
  { run_id, week_start_date, approved_at, sent_at, pdf_filename, recipients_count }

// 48-hour no-approval guard (parallel timer from Sheets write completion):
if APPROVAL_CELL_REF != 'Approved' after 48h:
  POST Slack reminder: { run_id, sheet_link, message: 'Forecast awaiting approval' }

// ─────────────────────────────────────────────────────────────────────
// END OF RUN
// ─────────────────────────────────────────────────────────────────────
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation tool with native scheduling, a shared encrypted credential store, and support for multi-step branching logic. One workflow per agent is the recommended structure: three workflows total (Data Collection, Categorisation, Distribution), linked by payload passing or a shared staging object. All API credentials (Xero OAuth tokens, Stripe restricted key, Google OAuth tokens, Gmail OAuth tokens, Slack bot token) must be stored in the platform's credential store and referenced by name, never hardcoded.
Webhook configuration
No inbound webhook is required for the initial trigger (schedule-based). An inbound webhook endpoint should be provisioned for future use (e.g. manual re-run or external trigger). The Distribution Agent's approval detection uses polling rather than a webhook; if the chosen platform supports Google Sheets change notifications, switch to that method and register the notification channel against APPROVAL_CELL_REF using the Sheets API push notification endpoint.
Templating approach
Gmail subject lines, email body text, and Slack messages are built from string templates with named placeholders: {{week_start_date}}, {{run_timestamp}}, {{flagged_item_count}}, {{opening_balance}}, {{projected_close_4w}}. Templates are stored as config variables in the orchestration layer, not hardcoded in workflow nodes, so the finance lead can request copy changes without a code change. The PDF is exported directly from the Google Sheets summary tab; no additional PDF rendering library is required.
Error logging
All agent run events (start, success, partial failure, full failure) must be written to a Supabase table: automation_run_log { run_id, agent_name, status, error_code, error_message, timestamp }. On any failure status, an alert is simultaneously posted to the Slack finance channel referencing the run_id so the finance lead and the FullSpec team can cross-reference the log. The Supabase project URL and anon/service key must be stored in the orchestration credential store.
Testing approach
All agent builds must be validated in sandbox environments first: Xero demo company, Stripe test mode (test API key), a cloned Google Sheet with non-production data, Gmail sandbox send-to-self, and a private Slack test channel. End-to-end testing uses live sandbox data for two full Monday cycles before any production credentials are substituted. Production cutover happens only after both QA cycles complete without errors and the finance lead has signed off on the categorisation output.
Estimated total build time
Data Collection Agent: 7 hours. Categorisation Agent: 8 hours. Distribution Agent: 5 hours. End-to-end integration testing and QA: 2 hours. Total: 22 hours across a 4-week delivery schedule.
Before build begins, the following must be confirmed with the process owner: (1) Xero plan tier is Established or higher for AR report API access; (2) number of Xero bank accounts and whether any multi-tenant setup is needed; (3) the Google Sheets forecast model column structure is frozen and a cloned staging copy is available; (4) the chart-of-accounts category_rules minimum viable set of 20 rules is documented and agreed with the finance lead; (5) the stakeholder distribution list (gmail_to) is confirmed in writing; (6) Slack finance channel ID is provided. Contact support@gofullspec.com with these confirmations to unblock the build start.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Cash Flow Forecasting.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Integration and API Spec
Technical · Developer
View
Test and QA Plan
Quality · Developer
View