Back to Asset & Equipment Tracking

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

Asset and Equipment Tracking

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

This document is the primary technical reference for the FullSpec team building the Asset and Equipment Tracking automation. It covers the full current-state process, all three agent specifications, the end-to-end data flow, and the recommended build stack. The build replaces nine of eleven manual steps, leaving only physical QR label attachment and manager audit sign-off as human actions. Every constraint, field name, tool tier, and integration behaviour described here is binding for the build.

01Process snapshot

Step
Name
Description
1
Receive New Asset
Staff member notes make, model, serial number, and purchase date on a paper intake form or sticky note. Coordinator collects and processes the form. Time cost: 15 min.
2
Add Asset to Register
Coordinator opens Airtable and manually enters all asset details, assigning a unique asset ID by hand. Time cost: 10 min.
3
Print and Attach Asset Label
QR code or barcode label is generated manually via browser tool, printed, and physically attached to the asset. Time cost: 10 min.
4
Assign Asset to Staff Member
Coordinator updates the Airtable row with assignee name and sends a brief email notification via Google Workspace. Time cost: 10 min.
5
Log Maintenance Schedule
Maintenance intervals are entered manually into a shared Google Calendar or noted in the register. No automated check exists. Time cost: 8 min. BOTTLENECK: calendar entries are missed or not created at all.
6
Send Maintenance Reminder
Coordinator manually sends a Slack message or email to the responsible person when a maintenance date approaches. Time cost: 5 min.
7
Record Asset Movement or Loan
When an asset is loaned or moved, the coordinator updates the Airtable row and notes the return date if known. Frequently skipped under workload pressure. Time cost: 10 min. BOTTLENECK: register goes stale when this step is skipped.
8
Chase Overdue Returns
Coordinator manually reviews loan records and sends individual Slack follow-ups to anyone who has not returned an asset by its due date. Time cost: 12 min. BOTTLENECK: entirely dependent on coordinator availability.
9
Conduct Periodic Audit
Operations Manager exports or prints the register and physically checks each asset against the list every quarter. Time cost: 90 min. BOTTLENECK: longest single task; blocks other work for half a day.
10
Update Depreciation in Accounts
Finance Officer manually transfers asset values and purchase dates into Xero to post depreciation entries. Time cost: 20 min.
11
Record Disposal or Write-Off
Coordinator marks the asset inactive in Airtable and notifies Finance to write it off in Xero. Time cost: 15 min.
Time cost summary: total manual time per cycle is approximately 205 minutes (3 hours 25 min) across all 11 steps. At approximately 60 asset events per month, the process costs roughly 6.5 hours per week in coordinator and manager time. The automation replaces steps 1, 2, 3, 4 (Asset Intake Agent), steps 5, 6, 8 (Maintenance Scheduler Agent), and steps 10, 11 (Finance Sync Agent). Steps 7 (physical QR label attachment, reduced to 3 min) and 9 (manager audit review, reduced to 10 min) remain as human actions. Two manual steps are retained by design.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Asset Intake Agent

Validates every new or updated asset record in Airtable for field completeness, auto-generates a unique sequential asset ID if one is absent, calls the QR Code Generator API to produce a printable label URL, writes that URL back to the Airtable record, and sends a Slack assignment notification to the named assignee. This agent replaces the four highest-frequency manual steps, reducing new-asset intake from 35 minutes to under 2 minutes. It must handle both net-new rows and material updates (for example, a reassignment or a serial-number correction) without creating duplicate asset IDs.

Trigger
New row created OR existing row updated in Airtable asset register (watch for field changes: asset_name, serial_number, assigned_to, purchase_date, purchase_cost)
Tools
Airtable, QR Code Generator API, Slack
Replaces steps
1, 2, 3, 4
Estimated build
10 hours, Moderate complexity
// Input
airtable.record_id         // internal Airtable row ID
airtable.asset_name        // string, required
airtable.serial_number     // string, required
airtable.purchase_date     // ISO 8601 date, required
airtable.purchase_cost     // number (USD), required
airtable.assigned_to       // Airtable linked record -> staff_member.slack_user_id
airtable.asset_category    // select field: IT Hardware | Tools | Furniture | Vehicle | Other
airtable.site_location     // string, optional

// Validation rules
// asset_name, serial_number, purchase_date, purchase_cost must be non-null
// If asset_id is null -> generate: ASSET-{YYYY}-{zero-padded sequence, 4 digits}
// If asset_id already exists -> skip ID generation, proceed to QR step
// Duplicate check: query Airtable for existing serial_number before write

// Output
airtable.asset_id          // written back to record, e.g. ASSET-2025-0047
airtable.qr_code_url       // written back to record, public URL from QR API
airtable.intake_status     // set to 'Confirmed'
slack.message_ts           // Slack message timestamp, stored for audit trail

// Slack notification payload
slack.channel              // resolved from assigned_to.slack_user_id (DM)
slack.text                 // 'Asset {asset_name} ({asset_id}) assigned to you. QR label: {qr_code_url}. Please check care instructions in the register.'
  • Airtable plan must be Pro or above for automation triggers via webhook; confirm the base shares API access with the automation platform service account before build.
  • QR Code Generator API: use the QR Server free tier (api.qrserver.com/v1/create-qr-code/) or the GoQR.me endpoint. The URL embedded in the QR code must be the permanent Airtable record URL, not a short-lived share link. Store the full image URL, not the base64 blob.
  • Asset ID format: ASSET-{YYYY}-{NNNN} where NNNN is a zero-padded global counter. The counter must be read from a dedicated Airtable single-row config table (table name: asset_id_counter, field: last_used_id) and incremented atomically before write to prevent race conditions on concurrent events.
  • Dedupe logic: before creating a new record or ID, query Airtable filtering by serial_number. If a match is found, route to an update branch rather than a create branch. Log the duplicate detection event to the error log table.
  • Slack user ID must be resolved from the assigned_to linked record. The staff_members table must contain a slack_user_id field. If slack_user_id is null, fall back to posting in the #ops-assets channel with the assignee name in the message body.
  • Confirm before build: Airtable base ID, table names, exact field names (case-sensitive), and the Slack workspace bot token with the chat:write and users:read OAuth scopes granted.
Maintenance Scheduler Agent

Reads maintenance interval data from each confirmed asset record and manages the complete reminder lifecycle. It creates recurring Google Calendar events for each scheduled maintenance date, sends a Slack reminder to the responsible person 48 hours before each due date, and fires an overdue loan alert to both the borrower and the operations manager when an asset's return_due_date passes without a status update in Airtable. This agent eliminates calendar juggling and manual Slack messages entirely and ensures 100 percent on-time reminder delivery regardless of coordinator availability.

Trigger
Asset intake_status set to 'Confirmed' in Airtable OR maintenance_due_date field updated OR daily scheduled poll at 07:00 local time to check overdue loans
Tools
Airtable, Google Workspace (Calendar, Gmail), Slack
Replaces steps
5, 6, 8
Estimated build
10 hours, Moderate complexity
// Input (maintenance scheduling branch)
airtable.asset_id              // string
airtable.asset_name            // string
airtable.maintenance_interval  // select: Monthly | Quarterly | Annually | Custom
airtable.maintenance_due_date  // ISO 8601 date
airtable.assigned_to           // linked record -> staff_member
airtable.responsible_email     // resolved from staff_member table

// Input (overdue loan branch, daily poll)
airtable.loan_status           // select: On Loan | Returned | Overdue
airtable.return_due_date       // ISO 8601 date
airtable.borrower_slack_id     // from staff_member table
airtable.ops_manager_slack_id  // from config table, static value

// Output (maintenance branch)
google_calendar.event_id       // created recurring event, stored in Airtable
airtable.calendar_event_id     // written back to record
slack.reminder_message_ts      // 48-hour pre-due Slack DM to responsible person

// Output (overdue loan branch)
airtable.loan_status           // updated to 'Overdue'
slack.overdue_alert_ts         // DM to borrower + post to #ops-assets tagging ops manager
  • Google Calendar API requires OAuth 2.0 with the https://www.googleapis.com/auth/calendar scope. The service account must be granted 'Make changes to events' permission on the shared ops calendar. The calendar ID must be stored in the config table, not hardcoded.
  • Maintenance interval mapping: Monthly = recurrence every 30 days, Quarterly = every 90 days, Annually = every 365 days, Custom = read from airtable.custom_interval_days field. If custom_interval_days is null and interval is Custom, flag the record in error log and halt scheduling for that asset.
  • The overdue loan poll must run as a scheduled trigger (cron: 07:00 daily) separate from the event-driven trigger. Filter Airtable for records where loan_status = 'On Loan' AND return_due_date < today. Update loan_status to 'Overdue' before sending alerts to avoid duplicate messages on repeat poll runs.
  • Slack overdue alert must tag the ops manager by user ID, not just name, so the mention fires a notification. The ops_manager_slack_id must be stored in the config table and confirmed by the operations team before go-live.
  • Google Workspace must be on Business Starter or above for Calendar API access. Confirm the service account has domain-wide delegation enabled if the build uses a shared calendar rather than per-user calendars.
  • Confirm before build: Google Calendar ID, service account credentials JSON, Airtable field names for maintenance_interval and return_due_date, and the Slack user ID for the operations manager.
Finance Sync Agent

Pushes confirmed asset data from Airtable to Xero as a fixed-asset record, capturing purchase date, purchase cost, asset category, and depreciation method. When a disposal flag is set in Airtable, the agent updates the Xero record to reflect the write-off. This removes the manual finance handoff entirely, ensuring Xero stays aligned with the physical register without requiring the Finance Officer to re-enter data. The agent also writes the Xero fixed-asset ID back to the Airtable record to maintain a bidirectional audit trail.

Trigger
Airtable field intake_status set to 'Confirmed' (new asset) OR disposal_flag set to TRUE (write-off)
Tools
Airtable, Xero
Replaces steps
10, 11
Estimated build
8 hours, Moderate complexity
// Input (new asset branch)
airtable.asset_id          // string, must exist before this agent fires
airtable.asset_name        // string -> Xero FixedAsset.Name
airtable.asset_category    // select -> mapped to Xero AssetType
airtable.purchase_date     // ISO 8601 -> Xero FixedAsset.PurchaseDate
airtable.purchase_cost     // number (USD) -> Xero FixedAsset.PurchasePrice
airtable.depreciation_method // select: Straight-Line | Diminishing Value | None
airtable.depreciation_rate // number, percentage, required if method is not None
airtable.xero_account_code // string, e.g. '1500', confirmed by finance team

// Input (disposal branch)
airtable.xero_asset_id     // Xero FixedAsset.AssetID, previously written back
airtable.disposal_date     // ISO 8601
airtable.disposal_reason   // string: Sold | Written Off | Donated | Lost

// Output (new asset branch)
xero.fixed_asset_id        // UUID returned by Xero API
airtable.xero_asset_id     // written back to Airtable record
airtable.xero_sync_status  // set to 'Synced'
airtable.xero_sync_date    // ISO 8601 timestamp of successful push

// Output (disposal branch)
xero.disposal_status       // confirmed via Xero API response
airtable.asset_status      // updated to 'Disposed'
airtable.xero_sync_status  // updated to 'Disposed'
  • Xero API authentication uses OAuth 2.0 with the offline_access and accounting.attachments scopes. The Xero tenant ID must be confirmed before build; a single-tenant connection is assumed. Token refresh must be handled automatically by the automation platform credential store.
  • Asset type mapping must be agreed with the Finance Officer before the agent is activated. Example mapping: IT Hardware -> account code 1500, Tools -> 1510, Furniture -> 1520, Vehicle -> 1530. Store mappings in the config table in Airtable, not hardcoded in the workflow.
  • Depreciation method must be one of the values Xero accepts via the Fixed Assets API: STRAIGHTLINE, DIMINISHINGVALUE100, or NOTDEPRECIATING. The Airtable select field values must map exactly to these strings. Include a translation step in the workflow.
  • The agent must not fire until xero_account_code is populated in the Airtable record. If xero_account_code is null, write 'Pending Finance Review' to xero_sync_status and log to the error table. Do not retry automatically; require a human correction in Airtable.
  • For the disposal branch, the agent must first confirm xero_asset_id exists on the record before calling the disposal endpoint. If xero_asset_id is null (asset was never synced), log the error and route a Slack alert to the ops manager rather than attempting a blind write.
  • Confirm before build: Xero tenant ID, OAuth client ID and secret, account code list from Finance Officer, depreciation rate defaults per asset category, and whether the Xero organisation uses a trial balance period that would block historical date entries.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow, Asset and Equipment Tracking automation
// ============================================================
// TRIGGER: Airtable asset register event
// ============================================================
EVENT: airtable.webhook -> table='assets', event_type IN ['create','update']
PAYLOAD: {
  record_id,          // Airtable internal row ID
  asset_name,         // string
  serial_number,      // string
  purchase_date,      // ISO 8601
  purchase_cost,      // number USD
  asset_category,     // select
  assigned_to,        // linked record ID
  maintenance_interval, // select
  maintenance_due_date, // ISO 8601
  loan_status,        // select
  return_due_date,    // ISO 8601
  disposal_flag,      // boolean
  xero_account_code   // string
}

// ============================================================
// AGENT HANDOFF 1: ASSET INTAKE AGENT
// ============================================================
STEP 1: validate required fields
  IF asset_name IS NULL OR serial_number IS NULL
    OR purchase_date IS NULL OR purchase_cost IS NULL
    -> write intake_status='Validation Failed'
    -> log to error_log table {record_id, missing_fields, timestamp}
    -> HALT

STEP 2: deduplicate by serial_number
  QUERY airtable.assets WHERE serial_number = payload.serial_number
  IF match found AND record_id != existing_record_id
    -> write intake_status='Duplicate Detected'
    -> log to error_log {record_id, duplicate_of: existing_record_id}
    -> HALT

STEP 3: generate asset_id if null
  READ airtable.asset_id_counter.last_used_id
  new_id = last_used_id + 1
  asset_id = 'ASSET-' + YEAR(NOW()) + '-' + LPAD(new_id, 4, '0')
  WRITE airtable.asset_id_counter.last_used_id = new_id  // atomic
  WRITE airtable.assets[record_id].asset_id = asset_id

STEP 4: generate QR code
  REQUEST GET https://api.qrserver.com/v1/create-qr-code/
    ?data={airtable_record_url}&size=200x200&format=png
  RESPONSE: qr_code_url (public PNG URL)
  WRITE airtable.assets[record_id].qr_code_url = qr_code_url

STEP 5: resolve assignee Slack ID
  QUERY airtable.staff_members WHERE id = assigned_to
  assignee_slack_id = staff_member.slack_user_id
  IF assignee_slack_id IS NULL
    -> fallback_channel = '#ops-assets'
  ELSE
    -> dm_channel = assignee_slack_id

STEP 6: send Slack assignment notification
  POST slack.chat.postMessage
    channel: dm_channel OR fallback_channel
    text: 'Asset {asset_name} ({asset_id}) has been assigned to you.
           QR label: {qr_code_url}. Check the register for care instructions.'
  WRITE airtable.assets[record_id].intake_status = 'Confirmed'
  WRITE airtable.assets[record_id].slack_message_ts = response.ts

// ============================================================
// AGENT HANDOFF 2: MAINTENANCE SCHEDULER AGENT
// ============================================================
TRIGGER: intake_status changed to 'Confirmed' OR maintenance_due_date updated

STEP 7: map maintenance interval to recurrence rule
  SWITCH maintenance_interval
    'Monthly'   -> RRULE:FREQ=MONTHLY
    'Quarterly' -> RRULE:FREQ=MONTHLY;INTERVAL=3
    'Annually'  -> RRULE:FREQ=YEARLY
    'Custom'    -> RRULE:FREQ=DAILY;INTERVAL={custom_interval_days}
    DEFAULT     -> log error, HALT scheduling for this record

STEP 8: create Google Calendar event
  POST google_calendar.events.insert
    calendarId: {ops_calendar_id from config}
    summary: 'Maintenance Due: {asset_name} ({asset_id})'
    start: maintenance_due_date
    recurrence: [RRULE from step 7]
    attendees: [{responsible_email}]
  RESPONSE: google_calendar.event_id
  WRITE airtable.assets[record_id].calendar_event_id = event_id

STEP 9: 48-hour pre-due Slack reminder (scheduled sub-workflow)
  SCHEDULE at maintenance_due_date - 48h
  POST slack.chat.postMessage
    channel: assignee_slack_id OR '#ops-assets'
    text: 'Reminder: {asset_name} ({asset_id}) is due for maintenance in 48 hours.'

// ============================================================
// AGENT HANDOFF 2b: OVERDUE LOAN BRANCH (daily scheduled poll)
// ============================================================
SCHEDULE: cron 07:00 daily
QUERY airtable.assets WHERE loan_status='On Loan' AND return_due_date < TODAY()
FOR EACH overdue_record:
  WRITE airtable.assets[record_id].loan_status = 'Overdue'
  POST slack.chat.postMessage to borrower_slack_id:
    'Your loan of {asset_name} ({asset_id}) was due back on {return_due_date}.
     Please return it or update the register.'
  POST slack.chat.postMessage to #ops-assets:
    '@{ops_manager} Overdue loan alert: {asset_name} ({asset_id}).
     Borrower: {assigned_to}. Due: {return_due_date}.'

// ============================================================
// AGENT HANDOFF 3: FINANCE SYNC AGENT
// ============================================================
TRIGGER A: intake_status = 'Confirmed' AND xero_sync_status IS NULL
TRIGGER B: disposal_flag = TRUE

// Branch A: new fixed-asset record
STEP 10: validate xero_account_code
  IF xero_account_code IS NULL
    -> WRITE xero_sync_status = 'Pending Finance Review'
    -> log to error_log {record_id, reason: 'Missing xero_account_code'}
    -> HALT

STEP 11: map depreciation method
  SWITCH depreciation_method
    'Straight-Line'      -> xero_method = 'STRAIGHTLINE'
    'Diminishing Value'  -> xero_method = 'DIMINISHINGVALUE100'
    'None'               -> xero_method = 'NOTDEPRECIATING'

STEP 12: POST to Xero Fixed Assets API
  POST https://api.xero.com/assets.xro/1.0/Assets
    AssetName: asset_name
    AssetNumber: asset_id
    PurchaseDate: purchase_date
    PurchasePrice: purchase_cost
    AssetStatus: 'REGISTERED'
    BookDepreciationSetting: {
      DepreciationMethod: xero_method,
      AveragingMethod: 'ActualDays',
      DepreciationRate: depreciation_rate,
      DepreciationCalculationMethod: 'None'
    }
    FixedAssetAccountCode: xero_account_code
  RESPONSE: xero.AssetID (UUID)
  WRITE airtable.assets[record_id].xero_asset_id = xero.AssetID
  WRITE airtable.assets[record_id].xero_sync_status = 'Synced'
  WRITE airtable.assets[record_id].xero_sync_date = NOW()

// Branch B: disposal / write-off
STEP 13: confirm xero_asset_id exists
  IF xero_asset_id IS NULL
    -> log error, POST Slack alert to #ops-assets, HALT
STEP 14: POST disposal to Xero
  POST https://api.xero.com/assets.xro/1.0/Assets/{xero_asset_id}/Dispose
    DisposalDate: disposal_date
    DisposalType: disposal_reason mapped to Xero enum
  WRITE airtable.assets[record_id].asset_status = 'Disposed'
  WRITE airtable.assets[record_id].xero_sync_status = 'Disposed'

// ============================================================
// SCHEDULED AUDIT REPORT (monthly, 09:00 first Monday)
// ============================================================
QUERY airtable.assets WHERE last_status_update < DATE_SUB(NOW(), INTERVAL 30 DAY)
BUILD summary: total_assets, active, on_loan, overdue, disposed, stale_records[]
POST slack.chat.postMessage to #ops-assets:
  'Monthly Asset Audit Report: {summary}. Stale records attached for review.'
// END OF FLOW
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
One workflow automation tool instance handling all three agents as separate workflows within a shared project. Each agent (Asset Intake, Maintenance Scheduler, Finance Sync) runs as an independent workflow with its own trigger. All three workflows share a single credential store to avoid duplicating API keys. The monthly audit report runs as a fourth scheduled workflow. No workflow should call another workflow inline; use Airtable record status fields as the handoff signal between agents.
Webhook configuration
Airtable native webhooks (Base > Automations > Webhook) deliver record events to the automation platform's inbound webhook URL. Register one webhook per trigger event type: (1) record created or updated for the Intake Agent, (2) intake_status field changed to 'Confirmed' for the Maintenance Scheduler and Finance Sync agents, (3) disposal_flag changed to TRUE for the Finance Sync disposal branch. Store all inbound webhook URLs in the credential store. Rotate Airtable API keys if any webhook URL is exposed. The overdue loan poll and monthly audit report use the automation platform's internal cron scheduler, not Airtable webhooks.
Templating approach
All Slack message bodies and Google Calendar event summaries are stored as string templates in the automation platform's environment variables, not hardcoded in workflow nodes. Template variables use double-brace syntax: {{asset_name}}, {{asset_id}}, {{return_due_date}}. This allows the operations team to adjust message copy without a workflow edit. Xero field mappings (account codes, depreciation methods) are stored in the Airtable config table (table name: automation_config) and read at runtime, making them editable by the Finance Officer without developer access.
Error logging
All validation failures, API errors, and halt conditions write a row to a dedicated Airtable table named error_log with fields: record_id (linked to assets table), error_type (select: Validation | API Failure | Duplicate | Missing Config), error_detail (long text), agent_name (select: Asset Intake | Maintenance Scheduler | Finance Sync), timestamp, and resolved (checkbox). On any error_type of API Failure, the automation platform also posts a Slack alert to #ops-alerts (separate from #ops-assets) tagging the operations manager. The error_log table is reviewed as part of the monthly audit report. No automatic retry on API failures; require manual resolution and re-trigger.
Testing approach
All three agents must be built and validated against a sandbox Airtable base (duplicate of production, named ASSETS-SANDBOX) and a Xero demo company before any production credentials are used. The Slack workspace has a #ops-sandbox channel for test notifications. QR code generation can be tested against production since it is stateless and read-only. Google Calendar events during testing are created on a dedicated test calendar, not the shared ops calendar. Switch to production credentials only after the full QA pass documented in the Test and QA Plan is signed off.
Estimated total build time
Asset Intake Agent: 10 hours. Maintenance Scheduler Agent: 10 hours. Finance Sync Agent: 8 hours. End-to-end integration testing and error-log validation: 6 hours. Total: 34 hours across the 3-4 week delivery window. Note: the template build effort is 28 hours; the additional 6 hours accounts for environment setup, credential configuration, and sandbox-to-production migration. Final build time is governed by register cleanliness and speed of Xero account code confirmation from the Finance Officer.
Pre-build checklist for the FullSpec team: (1) Confirm Airtable base ID, exact table names, and all field names (case-sensitive) with the operations coordinator. (2) Obtain Airtable API key and QR Code Generator endpoint agreement. (3) Collect Google service account credentials JSON and confirm domain-wide delegation for Calendar API. (4) Collect Slack bot token and confirm chat:write and users:read scopes. (5) Collect Xero OAuth client ID and secret, confirm tenant ID and demo company access. (6) Confirm Xero account codes and depreciation rates with the Finance Officer before activating the Finance Sync Agent. None of these credentials should be stored anywhere other than the automation platform's encrypted credential store. Contact the operations team at support@gofullspec.com for any credential handover queries.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Asset & Equipment Tracking.

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