FS-DOC-05Technical
Integration and API Spec
Bank Reconciliation Automation
[YourCompany.com] · Finance Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative technical reference for every external service connected in the Bank Reconciliation automation. It covers authentication methods, exact OAuth scopes, webhook and polling setup, field mappings between agents, orchestration layout, credential storage, and defined error handling behaviour for every integration point. The FullSpec team uses this specification to build and configure the automation; it remains on file as the living record of how the system is wired together.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agents
Plaid
Bank feed connection and transaction pull
API key + OAuth 2.0 (Link token flow)
Development (sandbox) / Production
Agent 1
Xero
Ledger data fetch, reconciliation posting, period close record
OAuth 2.0 (PKCE flow)
Starter or above
Agents 1, 2, 3
Google Sheets
Exceptions review sheet: write unmatched items, read sign-off status
OAuth 2.0 (service account)
Free (Google Workspace or personal)
Agent 2
Slack
Finance team alert with reconciliation summary and exception count
OAuth 2.0 (Bot token)
Free or Pro
Agent 3
Gmail
Confirmation email to finance lead on period close
OAuth 2.0 (PKCE flow, delegated)
Any Google Workspace plan
Agent 3
Workflow automation platform
Orchestration, scheduling, credential store, retry logic
Internal (platform-managed)
Platform-dependent
All agents
Before you connect anything: all connections must be tested against sandbox or development credentials before any production credentials are entered. Plaid provides a full Sandbox environment. Xero provides a Demo Company tenant. Google, Slack, and Gmail connections must be tested with a non-production account or a dedicated test workspace. No live bank data or real ledger entries should be touched during integration testing.
02Per-tool integration detail
Plaid
Plaid is the bank feed connector for Agent 1 (Bank Feed Sync Agent). It retrieves new transactions from the connected bank account(s) using the Transactions product. The access token is generated once via the Link token flow and stored in the credential store; it is exchanged for a permanent access_token which is refreshed automatically. At approximately 400 transactions per month, Plaid API call volume is well within free-tier and Development-tier rate limits; no throttling logic is required at current volume.
Auth method
API key (client_id + secret) for server-side calls; OAuth 2.0 Link token flow for end-user bank authorisation. The resulting access_token is stored in the credential store and never hardcoded.
Required scopes
transactions:read (required for /transactions/get and /transactions/sync)
Webhook / trigger setup
Configure a webhook endpoint in the Plaid Dashboard under Webhooks. Subscribe to TRANSACTIONS_REMOVED and DEFAULT_UPDATE webhook types. The automation platform exposes an HTTPS endpoint; Plaid signs payloads using a JWT verification key retrieved from GET /webhook_verification_key/get. The orchestration layer validates the JWT before processing any payload.
Required configuration
PLAID_CLIENT_ID and PLAID_SECRET stored in credential store. PLAID_ENV set to 'sandbox' for testing, 'production' for live. PLAID_ACCESS_TOKEN per bank account stored in credential store (one entry per account). Cursor value for /transactions/sync stored in the workflow state to enable incremental pulls.
Rate limits
Development tier: 100 requests/minute. Production tier: varies by plan, minimum 100 requests/minute. At 400 transactions/month with daily polling, actual call volume is fewer than 10 API calls per day. No throttling needed at current volume.
Constraints
Some smaller US credit unions and regional banks are not on the Plaid network. A manual CSV upload fallback must be implemented for any account not connectable via Plaid. Plaid access tokens do not expire but may require re-authentication if the user revokes consent at the bank. Token refresh errors must trigger an alert to the finance lead.
// Input
GET /transactions/sync
{ access_token, cursor }
// Output
{
added: [ { transaction_id, account_id, date, amount, name, payment_channel, pending } ],
modified: [ ... ],
removed: [ { transaction_id } ],
next_cursor: string
}Xero
Xero is used by all three agents: Agent 1 fetches unreconciled ledger transactions, Agent 2 posts confirmed matched pairs and marks them reconciled, and Agent 3 posts the final period close record. The same OAuth 2.0 connection and access token serve all three agents via the shared credential store. Xero access tokens expire after 30 minutes and use a refresh token (valid 60 days) to obtain new ones; the orchestration layer must handle token refresh transparently before each API call.
Auth method
OAuth 2.0 with PKCE. Authorisation code flow initiated once; refresh_token stored in credential store and rotated automatically on each use.
Required scopes
openid profile email accounting.transactions accounting.transactions.read accounting.reports.read offline_access
Webhook / trigger setup
Xero webhooks are not used as a trigger in this build. Agent 1 is triggered by the scheduler; Agent 2 by completion of Agent 1; Agent 3 by a polling check on the exceptions sheet sign-off column. If Xero webhook delivery is added in a future iteration, subscribe to BankTransaction events via the Xero Developer Portal and validate the x-xero-signature HMAC-SHA256 header using the shared webhook key stored in the credential store.
Required configuration
XERO_CLIENT_ID and XERO_CLIENT_SECRET stored in credential store. XERO_TENANT_ID stored in credential store (retrieved once via GET /connections after initial auth). XERO_REFRESH_TOKEN stored in credential store and updated on every token refresh. Target bank account ID(s) and reconciliation account codes stored as named variables in the credential store, not hardcoded in workflow steps.
Rate limits
60 API calls/minute per app per tenant (standard OAuth apps). At current volume, Agent 1 makes 2 calls per run (GET BankTransactions, GET Accounts); Agent 2 makes up to 10 calls per run to POST reconciled pairs in batches of 50. Total is well below the limit. No throttling needed at current volume; implement a 1-second inter-call delay as a precaution.
Constraints
Xero's reconciliation API does not support all account types in all regions. Loan accounts and some foreign currency accounts may not be reconcilable via API. Confirm with the client which account types are in scope before build. The Xero API returns amounts as positive values for both debits and credits; sign convention must be normalised in the mapping layer.
// Input: fetch unreconciled bank transactions
GET /api.xro/2.0/BankTransactions
?where=IsReconciled=false AND Status=AUTHORISED
&DateFrom={run_start_date}&DateTo={run_end_date}
// Input: post reconciled pair
POST /api.xro/2.0/BankTransactions
{
BankTransactions: [
{ BankTransactionID: string, IsReconciled: true }
]
}
// Output: reconciliation confirmation
{ BankTransactions: [ { BankTransactionID, IsReconciled, UpdatedDateUTC } ] }Google Sheets
Google Sheets is used exclusively by Agent 2 (Matching and Exceptions Agent) to write unmatched and probable-match transactions to the exceptions tab, and to poll for bookkeeper sign-off before Agent 3 fires. The sheet must be pre-created from the FullSpec exceptions template and its Spreadsheet ID stored in the credential store. The service account is granted Editor access to the specific spreadsheet only.
Auth method
OAuth 2.0 via Google service account. A service account JSON key is generated in the Google Cloud Console, stored in the credential store, and used to obtain short-lived access tokens. The service account is added as an Editor on the exceptions spreadsheet.
Required scopes
https://www.googleapis.com/auth/spreadsheets (read and write to the exceptions sheet)
Webhook / trigger setup
Google Sheets does not support outbound webhooks natively. Agent 3 trigger is implemented as a polling loop: the orchestration layer reads the sign-off column (column H, row by row) every 5 minutes after Agent 2 completes. When all rows in the active exceptions range contain a value in column H (Resolved or Waived), the trigger condition for Agent 3 is met.
Required configuration
SHEETS_SERVICE_ACCOUNT_JSON stored in credential store. EXCEPTIONS_SPREADSHEET_ID stored in credential store. Sheet tab names must match exactly: 'Exceptions_Active' for current-cycle items, 'Exceptions_Archive' for resolved items. Column structure must follow the FullSpec exceptions template (columns A to H as defined in the field mapping section).
Rate limits
Google Sheets API v4: 300 requests/minute per project, 60 requests/minute per user. Agent 2 writes at most 400 rows per month in batches; Agent 3 polls at 5-minute intervals. Total daily call volume is under 30 requests. No throttling needed at current volume.
Constraints
The exceptions spreadsheet must not be renamed or have its tab structure changed by users during an active reconciliation cycle, as the automation references tabs and column indices by name. If the sheet is deleted or access is revoked, Agent 2 will fail and trigger a fallback alert.
// Input: write exceptions batch
POST /v4/spreadsheets/{SPREADSHEET_ID}/values/{range}:append
valueInputOption=USER_ENTERED
{
values: [
[ run_date, bank_ref, amount, description, suggested_category, match_type, context_notes, '' ]
]
}
// Input: poll sign-off column
GET /v4/spreadsheets/{SPREADSHEET_ID}/values/Exceptions_Active!H2:H
// Output
{ range, majorDimension, values: [ ['Resolved'] | ['Waived'] | [''] ] }Integration and API SpecPage 1 of 3
FS-DOC-05Technical
Slack
Slack is used by Agent 3 (Notifications and Close Agent) to post a structured reconciliation summary to the designated finance channel immediately after Agent 2 completes. The message includes the run date, total transactions processed, matched count, exception count, closing balance, and a hyperlink to the exceptions sheet. The bot token is stored in the credential store and must have the channel pre-joined.
Auth method
OAuth 2.0 Bot token (xoxb-). Installed to the Slack workspace via a Slack App configuration. Bot token stored in credential store as SLACK_BOT_TOKEN.
Required scopes
chat:write chat:write.public channels:read
Webhook / trigger setup
Slack is not used as a trigger. The automation posts outbound messages via the chat.postMessage method. No inbound webhook is configured. If a future iteration adds interactive buttons (for exception approval within Slack), an Interactivity endpoint will need to be registered in the Slack App manifest.
Required configuration
SLACK_BOT_TOKEN stored in credential store. SLACK_FINANCE_CHANNEL_ID stored in credential store (not the channel name, which can change). The Slack App must be invited to the target channel before the first run. Message block template defined as a named variable in the orchestration layer, not hardcoded per-step.
Rate limits
Slack Web API tier 3: 50 requests/minute for chat.postMessage. The automation posts one message per reconciliation run. No throttling needed at current volume.
Constraints
If the bot is removed from the channel or the token is revoked, Agent 3 will fail. This must be caught and re-routed to a Gmail fallback. The channel ID must be stored rather than the channel name to survive channel renames.
// Input: post summary message
POST https://slack.com/api/chat.postMessage
{
channel: SLACK_FINANCE_CHANNEL_ID,
blocks: [
{ type: 'header', text: { type: 'plain_text', text: 'Reconciliation complete: {run_date}' } },
{ type: 'section', fields: [
{ type: 'mrkdwn', text: '*Transactions processed:* {total_count}' },
{ type: 'mrkdwn', text: '*Matched:* {matched_count}' },
{ type: 'mrkdwn', text: '*Exceptions:* {exception_count}' },
{ type: 'mrkdwn', text: '*Closing balance:* {closing_balance}' }
] },
{ type: 'actions', elements: [
{ type: 'button', text: { text: 'View exceptions sheet' }, url: {sheet_url} }
] }
]
}
// Output
{ ok: true, channel: string, ts: string, message: { ... } }Gmail
Gmail is used by Agent 3 to send a plain-text confirmation email to the finance lead once the Xero period close record has been posted. The email confirms the period is closed, lists the matched transaction count and any waived exceptions, and attaches no files. The sending address is a designated service account or delegated Gmail address stored in the credential store.
Auth method
OAuth 2.0 with PKCE (delegated access). A service Gmail account is used as the sender; its refresh_token is stored in the credential store as GMAIL_REFRESH_TOKEN. The Google Cloud project must have the Gmail API enabled.
Required scopes
https://www.googleapis.com/auth/gmail.send
Webhook / trigger setup
Gmail is not used as a trigger in this build. Outbound only, using the gmail.users.messages.send method with a RFC 2822 MIME message encoded in base64url.
Required configuration
GMAIL_CLIENT_ID and GMAIL_CLIENT_SECRET stored in credential store. GMAIL_REFRESH_TOKEN stored in credential store. GMAIL_SENDER_ADDRESS stored in credential store. FINANCE_LEAD_EMAIL stored in credential store (not hardcoded). Email subject and body templates defined as named variables in the orchestration layer.
Rate limits
Gmail API: 250 quota units/user/second; gmail.users.messages.send costs 100 units. One email per reconciliation run. No throttling needed at current volume.
Constraints
If the delegated account's refresh token expires (tokens expire after 7 days if the app is in 'Testing' status in Google Cloud; promote to 'Production' to obtain long-lived tokens). The Gmail API does not support sending from arbitrary addresses; the sender must match the authenticated account. Plain-text email only; no HTML templates without additional scope.
// Input: send confirmation email
POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
{
raw: base64url_encode(
'From: {GMAIL_SENDER_ADDRESS}\r\n'
+ 'To: {FINANCE_LEAD_EMAIL}\r\n'
+ 'Subject: Reconciliation closed: {period_label}\r\n'
+ '\r\n'
+ 'Period: {period_label}\r\n'
+ 'Matched transactions: {matched_count}\r\n'
+ 'Exceptions resolved: {resolved_count}\r\n'
+ 'Exceptions waived: {waived_count}\r\n'
+ 'Xero period close record posted: {xero_record_id}\r\n'
)
}
// Output
{ id: string, threadId: string, labelIds: ['SENT'] }03Field mappings between tools
The following tables define the exact field mappings at each agent handoff. Source and destination field names are written as they appear in the respective API response or request payload. All intermediate normalisation (amount sign convention, date format to ISO 8601, currency code to ISO 4217) is performed inside the orchestration layer before the destination write.
Agent 1 handoff: Plaid to normalised dataset (consumed by Agent 2)
Source tool
Source field
Destination tool
Destination field
Plaid
transaction_id
Normalised dataset
bank_txn_id
Plaid
account_id
Normalised dataset
bank_account_id
Plaid
date
Normalised dataset
txn_date (ISO 8601)
Plaid
amount
Normalised dataset
bank_amount (negated: Plaid credits are positive, debits negative)
Plaid
name
Normalised dataset
bank_description
Plaid
payment_channel
Normalised dataset
bank_channel
Plaid
pending
Normalised dataset
is_pending (boolean)
Xero
BankTransactionID
Normalised dataset
ledger_txn_id
Xero
Date
Normalised dataset
ledger_date (ISO 8601)
Xero
Total
Normalised dataset
ledger_amount
Xero
Reference
Normalised dataset
ledger_reference
Xero
LineItems[0].Description
Normalised dataset
ledger_description
Xero
BankAccount.AccountID
Normalised dataset
ledger_account_id
Agent 2 handoff: normalised dataset to Google Sheets exceptions tab
Source tool
Source field
Destination tool
Destination field
Normalised dataset
run_date
Google Sheets col A
Run Date
Normalised dataset
bank_txn_id
Google Sheets col B
Bank Reference
Normalised dataset
bank_amount
Google Sheets col C
Amount
Normalised dataset
bank_description
Google Sheets col D
Bank Description
Normalised dataset
suggested_category
Google Sheets col E
Suggested Category
Normalised dataset
match_type
Google Sheets col F
Match Type (Unmatched / Probable)
Normalised dataset
context_notes
Google Sheets col G
Context Notes
(blank on write)
(empty string)
Google Sheets col H
Bookkeeper Sign-Off (Resolved / Waived)
Agent 2 handoff: normalised dataset to Xero (confirmed matched pairs)
Source tool
Source field
Destination tool
Destination field
Normalised dataset
ledger_txn_id
Xero PATCH BankTransactions
BankTransactionID
Normalised dataset
(literal true)
Xero PATCH BankTransactions
IsReconciled
Normalised dataset
run_date
Xero PATCH BankTransactions
UpdatedDateUTC (set by Xero server)
Agent 3 handoff: reconciliation summary to Slack and Gmail
Source tool
Source field
Destination tool
Destination field
Orchestration state
run_date
Slack block / Gmail body
run_date
Orchestration state
total_count
Slack block
total_count
Orchestration state
matched_count
Slack block / Gmail body
matched_count
Orchestration state
exception_count
Slack block
exception_count
Orchestration state
closing_balance
Slack block
closing_balance
Google Sheets
EXCEPTIONS_SPREADSHEET_ID (URL constructed)
Slack block
sheet_url
Orchestration state
resolved_count
Gmail body
resolved_count
Orchestration state
waived_count
Gmail body
waived_count
Xero POST response
BankTransactionID (period close)
Gmail body
xero_record_id
Orchestration state
period_label
Gmail subject + body
period_label
Integration and API SpecPage 2 of 3
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
Three discrete workflows, one per agent. Each workflow is a self-contained unit with its own trigger, steps, error handling, and logging. Workflows share a single credential store; credentials are referenced by named key, never duplicated across workflows.
Shared credential store
One central credential store scoped to this automation. All API keys, OAuth tokens, spreadsheet IDs, channel IDs, and email addresses are stored here. No credential value appears in any workflow step configuration directly.
Agent 1 trigger mechanism
Scheduled poll. The workflow automation platform fires Agent 1 on a cron schedule (daily at 06:00 UTC for routine runs; a manual trigger endpoint is also exposed for out-of-cycle runs requested by the bookkeeper). On each run, the Plaid /transactions/sync cursor is read from workflow state, used to fetch only new transactions, and the updated cursor is written back to state immediately after a successful response.
Agent 2 trigger mechanism
Event-based, chained from Agent 1. Agent 1 emits a completion event (an internal workflow trigger, not an external webhook) containing the normalised dataset payload. Agent 2 fires on receipt of this event. No polling is involved. If Agent 1 fails, Agent 2 does not fire and the failure is logged with a fallback alert sent to the finance lead.
Agent 3 trigger mechanism
Polling with sign-off detection. After Agent 2 writes exceptions to Google Sheets, Agent 3 enters a polling loop checking the sign-off column (Exceptions_Active!H2:H) every 5 minutes. When all non-empty rows in the column contain 'Resolved' or 'Waived', the trigger condition is met and Agent 3 proceeds. A maximum poll duration of 72 hours is enforced; if exceeded, Agent 3 sends an escalation alert to the finance lead and halts.
Webhook signature validation
Plaid webhook payloads are validated using the JWT verification key from GET /webhook_verification_key/get before any payload data is processed. Unvalidated payloads are rejected with a 401 response and logged.
Run state persistence
Each workflow run writes its state (cursor values, run_date, counts, step completion flags) to the platform's built-in state store keyed by run_id. This enables safe retry and idempotency checks.
Credential store contents (all entries below must be populated before any workflow is activated):
Credential store: all entries required before workflow activation
// Credential store: Bank Reconciliation Automation
// All values are stored as encrypted named secrets.
// Never hardcode any value from this list in a workflow step.
PLAID_CLIENT_ID = "<from Plaid Dashboard>"
PLAID_SECRET_SANDBOX = "<from Plaid Dashboard, Sandbox>"
PLAID_SECRET_PRODUCTION = "<from Plaid Dashboard, Production>"
PLAID_ENV = "sandbox" // switch to "production" at go-live
PLAID_ACCESS_TOKEN_ACC1 = "<generated via Link token flow, account 1>"
PLAID_ACCESS_TOKEN_ACC2 = "<generated via Link token flow, account 2 if applicable>"
PLAID_WEBHOOK_SECRET = "<JWT verification key from /webhook_verification_key/get>"
PLAID_SYNC_CURSOR_ACC1 = "<updated on each successful /transactions/sync call>"
XERO_CLIENT_ID = "<from Xero Developer Portal>"
XERO_CLIENT_SECRET = "<from Xero Developer Portal>"
XERO_TENANT_ID = "<retrieved via GET /connections after initial auth>"
XERO_REFRESH_TOKEN = "<rotated on each token refresh>"
XERO_BANK_ACCOUNT_ID = "<AccountID of target bank account in Xero>"
XERO_RECON_ACCOUNT_CODE = "<Xero account code for reconciliation, e.g. '090'>"
SHEETS_SERVICE_ACCOUNT_JSON = "<full JSON key for Google service account>"
EXCEPTIONS_SPREADSHEET_ID = "<Google Sheets file ID from URL>"
SLACK_BOT_TOKEN = "xoxb-<from Slack App configuration>"
SLACK_FINANCE_CHANNEL_ID = "<channel ID, e.g. C0123456789>"
GMAIL_CLIENT_ID = "<from Google Cloud Console>"
GMAIL_CLIENT_SECRET = "<from Google Cloud Console>"
GMAIL_REFRESH_TOKEN = "<from delegated Gmail account OAuth flow>"
GMAIL_SENDER_ADDRESS = "<sending Gmail address>"
FINANCE_LEAD_EMAIL = "<recipient email address for period close confirmation>"
05Error handling and retry logic
Unhandled exceptions must never fail silently. Every integration point must either complete successfully, retry with exponential backoff, or route to a defined fallback that notifies the finance lead. A silent failure in any step is treated as a build defect and must be fixed before go-live.
Integration
Scenario
Required behaviour
Plaid: /transactions/sync
HTTP 429 rate limit response
Retry after the Retry-After header value (default 60 seconds if header absent). Maximum 3 retries with exponential backoff (60s, 120s, 240s). If all retries fail, log the error, skip the run, and send a fallback alert email to the finance lead. Do not advance the cursor.
Plaid: /transactions/sync
ITEM_LOGIN_REQUIRED error (bank re-auth needed)
Halt Agent 1 immediately. Send an alert to the finance lead via Gmail with a Plaid Link re-authentication URL. Do not proceed with any downstream agents. Log the error with the affected account ID and timestamp.
Plaid: webhook delivery
Invalid JWT signature on inbound webhook payload
Reject the request with HTTP 401. Log the rejected payload metadata (timestamp, IP, webhook_type). Do not process the payload. Raise an amber alert to the FullSpec monitoring log.
Xero: GET BankTransactions
HTTP 401 (token expired)
Attempt token refresh using the stored XERO_REFRESH_TOKEN. If refresh succeeds, retry the original call once. If refresh fails (e.g. refresh token revoked), halt all agents, log the error, and send a Gmail alert to the finance lead with instructions to re-authorise the Xero connection.
Xero: PATCH BankTransactions (post matched pairs)
HTTP 400 (validation error on one or more transactions)
Log the failed BankTransactionIDs and the Xero error detail. Retry the batch excluding the failed IDs. Write the failed IDs to the exceptions sheet with match_type set to 'Post Error' and context_notes containing the Xero error message. Continue the run for all other matched pairs.
Xero: POST period close record
HTTP 503 or network timeout
Retry up to 3 times with exponential backoff (30s, 60s, 120s). If all retries fail, log the failure and send a Gmail alert to the finance lead. The period close must not be marked complete in the orchestration state until Xero confirms with a 200 response. Do not send the Gmail confirmation email until the Xero post succeeds.
Google Sheets: append exceptions
HTTP 403 (service account access revoked)
Halt Agent 2. Log the error. Send a Gmail alert to the finance lead listing all unwritten exceptions as plain text in the email body so the data is not lost. Flag the run as incomplete in orchestration state.
Google Sheets: poll sign-off column
Spreadsheet deleted or tab renamed during polling
Catch the 404 or invalid range error. Halt polling. Send a Gmail alert to the finance lead explaining that the exceptions sheet cannot be found and listing the expected spreadsheet ID and tab name. Do not proceed to Agent 3 until the sheet is restored and confirmed.
Google Sheets: sign-off polling timeout
72-hour poll limit reached without full sign-off
Halt polling. Send an escalation alert via Gmail to the finance lead and via Slack to the finance channel. Log the run as 'Stalled: awaiting sign-off'. The run remains in this state until manually cleared or exceptions are resolved and the automation is re-triggered.
Slack: chat.postMessage
HTTP 404 (bot not in channel) or invalid_auth
Catch the Slack error. Log it. Fall back immediately to sending the reconciliation summary via Gmail to the finance lead, using the same data fields. Mark the Slack step as 'Fallback used' in the run log. Do not halt Agent 3; continue to the Xero period close step.
Gmail: messages.send
HTTP 401 (refresh token expired or revoked)
Attempt one token refresh. If it fails, log the error and write the unsent email content (subject, body) to the orchestration run log so the content is not lost. Raise a platform-level alert to support@gofullspec.com. The period close step in Xero has already completed at this point; flag the Gmail step as failed in the run record.
All agents: unhandled exception
Any error type not covered by a specific rule above
Catch the exception at the workflow level. Log the full error payload (step name, error type, message, timestamp, run_id). Send a generic failure alert to support@gofullspec.com and to the finance lead via whichever channel (Slack or Gmail) is currently reachable. Halt the affected agent. Never allow an unhandled exception to pass silently.
Integration and API SpecPage 3 of 3