Skip to main content

What are Providers?

Providers are plugin modules that integrate Mirobody Health with external health and fitness device APIs. Each provider handles OAuth authentication, data fetching, and transformation into a standardized format.
Provider architecture diagram

Provider Architecture

Mirobody Health uses an extensible provider architecture that makes it easy to add new health device integrations:
BaseThetaProvider (Abstract Base Class)

ThetaGarminProvider (OAuth1 Implementation)
ThetaWhoopProvider (OAuth2 Implementation)
YourCustomProvider (Your Implementation)

Core Components

Authentication Handler

Manages OAuth 1.0 or OAuth 2.0 flows for secure user authorization

Data Puller

Fetches health data from vendor APIs with pagination and rate limiting

Data Formatter

Transforms vendor-specific data into standardized health indicators

Database Service

Persists both raw and formatted data for audit trails

Available Providers

Garmin Connect

Authentication: OAuth 1.0
Status: Production Ready
Data Types: Activities, Sleep, Heart Rate, HRV, Respiration, Stress, Body Composition
Garmin Connect is one of the most comprehensive health data sources, providing detailed metrics from Garmin wearables and fitness devices.

Whoop

Authentication: OAuth 2.0
Status: Production Ready
Data Types: Sleep, Recovery, Cycles, Workouts, Body Measurements
Whoop provides comprehensive recovery and strain metrics popular with athletes and fitness enthusiasts.

Provider Lifecycle

Understanding the provider lifecycle helps you work with providers effectively:
1

Initialization

Providers are automatically discovered and initialized on application startup from directories specified in THETA_PROVIDER_DIRS.
config.yaml
THETA_PROVIDER_DIRS:
  - connect/theta
2

Registration

Each provider’s create_provider() class method is called to validate configuration and create an instance.
@classmethod
def create_provider(cls, config: Dict[str, Any]) -> Optional['ThetaYourProvider']:
    if not cls._validate_config(config):
        return None
    return cls()
3

User Linking

When a user initiates linking, the provider’s link() method generates an OAuth URL.
async def link(self, request: LinkRequest) -> Dict[str, Any]:
    # Generate OAuth authorization URL
    return {"link_web_url": authorization_url}
4

Authentication

After user authorization, the provider’s callback() method exchanges tokens and saves credentials.
async def callback(self, code: str, state: str) -> Dict[str, Any]:
    # Exchange code for tokens
    # Save credentials to database
    # Trigger initial data pull
    return {"provider_slug": self.info.slug, "stage": "completed"}
5

Data Synchronization

Periodically or on-demand, the provider pulls data from the vendor API, saves raw data, formats it, and pushes to the platform.
async def _pull_and_push_for_user(self, credentials: Dict) -> bool:
    # Pull from vendor API
    # Save raw data
    # Format to standard structure
    # Push to platform
    pass
6

Unlinking

When a user unlinks, the provider’s unlink() method revokes access and removes stored credentials.
async def unlink(self, user_id: str) -> Dict[str, Any]:
    # Revoke access at vendor (optional)
    # Delete credentials from database
    return {"success": True}

Standard Health Indicators

All provider data is transformed into standardized health indicators for consistency across devices:
  • DAILY_STEPS: Step count
  • DAILY_DISTANCE: Distance traveled (meters)
  • DAILY_CALORIES_ACTIVE: Active calories burned (kcal)
  • DAILY_CALORIES_BASAL: Basal metabolic rate calories (kcal)
  • DAILY_FLOORS_CLIMBED: Floors climbed (count)
  • ACTIVE_TIME: Active time duration (minutes)
  • HEART_RATE: Instantaneous heart rate (bpm)
  • DAILY_HEART_RATE_MIN: Minimum daily heart rate (bpm)
  • DAILY_HEART_RATE_MAX: Maximum daily heart rate (bpm)
  • DAILY_AVG_HEART_RATE: Average daily heart rate (bpm)
  • DAILY_HEART_RATE_RESTING: Resting heart rate (bpm)
  • HRV: Heart rate variability RMSSD (ms)
  • DAILY_SLEEP_DURATION: Total sleep duration (ms)
  • SLEEP_IN_BED: Time in bed (ms)
  • DAILY_AWAKE_TIME: Time awake during sleep (ms)
  • DAILY_LIGHT_SLEEP: Light sleep duration (ms)
  • DAILY_DEEP_SLEEP: Deep sleep duration (ms)
  • DAILY_REM_SLEEP: REM sleep duration (ms)
  • SLEEP_EFFICIENCY: Sleep efficiency percentage
  • WEIGHT: Body weight (kg)
  • HEIGHT: Height (m)
  • BMI: Body mass index
  • BODY_FAT_PERCENTAGE: Body fat percentage
  • SKELETAL_MUSCLE_MASS: Skeletal muscle mass (kg)
  • RESPIRATORY_RATE: Respiration rate (breaths/min)
  • BLOOD_OXYGEN: SpO2 percentage
  • WORKOUT_DURATION_LOW: Low intensity duration (min)
  • WORKOUT_DURATION_MEDIUM: Medium intensity duration (min)
  • WORKOUT_DURATION_HIGH: High intensity duration (min)
  • ALTITUDE_GAIN: Altitude gained (m)
  • SPEED: Average speed (m/s)
For a complete list of standard indicators, see the StandardIndicator enum in the mirobody framework documentation.

Provider Requirements

To ensure quality and consistency, all providers must implement:
class ThetaYourProvider(BaseThetaProvider):
    @classmethod
    def create_provider(cls, config: Dict) -> Optional['ThetaYourProvider']:
        """Factory method for provider instantiation"""
        
    @property
    def info(self) -> ProviderInfo:
        """Provider metadata"""
        
    async def link(self, request: Any) -> Dict[str, Any]:
        """Initiate OAuth flow"""
        
    async def callback(self, *args, **kwargs) -> Dict[str, Any]:
        """Handle OAuth callback"""
        
    async def unlink(self, user_id: str) -> Dict[str, Any]:
        """Unlink user connection"""
        
    async def format_data(self, raw_data: Dict) -> StandardPulseData:
        """Format raw data to standard format"""
        
    async def pull_from_vendor_api(self, *args, **kwargs) -> List[Dict]:
        """Pull data from vendor API"""
        
    async def save_raw_data_to_db(self, raw_data: Dict) -> List[Dict]:
        """Save raw data to database"""
        
    async def is_data_already_processed(self, raw_data: Dict) -> bool:
        """Check if data already processed"""
        
    async def _pull_and_push_for_user(self, credentials: Dict) -> bool:
        """Pull and push data for user"""

Provider Discovery

Mirobody Health automatically discovers and loads providers from configured directories:
# In config.yaml
THETA_PROVIDER_DIRS:
  - connect/theta      # Your custom providers
  - providers/theta    # Additional provider directory
Directory structure:
connect/theta/
├── mirobody_garmin_connect/
│   ├── __init__.py
│   └── provider_garmin.py
├── mirobody_whoop/
│   ├── __init__.py
│   └── provider_whoop.py
└── mirobody_your_provider/
    ├── __init__.py
    └── provider_your_provider.py
Provider directory names should follow the pattern mirobody_<provider_name> for consistency.

Best Practices

  • Always use try-except blocks for external API calls
  • Log errors with context (user_id, timestamp, error details)
  • Return empty results on failure, don’t crash
  • Implement exponential backoff for transient errors
  • Handle rate limiting gracefully
  • Validate OAuth tokens before use
  • Check data types before conversion
  • Handle missing or null values gracefully
  • Verify timestamp formats
  • Validate units match expected values
  • Use async/await for all I/O operations
  • Implement pagination for large datasets
  • Limit concurrent requests to vendor APIs
  • Cache frequently accessed configuration
  • Use connection pooling for database access
  • Never log sensitive data (tokens, passwords)
  • Store credentials encrypted in database
  • Use HTTPS for all external communications
  • Validate OAuth state parameters
  • Implement token refresh for OAuth2

Next Steps