# External Integrations **Analysis Date:** 2026-01-18 ## APIs & External Services **Apple CloudKit (Primary Data Source):** - Purpose: Sync game schedules, teams, stadiums, league structure to all users - Container: `iCloud.com.sportstime.app` - Database: Public (read-only for users, admin writes via Python uploader) - Client: `Core/Services/CloudKitService.swift` - Sync: `Core/Services/CanonicalSyncService.swift` - Record types: `Game`, `Team`, `Stadium`, `LeagueStructure`, `TeamAlias`, `StadiumAlias`, `Sport` - Auth: User iCloud account (automatic), server-to-server JWT (Python uploader) **Apple MapKit:** - Purpose: Geocoding, routing, map snapshots, EV charger search, POI search - Services used: - `MKLocalSearch` - Address and POI search (`LocationService.swift`) - `MKDirections` - Driving routes and ETA (`LocationService.swift`) - `MKMapSnapshotter` - Static map images for PDF (`Export/Services/MapSnapshotService.swift`) - `MKLocalPointsOfInterestRequest` - EV chargers (`EVChargingService.swift`) - Client: `Core/Services/LocationService.swift`, `Core/Services/EVChargingService.swift` - Auth: Automatic via Apple frameworks - Rate limits: Apple's standard MapKit limits **Apple StoreKit 2:** - Purpose: In-app subscriptions (Pro tier) - Products: `com.sportstime.pro.monthly`, `com.sportstime.pro.annual` - Client: `Core/Store/StoreManager.swift` - Features gated: Trip limit (1 free, unlimited Pro) **Sports League APIs (Official):** | API | URL | Sport | Reliability | Client | |-----|-----|-------|-------------|--------| | MLB Stats API | `https://statsapi.mlb.com/api/v1` | MLB | Official | `ScoreAPIProviders/MLBStatsProvider.swift` | | NHL Stats API | `https://api-web.nhle.com/v1` | NHL | Official | `ScoreAPIProviders/NHLStatsProvider.swift` | | NBA Stats API | `https://stats.nba.com/stats` | NBA | Unofficial | `ScoreAPIProviders/NBAStatsProvider.swift` | **NBA Stats API Notes:** - Requires specific headers to avoid 403 (User-Agent, Referer, x-nba-stats-origin, x-nba-stats-token) - May break without notice (unofficial) **Sports Reference Sites (Scraping Fallback):** | Site | URL Pattern | Sport | Client | |------|-------------|-------|--------| | Baseball-Reference | `baseball-reference.com/boxes/?month=M&day=D&year=Y` | MLB | `HistoricalGameScraper.swift` | | Basketball-Reference | `basketball-reference.com/boxscores/?month=M&day=D&year=Y` | NBA | `HistoricalGameScraper.swift` | | Hockey-Reference | `hockey-reference.com/boxscores/?month=M&day=D&year=Y` | NHL | `HistoricalGameScraper.swift` | | Pro-Football-Reference | `pro-football-reference.com/boxscores/...` | NFL | `HistoricalGameScraper.swift` | - Uses SwiftSoup for HTML parsing - On-device scraping (no server costs, unlimited scale) - Cached per-session to avoid redundant requests ## Data Storage **SwiftData (Local):** - Purpose: Offline-first data storage, user trips, preferences - Location: App sandbox (automatic) - CloudKit sync: Disabled (`cloudKitDatabase: .none`) - Schema: See `SportsTimeApp.swift` ModelContainer configuration **CloudKit Public Database (Remote):** - Purpose: Shared schedule data for all users - Container: `iCloud.com.sportstime.app` - Access: Read (all users), Write (admin via Python uploader) - Sync method: Date-based delta sync (modificationDate filtering) **URLCache (Images):** - Purpose: Cache team logos and stadium photos - Memory: 50 MB - Disk: 100 MB - Path: `ImageCache` - Client: `Export/Services/RemoteImageService.swift` **File Storage:** - Local filesystem only for PDF export (temporary) - Photo library access for visit photo imports **Caching:** - In-memory game score cache: `ScoreResolutionCache.swift` - In-memory scraper cache: `HistoricalGameScraper.swift` - URLSession cache: Team logos, stadium photos ## Authentication & Identity **iCloud (Automatic):** - Users authenticate via their iCloud account - Required for: CloudKit sync, poll voting identity - Optional: App works offline without iCloud **CloudKit Server-to-Server (Python Uploader):** - JWT authentication with ECDSA P-256 key - Key ID and team ID from Apple Developer portal - Used by `Scripts/sportstime_parser/uploaders/cloudkit.py` ## Monitoring & Observability **Error Tracking:** - None (no Sentry, Crashlytics, etc.) - Errors logged to console via `os.Logger` **Logs:** - `os.Logger` subsystem: `com.sportstime.app` - Categories: `BackgroundSyncManager`, `NetworkMonitor` - Rich console output in Python scraper ## CI/CD & Deployment **Hosting:** - iOS App Store (planned) - CloudKit public database (Apple infrastructure) **CI Pipeline:** - None configured (no GitHub Actions, Xcode Cloud, etc.) **Data Pipeline:** - Python CLI (`sportstime-parser`) scrapes schedules - Uploads to CloudKit via CloudKit Web Services API - iOS apps sync from CloudKit automatically ## Background Processing **BGTaskScheduler:** - Refresh task: `com.sportstime.app.refresh` (periodic CloudKit sync) - Processing task: `com.sportstime.app.db-cleanup` (overnight heavy sync) - Manager: `Core/Services/BackgroundSyncManager.swift` **Network Monitoring:** - `NWPathMonitor` triggers sync on connectivity restoration - Debounce: 2.5 seconds (handles WiFi/cellular handoffs) - Manager: `Core/Services/NetworkMonitor.swift` **Push Notifications:** - CloudKit subscriptions for data changes - Silent notifications (`shouldSendContentAvailable`) - Subscription IDs: `game-updates`, `league-structure-updates`, `team-alias-updates`, `stadium-alias-updates` ## Environment Configuration **Required Environment Variables:** *iOS App:* - None required (CloudKit container ID hardcoded) *Python Uploader:* - CloudKit key ID (or in config) - CloudKit team ID (or in config) - ECDSA private key path **Secrets Location:** - iOS: Entitlements file (`SportsTime.entitlements`) - Python: Environment variables or local config file ## Webhooks & Callbacks **Incoming:** - CloudKit silent push notifications (background sync trigger) - Deep links: `sportstime://poll/{shareCode}` (poll sharing) **Outgoing:** - None ## Rate Limiting **Internal Rate Limiter:** - `Core/Services/RateLimiter.swift` - Per-provider keys: `mlb_stats`, `nba_stats`, `nhl_stats` - Prevents API abuse when resolving historical game scores **Provider Auto-Disable:** - Official APIs: Never auto-disabled - Unofficial APIs: Disabled after 3 failures (24h cooldown) - Scraped sources: Disabled after 2 failures (24h cooldown) ## Polls (Group Trip Planning) **CloudKit-Based Polling:** - Record type: `TripPoll`, `PollVote` - Share codes: 6-character uppercase alphanumeric - Deep link: `sportstime://poll/{shareCode}` - Service: `Core/Services/PollService.swift` --- *Integration audit: 2026-01-18*