Airtable Sync¶
Sync PropertyMe data to Airtable for custom views, reporting, and integrations.
Installation¶
uv add pypropertyme[airtable]
Configuration¶
Set these environment variables (in .env or your shell):
| Variable | Description |
|---|---|
AIRTABLE_API_TOKEN |
Airtable personal access token |
AIRTABLE_BASE_ID |
Airtable base ID (e.g., appXXXXXXXXXXXXXX) |
Quick Start¶
# 1. Set environment variables
export AIRTABLE_API_TOKEN="patXXX..."
export AIRTABLE_BASE_ID="appXXX..."
# 2. Initialize Airtable base with schema
pypropertyme-airtable init
# 3. Sync data
pypropertyme-airtable sync
# 4. Check status
pypropertyme-airtable status
CLI Commands¶
pypropertyme-airtable init¶
Creates all required tables and fields in an Airtable base.
pypropertyme-airtable init
Note
The Airtable base must already exist. Create one at airtable.com.
pypropertyme-airtable sync¶
Syncs active records from PropertyMe to Airtable.
pypropertyme-airtable sync [--dry-run] [--table <TABLE>]
| Option | Description |
|---|---|
--dry-run |
Preview sync without writing to Airtable |
--table |
Sync specific table only (e.g., Contacts, Properties) |
pypropertyme-airtable status¶
Shows table statistics and record counts.
pypropertyme-airtable status
pypropertyme-airtable test-connection¶
Tests connections to both PropertyMe and Airtable.
pypropertyme-airtable test-connection
Tables¶
The sync creates 7 tables in Airtable:
| Table | Description | Primary Field |
|---|---|---|
| Contacts | Owners, tenants, suppliers | Name |
| Members | Agency staff | Name |
| Properties | Managed properties | Reference |
| Tenancy Balances | Financial data (rent, arrears, bond) | Label |
| Jobs | Maintenance jobs | Summary |
| Inspections | Property inspections | Summary |
| Tasks | Tasks and reminders | Summary |
Note
Tenancy Balances is a superset of tenancy data, containing all tenancies (active and closed) plus financial balance information. A separate Tenancies table is not needed.
Field Naming¶
All PyPropertyMe-managed fields use the PME prefix to distinguish them from user-created fields:
PME PropertyMe ID- Unique identifier (upsert key)PME Name,PME Email, etc. - Data fieldsPME JSON Data- Complete raw JSON from PropertyMe (see below)
JSON Data Field¶
Every table includes a PME JSON Data field containing the complete JSON payload from PropertyMe. This provides:
- Access to fields not explicitly mapped to Airtable columns
- Future PropertyMe API fields without schema updates
- Raw data for custom integrations or debugging
- Complete data for export/backup purposes
Info
JSON is formatted with 2-space indentation for readability, except for Inspections which uses minified JSON due to Airtable field size limits.
How It Works¶
Multi-Phase Sync¶
- Phase 1: Sync core tables (Contacts, Members, Properties) without linked records
- Phase 2: Build ID mappings (PropertyMe ID → Airtable Record ID)
- Phase 3: Sync dependent tables with linked record resolution
- Phase 4: Re-sync Properties to add Owner/Tenant/Manager links
- Phase 5: Refresh stale records
Upsert Logic¶
Uses Airtable's batch_upsert with PME PropertyMe ID as the key field. Records are created if new, updated if existing.
Stale Record Refresh¶
After syncing, records in Airtable that weren't included in the PropertyMe sync response are refreshed individually:
- For each stale record, the sync fetches its current state from PropertyMe
- Records that return 404 (deleted from PropertyMe) are logged and skipped
- Records that have become archived/closed are updated with their true state
Schema Evolution¶
The implementation supports adding new fields to existing tables.
Supported:
- Adding new fields to existing tables
Not supported:
- Modifying existing field types or options
- Removing fields no longer in the schema
- Renaming fields
Workflow for adding new fields:
# 1. Add new field definition to schema.py
# 2. Re-run init to add missing fields
pypropertyme-airtable init
# 3. Update transformer in transformers.py to populate the new field
# 4. Run sync to populate data
pypropertyme-airtable sync
Module Structure¶
src/pypropertyme/sync/airtable/
├── __init__.py # Module exports
├── cli.py # CLI commands (pypropertyme-airtable)
├── creator.py # Table/field creation logic
├── schema.py # Table schema definitions
├── sync.py # Core sync logic
└── transformers.py # PyPropertyMe model → Airtable record transformers
Programmatic Usage¶
from pypropertyme.client import Client
from pypropertyme.sync.airtable import AirtableSync
# Create PropertyMe client first
pme_client = Client.get_client(token)
# Create sync instance
sync = AirtableSync(pme_client, "patXXX...", "appXXX...")
# Initialize schema (not async)
sync.init_base()
# Run sync
await sync.sync_all()
# Sync specific table
await sync.sync_table("Contacts")