We wanted to be able to ask: “Where was I on January 3rd at 2pm?” and get a precise answer — automatically, without any manual logging, forever. No app to open, no check-in required. Just a background daemon that runs silently and never forgets.
This post covers how we built a continuous iPhone location history system using the iCloud Find My API, a local SQLite database, and a smart compression model that keeps storage under 100MB indefinitely.
The Approach: iCloud Find My + pyicloud
Apple’s iCloud Find My service already knows where your iPhone is at all times — it’s how you locate a lost device. The pyicloud Python library exposes this data programmatically, letting a script on your Mac poll your iPhone’s location without any on-device app or background process.
The setup is straightforward: authenticate once with 2FA, and the session persists for weeks. After that, location data is available on demand:
from pyicloud import PyiCloudService
api = PyiCloudService("your@icloud.com", "password",
cookie_directory="~/.icloud_session")
# .location is a property in pyicloud 2.x (not a method)
loc = api.iphone.location
# → {"latitude": 40.75, "longitude": -73.98, "horizontalAccuracy": 12, ...}
A launchd agent fires the polling script every 5 minutes, 24 hours a day. Even while the Mac is asleep, launchd queues the wake and runs it on schedule.
Smart Compression: One Row Per Location, Not One Row Per Poll
The naive approach — one database row per 5-minute poll — would generate 288 rows per day, most of which are redundant. If you’re asleep for 8 hours, that’s 96 identical coordinate pairs.
Instead, the system uses a start/end timestamp model with a stationary threshold:
STATIONARY_M = 100 # meters — within this = same place
MAX_EXTEND_SEC = 14400 # 4 hours max per record
def ingest(lat, lon, accuracy, source):
last = db.get_most_recent_record()
if last:
dist = haversine(last.lat, last.lon, lat, lon)
if dist < STATIONARY_M and (now - last.ts_end) < MAX_EXTEND_SEC:
db.update(ts_end=now) # extend current record
return "extended"
db.insert(ts_start=now, ts_end=now, lat=lat, lon=lon)
return "new"
A full night of sleep at home becomes a single row: ts_start=10:45pm, ts_end=6:30am with a single coordinate pair. The 4-hour cap ensures no record silently covers more than 4 hours — even during extended inactivity, the history is never stale for long.
Storage Math
Typical usage generates roughly 15 records per day after compression — one per distinct location visited. At approximately 200 bytes per row in SQLite, that’s about 1MB per year. A 100MB cap accommodates nearly a century of data at that rate.
Even in a worst-case always-moving scenario (288 records/day), you’d approach the limit after 5 years — at which point a pruning function trims the oldest 10% of records automatically.
Dual-Source Redundancy: iCloud + iPhone Shortcuts
iCloud polling is the primary source, but network latency, 2FA session expiry, or Apple’s Find My service being temporarily unreachable can cause gaps. A secondary source fills these in.
An iPhone Shortcut triggers on “Arrive” and “Depart” location events and writes a JSON payload to iCloud Drive:
# iPhone Shortcuts → iCloud Drive → ~/iCloud Drive/location_push.json
{
"lat": 40.75,
"lon": -73.98,
"accuracy": 8,
"ts": 1704312000
}
The daemon reads and clears this file on every poll cycle. Transition events (arriving somewhere, leaving somewhere) are typically higher-accuracy GPS fixes than the passive Find My polls, making them a useful complement.
Querying From Any Agent
The query interface is designed to be called by AI agents with natural-language-style inputs:
from location_helper import where_was_i, location_history, location_status
# Point-in-time query
where_was_i("2026-01-03 14:00")
# → {"place": "Midtown, Example City, ST", "lat": 40.75, "lon": -73.98,
# "start": "2026-01-03 13:45", "end": "2026-01-03 15:20", "accuracy_m": 12}
# Full day history
location_history("2026-01-03")
# → [list of location records with place names, durations, sources]
# Current location
location_status()
# → most recent record + age in minutes
Place names are resolved via Nominatim reverse geocoding and cached locally for 30 days — no API key required, and no repeated network lookups for frequently visited locations.
The point-in-time query uses a simple SQL predicate: WHERE ts_start <= ? AND ts_end >= ?. If no record contains the exact timestamp (a gap during an outage), it falls back to the nearest record by time distance.
Privacy Considerations
All data stays local. Nothing is sent to a third-party service — the only external call is the iCloud Find My API (the same API your Mac uses when you open the Find My app) and Nominatim for reverse geocoding (which receives coordinates, not identifiers).
The database lives at a local file path, readable only by the system user. Agents with access to this data can use it freely for internal tasks — scheduling, context, planning — but it should not appear in public-facing content, outreach, or external communications.
What’s Next
The most natural extension is semantic place tagging — automatically labeling frequently visited coordinates (“home,” “gym,” “office”) based on visit frequency and time patterns. Once a place is tagged, queries return human-friendly context rather than raw addresses, and patterns become visible: average commute time, how often you’re at the office on Fridays, which weeks had more travel than usual.
A second direction is cross-referencing with calendar events — correlating location records with scheduled meetings or events to automatically build a ground-truth timeline of where you actually were vs. where you planned to be.
The system is designed to run forever with zero maintenance. Once the launchd agent is registered and the iCloud session is trusted, it just works — quietly accumulating a complete location history that any agent can query at any time.
Applied Intelligence Systems builds custom AI infrastructure for individuals and small teams. If you’re interested in implementing similar systems, get in touch.
Ready to put this to work in your business?
Applied Intelligence helps San Diego and Southern California businesses automate workflows, reduce manual work, and grow without adding headcount. The first conversation is free and takes 20 minutes.
Book a Free Discovery Call →