FS-DOC-05Technical
Integration and API Spec
Purchase Order Management
[YourCompany.com] · Operations Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative technical reference for every integration point in the Purchase Order Management automation. It covers tool authentication, exact OAuth scopes, webhook configuration, field mappings between systems, orchestration layout, credential storage, and failure handling. The FullSpec team uses this specification to configure and test each connection. No credentials or integration IDs should be hardcoded in the workflow; all values belong in the shared credential store as defined in Section 04.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agents
Google Forms
Structured purchase request intake trigger
OAuth 2.0 (Google Workspace)
Google Workspace Starter (or personal Gmail for testing)
Agent 1
Slack
Approval routing, interactive buttons, requester notifications
OAuth 2.0 (Slack app install)
Slack Pro (interactive components require paid plan)
Agents 1, 2, 3
Xero
Purchase order creation and financial records
OAuth 2.0 (Xero connected app)
Xero Starter or above with API access enabled
Agent 3
Gmail
Supplier PO delivery and 48-hour follow-up email
OAuth 2.0 (Google Workspace, same identity as Forms)
Google Workspace Starter
Agent 3
Google Sheets
Procurement register and audit log
OAuth 2.0 (Google Workspace, same identity as Forms)
Google Workspace Starter
Agent 3
Workflow automation platform
Orchestration layer; hosts all three agent workflows, credential store, retry logic, and scheduling
Native (internal platform credential manager)
Paid tier with webhook support and multi-step workflows
All agents
Before you connect anything: provision sandbox or test credentials for every tool before touching production. For Google, use a test Workspace account. For Xero, use the Xero demo company. For Slack, create a dedicated test workspace. Only replace with production credentials after all end-to-end tests defined in the Test and QA Plan pass without error.
02Per-tool integration detail
Google Forms
Acts as the single intake trigger for the automation. A new form response fires a push notification or is detected via polling depending on the orchestration platform's Google Forms connector capability. The form schema must enforce required fields so the validation agent receives a consistent payload.
Auth method
OAuth 2.0. Authorise under the Google Workspace account that owns the form. Use the same service account or OAuth client for Forms, Sheets, and Gmail to minimise credential entries.
Required scopes
https://www.googleapis.com/auth/forms.responses.readonly | https://www.googleapis.com/auth/drive.readonly (needed to read associated form metadata)
Trigger setup
If the platform supports Google Forms webhook push: enable push notifications on the form response spreadsheet mirror via the Google Apps Script onFormSubmit trigger, which POSTs to the platform's inbound webhook URL. If polling: poll the Forms API endpoint GET /v1/forms/{formId}/responses on a 2-minute interval, filtering by lastSubmittedTime greater than the last successful run timestamp.
Required configuration
FORM_ID stored in credential store (not hardcoded). Form must contain fields: requester_name, requester_email, department_code, supplier_name, supplier_email, item_description, quantity, unit_cost, total_cost, budget_code. All fields set as required in the form builder.
Rate limits
Google Forms API: 500 requests/100 seconds per project, 1000 requests/100 seconds per user. At ~60 POs/month (approximately 2 submissions/day) throttling is not required. A 2-minute poll interval generates at most 720 requests/day, well within quota.
Constraints
Form must not allow edit-after-submit; enable 'collect email addresses' so requester_email is always present. Response editing must be disabled to prevent duplicate trigger events.
// Trigger payload (Google Forms response object)
response.responseId -> internal run_id
response.answers[requester_name].textAnswers.answers[0].value
response.answers[requester_email].textAnswers.answers[0].value
response.answers[department_code].textAnswers.answers[0].value
response.answers[supplier_name].textAnswers.answers[0].value
response.answers[supplier_email].textAnswers.answers[0].value
response.answers[item_description].textAnswers.answers[0].value
response.answers[quantity].textAnswers.answers[0].value
response.answers[unit_cost].textAnswers.answers[0].value
response.answers[total_cost].textAnswers.answers[0].value
response.answers[budget_code].textAnswers.answers[0].value
response.lastSubmittedTime -> submission_timestamp
Slack
Used by all three agents: Agent 1 sends missing-field prompts to the requester, Agent 2 sends the structured approval request with interactive buttons to the approver and a 24-hour reminder, and Agent 3 sends the final PO confirmation to the requester. Requires a custom Slack app installed in the workspace with interactive component support.
Auth method
OAuth 2.0 via Slack app install. The app must be installed to the workspace by a Slack admin. Store the Bot User OAuth Token (xoxb-...) in the credential store as SLACK_BOT_TOKEN. Do not use legacy Slack tokens.
Required scopes
chat:write | chat:write.public | users:read | users:read.email | channels:read | im:write | commands (if slash commands are added later) | incoming-webhook (for simple notifications if interactive messages are not needed in Agent 1 and 3)
Webhook / trigger setup
Approval responses (approve/reject buttons) use Slack interactive components. The Slack app must have an Interactivity Request URL configured pointing to the orchestration platform's inbound webhook endpoint. Validate the X-Slack-Signature header on every inbound payload using HMAC-SHA256 with the SLACK_SIGNING_SECRET stored in the credential store. Agent 2 sets a 24-hour wait step; if no interaction payload arrives, the reminder branch fires a second chat.postMessage to the same approver DM.
Required configuration
SLACK_BOT_TOKEN in credential store. SLACK_SIGNING_SECRET in credential store. SLACK_APPROVAL_CHANNEL_ID or approver user IDs resolved at runtime via users.lookupByEmail using the approver email derived from routing rules. Spend threshold routing rules stored as a configuration variable (APPROVAL_THRESHOLDS_JSON) in the credential store, not hardcoded.
Rate limits
Slack Web API: 1 request/second per method for Tier 3 methods (chat.postMessage). At 60 POs/month the automation sends at most 4 Slack messages per PO (missing-field prompt, approval request, optional reminder, requester notification) totalling ~240 messages/month. No throttling required. Add a 1-second delay between consecutive Slack calls within the same workflow run as a precaution.
Constraints
Interactive components require the Slack app to be on a Pro workspace or above. Block Kit payloads for approval messages must not exceed 50 blocks. The interactivity request URL must be HTTPS with a valid TLS certificate. Slack will retry failed deliveries three times with exponential backoff; the platform endpoint must return HTTP 200 within 3 seconds to acknowledge receipt.
// Outbound: missing-field prompt (Agent 1)
POST https://slack.com/api/chat.postMessage
{
channel: <requester_slack_user_id>,
text: 'Your purchase request is missing required fields.',
blocks: [ Section(missing_fields_list) ]
}
// Outbound: approval request (Agent 2)
POST https://slack.com/api/chat.postMessage
{
channel: <approver_slack_user_id>,
blocks: [ Header, Section(spend_summary), Actions(approve_btn, reject_btn) ]
}
// Inbound: interactive callback payload
payload.actions[0].action_id -> 'approve' | 'reject'
payload.user.id -> approver_slack_user_id
payload.message.ts -> original_message_timestamp
payload.view.state -> optional rejection reason
// Outbound: requester notification (Agent 3)
POST https://slack.com/api/chat.postMessage
{
channel: <requester_slack_user_id>,
text: 'Your PO {po_number} has been raised and sent to {supplier_name}.',
blocks: [ Section(po_number, supplier, expected_delivery) ]
}Xero
Used exclusively by Agent 3 to create a draft purchase order on approval. Requires a Xero connected app with OAuth 2.0. The PO is created with line items derived from the validated form data and linked to the correct account code using the budget_code field. Xero plan must support API access; confirm this before build start.
Auth method
OAuth 2.0 (PKCE flow for connected app). Register a connected app in Xero developer portal. Store XERO_CLIENT_ID, XERO_CLIENT_SECRET, and XERO_TENANT_ID in the credential store. The access token expires after 30 minutes; the platform must handle automatic refresh using the refresh token stored as XERO_REFRESH_TOKEN.
Required scopes
openid | profile | email | offline_access | accounting.transactions | accounting.contacts.read | accounting.settings.read
Webhook / trigger setup
Xero is a write target only in this automation; no inbound webhook is required. Supplier confirmation monitoring is handled via Gmail polling (see Gmail block), not Xero webhooks.
Required configuration
XERO_CLIENT_ID, XERO_CLIENT_SECRET, XERO_TENANT_ID, XERO_REFRESH_TOKEN all in credential store. Account codes for each department budget_code must be mapped in a configuration lookup table (XERO_ACCOUNT_CODE_MAP) stored in the credential store as JSON. PO number sequence is managed by Xero's auto-increment; do not maintain a separate counter. The PO is created in DRAFT status; do not auto-submit for approval inside Xero as the Slack approval serves that function.
Rate limits
Xero API: 60 calls/minute and 5000 calls/day per connected app (Standard plan). At 60 POs/month the automation makes at most 2 Xero API calls per PO (create PO, read back PO number), totalling ~120 calls/month. No throttling required. If Xero returns HTTP 429, implement a 60-second wait and single retry before escalating to the error handler.
Constraints
Xero Starter plan limits the number of invoices and POs per month (20 on Starter). Confirm the business is on Xero Growing or above before go-live. The connected app must be authorised by a Xero user with Purchase Orders - Edit permission. Refresh token rotation: store the new refresh token returned on each token refresh immediately; the old token is immediately invalidated.
// POST https://api.xero.com/api.xro/2.0/PurchaseOrders
{
'PurchaseOrders': [{
'Contact': { 'Name': supplier_name },
'Date': submission_date (ISO 8601),
'DeliveryDate': null,
'LineItems': [{
'Description': item_description,
'Quantity': quantity,
'UnitAmount': unit_cost,
'AccountCode': XERO_ACCOUNT_CODE_MAP[budget_code]
}],
'Status': 'DRAFT',
'Reference': run_id
}]
}
// Response: extract
PurchaseOrders[0].PurchaseOrderID -> xero_po_id
PurchaseOrders[0].PurchaseOrderNumber -> po_numberGmail
Used by Agent 3 to send the purchase order PDF to the supplier and to monitor for a supplier confirmation reply. If no reply arrives within 48 hours, a follow-up email is sent automatically. Gmail is accessed via the same Google OAuth client as Forms and Sheets.
Auth method
OAuth 2.0 (Google Workspace). Reuse the same OAuth client as Google Forms. Store GMAIL_SENDER_ADDRESS in the credential store. The sending address must match the authorised OAuth identity.
Required scopes
https://www.googleapis.com/auth/gmail.send | https://www.googleapis.com/auth/gmail.readonly (required for supplier reply monitoring)
Webhook / trigger setup
Supplier reply monitoring: use Gmail API push notifications (Gmail watch) on the INBOX label. Register the watch via POST /gmail/v1/users/me/watch with a Pub/Sub topic. The platform subscribes to the Pub/Sub topic and receives a push notification when a new message matches the thread. Alternatively, poll GET /gmail/v1/users/me/messages?q=in:inbox+from:{supplier_email}+after:{send_timestamp} on a 30-minute interval if the platform does not support Pub/Sub. The 48-hour follow-up is implemented as a scheduled delay step in the Agent 3 workflow, cancelled if a reply is detected before it fires.
Required configuration
GMAIL_SENDER_ADDRESS in credential store. PO email template subject line and body stored as GMAIL_PO_TEMPLATE_SUBJECT and GMAIL_PO_TEMPLATE_BODY in the credential store with placeholders: {{supplier_name}}, {{po_number}}, {{item_description}}, {{total_cost}}, {{requester_name}}. The PO PDF is generated by the orchestration platform from Xero response data and attached inline; no external PDF service is required unless the platform cannot render PDFs natively.
Rate limits
Gmail API: 250 quota units/user/second; sending an email costs 100 units. At 60 POs/month plus potential follow-ups the volume is negligible. Daily sending limit is 2000 messages/day for Google Workspace. No throttling required.
Constraints
Gmail watch subscriptions expire after 7 days and must be renewed. Add a scheduled daily renewal step to the orchestration platform. Attachment size limit is 25 MB; PO PDFs will typically be under 1 MB. Do not send the PO from a personal Gmail address in production; use the Google Workspace address to ensure consistent sender reputation and audit trail.
// Outbound: supplier PO email
POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
{
raw: base64url(
To: supplier_email,
From: GMAIL_SENDER_ADDRESS,
Subject: GMAIL_PO_TEMPLATE_SUBJECT (populated),
Body: GMAIL_PO_TEMPLATE_BODY (populated),
Attachment: po_pdf (base64 encoded)
)
}
// Store returned message.id as gmail_sent_message_id
// Inbound: reply detection
GET /gmail/v1/users/me/threads/{threadId}
messages[-1].payload.headers['From'] -> supplier_reply_from
messages[-1].internalDate -> supplier_reply_timestamp
// If reply detected: cancel 48-hour follow-up, log confirmed_at to SheetsGoogle Sheets
Used by Agent 3 as the procurement register and audit log. Each approved PO appends a new row with all key fields. Finance and ops teams read this sheet directly; the automation only writes to it and never deletes rows.
Auth method
OAuth 2.0 (Google Workspace). Reuse the same OAuth client as Forms and Gmail. Store SHEETS_SPREADSHEET_ID and SHEETS_REGISTER_SHEET_NAME in the credential store.
Required scopes
https://www.googleapis.com/auth/spreadsheets
Webhook / trigger setup
Google Sheets is a write target only. No trigger or webhook required from Sheets. Do not use Sheets as a trigger source; the Google Forms submission is the authoritative trigger.
Required configuration
SHEETS_SPREADSHEET_ID and SHEETS_REGISTER_SHEET_NAME (e.g. 'Procurement Register') in credential store. The register sheet must have the following column headers in row 1, in this exact order: run_id | submission_timestamp | requester_name | requester_email | department_code | supplier_name | supplier_email | item_description | quantity | unit_cost | total_cost | budget_code | approver_name | approver_slack_id | approved_at | po_number | xero_po_id | gmail_sent_message_id | supplier_confirmed | confirmed_at | follow_up_sent | notes. The automation appends to the next empty row using the Sheets API append endpoint.
Rate limits
Google Sheets API: 300 requests/minute/project, 60 requests/minute/user. At 60 POs/month the automation makes 1-2 Sheets API calls per PO. No throttling required.
Constraints
Do not restructure column order after go-live without updating the field mapping in the workflow. Protect row 1 (headers) using Google Sheets range protection to prevent accidental deletion. The sheet must not be set to 'View only' for the OAuth service account; it must have Editor access.
// POST https://sheets.googleapis.com/v4/spreadsheets/{SHEETS_SPREADSHEET_ID}/values/{SHEETS_REGISTER_SHEET_NAME}!A:V:append
valueInputOption: 'USER_ENTERED'
insertDataOption: 'INSERT_ROWS'
values: [[
run_id, submission_timestamp, requester_name, requester_email,
department_code, supplier_name, supplier_email, item_description,
quantity, unit_cost, total_cost, budget_code,
approver_name, approver_slack_id, approved_at,
po_number, xero_po_id, gmail_sent_message_id,
supplier_confirmed (FALSE), confirmed_at (''),
follow_up_sent (FALSE), notes ('')
]]
// Update on supplier confirmation:
PATCH /values/{SHEETS_REGISTER_SHEET_NAME}!S{row}:U{row}
values: [[ TRUE, confirmed_at_timestamp, FALSE ]]Integration and API SpecPage 1 of 3
FS-DOC-05Technical
03Field mappings between tools
The tables below define the exact field mappings at each agent handoff. Use these as the canonical reference when configuring data transformation steps in the orchestration platform. All field names are shown in monospace as they appear in the source API response or internal data object.
Agent 1 handoff: Google Forms response to Intake and Validation Agent internal record
Source tool
Source field
Destination tool
Destination field
Google Forms
response.responseId
Internal record
run_id
Google Forms
response.lastSubmittedTime
Internal record
submission_timestamp
Google Forms
response.answers[requester_name].textAnswers.answers[0].value
Internal record
requester_name
Google Forms
response.answers[requester_email].textAnswers.answers[0].value
Internal record
requester_email
Google Forms
response.answers[department_code].textAnswers.answers[0].value
Internal record
department_code
Google Forms
response.answers[supplier_name].textAnswers.answers[0].value
Internal record
supplier_name
Google Forms
response.answers[supplier_email].textAnswers.answers[0].value
Internal record
supplier_email
Google Forms
response.answers[item_description].textAnswers.answers[0].value
Internal record
item_description
Google Forms
response.answers[quantity].textAnswers.answers[0].value
Internal record
quantity (integer)
Google Forms
response.answers[unit_cost].textAnswers.answers[0].value
Internal record
unit_cost (float)
Google Forms
response.answers[total_cost].textAnswers.answers[0].value
Internal record
total_cost (float)
Google Forms
response.answers[budget_code].textAnswers.answers[0].value
Internal record
budget_code
Agent 1 to Agent 2 handoff: validated internal record to Approval Routing Agent (Slack)
Source tool
Source field
Destination tool
Destination field
Internal record
run_id
Slack Block Kit payload
metadata.run_id (in private_metadata)
Internal record
requester_name
Slack Block Kit payload
blocks[Section].text (spend summary)
Internal record
department_code
Approval routing logic
approver lookup key (APPROVAL_THRESHOLDS_JSON)
Internal record
total_cost
Approval routing logic
threshold comparison value
Internal record
supplier_name
Slack Block Kit payload
blocks[Section].fields[supplier]
Internal record
item_description
Slack Block Kit payload
blocks[Section].fields[item]
Internal record
total_cost
Slack Block Kit payload
blocks[Section].fields[amount]
Internal record
budget_code
Slack Block Kit payload
blocks[Section].fields[budget_code]
Slack interactive callback to Agent 3 handoff: approval decision to PO Creation and Notification Agent
Source tool
Source field
Destination tool
Destination field
Slack callback payload
payload.actions[0].action_id
Agent 3 branch logic
approval_decision ('approve' | 'reject')
Slack callback payload
payload.user.id
Internal record
approver_slack_id
Slack callback payload
payload.user.name
Internal record
approver_name
Slack callback payload
payload.message.ts
Internal record
approved_at (epoch converted to ISO 8601)
Slack callback payload
payload.message.metadata.run_id
Internal record
run_id (used to retrieve original request data)
Agent 3 internal record to Xero
Source tool
Source field
Destination tool
Destination field
Internal record
supplier_name
Xero PurchaseOrders API
PurchaseOrders[0].Contact.Name
Internal record
submission_timestamp
Xero PurchaseOrders API
PurchaseOrders[0].Date (ISO 8601 date part)
Internal record
item_description
Xero PurchaseOrders API
PurchaseOrders[0].LineItems[0].Description
Internal record
quantity
Xero PurchaseOrders API
PurchaseOrders[0].LineItems[0].Quantity
Internal record
unit_cost
Xero PurchaseOrders API
PurchaseOrders[0].LineItems[0].UnitAmount
Internal record
budget_code
Xero PurchaseOrders API
PurchaseOrders[0].LineItems[0].AccountCode (via XERO_ACCOUNT_CODE_MAP lookup)
Internal record
run_id
Xero PurchaseOrders API
PurchaseOrders[0].Reference
Xero API response
PurchaseOrders[0].PurchaseOrderNumber
Internal record
po_number
Xero API response
PurchaseOrders[0].PurchaseOrderID
Internal record
xero_po_id
Agent 3 internal record to Gmail
Source tool
Source field
Destination tool
Destination field
Internal record
supplier_email
Gmail send API
To header
Internal record
supplier_name
Gmail template
{{supplier_name}} placeholder
Internal record
po_number
Gmail template
{{po_number}} placeholder
Internal record
item_description
Gmail template
{{item_description}} placeholder
Internal record
total_cost
Gmail template
{{total_cost}} placeholder
Internal record
requester_name
Gmail template
{{requester_name}} placeholder
Gmail API response
id
Internal record
gmail_sent_message_id
Agent 3 internal record to Google Sheets
Source tool
Source field
Destination tool
Destination field
Internal record
run_id
Google Sheets row
Column A: run_id
Internal record
submission_timestamp
Google Sheets row
Column B: submission_timestamp
Internal record
requester_name
Google Sheets row
Column C: requester_name
Internal record
requester_email
Google Sheets row
Column D: requester_email
Internal record
department_code
Google Sheets row
Column E: department_code
Internal record
supplier_name
Google Sheets row
Column F: supplier_name
Internal record
supplier_email
Google Sheets row
Column G: supplier_email
Internal record
item_description
Google Sheets row
Column H: item_description
Internal record
quantity
Google Sheets row
Column I: quantity
Internal record
unit_cost
Google Sheets row
Column J: unit_cost
Internal record
total_cost
Google Sheets row
Column K: total_cost
Internal record
budget_code
Google Sheets row
Column L: budget_code
Internal record
approver_name
Google Sheets row
Column M: approver_name
Internal record
approver_slack_id
Google Sheets row
Column N: approver_slack_id
Internal record
approved_at
Google Sheets row
Column O: approved_at
Internal record
po_number
Google Sheets row
Column P: po_number
Internal record
xero_po_id
Google Sheets row
Column Q: xero_po_id
Internal record
gmail_sent_message_id
Google Sheets row
Column R: gmail_sent_message_id
Integration and API SpecPage 2 of 3
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
Three separate workflows, one per agent: (1) Intake and Validation Agent workflow, (2) Approval Routing Agent workflow, (3) PO Creation and Notification Agent workflow. All three share a single credential store within the automation platform. Workflows are linked by passing the run_id and the internal request data object between them via the platform's built-in data passing mechanism or a shared intermediate storage key. No workflow hardcodes a credential value.
Agent 1 trigger mechanism
Webhook (preferred): Google Forms onFormSubmit Apps Script trigger POSTs to the Intake Agent workflow's inbound webhook URL. Polling fallback: poll GET /v1/forms/{FORM_ID}/responses every 2 minutes, compare lastSubmittedTime to the last stored run timestamp. Signature validation: not natively supported by Google Forms push; validate the source IP range or use a shared secret query parameter appended to the webhook URL and compared on receipt.
Agent 2 trigger mechanism
Triggered programmatically by Agent 1 on successful validation: Agent 1 calls the Approval Routing Agent workflow via an internal platform trigger or HTTP call with the validated request payload. Not a scheduled or polled trigger. Inbound Slack interactive callback: the Slack app's Interactivity Request URL points to the Agent 2 workflow's inbound webhook. Validate X-Slack-Signature using HMAC-SHA256 and SLACK_SIGNING_SECRET on every inbound payload. Reject any payload where the timestamp in the X-Slack-Request-Timestamp header is more than 5 minutes old.
Agent 3 trigger mechanism
Triggered programmatically by Agent 2 when payload.actions[0].action_id equals 'approve'. Agent 2 passes the full internal record object plus approved_at and approver fields. The supplier reply monitoring step is a scheduled delay of 48 hours within the Agent 3 workflow; if the Gmail polling step detects a reply before the delay expires, the follow-up branch is skipped and the Sheets row is updated. Gmail watch push (preferred) or 30-minute Gmail poll (fallback) as described in the Gmail per-tool block.
Credential store structure
See code block below. All secrets are stored in the platform's encrypted credential store. No secret appears in a workflow node's inline configuration fields.
Credential store contents (all values encrypted at rest; do not hardcode in workflow nodes)
# Google (shared OAuth client for Forms, Gmail, Sheets)
GOOGLE_OAUTH_CLIENT_ID = '<from Google Cloud Console>'
GOOGLE_OAUTH_CLIENT_SECRET = '<from Google Cloud Console>'
GOOGLE_OAUTH_REFRESH_TOKEN = '<generated during OAuth flow>'
GMAIL_SENDER_ADDRESS = 'ops@[YourCompany.com]'
FORM_ID = '<Google Forms form ID string>'
SHEETS_SPREADSHEET_ID = '<Google Sheets spreadsheet ID string>'
SHEETS_REGISTER_SHEET_NAME = 'Procurement Register'
# Slack
SLACK_BOT_TOKEN = 'xoxb-<workspace-token>'
SLACK_SIGNING_SECRET = '<from Slack app credentials page>'
# Xero
XERO_CLIENT_ID = '<from Xero developer portal>'
XERO_CLIENT_SECRET = '<from Xero developer portal>'
XERO_TENANT_ID = '<Xero organisation tenant ID>'
XERO_REFRESH_TOKEN = '<stored and updated on each token refresh>'
# Configuration variables (not secrets but must not be hardcoded in nodes)
APPROVAL_THRESHOLDS_JSON = '{
"default": { "under_500": "<approver_email_A>", "500_to_2000": "<approver_email_B>", "over_2000": "<approver_email_C>" },
"department_overrides": { "IT": { "under_500": "<approver_email_D>" } }
}'
XERO_ACCOUNT_CODE_MAP = '{
"OPS": "400", "IT": "420", "MKTG": "430", "HR": "450", "FINANCE": "460"
}'
GMAIL_PO_TEMPLATE_SUBJECT = 'Purchase Order {{po_number}} from [YourCompany.com]'
GMAIL_PO_TEMPLATE_BODY = '<path to template file or inline template string>'
# Webhook endpoints (stored for reference; not secrets)
AGENT1_INBOUND_WEBHOOK_URL = 'https://<platform-host>/webhook/<agent1-id>'
AGENT2_SLACK_CALLBACK_URL = 'https://<platform-host>/webhook/<agent2-id>'
# Gmail watch (refresh daily)
GMAIL_PUBSUB_TOPIC = 'projects/<project-id>/topics/gmail-po-replies'Token rotation for Xero: every time the Xero access token is refreshed, the new refresh token returned in the response must immediately overwrite XERO_REFRESH_TOKEN in the credential store. Xero invalidates the old refresh token on rotation. A stale refresh token will cause all Xero calls to fail until manual re-authorisation. Build an automatic overwrite step into every Xero token-refresh sequence.
05Error handling and retry logic
Every integration point has a defined failure behaviour. Unhandled exceptions must never fail silently. If a retry sequence is exhausted without success, the platform must log the error with full context, alert the ops team via a fallback Slack message (or email to GMAIL_SENDER_ADDRESS if Slack itself is unavailable), and halt the affected run without retrying indefinitely.
Integration
Scenario
Required behaviour
Google Forms (trigger)
Form submission webhook not received / missed event
Run the 2-minute polling fallback. Compare lastSubmittedTime to last-processed timestamp. If a gap of more than 10 minutes is detected with no processed run, raise an amber alert to the ops Slack channel. Log the raw response ID for audit.
Google Forms (trigger)
Forms API returns HTTP 429 (quota exceeded)
Pause polling for 60 seconds. Retry with exponential backoff: 60s, 120s, 240s. After 3 failed retries, send a platform alert email to GMAIL_SENDER_ADDRESS and halt the polling loop until manually resumed.
Slack (Agent 1 missing-field prompt)
chat.postMessage returns non-200 or rate-limit 429
Retry after 1 second. If second attempt fails, log the missing-field notification failure against the run_id in the platform error log. Do not block the validation result from being stored; the run pauses awaiting a manual re-send of the prompt.
Slack (Agent 2 approval request)
Approver does not interact within 24 hours
Fire the single automated reminder message to the same approver DM. Log reminder_sent_at. If no interaction arrives within a further 24 hours (48 hours total), escalate: post an alert to the ops channel tagging the ops manager, and log status as 'approval_timeout'. Do not auto-approve or auto-reject.
Slack (Agent 2 callback)
Incoming interactive payload fails HMAC-SHA256 signature validation
Reject the request immediately with HTTP 200 (to prevent Slack retries) but log the payload and source IP as a security event. Do not process the action. Alert the FullSpec team at support@gofullspec.com.
Slack (Agent 2 callback)
Payload received but run_id not found in active runs
Return HTTP 200 to acknowledge. Log the orphaned payload. Post a DM to the approver explaining the approval could not be matched and asking them to contact the ops team. Do not create a PO.
Xero (PO creation)
HTTP 401 Unauthorized (expired or revoked token)
Attempt token refresh using XERO_REFRESH_TOKEN. Store the new refresh token. Retry the PO creation request once. If the refresh also fails, halt the run, log the error with run_id, and send a fallback email to GMAIL_SENDER_ADDRESS with the full request details so PO creation can be completed manually.
Xero (PO creation)
HTTP 429 (rate limit) or HTTP 503 (service unavailable)
Wait 60 seconds. Retry up to 3 times with 60-second intervals. If all retries fail, halt the run and alert the ops team with full request data for manual PO entry. Log the Xero error response body for diagnosis.
Xero (PO creation)
Validation error: unrecognised account code (budget_code not in XERO_ACCOUNT_CODE_MAP)
Halt PO creation. Post a Slack alert to the ops channel listing the unrecognised budget_code and the run_id. Do not proceed to Gmail send or Sheets log until the mapping is corrected and the run is manually retried.
Gmail (supplier send)
HTTP 400 invalid recipient address or send failure
Log the failed send against the run_id. Post a Slack alert to the ops channel with the supplier_email and po_number so the ops manager can send manually. Update the Sheets row notes column with 'gmail_send_failed'. Do not retry automatically with an invalid address.
Gmail (48-hour follow-up / reply monitoring)
Gmail watch subscription expired (watches expire after 7 days)
A scheduled daily renewal step must re-register the watch via POST /watch before expiry. If the renewal fails, fall back to 30-minute polling for the remainder of the day and send an alert to the ops Slack channel. Log watch renewal failures for diagnosis.
Google Sheets (register append)
Sheets API returns HTTP 403 (permission denied) or HTTP 429
For 403: halt the run and alert the ops manager; the OAuth account likely lost Editor access to the sheet. For 429: retry after 30 seconds up to 3 times with exponential backoff (30s, 60s, 120s). If all retries fail, log the full row data to the platform error log so it can be manually appended. Never discard a Sheets write failure silently.
Fallback contact for all unresolved integration failures: support@gofullspec.com. Include the run_id, the tool name, the HTTP status code, and the full error response body in any support request. Do not share raw credential values in support communications.
Integration and API SpecPage 3 of 3