Developer Handover Pack
Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.
Developer Handover Pack
Contractor Management Automation
[YourCompany.com] · Operations Department · Prepared by FullSpec · [Today's Date]
This document gives the FullSpec build team everything needed to implement the Contractor Management automation end to end. It covers the current-state process map with bottlenecks identified, full agent specifications with IO contracts, a traced data-flow diagram, and the recommended build stack. The process owner and finance contact are responsible for granting API credentials and confirming Airtable schema before build begins. FullSpec handles all orchestration, integration wiring, error handling, and testing.
01Process snapshot
02Agent specifications
Triggered by a new Google Forms submission, this agent orchestrates the full onboarding chain without human intervention. It parses every field from the form response, creates a contractor record in Airtable with document status set to Pending, files submitted compliance documents into a named Google Drive folder, creates a matching supplier entry in Xero using the payment details provided on the form, and posts a formatted Slack notification to the operations channel confirming the contractor is active. This agent replaces the most labour-intensive cluster of manual steps and eliminates the document-chasing loop entirely by capturing all required data at submission time. Complexity is rated Moderate due to the four-tool integration chain and the conditional logic needed for document completeness checking.
// Input
GoogleForms.response -> {
contractor_name: string,
email: string,
phone: string,
abn_or_tax_id: string,
bank_bsb: string,
bank_account: string,
agreed_rate_usd: number,
engagement_start_date: date,
hiring_manager_name: string,
documents: [{ file_name: string, file_url: string, expiry_date: date|null }]
}
// Output
Airtable.contractor_record -> {
record_id: string,
contractor_name: string,
email: string,
agreed_rate_usd: number,
engagement_start_date: date,
document_status: 'Pending' | 'Complete',
drive_folder_url: string,
xero_supplier_id: string,
compliance_expiry_dates: [{ doc_type: string, expiry_date: date }]
}
GoogleDrive.folder -> named '{contractor_name}_{engagement_start_date}'
Xero.contact -> { contact_id: string, name: contractor_name, bank_account_details: {...} }
Slack.message -> { channel: '#operations', text: 'New contractor active: {contractor_name} from {engagement_start_date}' }- Google Forms must use fixed field names matching the Airtable schema exactly. Any form redesign prior to build requires a full field-map review before wiring begins.
- Airtable base must be confirmed on a paid tier (Plus or above) to allow API access. The base ID and table name must be provided before build starts.
- Google Drive folder structure must be agreed before build: recommended root is '/Contractors/{Year}/' with subfolders auto-named '{contractor_name}_{engagement_start_date}'. Confirm with the operations manager.
- Xero supplier creation requires OAuth 2.0 authorisation from a Xero admin (finance manager). The connection must be authorised in a Xero Standard or Premium plan; Starter plan does not support supplier creation via API.
- Xero rate limit is 60 calls/minute per organisation. At 6 onboardings/month this is well within threshold, but batch testing must not exceed this.
- Slack webhook must target a confirmed channel (e.g. '#operations'). The Slack app must have 'chat:write' and 'incoming-webhook' scopes.
- If a required form field is missing or empty, the agent must pause and post a Slack alert to the operations manager naming the missing fields. It must not create a partial Airtable record.
- Dedupe check: before creating an Airtable record, query the base for an existing row with a matching email field. If found, update the existing record rather than creating a duplicate.
- Confirm whether the Google Form collects document expiry dates as a date picker or a free-text field. Date picker is required for reliable parsing.
Monitors for incoming contractor invoices, either via a dedicated email inbox or a contractor-facing submission form, and routes them to the correct approver via Slack with a one-click approve or query response. Once the approver confirms, the agent validates the invoice amount against the agreed rate stored in the contractor's Airtable record. If the amount matches within the configured tolerance (default: exact match), it creates a Xero bill against the existing supplier record and updates the Airtable row to reflect payment-scheduled status. If the amount does not match, the agent flags the discrepancy to the finance manager and halts automated processing for that invoice. Complexity is rated Moderate due to the conditional approval routing, rate-matching logic, and two-way Slack interaction pattern.
// Input
InvoiceSource.email | InvoiceSource.form -> {
invoice_number: string,
contractor_name: string,
contractor_email: string,
invoice_amount_usd: number,
invoice_date: date,
invoice_period: string,
file_attachment_url: string
}
// Lookup
Airtable.contractor_record -> {
record_id: string,
agreed_rate_usd: number,
xero_supplier_id: string,
hiring_manager_slack_id: string
}
// On approval (amount matches)
Xero.bill -> {
bill_id: string,
contact_id: xero_supplier_id,
amount_usd: invoice_amount_usd,
due_date: next_payment_run_date,
status: 'AUTHORISED'
}
Airtable.contractor_record -> { payment_status: 'Scheduled', last_invoice_date: invoice_date }
// On discrepancy (amount does not match)
Slack.message -> { user: finance_manager_slack_id, text: 'Invoice discrepancy: {contractor_name} submitted ${invoice_amount_usd}, agreed rate is ${agreed_rate_usd}. Manual review required.' }
Airtable.contractor_record -> { payment_status: 'Flagged', flag_reason: 'Amount mismatch' }- Invoice detection method must be confirmed before build: monitored Gmail inbox (requires Gmail API with 'gmail.readonly' scope and a dedicated label or filter) or a Google Form. A Gmail-based approach requires the inbox address to be a Google Workspace account accessible via service account or OAuth.
- Contractor name matching between invoice and Airtable relies on consistent naming. Confirm a canonical naming convention (e.g. 'FirstName LastName') and enforce it on the submission side. Mismatches will route to manual review.
- The Slack approval interaction requires a Slack app with 'chat:write', 'commands', and 'im:write' scopes plus an interactive components endpoint (webhook URL) to receive button-click responses.
- Xero bill creation requires the supplier's contact_id from the Xero API, which must be stored in Airtable at onboarding time by the Contractor Onboarding Agent. Confirm this field is being written correctly before testing this agent.
- Rate-matching tolerance is set to exact match by default. If the business allows rounding or GST-inclusive amounts, a tolerance percentage (e.g. plus or minus 1%) must be agreed and documented before build.
- Confirm the next payment run date logic: fixed day of month, rolling 7-day cycle, or fortnightly. This determines how the Xero bill due date is calculated.
- The finance manager's Slack user ID must be stored in a configuration table or environment variable, not hardcoded.
Runs on a daily schedule and queries the Airtable contractor records for any compliance document with an expiry date falling within the next 30 days. For each match, it sends a Slack message to the operations manager and a separate notification to the contractor's email address, including the document type, the exact expiry date, the number of days remaining, and a re-upload link pointing to the onboarding form or a dedicated upload endpoint. The agent does not take any automated action on expired documents; it alerts only, and the operations manager owns the follow-up. Complexity is rated Simple because it uses only two tools and contains no branching approval logic.
// Input
Schedule.daily_trigger -> timestamp
Airtable.compliance_expiry_dates -> [
{
record_id: string,
contractor_name: string,
contractor_email: string,
doc_type: string,
expiry_date: date,
days_remaining: integer // computed: expiry_date - today
}
] where days_remaining <= 30
// Output (per matching record)
Slack.message -> {
channel: '#operations' | dm to ops_manager_slack_id,
text: 'Compliance alert: {contractor_name} — {doc_type} expires on {expiry_date} ({days_remaining} days remaining). Re-upload link: {upload_url}'
}
Email.send -> {
to: contractor_email,
subject: 'Action required: {doc_type} expiring in {days_remaining} days',
body: 'Please re-upload your {doc_type} before {expiry_date}. Link: {upload_url}'
}- Airtable must store compliance_expiry_dates as a Date field type, not a text field. If currently stored as text, a data migration step is required before this agent is built.
- The upload_url in alerts must be confirmed: either a static Google Forms link or a pre-filled form URL that includes the contractor's record_id as a hidden field so the re-upload maps back to the correct Airtable row automatically.
- Email sending for contractor-facing alerts requires a configured SMTP integration or a transactional email service (e.g. SendGrid or Gmail via OAuth). Confirm which service is available before build.
- If a contractor has multiple compliance documents, the agent sends one alert per expiring document, not a bundled message. Confirm this behaviour is acceptable or adjust to a grouped-digest format.
- Alert deduplication: the agent must not send the same alert to the same contractor more than once per day. Implement a 'last_alerted_date' field in Airtable and skip records already alerted today.
- Ops manager Slack ID and email address must be stored in a configuration record or environment variable.
03End-to-end data flow
// ─────────────────────────────────────────────────────────────────
// TRIGGER: Google Forms submission received
// ─────────────────────────────────────────────────────────────────
GoogleForms.onSubmit -> {
contractor_name,
email,
phone,
abn_or_tax_id,
bank_bsb,
bank_account,
agreed_rate_usd,
engagement_start_date,
hiring_manager_name,
documents[] // [{file_name, file_url, expiry_date}]
}
// ─────────────────────────────────────────────────────────────────
// AGENT 1: Contractor Onboarding Agent
// ─────────────────────────────────────────────────────────────────
// Step 1 — Dedupe check
Airtable.GET contractors WHERE email == form.email
-> if record exists: UPDATE existing record
-> if not found: proceed to CREATE
// Step 2 — Create Airtable record
Airtable.POST contractors -> {
contractor_name,
email,
phone,
abn_or_tax_id,
agreed_rate_usd,
engagement_start_date,
hiring_manager_name,
document_status: 'Pending',
payment_status: null,
xero_supplier_id: null, // populated at step 4
drive_folder_url: null, // populated at step 3
compliance_expiry_dates: [] // populated at step 3
}
-> record_id returned
// Step 3 — File documents to Google Drive
GoogleDrive.createFolder -> path: '/Contractors/{year}/{contractor_name}_{engagement_start_date}'
-> folder_id, folder_url
for each document in form.documents:
GoogleDrive.uploadFile(file_url) -> drive_file_id
Airtable.PATCH record_id -> {
drive_folder_url: folder_url,
document_status: 'Complete',
compliance_expiry_dates: [{doc_type, expiry_date}]
}
// Step 4 — Create Xero supplier
Xero.POST contacts -> {
name: contractor_name,
emailAddress: email,
phones: [{ PhoneType: 'DEFAULT', PhoneNumber: phone }],
bankAccountDetails: { sortCode: bank_bsb, accountNumber: bank_account },
isSupplier: true
}
-> xero_contact_id returned
Airtable.PATCH record_id -> { xero_supplier_id: xero_contact_id }
// Step 5 — Slack team notification
Slack.postMessage(channel='#operations') -> {
text: 'New contractor active: {contractor_name} | Start: {engagement_start_date} | Rate: ${agreed_rate_usd}/day | HM: {hiring_manager_name}'
}
// ─────────────────────────────────────────────────────────────────
// AGENT 2: Invoice Approval and Payment Agent
// ─────────────────────────────────────────────────────────────────
// Trigger: new invoice email or form submission
InvoiceSource.detect -> {
invoice_number,
contractor_name,
contractor_email,
invoice_amount_usd,
invoice_date,
invoice_period,
file_attachment_url
}
// Step 6 — Lookup contractor record
Airtable.GET contractors WHERE email == contractor_email
-> { record_id, agreed_rate_usd, xero_supplier_id, hiring_manager_slack_id }
// Step 7 — Route for approval via Slack
Slack.postMessage(user=hiring_manager_slack_id) -> {
text: 'Invoice received from {contractor_name}: ${invoice_amount_usd} for {invoice_period}.',
actions: [{ id: 'approve', text: 'Approve' }, { id: 'query', text: 'Query' }]
}
await Slack.interactiveResponse -> { action_id: 'approve' | 'query' }
// Step 8 — Rate match decision
if invoice_amount_usd == agreed_rate_usd:
// Step 9a — Create Xero bill
Xero.POST invoices -> {
type: 'ACCPAY',
contact: { contactID: xero_supplier_id },
lineItems: [{ description: invoice_period, unitAmount: invoice_amount_usd, quantity: 1 }],
date: invoice_date,
dueDate: next_payment_run_date,
status: 'AUTHORISED'
}
-> xero_bill_id returned
Airtable.PATCH record_id -> {
payment_status: 'Scheduled',
last_invoice_date: invoice_date,
last_invoice_amount: invoice_amount_usd
}
else:
// Step 9b — Flag discrepancy
Slack.postMessage(user=finance_manager_slack_id) -> {
text: 'Discrepancy: {contractor_name} invoiced ${invoice_amount_usd}, agreed rate ${agreed_rate_usd}. Manual review required.'
}
Airtable.PATCH record_id -> {
payment_status: 'Flagged',
flag_reason: 'Amount mismatch — invoice: ${invoice_amount_usd}, agreed: ${agreed_rate_usd}'
}
// HUMAN STEP: Finance Manager reviews and resolves
// ─────────────────────────────────────────────────────────────────
// AGENT 3: Compliance Monitor Agent (daily schedule)
// ─────────────────────────────────────────────────────────────────
Schedule.daily(08:00) -> trigger
Airtable.GET contractors
WHERE compliance_expiry_dates[].expiry_date <= (today + 30 days)
AND last_alerted_date != today
-> [{ record_id, contractor_name, contractor_email, doc_type, expiry_date, days_remaining }]
for each expiring_doc:
Slack.postMessage(channel='#operations' | user=ops_manager_slack_id) -> {
text: 'Alert: {contractor_name} — {doc_type} expires {expiry_date} ({days_remaining} days). Re-upload: {upload_url}'
}
Email.send(to=contractor_email) -> {
subject: '{doc_type} expiring in {days_remaining} days',
body: 'Please re-upload before {expiry_date}. Link: {upload_url}'
}
Airtable.PATCH record_id -> { last_alerted_date: today }
// ─────────────────────────────────────────────────────────────────
// END OF AUTOMATED FLOW
// Retained human steps: Step 12 (offboarding), discrepancy resolution
// ─────────────────────────────────────────────────────────────────04Recommended build stack
More documents for this process
Every document generated for Contractor Management.