{"basePath":"/v2","definitions":{"LanguageInfo":{"properties":{"code":{"type":"string"},"is_default":{"type":"boolean"},"is_rtl":{"type":"boolean"},"name":{"type":"string"},"native_name":{"type":"string"}},"type":"object"},"LanguagesResponse":{"properties":{"languages":{"items":{"$ref":"#/definitions/LanguageInfo"},"type":"array"}},"type":"object"},"LatestReadingsResponse":{"properties":{"count":{"example":10,"type":"integer"},"location_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"period":{"example":"24h","type":"string"},"readings":{"items":{"additionalProperties":true,"type":"object"},"type":"array"}},"type":"object"},"LocationSensorsResponse":{"properties":{"count":{"example":3,"type":"integer"},"sensors":{"items":{"additionalProperties":true,"type":"object"},"type":"array"}},"type":"object"},"NamespacesResponse":{"properties":{"namespaces":{"items":{"type":"string"},"type":"array"}},"type":"object"},"OutdoorSensorInfo":{"properties":{"description":{"example":"Public outdoor air quality sensor","type":"string"},"device_url":{"example":"","type":"string"},"id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"is_active":{"example":true,"type":"boolean"},"latitude":{"example":25.1972,"type":"number"},"longitude":{"example":55.2744,"type":"number"},"name":{"example":"Downtown Station","type":"string"}},"type":"object"},"PublicOutdoorSensorsResponse":{"properties":{"count":{"example":5,"type":"integer"},"sensors":{"items":{"$ref":"#/definitions/OutdoorSensorInfo"},"type":"array"}},"type":"object"},"TimezoneResponse":{"properties":{"abbreviation":{"description":"e.g., \"GST\", \"EST\"","type":"string"},"city":{"type":"string"},"country":{"type":"string"},"gmtOffset":{"description":"Offset in seconds from UTC","type":"integer"},"timezone":{"type":"string"},"zoneName":{"description":"Full timezone name","type":"string"}},"type":"object"},"TranslationResponse":{"properties":{"language":{"type":"string"},"last_updated":{"type":"string"},"namespace":{"type":"string"},"translations":{"additionalProperties":{"type":"string"},"type":"object"}},"type":"object"},"internal_handlers.AirSenseGradeJSON":{"properties":{"device_id":{"type":"string"},"first_reading_date":{"type":"string"},"humidity_baseline_band":{"type":"string"},"humidity_beta":{"type":"number"},"humidity_calculated_at":{"type":"string"},"humidity_comfort_pct":{"type":"number"},"humidity_confidence":{"type":"string"},"humidity_delta10":{"type":"number"},"humidity_grade":{"type":"string"},"humidity_grade_long":{"type":"integer"},"humidity_grade_short":{"description":"DEPRECATED: Backward compatibility fields for iOS app (v3.0 schema changes)\nTODO: Remove after iOS app update is released (v3.1+)","type":"integer"},"humidity_mean":{"type":"number"},"humidity_mean_long":{"type":"number"},"humidity_mean_short":{"type":"number"},"humidity_period_days":{"type":"integer"},"humidity_sample_count":{"type":"integer"},"humidity_trend":{"type":"string"},"humidity_vtr":{"type":"number"},"indoor_sensor_id":{"type":"string"},"indoor_sensor_name":{"type":"string"},"outdoor_sensor_id":{"type":"string"},"pm25_aqi":{"type":"integer"},"pm25_aqi_color":{"type":"string"},"pm25_aqi_label":{"type":"string"},"pm25_baseline_band":{"type":"string"},"pm25_beta":{"type":"number"},"pm25_calculated_at":{"type":"string"},"pm25_confidence":{"type":"string"},"pm25_delta10":{"type":"number"},"pm25_grade":{"type":"string"},"pm25_grade_long":{"type":"integer"},"pm25_grade_short":{"description":"DEPRECATED: Backward compatibility fields for iOS app (v3.0 schema changes)\nTODO: Remove after iOS app update is released (v3.1+)","type":"integer"},"pm25_mean":{"type":"number"},"pm25_mean_long":{"type":"number"},"pm25_mean_short":{"type":"number"},"pm25_period_days":{"type":"integer"},"pm25_regime_detected":{"description":"Split-regime detection 2-day (v3.1)","type":"boolean"},"pm25_regime_detected_longterm":{"description":"Split-regime detection 7-day (v3.1)","type":"boolean"},"pm25_responsiveness_band":{"type":"string"},"pm25_sample_count":{"type":"integer"},"pm25_trend":{"type":"string"},"pm25_vtr":{"type":"number"},"sensor_model":{"type":"string"},"updated_at":{"type":"string"}},"type":"object"},"internal_handlers.AlertDismissResponse":{"properties":{"message":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers.AlertResponse":{"properties":{"alert_type":{"type":"string"},"baseline_value":{"type":"number"},"confidence":{"type":"number"},"current_value":{"type":"number"},"degradation_pct":{"type":"number"},"detected_at":{"type":"string"},"dismissed_at":{"type":"string"},"dismissed_by":{"type":"string"},"event_end_date":{"type":"string"},"event_start_date":{"type":"string"},"id":{"type":"string"},"indoor_sensor_id":{"type":"string"},"message":{"type":"string"},"metric_type":{"type":"string"},"predicted_maintenance_date":{"type":"string"},"recommendation":{"type":"string"},"severity":{"type":"string"}},"type":"object"},"internal_handlers.CalculateEffectivenessRequest":{"properties":{"intervention_id":{"type":"string"}},"type":"object"},"internal_handlers.DetectedEventResponse":{"properties":{"affected_sensor_count":{"type":"integer"},"affected_sensor_ids":{"items":{"type":"string"},"type":"array"},"co2_baseline":{"type":"number"},"co2_increase_pct":{"type":"number"},"co2_peak":{"type":"number"},"confidence_score":{"type":"number"},"correlation_id":{"type":"string"},"created_at":{"type":"string"},"duration_minutes":{"type":"integer"},"end_time":{"type":"string"},"event_category":{"type":"string"},"event_type":{"type":"string"},"id":{"type":"string"},"indoor_outdoor_ratio":{"type":"number"},"location_id":{"type":"string"},"outcome":{"type":"string"},"outcome_at":{"type":"string"},"outcome_by":{"type":"string"},"outcome_by_name":{"type":"string"},"outdoor_pm25_at_event":{"type":"number"},"peak_time":{"type":"string"},"pm25_baseline":{"type":"number"},"pm25_increase_pct":{"type":"number"},"pm25_peak":{"type":"number"},"recovery_time_minutes":{"type":"integer"},"sensor_id":{"type":"string"},"sensor_names":{"type":"string"},"severity_score":{"type":"number"},"start_time":{"type":"string"},"time_of_day_category":{"type":"string"},"user_confirmed":{"type":"boolean"},"user_feedback":{"type":"string"},"voc_increase_pct":{"type":"number"}},"type":"object"},"internal_handlers.DetectedEventsListResponse":{"properties":{"count":{"example":5,"type":"integer"},"events":{"items":{"$ref":"#/definitions/internal_handlers.DetectedEventResponse"},"type":"array"}},"type":"object"},"internal_handlers.DismissAlertRequest":{"properties":{"user_id":{"type":"string"}},"type":"object"},"internal_handlers.EventOutcomeResponse":{"properties":{"message":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers.HealthResponse":{"properties":{"hostname":{"type":"string"},"service":{"example":"api-gateway","type":"string"},"status":{"example":"healthy","type":"string"},"timestamp":{"type":"string"},"version":{"example":"2.0.0","type":"string"}},"type":"object"},"internal_handlers.LogInterventionRequest":{"properties":{"intervention_date":{"example":"2025-11-08T10:00:00Z","type":"string"},"intervention_type":{"example":"filter_change","type":"string"},"notes":{"example":"Replaced MERV-13 filter","type":"string"},"user_id":{"type":"string"}},"type":"object"},"internal_handlers.OnboardingCompletedResponse":{"properties":{"onboarding_completed":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers.ReadyResponse":{"properties":{"internal_api":{"example":true,"type":"boolean"},"ready":{"example":true,"type":"boolean"},"storage":{"example":true,"type":"boolean"},"timestamp":{"type":"string"}},"type":"object"},"internal_handlers.SelectedLocationResponse":{"properties":{"selected_location_id":{"example":"a1b2c3d4-...","type":"string"}},"type":"object"},"internal_handlers.SensorAlertsResponse":{"properties":{"alerts":{"items":{"$ref":"#/definitions/internal_handlers.AlertResponse"},"type":"array"},"count":{"example":2,"type":"integer"}},"type":"object"},"internal_handlers.SensorGradeHistoryResponse":{"properties":{"history":{"items":{"additionalProperties":true,"type":"object"},"type":"array"}},"type":"object"},"internal_handlers.SensorGradesResponse":{"properties":{"grades":{"items":{"$ref":"#/definitions/internal_handlers.AirSenseGradeJSON"},"type":"array"},"resolved_outdoor_sensor_id":{"type":"string"}},"type":"object"},"internal_handlers.SetEventOutcomeRequest":{"properties":{"outcome":{"description":"\"false_alert\", \"real_alert\", or \"hidden\"","type":"string"}},"type":"object"},"internal_handlers.WidgetLocationResponse":{"properties":{"widget_location_id":{"example":"a1b2c3d4-...","type":"string"}},"type":"object"},"internal_handlers.YF10XEventRequest":{"properties":{"device_id":{"type":"string"},"event_type":{"description":"\"vape\" or \"bullying\"","type":"string"},"raw_json":{"type":"string"},"timestamp":{"type":"integer"}},"type":"object"},"internal_handlers.YF10XEventResponse":{"properties":{"event_id":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers.YF10XReadingRequest":{"properties":{"atm":{"type":"integer"},"c2h5oh":{"type":"integer"},"ch4":{"type":"integer"},"co":{"type":"integer"},"co2":{"type":"integer"},"consumed_airtime":{"type":"number"},"data_rate":{"type":"string"},"device_id":{"type":"string"},"ex":{"type":"integer"},"f_cnt":{"type":"integer"},"gateway_count":{"type":"integer"},"h2":{"type":"integer"},"h2s":{"type":"integer"},"hcho":{"type":"integer"},"humidity":{"type":"number"},"lux":{"type":"integer"},"motion":{"type":"integer"},"nh3":{"type":"integer"},"no":{"type":"integer"},"no2":{"type":"integer"},"noise":{"type":"number"},"nox":{"type":"integer"},"o2":{"type":"number"},"o3":{"type":"integer"},"pm1.0":{"type":"number"},"pm10":{"type":"number"},"pm2.5":{"type":"number"},"product_model":{"type":"string"},"rssi":{"description":"LoRaWAN metadata (optional, LoRaWAN sensors only)","type":"integer"},"sensor_timestamp":{"type":"integer"},"snr":{"type":"number"},"so2":{"type":"integer"},"temperature":{"type":"number"},"tvoc":{"type":"integer"}},"type":"object"},"internal_handlers.YF10XReadingResponse":{"properties":{"message":{"example":"Reading submitted successfully","type":"string"},"reading_id":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_auth.CheckEmailRequest":{"properties":{"email":{"example":"user@example.com","type":"string"}},"required":["email"],"type":"object"},"internal_handlers_auth.CheckEmailResponse":{"properties":{"exists":{"example":true,"type":"boolean"},"first_name":{"example":"John","type":"string"},"last_name":{"example":"Doe","type":"string"}},"type":"object"},"internal_handlers_auth.EmailVerificationRequest":{"properties":{"token":{"example":"verification-token-from-email","type":"string"}},"required":["token"],"type":"object"},"internal_handlers_auth.EmailVerificationResponse":{"properties":{"message":{"example":"Email verified successfully","type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_auth.LogoutResponse":{"properties":{"message":{"example":"Logged out successfully","type":"string"}},"type":"object"},"internal_handlers_auth.MagicLinkRequest":{"properties":{"client":{"description":"Optional client type: \"ios\", \"web\", etc.","example":"ios","type":"string"},"email":{"example":"user@example.com","type":"string"},"name":{"description":"Optional user name","example":"John Doe","type":"string"},"timezone":{"description":"Optional timezone from browser","example":"America/New_York","type":"string"}},"required":["email"],"type":"object"},"internal_handlers_auth.MagicLinkResponse":{"properties":{"message":{"example":"✅ Magic link sent to your email","type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_auth.MagicLinkVerifyResponse":{"properties":{"access_token":{"example":"eyJhbGciOiJIUzI1NiIs...","type":"string"},"expires_in":{"example":3600,"type":"integer"},"message":{"example":"Magic link verified successfully","type":"string"},"refresh_token":{"example":"eyJhbGciOiJIUzI1NiIs...","type":"string"},"success":{"example":true,"type":"boolean"},"token_type":{"example":"Bearer","type":"string"},"user":{}},"type":"object"},"internal_handlers_auth.PasswordResetConfirmRequest":{"properties":{"new_password":{"example":"NewSecurePass123!","minLength":8,"type":"string"},"token":{"example":"random-base64-token-string","type":"string"}},"required":["new_password","token"],"type":"object"},"internal_handlers_auth.PasswordResetConfirmResponse":{"properties":{"message":{"example":"Password has been reset successfully","type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_auth.PasswordResetRequest":{"properties":{"email":{"example":"user@example.com","type":"string"}},"required":["email"],"type":"object"},"internal_handlers_auth.PasswordResetResponse":{"properties":{"message":{"example":"If the email exists, a password reset link has been sent","type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_auth.RefreshRequest":{"properties":{"refresh_token":{"example":"eyJhbGciOiJIUzI1NiIs...","type":"string"}},"type":"object"},"internal_handlers_auth.RefreshResponse":{"properties":{"access_token":{"example":"eyJhbGciOiJIUzI1NiIs...","type":"string"},"expires_in":{"example":3600,"type":"integer"},"refresh_token":{"example":"eyJhbGciOiJIUzI1NiIs...","type":"string"},"token_type":{"example":"Bearer","type":"string"},"user":{}},"type":"object"},"internal_handlers_auth.RegisterRequest":{"properties":{"email":{"example":"user@example.com","type":"string"},"password":{"example":"SecurePass123!","type":"string"}},"type":"object"},"internal_handlers_auth.RegisterResponse":{"properties":{"created_at":{"example":"2024-01-15T10:30:00Z","type":"string"},"email":{"example":"user@example.com","type":"string"},"id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"is_active":{"example":true,"type":"boolean"},"role":{"example":"system_user","type":"string"}},"type":"object"},"internal_handlers_auth.ResendVerificationRequest":{"properties":{"email":{"example":"user@example.com","type":"string"}},"required":["email"],"type":"object"},"internal_handlers_auth.ResendVerificationResponse":{"properties":{"message":{"example":"Verification email sent","type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_auth.SocialLoginRequest":{"properties":{"email":{"example":"user@example.com","type":"string"},"name":{"example":"John Doe","type":"string"},"oauth_id":{"example":"1234567890","type":"string"},"provider":{"example":"google","type":"string"}},"required":["email","oauth_id","provider"],"type":"object"},"internal_handlers_auth.SocialLoginResponse":{"properties":{"access_token":{"example":"eyJhbGciOiJIUzI1NiIs...","type":"string"},"expires_in":{"example":3600,"type":"integer"},"refresh_token":{"example":"eyJhbGciOiJIUzI1NiIs...","type":"string"},"user":{}},"type":"object"},"internal_handlers_auth.VerificationCodeRequest":{"properties":{"email":{"example":"user@example.com","type":"string"},"first_name":{"example":"John","type":"string"},"last_name":{"example":"Doe","type":"string"},"timezone":{"example":"America/New_York","type":"string"}},"required":["email"],"type":"object"},"internal_handlers_auth.VerificationCodeResponse":{"properties":{"message":{"example":"✅ Verification code sent to your email","type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_auth.VerifyCodeRequest":{"properties":{"code":{"example":"123456","type":"string"},"email":{"example":"user@example.com","type":"string"},"first_name":{"example":"John","type":"string"},"last_name":{"example":"Doe","type":"string"}},"required":["code","email"],"type":"object"},"internal_handlers_devices.RegisterDeviceTokenRequest":{"properties":{"app_version":{"type":"string"},"device_model":{"type":"string"},"device_token":{"type":"string"},"device_type":{"description":"\"ios\" or \"android\"","type":"string"},"environment":{"description":"\"production\", \"sandbox\", \"development\", \"simulator\"","type":"string"},"os_version":{"type":"string"}},"type":"object"},"internal_handlers_devices.RegisterDeviceTokenResponse":{"properties":{"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"internal_handlers_devices.UnregisterDeviceTokenRequest":{"properties":{"device_token":{"type":"string"}},"type":"object"},"internal_handlers_devices.UnregisterDeviceTokenResponse":{"properties":{"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"internal_handlers_environmental.Band":{"properties":{"band_key":{"type":"string"},"band_name":{"type":"string"},"color":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"max_value":{"type":"number"},"metric_type":{"type":"string"},"min_value":{"description":"Omit if 0","type":"number"},"sort_order":{"type":"integer"},"text_color":{"type":"string"}},"type":"object"},"internal_handlers_environmental.BandForValueResponse":{"properties":{"band":{"$ref":"#/definitions/internal_handlers_environmental.EffectiveBand"},"metric_type":{"example":"aqi_outdoor","type":"string"},"value":{"example":42,"type":"number"}},"type":"object"},"internal_handlers_environmental.BandOverride":{"properties":{"band_key":{"example":"good","type":"string"},"band_name":{"type":"string"},"color":{"example":"#00E400","type":"string"},"created_at":{"example":"2026-06-03T08:00:00Z","type":"string"},"description":{"type":"string"},"id":{"example":7,"type":"integer"},"location_id":{"type":"string"},"max_value":{"type":"number"},"metric_type":{"example":"aqi_outdoor","type":"string"},"reason":{"type":"string"},"text_color":{"type":"string"}},"type":"object"},"internal_handlers_environmental.BandOverrideCreatedResponse":{"properties":{"band_key":{"example":"good","type":"string"},"id":{"example":7,"type":"integer"},"location_id":{"type":"string"},"message":{"type":"string"},"metric_type":{"example":"aqi_outdoor","type":"string"}},"type":"object"},"internal_handlers_environmental.BandOverrideMessageResponse":{"properties":{"id":{"example":7,"type":"integer"},"message":{"type":"string"}},"type":"object"},"internal_handlers_environmental.BandOverridesListResponse":{"properties":{"overrides":{"items":{"$ref":"#/definitions/internal_handlers_environmental.BandOverride"},"type":"array"}},"type":"object"},"internal_handlers_environmental.DailyForecastDay":{"properties":{"date":{"example":"2026-06-04","type":"string"},"day_name":{"example":"Thursday","type":"string"},"precipitation_probability":{"type":"number"},"temperature_max":{"type":"number"},"temperature_min":{"type":"number"},"uv_index_max":{"type":"number"},"weather_code":{"type":"integer"},"wind_speed_max":{"type":"number"}},"type":"object"},"internal_handlers_environmental.DailyForecastResponse":{"properties":{"forecast":{"items":{"$ref":"#/definitions/internal_handlers_environmental.DailyForecastDay"},"type":"array"},"source":{"type":"string"}},"type":"object"},"internal_handlers_environmental.EffectiveBand":{"properties":{"alert_title":{"type":"string"},"band_key":{"example":"good","type":"string"},"band_name":{"example":"Good","type":"string"},"color":{"example":"#00E400","type":"string"},"description":{"type":"string"},"display_order":{"type":"integer"},"id":{"example":12,"type":"integer"},"is_problematic":{"example":false,"type":"boolean"},"max_value":{"type":"number"},"priority":{"type":"integer"},"severity":{"type":"integer"},"text_color":{"type":"string"}},"type":"object"},"internal_handlers_environmental.EffectiveBandsResponse":{"properties":{"bands":{"items":{"$ref":"#/definitions/internal_handlers_environmental.EffectiveBand"},"type":"array"},"metric_type":{"example":"aqi_outdoor","type":"string"}},"type":"object"},"internal_handlers_environmental.EnvBandDefinition":{"properties":{"alert_title":{"type":"string"},"band_name":{"example":"Good","type":"string"},"color":{"example":"#00E400","type":"string"},"description":{"type":"string"},"display_order":{"type":"integer"},"icon":{"type":"string"},"id":{"example":12,"type":"integer"},"max_value":{"example":50,"type":"number"},"metric_type":{"example":"aqi_outdoor","type":"string"},"min_value":{"example":0,"type":"number"}},"type":"object"},"internal_handlers_environmental.EnvBandTheme":{"properties":{"bands":{"items":{"$ref":"#/definitions/internal_handlers_environmental.EnvBandDefinition"},"type":"array"},"created_at":{"type":"string"},"created_by":{"type":"string"},"id":{"example":3,"type":"integer"},"is_active":{"example":true,"type":"boolean"},"level":{"example":"system","type":"string"},"location_id":{"type":"string"},"name":{"example":"EPA","type":"string"},"updated_at":{"type":"string"}},"type":"object"},"internal_handlers_environmental.EnvBandThemesResponse":{"properties":{"themes":{"items":{"$ref":"#/definitions/internal_handlers_environmental.EnvBandTheme"},"type":"array"}},"type":"object"},"internal_handlers_environmental.EnvMetricType":{"properties":{"key":{"example":"aqi_outdoor","type":"string"},"name":{"example":"Outdoor Air Quality","type":"string"}},"type":"object"},"internal_handlers_environmental.EnvMetricTypesResponse":{"properties":{"metric_types":{"items":{"$ref":"#/definitions/internal_handlers_environmental.EnvMetricType"},"type":"array"}},"type":"object"},"internal_handlers_environmental.HeatIndexHistoryPoint":{"properties":{"heat_index":{"type":"number"},"humidity":{"type":"number"},"temperature":{"type":"number"},"timestamp":{"example":1717401600,"type":"integer"}},"type":"object"},"internal_handlers_environmental.HeatIndexHistoryResponse":{"properties":{"history":{"items":{"$ref":"#/definitions/internal_handlers_environmental.HeatIndexHistoryPoint"},"type":"array"},"source":{"type":"string"}},"type":"object"},"internal_handlers_environmental.HeatIndexResponse":{"properties":{"category":{"example":"Caution","type":"string"},"heat_index":{"example":28.5,"type":"number"},"humidity":{"example":70,"type":"number"},"location_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"temperature":{"example":25,"type":"number"},"timestamp":{"example":"2024-01-15T10:30:00Z","type":"string"}},"type":"object"},"internal_handlers_environmental.HourlyCurrentTime":{"properties":{"local":{"type":"string"},"utc":{"type":"string"}},"type":"object"},"internal_handlers_environmental.HourlyEntry":{"properties":{"aqi":{"additionalProperties":true,"type":"object"},"heat_index":{"additionalProperties":true,"type":"object"},"hour_display":{"type":"string"},"is_current":{"type":"boolean"},"is_future":{"type":"boolean"},"is_past":{"type":"boolean"},"local_datetime":{"type":"string"},"utc_datetime":{"type":"string"},"uv_index":{"additionalProperties":true,"type":"object"},"weather":{"additionalProperties":true,"type":"object"}},"type":"object"},"internal_handlers_environmental.HourlyLocation":{"properties":{"city_id":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"timezone":{"type":"string"},"timezone_offset_seconds":{"type":"integer"}},"type":"object"},"internal_handlers_environmental.HourlyResponse":{"properties":{"current_time":{"$ref":"#/definitions/internal_handlers_environmental.HourlyCurrentTime"},"hours":{"items":{"$ref":"#/definitions/internal_handlers_environmental.HourlyEntry"},"type":"array"},"location":{"$ref":"#/definitions/internal_handlers_environmental.HourlyLocation"}},"type":"object"},"internal_handlers_environmental.IndoorAQIAggregateEnvironment":{"properties":{"humidity":{"type":"number"},"temperature":{"type":"number"}},"type":"object"},"internal_handlers_environmental.IndoorAQIAggregateReadings":{"properties":{"aqi":{"type":"number"},"aqi_category":{"type":"string"},"aqi_type":{"type":"string"},"band_color":{"type":"string"},"band_key":{"type":"string"},"band_name":{"type":"string"},"band_text_color":{"type":"string"},"co2":{"type":"number"},"current_aqi":{"type":"number"},"current_pm1":{"type":"number"},"current_pm10":{"type":"number"},"current_pm25":{"type":"number"},"hcho":{"type":"number"},"lux":{"type":"number"},"noise":{"type":"number"},"pm1":{"type":"number"},"pm10":{"type":"number"},"pm25":{"type":"number"},"tvoc":{"type":"number"}},"type":"object"},"internal_handlers_environmental.IndoorAQIAggregateResponse":{"properties":{"aggregate":{"$ref":"#/definitions/internal_handlers_environmental.IndoorAQIAggregateSensor"},"sensor_count":{"type":"integer"},"sensors":{"items":{"$ref":"#/definitions/internal_handlers_environmental.IndoorAQIAggregateSensor"},"type":"array"}},"type":"object"},"internal_handlers_environmental.IndoorAQIAggregateSensor":{"properties":{"environment":{"$ref":"#/definitions/internal_handlers_environmental.IndoorAQIAggregateEnvironment"},"name":{"type":"string"},"readings":{"$ref":"#/definitions/internal_handlers_environmental.IndoorAQIAggregateReadings"},"sensor_id":{"type":"string"},"sensor_type":{"type":"string"},"timestamp":{"type":"integer"},"uuid":{"type":"string"}},"type":"object"},"internal_handlers_environmental.IndoorAQICurrentResponse":{"properties":{"aqi":{"example":32,"type":"integer"},"category":{"example":"Good","type":"string"},"location_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"sensor_id":{"example":"SENSOR001","type":"string"},"timestamp":{"example":"2024-01-15T10:30:00Z","type":"string"}},"type":"object"},"internal_handlers_environmental.IndoorAQIHistoryPoint":{"properties":{"aqi":{"example":42,"type":"number"},"co2":{"type":"number"},"humidity":{"type":"number"},"pm1":{"type":"number"},"pm10":{"type":"number"},"pm25":{"type":"number"},"temperature":{"type":"number"},"timestamp":{"example":1717401600,"type":"integer"},"value":{"type":"number"}},"type":"object"},"internal_handlers_environmental.IndoorAQIHistoryResponse":{"properties":{"history":{"items":{"$ref":"#/definitions/internal_handlers_environmental.IndoorAQIHistoryPoint"},"type":"array"}},"type":"object"},"internal_handlers_environmental.MetricDataPoint":{"properties":{"timestamp":{"example":1717401600,"type":"integer"},"value":{"example":42,"type":"number"}},"type":"object"},"internal_handlers_environmental.MetricHistoryResponse":{"properties":{"interval":{"example":"hour","type":"string"},"metric":{"example":"pm25","type":"string"},"sensor_count":{"example":3,"type":"integer"},"sensors":{"items":{"$ref":"#/definitions/internal_handlers_environmental.MetricHistorySensor"},"type":"array"},"time_range":{"example":"24h","type":"string"},"unit":{"example":"µg/m³","type":"string"}},"type":"object"},"internal_handlers_environmental.MetricHistorySensor":{"properties":{"data":{"items":{"$ref":"#/definitions/internal_handlers_environmental.MetricDataPoint"},"type":"array"},"floor":{"type":"string"},"name":{"type":"string"},"room":{"type":"string"},"sensor_id":{"type":"string"}},"type":"object"},"internal_handlers_environmental.NearestSensorResponse":{"properties":{"aqi":{"description":"AQI data","type":"integer"},"band_color":{"type":"string"},"band_name":{"type":"string"},"band_text_color":{"type":"string"},"city_id":{"type":"string"},"city_name":{"type":"string"},"distance_km":{"type":"number"},"heat_index":{"type":"number"},"humidity":{"type":"number"},"neighborhood":{"type":"string"},"pm10":{"type":"number"},"pm25":{"type":"number"},"sensor_id":{"description":"Sensor info","type":"string"},"sensor_name":{"type":"string"},"sunrise":{"type":"string"},"sunset":{"type":"string"},"temperature":{"description":"Weather data","type":"number"},"timestamp":{"type":"string"},"uv_index":{"type":"number"},"weather_code":{"type":"integer"},"weather_description":{"type":"string"}},"type":"object"},"internal_handlers_environmental.OutdoorAQICurrentResponse":{"properties":{"aqi":{"example":45,"type":"integer"},"category":{"example":"Good","type":"string"},"location_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"primary_pollutant":{"example":"PM2.5","type":"string"},"timestamp":{"example":"2024-01-15T10:30:00Z","type":"string"}},"type":"object"},"internal_handlers_environmental.OutdoorAQIForecastPoint":{"properties":{"aqi":{"type":"number"},"pm10":{"type":"number"},"pm25":{"type":"number"},"timestamp":{"example":1717401600,"type":"integer"},"value":{"type":"number"}},"type":"object"},"internal_handlers_environmental.OutdoorAQIPoint":{"properties":{"aqi":{"example":55,"type":"number"},"pm10":{"type":"number"},"pm25":{"type":"number"},"timestamp":{"example":1717401600,"type":"integer"},"value":{"example":55,"type":"number"}},"type":"object"},"internal_handlers_environmental.OutdoorAQIWidgetResponse":{"properties":{"aqi":{"type":"number"},"band_color":{"type":"string"},"band_name":{"type":"string"},"city_name":{"type":"string"},"heat_index":{"type":"number"},"humidity":{"type":"number"},"indoor_aqi":{"type":"integer"},"indoor_history":{"items":{"$ref":"#/definitions/internal_handlers_environmental.WidgetAQIHistoryPoint"},"type":"array"},"indoor_pm25":{"type":"number"},"location_name":{"example":"Home","type":"string"},"neighborhood":{"type":"string"},"no_outdoor_sensor":{"type":"boolean"},"outdoor_history":{"items":{"$ref":"#/definitions/internal_handlers_environmental.WidgetAQIHistoryPoint"},"type":"array"},"pm25":{"type":"number"},"sunrise":{"type":"string"},"sunset":{"type":"string"},"temperature":{"type":"number"},"timestamp":{"example":"2026-06-03T08:00:00Z","type":"string"},"uv_index":{"type":"number"},"weather_code":{"type":"integer"}},"type":"object"},"internal_handlers_environmental.SuccessMessageResponse":{"properties":{"message":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_environmental.Theme":{"properties":{"bands":{"items":{"$ref":"#/definitions/internal_handlers_environmental.Band"},"type":"array"},"created_at":{"type":"string"},"created_by":{"type":"string"},"description":{"type":"string"},"id":{"type":"string"},"is_active":{"type":"boolean"},"is_editable":{"type":"boolean"},"level":{"type":"string"},"location_id":{"type":"string"},"name":{"type":"string"},"updated_at":{"type":"string"}},"type":"object"},"internal_handlers_environmental.ThemesResponse":{"properties":{"themes":{"items":{"$ref":"#/definitions/internal_handlers_environmental.Theme"},"type":"array"}},"type":"object"},"internal_handlers_environmental.UVIndexHistoryPoint":{"properties":{"timestamp":{"example":1717401600,"type":"integer"},"uv_index":{"example":3.5,"type":"number"}},"type":"object"},"internal_handlers_environmental.UVIndexHistoryResponse":{"properties":{"history":{"items":{"$ref":"#/definitions/internal_handlers_environmental.UVIndexHistoryPoint"},"type":"array"},"source":{"type":"string"}},"type":"object"},"internal_handlers_environmental.UVIndexResponse":{"properties":{"band_color":{"type":"string"},"band_key":{"type":"string"},"band_name":{"type":"string"},"band_text_color":{"type":"string"},"current_uv":{"example":4.5,"type":"number"},"hourly_timestamps":{"items":{"type":"string"},"type":"array"},"hourly_uv":{"items":{"type":"number"},"type":"array"},"provider":{"example":"tomorrow.io","type":"string"},"status":{"example":"success","type":"string"},"timestamp":{"example":"2024-01-15T10:30:00Z","type":"string"},"uv_index":{"example":4.5,"type":"number"}},"type":"object"},"internal_handlers_environmental.WeatherCurrentResponse":{"properties":{"condition":{"example":"partly_cloudy","type":"string"},"humidity":{"example":65,"type":"number"},"location_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"temperature":{"example":22.5,"type":"number"},"timestamp":{"example":"2024-01-15T10:30:00Z","type":"string"}},"type":"object"},"internal_handlers_environmental.WeatherMapping":{"properties":{"description":{"type":"string"},"icon_day":{"type":"string"},"icon_night":{"type":"string"},"normalized_code":{"type":"string"},"provider":{"type":"string"},"provider_code":{"type":"string"},"severity":{"type":"integer"}},"type":"object"},"internal_handlers_environmental.WeatherMappingsResponse":{"properties":{"mappings":{"items":{"$ref":"#/definitions/internal_handlers_environmental.WeatherMapping"},"type":"array"}},"type":"object"},"internal_handlers_environmental.WidgetAQIHistoryPoint":{"properties":{"aqi":{"example":55,"type":"integer"},"pm10":{"type":"number"},"pm25":{"type":"number"},"timestamp":{"example":1717401600,"type":"integer"},"value":{"example":55,"type":"integer"}},"type":"object"},"internal_handlers_environmental.nearbySensor":{"properties":{"city_id":{"type":"string"},"distance_km":{"type":"number"},"id":{"type":"string"},"is_public":{"type":"boolean"},"latitude":{"type":"number"},"longitude":{"type":"number"},"name":{"type":"string"},"owning_location_id":{"type":"string"}},"type":"object"},"internal_handlers_environmental.nearbySensorsResponse":{"properties":{"sensors":{"items":{"$ref":"#/definitions/internal_handlers_environmental.nearbySensor"},"type":"array"}},"type":"object"},"internal_handlers_feedback.SubmitFeedbackRequest":{"properties":{"app_version":{"description":"e.g., \"1.2.3\"","type":"string"},"category":{"description":"bug, feature, question, other","type":"string"},"device_model":{"description":"e.g., \"iPhone 15 Pro\"","type":"string"},"message":{"description":"The feedback message","type":"string"},"os_type":{"description":"ios, android","type":"string"},"os_version":{"description":"e.g., \"17.2\"","type":"string"},"screen_context":{"description":"Optional: which screen they were on","type":"string"},"screenshot_base64":{"description":"Optional: base64-encoded screenshot","type":"string"}},"type":"object"},"internal_handlers_feedback.SubmitFeedbackResponse":{"properties":{"email_sent":{"type":"boolean"},"feedback_id":{"type":"string"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"internal_handlers_location_profile.Fact":{"properties":{"category":{"type":"string"},"confidence":{"type":"number"},"created_at":{"type":"string"},"created_by":{"type":"string"},"fact":{"type":"string"},"id":{"type":"string"},"location_id":{"type":"string"},"source":{"type":"string"},"updated_at":{"type":"string"}},"type":"object"},"internal_handlers_location_profile.FactsResponse":{"properties":{"facts":{"items":{"$ref":"#/definitions/internal_handlers_location_profile.Fact"},"type":"array"},"total":{"example":7,"type":"integer"}},"type":"object"},"internal_handlers_locations.AIInsightMetric":{"properties":{"band_color":{"example":"#F59E0B","type":"string"},"band_id":{"example":123,"type":"integer"},"band_name":{"example":"Elevated","type":"string"},"band_text_color":{"example":"#000000","type":"string"},"metric_type":{"example":"co2","type":"string"},"priority":{"example":1,"type":"integer"},"sensor_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"sensor_name":{"example":"Living Room","type":"string"},"severity":{"example":2,"type":"integer"},"template_text":{"example":"Elevated CO2 - open windows","type":"string"},"value":{"example":850.5,"type":"number"}},"type":"object"},"internal_handlers_locations.AIInsightSection":{"properties":{"has_issues":{"example":true,"type":"boolean"},"message":{"example":"Elevated CO2 detected","type":"string"},"metrics":{"items":{"$ref":"#/definitions/internal_handlers_locations.AIInsightMetric"},"type":"array"},"recommendations":{"items":{"type":"string"},"type":"array"}},"type":"object"},"internal_handlers_locations.AIInsightsResponse":{"properties":{"generated_at":{"example":"2024-01-15T10:30:00Z","type":"string"},"has_any_issues":{"example":true,"type":"boolean"},"highest_priority":{"example":1,"type":"integer"},"id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"language":{"example":"en","type":"string"},"location_id":{"example":"123e4567-e89b-12d3-a456-426614174000","type":"string"},"metrics_covered":{"items":{"type":"string"},"type":"array"},"sections":{"additionalProperties":{"$ref":"#/definitions/internal_handlers_locations.AIInsightSection"},"type":"object"},"total_metrics_analyzed":{"example":12,"type":"integer"},"total_sensors_analyzed":{"example":3,"type":"integer"}},"type":"object"},"internal_handlers_locations.AcceptInvitationRequest":{"properties":{"invitation_id":{"type":"string"},"invitation_token":{"type":"string"}},"type":"object"},"internal_handlers_locations.AcceptedInvitationLocation":{"properties":{"description":{"type":"string"},"id":{"type":"string"},"name":{"example":"Home","type":"string"},"role":{"example":"location_user","type":"string"}},"type":"object"},"internal_handlers_locations.AddUserRequest":{"properties":{"email":{"description":"Support both v1 and v2 field names","example":"newuser@example.com","type":"string"},"permission":{"description":"v2 field name","example":"viewer","type":"string"},"role":{"description":"v1 field name","type":"string"},"send_invite":{"example":true,"type":"boolean"},"user_email":{"description":"v1 field name","type":"string"}},"type":"object"},"internal_handlers_locations.AddedLocationUser":{"properties":{"email":{"example":"member@example.com","type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"}},"type":"object"},"internal_handlers_locations.CancelInvitationRequest":{"properties":{"email":{"example":"user@example.com","type":"string"}},"type":"object"},"internal_handlers_locations.CardInsightsResponse":{"type":"object"},"internal_handlers_locations.CreateLocationRequest":{"properties":{"description":{"example":"My home office air quality monitoring","type":"string"},"is_public":{"example":false,"type":"boolean"},"latitude":{"example":40.7128,"type":"number"},"location_type":{"example":"default","type":"string"},"longitude":{"example":-74.006,"type":"number"},"name":{"example":"Home Office","type":"string"},"primary_language":{"example":"en","type":"string"},"units_preference":{"example":"metric","type":"string"}},"type":"object"},"internal_handlers_locations.CreateLocationResponse":{"properties":{"data":{"properties":{"created_at":{"example":"2024-01-15T10:30:00Z","type":"string"},"created_by":{"example":"user@example.com","type":"string"},"description":{"example":"My home office air quality monitoring","type":"string"},"id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"is_public":{"example":false,"type":"boolean"},"latitude":{"example":40.7128,"type":"number"},"location_type":{"example":"default","type":"string"},"longitude":{"example":-74.006,"type":"number"},"name":{"example":"Home Office","type":"string"},"primary_language":{"example":"en","type":"string"},"units_preference":{"example":"metric","type":"string"},"updated_at":{"example":"2024-01-15T10:30:00Z","type":"string"}},"type":"object"}},"type":"object"},"internal_handlers_locations.CreateLocationSensorRequest":{"properties":{"device_url":{"example":"","type":"string"},"is_active":{"example":true,"type":"boolean"},"is_public":{"example":false,"type":"boolean"},"name":{"example":"Living Room Sensor","type":"string"},"sensor_id":{"example":"UUID for public sensors","type":"string"},"sensor_type":{"example":"indoor_air_quality","type":"string"}},"required":["sensor_type"],"type":"object"},"internal_handlers_locations.CreateSuppressionRequest":{"properties":{"band_id":{"example":3,"type":"integer"},"duration_days":{"description":"0 = forever","example":30,"type":"integer"},"location_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"metric_type":{"example":"humidity","type":"string"},"note":{"example":"Kitchen when cooking","type":"string"},"scope":{"description":"\"personal\" or \"location_wide\"","example":"personal","type":"string"},"sensor_id":{"example":"550e8400-e29b-41d4-a716-446655440001","type":"string"}},"type":"object"},"internal_handlers_locations.CreateSuppressionResponse":{"properties":{"created_at":{"example":"2025-10-26T10:00:00Z","type":"string"},"scope":{"example":"personal","type":"string"},"suppressed_until":{"description":"null if forever","example":"2025-11-25T10:00:00Z","type":"string"},"suppression_id":{"example":"550e8400-e29b-41d4-a716-446655440002","type":"string"}},"type":"object"},"internal_handlers_locations.DeclineInvitationRequest":{"properties":{"invitation_id":{"type":"string"}},"required":["invitation_id"],"type":"object"},"internal_handlers_locations.GetLocationResponse":{"properties":{"created_at":{"example":"2024-01-15T10:30:00Z","type":"string"},"created_by":{"example":"user@example.com","type":"string"},"description":{"example":"My home office air quality monitoring","type":"string"},"id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"is_public":{"example":false,"type":"boolean"},"latitude":{"example":40.7128,"type":"number"},"location_type":{"example":"default","type":"string"},"longitude":{"example":-74.006,"type":"number"},"name":{"example":"Home Office","type":"string"},"primary_language":{"example":"en","type":"string"},"sensor_count":{"example":3,"type":"integer"},"units_preference":{"example":"metric","type":"string"},"updated_at":{"example":"2024-01-15T10:30:00Z","type":"string"},"user_role":{"example":"owner","type":"string"}},"type":"object"},"internal_handlers_locations.IndoorSensorRegisterResponse":{"properties":{"message":{"type":"string"},"sensor":{"$ref":"#/definitions/internal_handlers_locations.RegisteredIndoorSensor"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_locations.InsightSuppressionItem":{"properties":{"band_id":{"example":3,"type":"integer"},"band_name":{"example":"Too Humid","type":"string"},"created_at":{"example":"2025-10-26T10:00:00Z","type":"string"},"created_by_id":{"example":"550e8400-e29b-41d4-a716-446655440003","type":"string"},"created_by_name":{"example":"Admin Name","type":"string"},"id":{"example":"550e8400-e29b-41d4-a716-446655440002","type":"string"},"location_name":{"example":"Home","type":"string"},"metric_type":{"example":"humidity","type":"string"},"note":{"example":"Kitchen when cooking","type":"string"},"scope":{"example":"personal","type":"string"},"sensor_id":{"example":"550e8400-e29b-41d4-a716-446655440001","type":"string"},"sensor_name":{"example":"Kitchen","type":"string"},"suppressed_until":{"description":"null if forever","example":"2025-11-25T10:00:00Z","type":"string"}},"type":"object"},"internal_handlers_locations.InvitationAcceptedResponse":{"properties":{"location":{"$ref":"#/definitions/internal_handlers_locations.AcceptedInvitationLocation"},"message":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_locations.InvitationCancelledResponse":{"properties":{"message":{"type":"string"},"timestamp":{"example":"2026-06-03T08:00:00Z","type":"string"}},"type":"object"},"internal_handlers_locations.InvitationDetails":{"properties":{"expires_at":{"example":"2026-06-10T08:00:00Z","type":"string"},"invitation_token":{"type":"string"},"invited_by":{"type":"string"},"location_id":{"type":"string"},"location_name":{"example":"Home","type":"string"},"message":{"type":"string"},"recipient":{"example":"invitee@example.com","type":"string"},"role":{"example":"location_user","type":"string"}},"type":"object"},"internal_handlers_locations.InvitationRequest":{"properties":{"email":{"example":"newuser@example.com","type":"string"},"message":{"example":"Join my location!","type":"string"},"role":{"enum":["location_user","location_admin"],"example":"location_user","type":"string"}},"required":["email","role"],"type":"object"},"internal_handlers_locations.InvitationSentResponse":{"properties":{"details":{"$ref":"#/definitions/internal_handlers_locations.InvitationDetails"},"email_error":{"type":"string"},"email_sent":{"example":true,"type":"boolean"},"message":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_locations.ListLocationsResponse":{"properties":{"count":{"example":5,"type":"integer"},"locations":{"items":{"additionalProperties":true,"type":"object"},"type":"array"}},"type":"object"},"internal_handlers_locations.ListSuppressionsResponse":{"properties":{"suppressions":{"items":{"$ref":"#/definitions/internal_handlers_locations.InsightSuppressionItem"},"type":"array"},"total":{"example":5,"type":"integer"}},"type":"object"},"internal_handlers_locations.LocationActionResponse":{"properties":{"message":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_locations.LocationInvitation":{"properties":{"created_at":{"example":"2026-06-03T08:00:00Z","type":"string"},"email":{"example":"invitee@example.com","type":"string"},"expires_at":{"example":"2026-06-10T08:00:00Z","type":"string"},"id":{"type":"string"},"invited_by":{"type":"string"},"invited_by_email":{"type":"string"},"invited_email":{"example":"invitee@example.com","type":"string"},"message":{"type":"string"},"role":{"example":"location_user","type":"string"},"status":{"example":"pending","type":"string"}},"type":"object"},"internal_handlers_locations.LocationInvitationsListResponse":{"properties":{"invitations":{"items":{"$ref":"#/definitions/internal_handlers_locations.LocationInvitation"},"type":"array"},"total":{"example":1,"type":"integer"}},"type":"object"},"internal_handlers_locations.LocationSensorMutationResponse":{"properties":{"message":{"type":"string"},"sensor":{"$ref":"#/definitions/internal_handlers_locations.LocationSensorResponse"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_locations.LocationSensorReadingsResponse":{"properties":{"location_id":{"type":"string"},"query":{"$ref":"#/definitions/internal_handlers_locations.SensorReadingsQuery"},"readings":{"items":{"additionalProperties":true,"type":"object"},"type":"array"},"sensor_id":{"type":"string"},"total":{"type":"integer"}},"type":"object"},"internal_handlers_locations.LocationSensorResponse":{"properties":{"buzzer_enabled":{"example":false,"type":"boolean"},"created_at":{"example":"2024-01-15T10:30:00Z","type":"string"},"id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"is_active":{"example":true,"type":"boolean"},"last_seen":{"example":"2024-01-15T15:45:00Z","type":"string"},"led_enabled":{"example":false,"type":"boolean"},"location_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"name":{"example":"Living Room Sensor","type":"string"},"sensor_type":{"example":"indoor_air_quality","type":"string"},"serial_number":{"example":"SENSOR123","type":"string"}},"type":"object"},"internal_handlers_locations.LocationUserAddedResponse":{"properties":{"message":{"type":"string"},"success":{"example":true,"type":"boolean"},"user":{"$ref":"#/definitions/internal_handlers_locations.AddedLocationUser"}},"type":"object"},"internal_handlers_locations.LocationUserPermissionsResponse":{"properties":{"can_delete_location":{"example":false,"type":"boolean"},"can_edit_location":{"example":false,"type":"boolean"},"can_manage_sensors":{"example":false,"type":"boolean"},"can_manage_users":{"example":false,"type":"boolean"},"can_view_location":{"example":true,"type":"boolean"},"can_view_sensors":{"example":true,"type":"boolean"},"location_id":{"type":"string"},"role":{"example":"location_user","type":"string"},"user_id":{"type":"string"}},"type":"object"},"internal_handlers_locations.LocationUserSummary":{"properties":{"added_at":{"example":"2026-06-03T08:00:00Z","type":"string"},"added_by":{"type":"string"},"id":{"type":"string"},"role":{"example":"location_user","type":"string"},"status":{"example":"active","type":"string"},"user_email":{"example":"member@example.com","type":"string"},"user_name":{"type":"string"}},"type":"object"},"internal_handlers_locations.LocationUsersListResponse":{"properties":{"count":{"example":2,"type":"integer"},"location_id":{"type":"string"},"users":{"items":{"$ref":"#/definitions/internal_handlers_locations.LocationUserSummary"},"type":"array"}},"type":"object"},"internal_handlers_locations.QRCodeResponse":{"properties":{"location_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"message":{"example":"Use a QR code library to generate image from qr_data","type":"string"},"qr_data":{"example":"https://podqair.com/locations/550e8400-e29b-41d4-a716-446655440000/join","type":"string"},"share_url":{"example":"https://podqair.com/locations/550e8400-e29b-41d4-a716-446655440000/join","type":"string"}},"type":"object"},"internal_handlers_locations.RegisterIndoorSensorRequest":{"properties":{"device_id":{"example":"2025100712400006","type":"string"},"force_register":{"example":false,"type":"boolean"},"name":{"example":"Living Room Sensor","type":"string"}},"required":["device_id"],"type":"object"},"internal_handlers_locations.RegisteredIndoorSensor":{"properties":{"device_id":{"type":"string"},"location_id":{"type":"string"},"name":{"type":"string"}},"type":"object"},"internal_handlers_locations.SensorReadingsQuery":{"properties":{"limit":{"example":100,"type":"integer"},"offset":{"example":0,"type":"integer"}},"type":"object"},"internal_handlers_locations.SuppressionDeleteResponse":{"properties":{"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_locations.UpdateLocationRequest":{"properties":{"description":{"example":"My home office air quality monitoring - updated","type":"string"},"is_public":{"description":"Pointer to distinguish null from false","example":false,"type":"boolean"},"latitude":{"example":40.7128,"type":"number"},"longitude":{"example":-74.006,"type":"number"},"name":{"example":"Home Office - Updated","type":"string"},"primary_language":{"example":"en","type":"string"},"primary_outdoor_sensor_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"units_preference":{"description":"\"metric\" or \"imperial\"","example":"metric","type":"string"}},"type":"object"},"internal_handlers_locations.UpdateLocationSensorRequest":{"properties":{"buzzer_enabled":{"example":false,"type":"boolean"},"delta_pm":{"description":"PM threshold for vape detection","example":3,"type":"integer"},"delta_voc":{"description":"VOC threshold for vape detection","example":5,"type":"integer"},"is_active":{"example":true,"type":"boolean"},"is_primary":{"description":"Set as primary outdoor sensor","example":false,"type":"boolean"},"is_public":{"example":false,"type":"boolean"},"last_time":{"description":"Detection window duration (seconds)","example":15,"type":"integer"},"led_enabled":{"example":true,"type":"boolean"},"max_noise":{"description":"Noise threshold for bullying detection (dB)","example":90,"type":"integer"},"name":{"example":"Updated Sensor Name","type":"string"},"offline_alert_after_minutes_override":{"description":"0 clears override","example":1440,"type":"integer"},"sensor_type":{"example":"indoor_air_quality","type":"string"}},"type":"object"},"internal_handlers_locations.UpdateUserPermissionRequest":{"properties":{"permission":{"description":"Support both v1 and v2 field names","example":"editor","type":"string"},"role":{"description":"v1 field name","type":"string"}},"type":"object"},"internal_handlers_notifications.AllowOneMoreRequest":{"properties":{"channel":{"type":"string"},"location_id":{"type":"string"},"message_type":{"type":"string"},"user_email":{"type":"string"}},"type":"object"},"internal_handlers_notifications.AllowOneMoreResponse":{"properties":{"allowed_today":{"type":"integer"},"message":{"type":"string"},"new_limit":{"type":"integer"},"success":{"type":"boolean"}},"type":"object"},"internal_handlers_notifications.BandInfoJSON":{"properties":{"band_key":{"type":"string"},"band_name":{"type":"string"},"color_hex":{"type":"string"},"max_value":{"type":"number"},"sort_order":{"type":"integer"}},"type":"object"},"internal_handlers_notifications.ChannelCount":{"properties":{"additional":{"example":0,"type":"integer"},"limit":{"example":5,"type":"integer"},"sent":{"example":3,"type":"integer"}},"type":"object"},"internal_handlers_notifications.MessageInfo":{"properties":{"sent_at":{"type":"string"},"subject":{"type":"string"}},"type":"object"},"internal_handlers_notifications.MessageLogEntry":{"properties":{"delivered":{"example":true,"type":"boolean"},"delivered_at":{"type":"string"},"error":{"type":"string"},"id":{"type":"string"},"message":{"type":"string"},"message_type":{"example":"daily_forecast","type":"string"},"read":{"example":false,"type":"boolean"},"read_at":{"type":"string"},"sent_at":{"example":"2026-06-03T08:00:00Z","type":"string"},"status":{"example":"sent","type":"string"},"subject":{"type":"string"},"type":{"example":"email","type":"string"}},"type":"object"},"internal_handlers_notifications.MessageLogsResponse":{"properties":{"messages":{"items":{"$ref":"#/definitions/internal_handlers_notifications.MessageLogEntry"},"type":"array"},"total":{"type":"integer"}},"type":"object"},"internal_handlers_notifications.MessageTypeChannels":{"properties":{"email":{"$ref":"#/definitions/internal_handlers_notifications.ChannelCount"},"push":{"$ref":"#/definitions/internal_handlers_notifications.ChannelCount"},"whatsapp":{"$ref":"#/definitions/internal_handlers_notifications.ChannelCount"}},"type":"object"},"internal_handlers_notifications.SensorAlertPreferenceJSON":{"properties":{"alert_state":{"type":"string"},"alerts_enabled":{"type":"boolean"},"created_at":{"type":"string"},"custom_action_message":{"type":"string"},"email_enabled":{"type":"boolean"},"frequency_hours":{"type":"integer"},"id":{"type":"string"},"last_alert_sent":{"type":"string"},"last_reminder_at":{"type":"string"},"last_state_change_at":{"type":"string"},"location_id":{"type":"string"},"metric_type":{"type":"string"},"push_enabled":{"type":"boolean"},"recovery_margin_percent":{"type":"integer"},"recovery_notification_enabled":{"type":"boolean"},"sensor_id":{"type":"string"},"threshold_value":{"type":"number"},"updated_at":{"type":"string"},"user_id":{"type":"string"},"whatsapp_enabled":{"type":"boolean"}},"type":"object"},"internal_handlers_notifications.SensorMetricInfoJSON":{"properties":{"bands":{"items":{"$ref":"#/definitions/internal_handlers_notifications.BandInfoJSON"},"type":"array"},"description":{"type":"string"},"label":{"type":"string"},"metric_type":{"type":"string"},"sensor_count":{"type":"integer"},"sensor_names":{"items":{"type":"string"},"type":"array"},"unit":{"type":"string"}},"type":"object"},"internal_handlers_notifications.TodaysMessageCountsResponse":{"properties":{"message_types":{"additionalProperties":{"$ref":"#/definitions/internal_handlers_notifications.MessageTypeChannels"},"type":"object"},"timezone":{"example":"Europe/Stockholm","type":"string"}},"type":"object"},"internal_handlers_notifications.TodaysMessagesResponse":{"properties":{"alert_email":{"items":{"$ref":"#/definitions/internal_handlers_notifications.MessageInfo"},"type":"array"},"alert_whatsapp":{"items":{"$ref":"#/definitions/internal_handlers_notifications.MessageInfo"},"type":"array"},"daily_forecast_email":{"items":{"$ref":"#/definitions/internal_handlers_notifications.MessageInfo"},"type":"array"},"daily_forecast_whatsapp":{"items":{"$ref":"#/definitions/internal_handlers_notifications.MessageInfo"},"type":"array"},"timezone":{"type":"string"}},"type":"object"},"internal_handlers_notifications.UpdateNotificationPreferencesRequest":{"properties":{"daily_forecast_email":{"type":"boolean"},"daily_forecast_push":{"type":"boolean"},"daily_forecast_time":{"items":{"type":"integer"},"type":"array"},"daily_forecast_whatsapp":{"type":"boolean"},"quiet_hours_enabled":{"type":"boolean"},"quiet_hours_end":{"items":{"type":"integer"},"type":"array"},"quiet_hours_start":{"items":{"type":"integer"},"type":"array"},"whatsapp_number":{"type":"string"}},"type":"object"},"internal_handlers_sensors.AggregatedReading":{"properties":{"average":{"type":"number"},"count":{"type":"integer"},"max":{"type":"number"},"min":{"type":"number"},"sensor_type":{"type":"string"},"time_bucket":{"example":"2026-06-03T08:00:00Z","type":"string"}},"type":"object"},"internal_handlers_sensors.BulkSensorDataRequest":{"properties":{"readings":{"items":{"$ref":"#/definitions/internal_handlers_sensors.V1SensorReading"},"minItems":1,"type":"array"}},"required":["readings"],"type":"object"},"internal_handlers_sensors.BulkSensorDataResponse":{"properties":{"created_ids":{"example":["[\"123e4567-e89b-12d3-a456-426614174000\"]"],"items":{"type":"string"},"type":"array"},"processed_count":{"example":1,"type":"integer"}},"type":"object"},"internal_handlers_sensors.BulkSilenceResponse":{"properties":{"errors":{"items":{"type":"string"},"type":"array"},"failed_count":{"type":"integer"},"silenced_count":{"type":"integer"}},"type":"object"},"internal_handlers_sensors.BulkSilenceSensorsRequest":{"properties":{"location_id":{"type":"string"},"reason":{"type":"string"},"sensor_ids":{"items":{"type":"string"},"type":"array"},"sensor_types":{"items":{"type":"string"},"type":"array"},"silence_type":{"type":"string"},"silenced_until":{"type":"string"}},"type":"object"},"internal_handlers_sensors.ConnectionStatusResponse":{"properties":{"auth_failed":{"description":"Recent failed auth attempt (device reaching server but wrong credentials)","type":"boolean"},"auth_failed_at":{"description":"When auth last failed","type":"string"},"auth_failed_attempts":{"description":"Number of failed attempts","type":"integer"},"auth_failed_reason":{"description":"Why auth failed: invalid_password, no_credentials","type":"string"},"device_id":{"type":"string"},"has_reported":{"description":"Has ever reported data","type":"boolean"},"is_online":{"type":"boolean"},"last_seen_at":{"description":"RFC3339 format","type":"string"},"seconds_ago":{"description":"How many seconds since last seen","type":"integer"}},"type":"object"},"internal_handlers_sensors.MQTTCredentialStatusResponse":{"properties":{"device_id":{"type":"string"},"has_credentials":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_sensors.MQTTSetupResponse":{"properties":{"device_id":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_sensors.MQTTVerifyResponse":{"properties":{"device_id":{"type":"string"},"matches":{"example":true,"type":"boolean"},"reason":{"type":"string"}},"type":"object"},"internal_handlers_sensors.NearbySensorResponse":{"properties":{"city_id":{"type":"string"},"description":{"type":"string"},"distance_km":{"description":"DeviceURL is intentionally omitted - use sensor ID to link","type":"number"},"id":{"type":"string"},"is_public":{"type":"boolean"},"latitude":{"type":"number"},"longitude":{"type":"number"},"name":{"type":"string"},"owning_location_id":{"description":"Location that physically owns this sensor","type":"string"}},"type":"object"},"internal_handlers_sensors.NearbySensorsListResponse":{"properties":{"count":{"example":5,"type":"integer"},"query":{"$ref":"#/definitions/internal_handlers_sensors.NearbySensorsQuery"},"sensors":{"items":{"$ref":"#/definitions/internal_handlers_sensors.NearbySensorResponse"},"type":"array"}},"type":"object"},"internal_handlers_sensors.NearbySensorsQuery":{"properties":{"latitude":{"type":"number"},"longitude":{"type":"number"},"radius_km":{"example":50,"type":"number"}},"type":"object"},"internal_handlers_sensors.OwnershipResponse":{"properties":{"can_claim":{"type":"boolean"},"can_reconfigure":{"type":"boolean"},"current_user_has_access":{"type":"boolean"},"device_id":{"type":"string"},"has_mqtt_credentials":{"description":"True if device has per-device MQTT auth set up","type":"boolean"},"owning_location_id":{"type":"string"},"owning_location_name":{"type":"string"},"sensor_type":{"description":"\"indoor\" for YF10X, \"outdoor\" for YF200, \"unknown\" if not yet determined","type":"string"},"status":{"description":"\"unclaimed\", \"claimed\"","type":"string"}},"type":"object"},"internal_handlers_sensors.ReadingsAggregateResponse":{"properties":{"data":{"items":{"$ref":"#/definitions/internal_handlers_sensors.AggregatedReading"},"type":"array"},"period":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_sensors.RegisterSensorRequest":{"properties":{"description":{"example":"Temperature sensor for living room monitoring","type":"string"},"location_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"max_value":{"example":50,"type":"number"},"min_value":{"example":-10,"type":"number"},"name":{"example":"Living Room Temperature","type":"string"},"type":{"enum":["temperature","humidity","co2","pm25","pm10","voc"],"example":"temperature","type":"string"},"unit":{"example":"celsius","type":"string"}},"required":["location_id","name","type"],"type":"object"},"internal_handlers_sensors.SensorActionResponse":{"properties":{"message":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_sensors.SensorCommandRequest":{"properties":{"buzzer":{"description":"Buzzer control: true=on, false=off (cmd:1)","type":"boolean"},"delta_pm":{"description":"PM threshold delta","type":"integer"},"delta_voc":{"description":"Threshold settings for vape/bullying detection (cmd:0)","type":"integer"},"last_time":{"description":"Duration threshold (seconds)","type":"integer"},"led":{"description":"Device controls","type":"boolean"},"max_noise":{"description":"Max noise level (dB)","type":"integer"},"raw_command":{"description":"Raw command (for advanced use)","items":{"type":"integer"},"type":"array"}},"type":"object"},"internal_handlers_sensors.SensorCommandResponse":{"properties":{"error":{"type":"string"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"internal_handlers_sensors.SensorDataSubmitResponse":{"properties":{"message":{"example":"Reading stored successfully with ID: abc123","type":"string"}},"type":"object"},"internal_handlers_sensors.SensorMetadata":{"properties":{"has_sensor1":{"type":"boolean"},"has_sensor2":{"type":"boolean"},"location":{"type":"string"},"sensor1_type":{"type":"string"},"sensor2_type":{"type":"string"},"sensor_brand":{"type":"string"},"sensor_type":{"type":"string"}},"type":"object"},"internal_handlers_sensors.SensorOfflineSilenceInfo":{"properties":{"reason":{"type":"string"},"silence_type":{"type":"string"}},"type":"object"},"internal_handlers_sensors.SensorOfflineStatus":{"properties":{"device_id":{"type":"string"},"is_silenced":{"type":"boolean"},"name":{"type":"string"},"offline_duration_seconds":{"type":"integer"},"sensor_id":{"type":"string"},"sensor_type":{"type":"string"},"silence_info":{"$ref":"#/definitions/internal_handlers_sensors.SensorOfflineSilenceInfo"},"status":{"example":"online","type":"string"}},"type":"object"},"internal_handlers_sensors.SensorOfflineStatusResponse":{"properties":{"critical_count":{"type":"integer"},"dead_count":{"type":"integer"},"online_count":{"type":"integer"},"silenced_count":{"type":"integer"},"statuses":{"items":{"$ref":"#/definitions/internal_handlers_sensors.SensorOfflineStatus"},"type":"array"},"total_sensors":{"type":"integer"},"warning_count":{"type":"integer"}},"type":"object"},"internal_handlers_sensors.SensorReadingsData":{"properties":{"aqi":{"type":"integer"},"aqi_category":{"type":"string"},"aqi_type":{"type":"string"},"co2":{"type":"number"},"humidity":{"type":"number"},"pm1":{"type":"number"},"pm10":{"type":"number"},"pm10_alt":{"type":"number"},"pm10_sens1":{"type":"number"},"pm10_sens2":{"type":"number"},"pm1_alt":{"type":"number"},"pm1_sens1":{"type":"number"},"pm1_sens2":{"type":"number"},"pm25":{"type":"number"},"pm25_alt":{"type":"number"},"pm25_sens1":{"type":"number"},"pm25_sens2":{"type":"number"},"pm4":{"type":"number"},"pm4_alt":{"type":"number"},"pm4_sens1":{"type":"number"},"pm4_sens2":{"type":"number"},"sensor1_type":{"type":"string"},"sensor2_type":{"type":"string"},"temperature":{"type":"number"},"voc":{"type":"number"}},"type":"object"},"internal_handlers_sensors.SetupCredentialsRequest":{"properties":{"device_id":{"type":"string"},"password":{"description":"Plain password, will be hashed server-side","type":"string"}},"type":"object"},"internal_handlers_sensors.SilenceSensorRequest":{"properties":{"location_id":{"type":"string"},"reason":{"type":"string"},"sensor_id":{"type":"string"},"sensor_type":{"type":"string"},"silence_type":{"type":"string"},"silenced_until":{"type":"string"}},"type":"object"},"internal_handlers_sensors.UnclaimedSensorResponse":{"properties":{"device_id":{"type":"string"},"first_seen":{"description":"registered_at","type":"string"},"last_reading":{"description":"last_seen_at","type":"string"},"seconds_ago":{"description":"seconds since last reading","type":"integer"},"sensor_model":{"type":"string"}},"type":"object"},"internal_handlers_sensors.UnclaimedSensorsListResponse":{"properties":{"count":{"example":3,"type":"integer"},"query":{"$ref":"#/definitions/internal_handlers_sensors.UnclaimedSensorsQuery"},"sensors":{"items":{"$ref":"#/definitions/internal_handlers_sensors.UnclaimedSensorResponse"},"type":"array"}},"type":"object"},"internal_handlers_sensors.UnclaimedSensorsQuery":{"properties":{"hours":{"example":24,"type":"integer"},"limit":{"example":20,"type":"integer"}},"type":"object"},"internal_handlers_sensors.UpdateSensorRequest":{"properties":{"description":{"example":"Updated description","type":"string"},"max_value":{"example":50,"type":"number"},"min_value":{"example":-10,"type":"number"},"name":{"example":"Living Room Temperature - Updated","type":"string"},"status":{"enum":["active","inactive","calibrating","maintenance"],"example":"active","type":"string"}},"type":"object"},"internal_handlers_sensors.V1SensorReading":{"properties":{"metadata":{"$ref":"#/definitions/internal_handlers_sensors.SensorMetadata"},"readings":{"$ref":"#/definitions/internal_handlers_sensors.SensorReadingsData"},"sensor_id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"source":{"example":"podq-sensor-v2","type":"string"},"timestamp":{"example":1705318200,"type":"integer"}},"required":["readings","sensor_id"],"type":"object"},"internal_handlers_sensors.VerifyCredentialsRequest":{"properties":{"device_id":{"type":"string"},"password":{"description":"Plain password to verify against stored hash","type":"string"}},"type":"object"},"internal_handlers_settings.ColorSchemeResponse":{"properties":{"colors":{"additionalProperties":true,"type":"object"},"created_at":{"type":"string"},"description":{"type":"string"},"id":{"type":"integer"},"is_default":{"type":"boolean"},"name":{"type":"string"}},"type":"object"},"internal_handlers_settings.EffectiveColorsResponse":{"properties":{"colors":{"additionalProperties":true,"type":"object"},"scheme_name":{"type":"string"},"source":{"description":"\"user\", \"location\", \"system\", \"default\"","type":"string"},"system_scheme_id":{"type":"integer"}},"type":"object"},"internal_handlers_settings.SettingsUpdateRequest":{"properties":{"color_scheme_id":{"type":"integer"},"custom_colors":{"additionalProperties":true,"type":"object"}},"type":"object"},"internal_handlers_settings.SettingsUpdateResult":{"properties":{"message":{"type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_users.AckResponse":{"properties":{"message":{"example":"Device deleted","type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_users.ChangePasswordRequest":{"properties":{"current_password":{"type":"string"},"new_password":{"type":"string"}},"type":"object"},"internal_handlers_users.ChangePasswordResponse":{"properties":{"message":{"example":"Password changed successfully","type":"string"}},"type":"object"},"internal_handlers_users.CheckAccessRequest":{"properties":{"email":{"type":"string"}},"type":"object"},"internal_handlers_users.CheckAccessResponse":{"properties":{"message":{"type":"string"},"status":{"description":"\"existing_user\", \"new_user\", \"needs_name\", \"not_invited\"","type":"string"}},"type":"object"},"internal_handlers_users.DeleteAccountRequest":{"properties":{"confirm":{"type":"boolean"},"password":{"description":"Optional - only required for password-based auth","type":"string"}},"type":"object"},"internal_handlers_users.DeleteAccountResponse":{"properties":{"message":{"example":"Account deleted successfully","type":"string"}},"type":"object"},"internal_handlers_users.DeviceInfo":{"properties":{"device_id":{"example":"7e3b1c2a-9f8d-4a1b-8c2e-1f2a3b4c5d6e","type":"string"},"device_name":{"example":"Fred's iPhone","type":"string"},"device_type":{"example":"ios","type":"string"},"first_seen":{"example":"2026-01-15T09:30:00Z","type":"string"},"last_seen":{"example":"2026-06-03T08:00:00Z","type":"string"}},"type":"object"},"internal_handlers_users.DeviceMutationResponse":{"properties":{"device":{"$ref":"#/definitions/internal_handlers_users.DeviceInfo"},"message":{"example":"Device registered","type":"string"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_users.DevicePreference":{"properties":{"applies_to_all_devices":{"example":false,"type":"boolean"},"created_at":{"type":"string"},"device_id":{"type":"string"},"display_mode":{"example":"aqi","type":"string"},"id":{"example":"b2c3d4e5-...","type":"string"},"location_id":{"type":"string"},"primary_sensor_id":{"type":"string"},"updated_at":{"type":"string"},"user_id":{"type":"string"}},"type":"object"},"internal_handlers_users.DevicePreferenceMutationResponse":{"properties":{"message":{"type":"string"},"preference":{"$ref":"#/definitions/internal_handlers_users.DevicePreference"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_users.DevicePreferencesListResponse":{"properties":{"preferences":{"items":{"$ref":"#/definitions/internal_handlers_users.DevicePreference"},"type":"array"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_users.DeviceRegistrationStatus":{"properties":{"deactivated_at":{"type":"string"},"deactivation_reason":{"type":"string"},"device_id":{"example":"7e3b1c2a-9f8d-4a1b-8c2e-1f2a3b4c5d6e","type":"string"},"device_name":{"example":"Fred's iPhone","type":"string"},"is_active":{"example":true,"type":"boolean"},"last_used_at":{"type":"string"},"needs_reregistration":{"example":false,"type":"boolean"}},"type":"object"},"internal_handlers_users.DevicesListResponse":{"properties":{"devices":{"items":{"$ref":"#/definitions/internal_handlers_users.DeviceInfo"},"type":"array"},"success":{"example":true,"type":"boolean"}},"type":"object"},"internal_handlers_users.GetMeResponse":{"properties":{"created_at":{"example":"2024-01-15T10:30:00Z","type":"string"},"email":{"example":"user@example.com","type":"string"},"first_name":{"example":"John","type":"string"},"id":{"example":"550e8400-e29b-41d4-a716-446655440000","type":"string"},"is_active":{"example":true,"type":"boolean"},"last_name":{"example":"Doe","type":"string"},"phone_number":{"example":"+971501234567","type":"string"},"role":{"example":"system_user","type":"string"}},"type":"object"},"internal_handlers_users.PendingInvitation":{"properties":{"created_at":{"example":"2026-06-03T08:00:00Z","type":"string"},"expires_at":{"example":"2026-06-10T08:00:00Z","type":"string"},"id":{"type":"string"},"invited_by":{"type":"string"},"invited_by_email":{"type":"string"},"invited_email":{"example":"user@example.com","type":"string"},"location_id":{"type":"string"},"location_name":{"example":"Home","type":"string"},"message":{"type":"string"},"role":{"example":"member","type":"string"},"status":{"example":"pending","type":"string"}},"type":"object"},"internal_handlers_users.PendingInvitationsResponse":{"properties":{"invitations":{"items":{"$ref":"#/definitions/internal_handlers_users.PendingInvitation"},"type":"array"},"total":{"example":2,"type":"integer"}},"type":"object"},"internal_handlers_users.ProfileUpdateMessageResponse":{"properties":{"message":{"example":"Profile updated successfully","type":"string"}},"type":"object"},"internal_handlers_users.UpdateProfilePhoneRequest":{"properties":{"phone_number":{"type":"string"}},"type":"object"},"internal_handlers_users.UpdateProfileRequest":{"properties":{"company":{"type":"string"},"email":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"},"phone_number":{"type":"string"}},"type":"object"},"internal_handlers_users.UpdateProfileResponse":{"properties":{"data":{}},"type":"object"},"internal_handlers_users.UpdateProfileV1Request":{"properties":{"name":{"type":"string"}},"type":"object"},"internal_handlers_users.UserProfileV1":{"properties":{"created_at":{"type":"string"},"email":{"example":"user@example.com","type":"string"},"email_verified":{"example":false,"type":"boolean"},"id":{"example":"user@example.com","type":"string"},"is_active":{"example":true,"type":"boolean"},"last_login":{"type":"string"},"name":{"example":"Fred Borgquist","type":"string"},"permissions":{"items":{"type":"string"},"type":"array"},"phone_number":{"example":"+15551234567","type":"string"},"role":{"example":"user","type":"string"}},"type":"object"},"internal_handlers_users.startupBootstrapResponse":{"properties":{"email_delivery_status":{},"onboarding_completed":{"type":"boolean"},"pending_invitations_total":{"type":"integer"},"selected_location_id":{"type":"string"},"server_time":{"type":"string"}},"type":"object"},"internal_handlers_webhooks.TyraWebhookAck":{"properties":{"error":{"type":"string"},"status":{"example":"success","type":"string"}},"type":"object"},"pb.CalculateInterventionEffectivenessResponse":{"properties":{"intervention":{"allOf":[{"$ref":"#/definitions/pb.InterventionInfo"}],"description":"Updated with calculated metrics"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"pb.DeleteSensorAlertPreferencesResponse":{"properties":{"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"pb.GetAllSensorsResponse":{"properties":{"indoor":{"items":{"$ref":"#/definitions/pb.SensorInfo"},"type":"array"},"outdoor":{"items":{"$ref":"#/definitions/pb.SensorInfo"},"type":"array"}},"type":"object"},"pb.GetInterventionsResponse":{"properties":{"interventions":{"items":{"$ref":"#/definitions/pb.InterventionInfo"},"type":"array"}},"type":"object"},"pb.InterventionInfo":{"properties":{"beta_after_30d":{"type":"number"},"beta_before_30d":{"description":"Before/after metrics (calculated)","type":"number"},"created_at":{"$ref":"#/definitions/timestamppb.Timestamp"},"effectiveness_score":{"description":"0.0 to 1.0","type":"number"},"grade_after":{"type":"string"},"grade_before":{"description":"\"A+\", \"A\", \"B\", \"C\", \"D\"","type":"string"},"id":{"type":"string"},"indoor_sensor_id":{"type":"string"},"intervention_date":{"$ref":"#/definitions/timestamppb.Timestamp"},"intervention_type":{"$ref":"#/definitions/pb.InterventionType"},"mean_indoor_after_30d":{"type":"number"},"mean_indoor_before_30d":{"type":"number"},"notes":{"type":"string"},"updated_at":{"$ref":"#/definitions/timestamppb.Timestamp"},"user_id":{"type":"string"}},"type":"object"},"pb.InterventionType":{"enum":[0,1,2,3,4,5,6,7,8,9],"format":"int32","type":"integer","x-enum-varnames":["InterventionType_INTERVENTION_TYPE_UNSPECIFIED","InterventionType_FILTER_CHANGE","InterventionType_HVAC_REPAIR","InterventionType_HVAC_MAINTENANCE","InterventionType_WINDOW_SEALED","InterventionType_DOOR_SEALED","InterventionType_DUCT_CLEANING","InterventionType_AIR_PURIFIER_ADDED","InterventionType_AIR_PURIFIER_REMOVED","InterventionType_OTHER"]},"pb.IntrospectTokenResponse":{"properties":{"active":{"type":"boolean"},"aud":{"description":"Audience","type":"string"},"client_id":{"type":"string"},"exp":{"description":"Expiration time (Unix timestamp)","type":"integer"},"iat":{"description":"Issued at (Unix timestamp)","type":"integer"},"iss":{"description":"Issuer","type":"string"},"jti":{"description":"JWT ID","type":"string"},"nbf":{"description":"Not before (Unix timestamp)","type":"integer"},"scope":{"type":"string"},"sub":{"description":"Subject (user ID)","type":"string"},"token_type":{"type":"string"},"username":{"type":"string"}},"type":"object"},"pb.LogInterventionResponse":{"properties":{"intervention_id":{"type":"string"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"},"pb.SensorInfo":{"properties":{"device_id":{"type":"string"},"first_reading_date":{"description":"First available data date (YYYY-MM-DD)","type":"string"},"location_id":{"description":"Location UUID","type":"string"},"location_name":{"description":"Location name for filtering","type":"string"},"name":{"description":"Human-readable name from location_sensors table","type":"string"},"pm25_multiplier":{"type":"number"},"sensor_category":{"description":"indoor, outdoor","type":"string"},"sensor_id":{"type":"string"},"sensor_model":{"type":"string"}},"type":"object"},"pb.TokenResponse":{"properties":{"access_token":{"type":"string"},"error":{"description":"Error fields","type":"string"},"error_description":{"type":"string"},"expires_in":{"type":"integer"},"id_token":{"description":"Additional fields","type":"string"},"refresh_token":{"type":"string"},"scope":{"type":"string"},"token_type":{"description":"\"Bearer\"","type":"string"}},"type":"object"},"pb.UserInfoResponse":{"properties":{"custom_claims":{"allOf":[{"$ref":"#/definitions/structpb.Struct"}],"description":"Additional claims"},"email":{"type":"string"},"email_verified":{"type":"boolean"},"family_name":{"type":"string"},"given_name":{"type":"string"},"locale":{"type":"string"},"name":{"type":"string"},"picture":{"type":"string"},"sub":{"description":"Subject (user ID)","type":"string"},"updated_at":{"$ref":"#/definitions/timestamppb.Timestamp"}},"type":"object"},"podq-air_api-gateway_internal_handlers_locations.InvitationCancelledResponse":{"properties":{"message":{"type":"string"},"timestamp":{"example":"2026-06-03T08:00:00Z","type":"string"}},"type":"object"},"podq-air_api-gateway_internal_handlers_locations.LocationUserPermissionsResponse":{"properties":{"can_delete_location":{"example":false,"type":"boolean"},"can_edit_location":{"example":false,"type":"boolean"},"can_manage_sensors":{"example":false,"type":"boolean"},"can_manage_users":{"example":false,"type":"boolean"},"can_view_location":{"example":true,"type":"boolean"},"can_view_sensors":{"example":true,"type":"boolean"},"location_id":{"type":"string"},"role":{"example":"location_user","type":"string"},"user_id":{"type":"string"}},"type":"object"},"structpb.Struct":{"properties":{"fields":{"additionalProperties":{"$ref":"#/definitions/structpb.Value"},"description":"Unordered map of dynamically typed values.","type":"object"}},"type":"object"},"structpb.Value":{"properties":{"kind":{"description":"The kind of value.\n\nTypes that are valid to be assigned to Kind:\n\n\t*Value_NullValue\n\t*Value_NumberValue\n\t*Value_StringValue\n\t*Value_BoolValue\n\t*Value_StructValue\n\t*Value_ListValue"}},"type":"object"},"timestamppb.Timestamp":{"properties":{"nanos":{"description":"Non-negative fractions of a second at nanosecond resolution. This field is\nthe nanosecond portion of the duration, not an alternative to seconds.\nNegative second values with fractions must still have non-negative nanos\nvalues that count forward in time. Must be between 0 and 999,999,999\ninclusive.","type":"integer"},"seconds":{"description":"Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must\nbe between -315576000000 and 315576000000 inclusive (which corresponds to\n0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z).","type":"integer"}},"type":"object"}},"host":"api.podqair.com","info":{"contact":{"email":"support@podqair.com","name":"PodQ Air Support","url":"https://podqair.com/support"},"description":"PodQ Air is a real-time air-quality, weather, and location-monitoring API for building applications on the PodQ Air platform.\n\n## Getting started\n1. **Get a token.** Sign in with `POST /v2/auth/magic-link` or `POST /v2/auth/social` to obtain a JWT access token and a refresh token.\n2. **Call the API.** Send the access token on every request as an `Authorization: Bearer \u003ctoken\u003e` header. In this UI, click **Authorize** and paste `Bearer \u003ctoken\u003e`.\n3. **Refresh.** When the access token expires, exchange the refresh token at `POST /v2/auth/refresh` for a fresh one.\n\nAll endpoints are served under the `/v2` base path over HTTPS. A few endpoints (health probes, public sensor lookups) need no authentication; everything else requires a valid token. Requests are rate limited — cache where you can and back off on `429` responses.","license":{"name":"Proprietary","url":"https://podqair.com/license"},"termsOfService":"https://podqair.com/terms","title":"PodQ Air API","version":"2.0"},"paths":{"/airsense-alerts/{alert_id}/dismiss":{"post":{"consumes":["application/json"],"description":"Dismiss a PodQ AirSense alert by ID","parameters":[{"description":"Alert ID","in":"path","name":"alert_id","required":true,"type":"string"},{"description":"Optional dismiss metadata","in":"body","name":"request","schema":{"$ref":"#/definitions/internal_handlers.DismissAlertRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Dismiss result","schema":{"$ref":"#/definitions/internal_handlers.AlertDismissResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Dismiss a sensor alert","tags":["Sensors"]}},"/airsense-interventions/calculate-effectiveness":{"post":{"consumes":["application/json"],"description":"Analyzes 30-day before/after metrics to determine intervention effectiveness","parameters":[{"description":"Intervention ID","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers.CalculateEffectivenessRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/pb.CalculateInterventionEffectivenessResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Intervention not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Calculate intervention effectiveness","tags":["Sensors"]}},"/auth/check-email":{"post":{"consumes":["application/json"],"description":"Check if email exists and return user's first/last name if it does","parameters":[{"description":"Email check request","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_auth.CheckEmailRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Email check result","schema":{"$ref":"#/definitions/internal_handlers_auth.CheckEmailResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Check if email exists","tags":["Authentication"]}},"/auth/logout":{"post":{"consumes":["application/json"],"description":"Logout the current user by revoking access and refresh tokens. Can be called with or without a valid token.","produces":["application/json"],"responses":{"200":{"description":"Logged out successfully","schema":{"$ref":"#/definitions/internal_handlers_auth.LogoutResponse"}}},"summary":"User logout","tags":["Authentication"]}},"/auth/magic-link":{"post":{"consumes":["application/json"],"description":"Send a passwordless authentication link to the user's email. The link is valid for 15 minutes.","parameters":[{"description":"Magic link request","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_auth.MagicLinkRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Magic link sent successfully","schema":{"$ref":"#/definitions/internal_handlers_auth.MagicLinkResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Failed to send email","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Email service unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Send magic link","tags":["Authentication"]}},"/auth/magic-link/verify":{"get":{"consumes":["application/json"],"description":"Verify the magic link token and authenticate the user. Returns JWT tokens upon successful verification.","parameters":[{"description":"Magic link token from email","in":"query","name":"token","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Magic link verified successfully","schema":{"$ref":"#/definitions/internal_handlers_auth.MagicLinkVerifyResponse"}},"400":{"description":"Missing or invalid token","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Invalid or expired magic link","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Session storage unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Verify magic link","tags":["Authentication"]}},"/auth/refresh":{"post":{"consumes":["application/json"],"description":"Generate a new access token using a valid refresh token. Returns both new access and refresh tokens with updated user data and location access.","parameters":[{"description":"Refresh token request","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_auth.RefreshRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Token refreshed successfully","schema":{"$ref":"#/definitions/internal_handlers_auth.RefreshResponse"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Invalid or expired refresh token","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Refresh access token","tags":["Authentication"]}},"/auth/register":{"post":{"consumes":["application/json"],"description":"Create a new user account with email and password. After successful registration, user must verify email and login separately.","parameters":[{"description":"Registration details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_auth.RegisterRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"User created successfully","schema":{"$ref":"#/definitions/internal_handlers_auth.RegisterResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"409":{"description":"Email already registered","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Register new user","tags":["Authentication"]}},"/auth/resend-verification":{"post":{"consumes":["application/json"],"description":"Resend email verification link to the user's email address. Can be used if the original email was not received or expired.","parameters":[{"description":"Resend verification request","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_auth.ResendVerificationRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Verification email sent","schema":{"$ref":"#/definitions/internal_handlers_auth.ResendVerificationResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Failed to send email","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Email service unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Resend verification email","tags":["Authentication"]}},"/auth/reset-password":{"post":{"consumes":["application/json"],"description":"Send a password reset link to the user's email. For security, always returns success regardless of whether the email exists. Reset link is valid for 1 hour.","parameters":[{"description":"Password reset request","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_auth.PasswordResetRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Request processed (email sent if account exists)","schema":{"$ref":"#/definitions/internal_handlers_auth.PasswordResetResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Request password reset","tags":["Authentication"]}},"/auth/reset-password/confirm":{"post":{"consumes":["application/json"],"description":"Complete the password reset by providing the token from email and a new password. Token is valid for 1 hour and can only be used once. All existing sessions are invalidated.","parameters":[{"description":"Password reset confirmation","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_auth.PasswordResetConfirmRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Password reset successfully","schema":{"$ref":"#/definitions/internal_handlers_auth.PasswordResetConfirmResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Invalid or expired reset token","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Failed to update password","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Session storage unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Confirm password reset","tags":["Authentication"]}},"/auth/send-code":{"post":{"consumes":["application/json"],"description":"Send a 6-digit verification code to the user's email. Valid for 10 minutes.","parameters":[{"description":"Code request","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_auth.VerificationCodeRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Code sent successfully","schema":{"$ref":"#/definitions/internal_handlers_auth.VerificationCodeResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"429":{"description":"Too many requests","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Failed to send email","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Send verification code","tags":["Authentication"]}},"/auth/social-login":{"post":{"consumes":["application/json"],"description":"Authenticate or register a user using social login providers (Google, Facebook, etc.). Creates a new account if the user doesn't exist.","parameters":[{"description":"Social login request","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_auth.SocialLoginRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Login successful","schema":{"$ref":"#/definitions/internal_handlers_auth.SocialLoginResponse"}},"400":{"description":"Invalid request or missing fields","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Social login failed","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Social login","tags":["Authentication"]}},"/auth/verify-code":{"post":{"consumes":["application/json"],"description":"Verify a 6-digit code and authenticate user","parameters":[{"description":"Code verification","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_auth.VerifyCodeRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Code verified","schema":{"$ref":"#/definitions/internal_handlers_auth.MagicLinkVerifyResponse"}},"400":{"description":"Invalid code","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"429":{"description":"Too many attempts","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Verify code","tags":["Authentication"]}},"/auth/verify-email":{"post":{"consumes":["application/json"],"description":"Verify user's email address using the token sent to their email. Token can only be used once.","parameters":[{"description":"Email verification request","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_auth.EmailVerificationRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Email verified successfully","schema":{"$ref":"#/definitions/internal_handlers_auth.EmailVerificationResponse"}},"400":{"description":"Invalid request or missing token","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Invalid or expired verification token","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Session storage unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Verify email address","tags":["Authentication"]}},"/detected-events/{event_id}/outcome":{"post":{"consumes":["application/json"],"description":"Set the outcome (false_alert, real_alert, or hidden) for a detected event","parameters":[{"description":"Event ID","in":"path","name":"event_id","required":true,"type":"string"},{"description":"Event outcome","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers.SetEventOutcomeRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Outcome result","schema":{"$ref":"#/definitions/internal_handlers.EventOutcomeResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Set detected event outcome","tags":["Locations"]}},"/devices/register-token":{"post":{"consumes":["application/json"],"description":"Register a device token to receive push notifications","parameters":[{"description":"Device token registration","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_devices.RegisterDeviceTokenRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/internal_handlers_devices.RegisterDeviceTokenResponse"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Service unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Register device token for push notifications","tags":["Devices"]}},"/devices/unregister-token":{"post":{"consumes":["application/json"],"description":"Unregister a device token to stop receiving push notifications","parameters":[{"description":"Device token unregistration","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_devices.UnregisterDeviceTokenRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Success","schema":{"$ref":"#/definitions/internal_handlers_devices.UnregisterDeviceTokenResponse"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Service unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Unregister device token","tags":["Devices"]}},"/environmental-bands":{"get":{"description":"Get environmental bands (themes) with optional band details","parameters":[{"description":"Theme level (system, location, user)","in":"query","name":"level","type":"string"},{"description":"Location ID","in":"query","name":"location_id","type":"string"},{"default":false,"description":"Include band details","in":"query","name":"include_bands","type":"boolean"}],"produces":["application/json"],"responses":{"200":{"description":"List of themes","schema":{"$ref":"#/definitions/internal_handlers_environmental.EnvBandThemesResponse"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Service unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get environmental bands/themes","tags":["Environmental Bands"]},"post":{"consumes":["application/json"],"description":"Create a new environmental band theme with bands","parameters":[{"description":"Theme details with bands","in":"body","name":"request","required":true,"schema":{"additionalProperties":true,"type":"object"}}],"produces":["application/json"],"responses":{"201":{"description":"Created theme","schema":{"$ref":"#/definitions/internal_handlers_environmental.EnvBandTheme"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Create environmental bands/theme","tags":["Environmental Bands"]}},"/environmental-bands/band-for-value":{"get":{"description":"Determine which band a specific metric value falls into","parameters":[{"description":"Metric type","in":"query","name":"metric_type","required":true,"type":"string"},{"description":"Value to classify","in":"query","name":"value","required":true,"type":"number"},{"description":"Location ID","in":"query","name":"location_id","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Band classification","schema":{"$ref":"#/definitions/internal_handlers_environmental.BandForValueResponse"}},"400":{"description":"Missing or invalid parameters","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Service unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get band for specific value","tags":["Environmental Bands"]}},"/environmental-bands/effective-bands":{"get":{"description":"Get the applicable environmental bands for a specific metric type and optional location","parameters":[{"description":"Metric type (aqi_outdoor, aqi_indoor, uv_index, heat_index, etc.)","in":"query","name":"metric_type","required":true,"type":"string"},{"description":"Location ID","in":"query","name":"location_id","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Effective bands","schema":{"$ref":"#/definitions/internal_handlers_environmental.EffectiveBandsResponse"}},"400":{"description":"Missing metric_type parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Service unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get effective bands for metric type","tags":["Environmental Bands"]}},"/environmental-bands/metric-types":{"get":{"description":"Get list of all supported environmental metric types","produces":["application/json"],"responses":{"200":{"description":"List of metric types","schema":{"$ref":"#/definitions/internal_handlers_environmental.EnvMetricTypesResponse"}}},"summary":"Get supported metric types","tags":["Environmental Bands"]}},"/environmental-bands/overrides":{"get":{"description":"List per-location environmental band overrides for a location","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Band overrides","schema":{"$ref":"#/definitions/internal_handlers_environmental.BandOverridesListResponse"}},"400":{"description":"Missing location_id","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"List environmental band overrides","tags":["Environmental"]},"post":{"consumes":["application/json"],"description":"Create a per-location override for an environmental band (color, max value, name, etc.)","parameters":[{"description":"Override (location_id, metric_type, band_key required; optional max_value, color, text_color, band_name, description, reason)","in":"body","name":"request","required":true,"schema":{"additionalProperties":true,"type":"object"}}],"produces":["application/json"],"responses":{"201":{"description":"Created override","schema":{"$ref":"#/definitions/internal_handlers_environmental.BandOverrideCreatedResponse"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Create an environmental band override","tags":["Environmental"]}},"/environmental-bands/overrides/{id}":{"delete":{"description":"Delete a per-location environmental band override by ID","parameters":[{"description":"Override ID","in":"path","name":"id","required":true,"type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Deletion result","schema":{"$ref":"#/definitions/internal_handlers_environmental.BandOverrideMessageResponse"}},"400":{"description":"Invalid override ID","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Override not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Delete an environmental band override","tags":["Environmental"]},"put":{"consumes":["application/json"],"description":"Update an existing per-location environmental band override by ID","parameters":[{"description":"Override ID","in":"path","name":"id","required":true,"type":"integer"},{"description":"Fields to update (max_value, color, text_color, band_name, description, reason)","in":"body","name":"request","required":true,"schema":{"additionalProperties":true,"type":"object"}}],"produces":["application/json"],"responses":{"200":{"description":"Updated override","schema":{"$ref":"#/definitions/internal_handlers_environmental.BandOverrideMessageResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Override not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update an environmental band override","tags":["Environmental"]}},"/environmental-bands/themes":{"get":{"description":"Get environmental themes with bands for display","parameters":[{"description":"Theme level filter","in":"query","name":"level","type":"string"},{"description":"Location ID filter","in":"query","name":"location_id","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Themes with bands","schema":{"$ref":"#/definitions/internal_handlers_environmental.ThemesResponse"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get environmental themes","tags":["Environmental Bands"]}},"/environmental-bands/{id}":{"delete":{"description":"Delete an environmental band theme","parameters":[{"description":"Theme ID","in":"path","name":"id","required":true,"type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Success response","schema":{"$ref":"#/definitions/internal_handlers_environmental.SuccessMessageResponse"}},"400":{"description":"Invalid theme ID","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Delete environmental bands/theme","tags":["Environmental Bands"]},"put":{"consumes":["application/json"],"description":"Update an existing environmental band theme","parameters":[{"description":"Theme ID","in":"path","name":"id","required":true,"type":"integer"},{"description":"Updated theme details","in":"body","name":"request","required":true,"schema":{"additionalProperties":true,"type":"object"}}],"produces":["application/json"],"responses":{"200":{"description":"Updated theme","schema":{"$ref":"#/definitions/internal_handlers_environmental.EnvBandTheme"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update environmental bands/theme","tags":["Environmental Bands"]}},"/environmental/air-quality":{"get":{"description":"Get combined environmental air-quality data. Not yet implemented — currently returns 501.","produces":["application/json"],"responses":{"501":{"description":"Not yet implemented","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get air quality","tags":["Environmental"]}},"/environmental/heat-index":{"get":{"description":"Calculate heat index (feels-like temperature) based on temperature and humidity","parameters":[{"description":"Location ID","in":"query","name":"location_id","type":"string"},{"description":"City ID (alternative to location_id)","in":"query","name":"city_id","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Heat index data","schema":{"$ref":"#/definitions/internal_handlers_environmental.HeatIndexResponse"}},"400":{"description":"Missing location_id or city_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get heat index","tags":["Environmental"]}},"/environmental/heat-index/history":{"get":{"description":"Get historical heat index data for a location","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"},{"description":"Start date (YYYY-MM-DD)","in":"query","name":"start_date","type":"string"},{"description":"End date (YYYY-MM-DD)","in":"query","name":"end_date","type":"string"},{"default":"\"day\"","description":"Data interval (hour, day)","in":"query","name":"interval","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Historical heat index data","schema":{"$ref":"#/definitions/internal_handlers_environmental.HeatIndexHistoryResponse"}},"400":{"description":"Missing required parameters","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Location not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get heat index history","tags":["Environmental"]}},"/environmental/hourly":{"get":{"description":"Get comprehensive hourly forecast including weather, AQI, UV index, and heat index","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"},{"default":6,"description":"Hours of history to include (max 24)","in":"query","name":"hours_before","type":"integer"},{"default":12,"description":"Hours of forecast to include (max 48)","in":"query","name":"hours_after","type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Hourly environmental data","schema":{"$ref":"#/definitions/internal_handlers_environmental.HourlyResponse"}},"400":{"description":"Missing location_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Location not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get hourly environmental forecast","tags":["Environmental"]}},"/environmental/uv-index":{"get":{"description":"Get current and hourly UV index forecast for a location","parameters":[{"description":"Location ID","in":"query","name":"location_id","type":"string"},{"description":"City ID (alternative to location_id)","in":"query","name":"city_id","type":"string"},{"description":"Forecast interval","in":"query","name":"interval","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"UV index data","schema":{"$ref":"#/definitions/internal_handlers_environmental.UVIndexResponse"}},"400":{"description":"Missing location_id or city_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get UV index","tags":["Environmental"]}},"/feedback":{"post":{"consumes":["application/json"],"description":"Submit feedback, bug reports, or feature requests from the mobile app","parameters":[{"description":"Feedback details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_feedback.SubmitFeedbackRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Feedback submitted successfully","schema":{"$ref":"#/definitions/internal_handlers_feedback.SubmitFeedbackResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Submit user feedback","tags":["Feedback"]}},"/health":{"get":{"description":"Get basic health status of the API gateway","produces":["application/json"],"responses":{"200":{"description":"Service is healthy","schema":{"$ref":"#/definitions/internal_handlers.HealthResponse"}}},"summary":"Health check","tags":["Health"]}},"/indoor-aqi/aggregate":{"get":{"description":"Get aggregate indoor air quality across all sensors (fast, no per-sensor breakdown)","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Aggregate indoor AQI","schema":{"$ref":"#/definitions/internal_handlers_environmental.IndoorAQIAggregateResponse"}},"400":{"description":"Missing location_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"No sensors configured","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get aggregate indoor AQI","tags":["Environmental"]}},"/indoor-aqi/current":{"get":{"description":"Get the current indoor Air Quality Index for a location based on sensor data","parameters":[{"description":"Location ID","in":"query","name":"location","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Current indoor AQI","schema":{"$ref":"#/definitions/internal_handlers_environmental.IndoorAQICurrentResponse"}},"400":{"description":"Missing location parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get current indoor AQI","tags":["Environmental"]}},"/indoor-aqi/history":{"get":{"description":"Get historical indoor air quality data for a location","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"},{"description":"Start date (YYYY-MM-DD)","in":"query","name":"start_date","type":"string"},{"description":"End date (YYYY-MM-DD)","in":"query","name":"end_date","type":"string"},{"default":"\"hour\"","description":"Data interval (hour, day)","in":"query","name":"interval","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Historical indoor AQI data","schema":{"$ref":"#/definitions/internal_handlers_environmental.IndoorAQIHistoryResponse"}},"400":{"description":"Missing location_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Location not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get indoor AQI history","tags":["Environmental"]}},"/indoor-aqi/metric-history":{"get":{"description":"Get historical data for any metric type (CO₂, AQI, PM2.5, temperature, etc.) with multi-sensor aggregation","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"},{"description":"Metric type (aqi, co2, pm25, tvoc, hcho, temperature, humidity)","in":"query","name":"metric","required":true,"type":"string"},{"default":24,"description":"Hours of history (1, 6, 24, 168, 720)","in":"query","name":"hours","type":"integer"},{"default":"\"auto\"","description":"Data interval (1minute, 5minute, 10minute, 1hour, 6hour)","in":"query","name":"interval","type":"string"},{"description":"Specific sensor ID (omit for aggregated)","in":"query","name":"sensor_id","type":"string"},{"default":"\"all\"","description":"Aggregation type (all, avg, min, max)","in":"query","name":"aggregation","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Metric history data","schema":{"$ref":"#/definitions/internal_handlers_environmental.MetricHistoryResponse"}},"400":{"description":"Missing or invalid parameters","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get multi-metric history","tags":["Environmental"]}},"/indoor-aqi/statistics":{"get":{"description":"Get aggregate indoor AQI statistics for a location. Not yet implemented — currently returns 501.","parameters":[{"description":"Location ID","in":"query","name":"location","type":"string"}],"produces":["application/json"],"responses":{"501":{"description":"Not yet implemented","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get indoor AQI statistics","tags":["Environmental"]}},"/indoor-aqi/{sensor_id}/history":{"get":{"description":"Get historical indoor AQI readings for a specific sensor over a time range","parameters":[{"description":"Sensor ID","in":"path","name":"sensor_id","required":true,"type":"string"},{"description":"Range start (RFC3339); defaults to 24h ago","in":"query","name":"start_date","type":"string"},{"description":"Range end (RFC3339); defaults to now","in":"query","name":"end_date","type":"string"},{"default":"hour","description":"Aggregation interval","in":"query","name":"interval","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Indoor AQI history","schema":{"$ref":"#/definitions/internal_handlers_environmental.IndoorAQIHistoryResponse"}},"400":{"description":"Missing sensor_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get indoor AQI history by sensor","tags":["Environmental"]}},"/insights/suppressions":{"get":{"description":"Get all suppressions (personal + location-wide) for a user at a location","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"},{"default":false,"description":"Include expired suppressions","in":"query","name":"include_expired","type":"boolean"}],"produces":["application/json"],"responses":{"200":{"description":"List of suppressions","schema":{"$ref":"#/definitions/internal_handlers_locations.ListSuppressionsResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"List insight warning suppressions","tags":["Insights"]},"post":{"consumes":["application/json"],"description":"Create a suppression to hide specific AI insight warnings. Admins can create location-wide suppressions.","parameters":[{"description":"Suppression details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.CreateSuppressionRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Suppression created","schema":{"$ref":"#/definitions/internal_handlers_locations.CreateSuppressionResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - non-admin trying location-wide suppression","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Create insight warning suppression","tags":["Insights"]}},"/insights/suppressions/{id}":{"delete":{"description":"Delete a suppression to re-enable warnings. Users can delete own suppressions, admins can delete location-wide.","parameters":[{"description":"Suppression ID","in":"path","name":"id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Success response","schema":{"$ref":"#/definitions/internal_handlers_locations.SuppressionDeleteResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - cannot delete another user's suppression","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Suppression not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Remove insight warning suppression","tags":["Insights"]}},"/location-profile/facts/{locationId}":{"get":{"description":"Get AI-generated facts about a location","parameters":[{"description":"Location ID","in":"path","name":"locationId","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Location facts","schema":{"$ref":"#/definitions/internal_handlers_location_profile.FactsResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get location profile facts","tags":["Location Profile"]}},"/locations":{"get":{"description":"Get all locations the authenticated user has access to (owned or shared)","produces":["application/json"],"responses":{"200":{"description":"List of locations","schema":{"$ref":"#/definitions/internal_handlers_locations.ListLocationsResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"List user locations","tags":["Locations"]},"post":{"consumes":["application/json"],"description":"Create a new location for air quality monitoring. The authenticated user becomes the owner with full permissions.","parameters":[{"description":"Location details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.CreateLocationRequest"}}],"produces":["application/json"],"responses":{"201":{"description":"Location created successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.CreateLocationResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Create new location","tags":["Locations"]}},"/locations/{id}":{"delete":{"description":"Permanently delete a location. Only the location owner can delete. This action cannot be undone.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"}],"responses":{"204":{"description":"Location deleted successfully"},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - only owner can delete","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Location not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Delete location","tags":["Locations"]},"get":{"description":"Get detailed information about a specific location including sensor count and user role","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Location details","schema":{"$ref":"#/definitions/internal_handlers_locations.GetLocationResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Location not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get location by ID","tags":["Locations"]},"put":{"consumes":["application/json"],"description":"Update an existing location. Only the location owner can update location details.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Updated location details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.UpdateLocationRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Location updated successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.CreateLocationResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - not location owner","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Location not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update location","tags":["Locations"]}},"/locations/{id}/ai-insights":{"get":{"description":"Get priority-based, unified AI insights for a location. Shows only problematic metrics organized by indoor/outdoor sections. Filters based on user's personal and location-wide suppressions.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Language code (default: location's primary language or 'en')","in":"query","name":"language","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"AI insights data","schema":{"$ref":"#/definitions/internal_handlers_locations.AIInsightsResponse"}},"404":{"description":"No insights found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get unified AI insights for location","tags":["Locations"]}},"/locations/{id}/card-insights":{"get":{"description":"DEPRECATED: Card insights feature has been removed. This endpoint returns empty for backwards compatibility. Use /locations/{id}/ai-insights instead.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Ignored - deprecated parameter","in":"query","name":"card_type","type":"string"},{"description":"Ignored - deprecated parameter","in":"query","name":"language","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Empty object (feature deprecated)","schema":{"$ref":"#/definitions/internal_handlers_locations.CardInsightsResponse"}}},"summary":"DEPRECATED - Get card insights (returns empty)","tags":["Locations"]}},"/locations/{id}/invitations":{"get":{"description":"Get all pending invitations for a location","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"List of invitations","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationInvitationsListResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"List location invitations","tags":["Locations"]}},"/locations/{id}/invitations/cancel":{"post":{"consumes":["application/json"],"description":"Cancel a pending invitation to a location","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Email of invited user","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.CancelInvitationRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Invitation cancelled successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.InvitationCancelledResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Cancel location invitation","tags":["Locations"]}},"/locations/{id}/invite":{"post":{"consumes":["application/json"],"description":"Send an email invitation to a user to join a location","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Invitation details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.InvitationRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Invitation sent successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.InvitationSentResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Send location invitation","tags":["Locations"]}},"/locations/{id}/invite/cancel":{"post":{"consumes":["application/json"],"description":"Cancel a pending invitation to a location by invitee email","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Invitee email (email)","in":"body","name":"request","required":true,"schema":{"additionalProperties":{"type":"string"},"type":"object"}}],"produces":["application/json"],"responses":{"200":{"description":"Cancellation result","schema":{"$ref":"#/definitions/podq-air_api-gateway_internal_handlers_locations.InvitationCancelledResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Cancel a location invitation","tags":["Locations"]}},"/locations/{id}/leave":{"post":{"description":"Allows a user to remove themselves from a location. If they are the only admin and there is exactly one other user, that user will be automatically promoted to admin.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"}],"responses":{"200":{"description":"Successfully left location","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationActionResponse"}},"400":{"description":"Invalid request or must promote another admin first","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Leave a location","tags":["Locations"]}},"/locations/{id}/outdoor-aqi":{"get":{"description":"Get the current outdoor Air Quality Index for a location or public sensor","parameters":[{"description":"Location ID","in":"query","name":"location_id","type":"string"},{"description":"Public Sensor ID","in":"query","name":"sensor_id","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Current outdoor AQI","schema":{"$ref":"#/definitions/internal_handlers_environmental.OutdoorAQICurrentResponse"}},"400":{"description":"Missing location_id or sensor_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Sensor is not public","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Sensor not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get current outdoor AQI","tags":["Environmental"]}},"/locations/{id}/qr-code":{"get":{"description":"Generate QR code data for sharing a location. Returns URL that can be encoded as QR code by client-side library.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"QR code data","schema":{"$ref":"#/definitions/internal_handlers_locations.QRCodeResponse"}},"400":{"description":"Location ID is required","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Generate QR code for location","tags":["Locations"]}},"/locations/{id}/sensors":{"get":{"description":"Get all sensors for a specific location (public locations accessible without auth)","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"default":false,"description":"Return only active sensors","in":"query","name":"active_only","type":"boolean"}],"produces":["application/json"],"responses":{"200":{"description":"List of sensors","schema":{"$ref":"#/definitions/LocationSensorsResponse"}},"400":{"description":"Invalid location ID","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"List location sensors","tags":["Sensors"]},"post":{"consumes":["application/json"],"description":"Add a sensor to a location. Requires location owner or admin permissions.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Sensor details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.CreateLocationSensorRequest"}}],"produces":["application/json"],"responses":{"201":{"description":"Sensor created successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationSensorMutationResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - insufficient permissions","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"409":{"description":"Sensor already exists in this location","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Create sensor in location","tags":["Locations"]}},"/locations/{id}/sensors/indoor/register":{"post":{"consumes":["application/json"],"description":"Register a YF10X or custom indoor sensor by device ID and link it to a location","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Sensor registration details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.RegisterIndoorSensorRequest"}}],"produces":["application/json"],"responses":{"201":{"description":"Sensor registered successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.IndoorSensorRegisterResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - insufficient permissions","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Sensor device not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"409":{"description":"Sensor already registered to a location","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Register indoor sensor to location","tags":["Locations"]}},"/locations/{id}/sensors/offline-status":{"get":{"description":"Get online/offline/silenced status for all sensors at a location","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Include online sensors","in":"query","name":"include_online","type":"boolean"},{"default":true,"description":"Include silenced sensors","in":"query","name":"include_silenced","type":"boolean"},{"collectionFormat":"csv","description":"Filter by sensor types","in":"query","items":{"type":"string"},"name":"sensor_types","type":"array"}],"produces":["application/json"],"responses":{"200":{"description":"Sensor offline statuses","schema":{"$ref":"#/definitions/internal_handlers_sensors.SensorOfflineStatusResponse"}},"400":{"description":"Missing location_id","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get sensor offline status","tags":["Sensors"]}},"/locations/{id}/sensors/{sensor_id}":{"delete":{"description":"Remove a sensor from a location. Requires location owner or admin permissions.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Sensor ID","in":"path","name":"sensor_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Sensor deleted successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationSensorMutationResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - insufficient permissions","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Sensor not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Delete sensor from location","tags":["Locations"]},"get":{"description":"Get details of a specific sensor in a location","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Sensor ID","in":"path","name":"sensor_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Sensor details","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationSensorMutationResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - insufficient permissions","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Sensor not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get sensor in location","tags":["Locations"]},"put":{"consumes":["application/json"],"description":"Update sensor details in a location. Requires location owner or admin permissions.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Sensor ID","in":"path","name":"sensor_id","required":true,"type":"string"},{"description":"Sensor update details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.UpdateLocationSensorRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Sensor updated successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationSensorMutationResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - insufficient permissions","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Sensor not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update sensor in location","tags":["Locations"]}},"/locations/{id}/sensors/{sensor_id}/readings":{"get":{"description":"Get readings for a specific sensor in a location","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Sensor ID","in":"path","name":"sensor_id","required":true,"type":"string"},{"default":100,"description":"Number of readings to return","in":"query","name":"limit","type":"integer"},{"default":0,"description":"Offset for pagination","in":"query","name":"offset","type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Sensor readings","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationSensorReadingsResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Sensor not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"501":{"description":"Not implemented","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get sensor readings","tags":["Locations"]}},"/locations/{id}/users":{"get":{"description":"Get all users who have access to a location with their permissions","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"List of users with permissions","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationUsersListResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - no access to location","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get location users","tags":["Locations"]},"post":{"consumes":["application/json"],"description":"Add a user to a location with specified permissions. Only location owners can add users.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"User details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.AddUserRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"User added successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationUserAddedResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - only owner can add users","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Add user to location","tags":["Locations"]}},"/locations/{id}/users/{userId}":{"delete":{"description":"Remove a user's access to a location. Only location owners can remove users.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"User ID","in":"path","name":"userId","required":true,"type":"string"}],"responses":{"200":{"description":"User removed successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationActionResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - only owner can remove users","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Remove user from location","tags":["Locations"]},"get":{"description":"Get the permissions for a specific user at a location","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"User ID","in":"path","name":"userId","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"User permissions","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationUserPermissionsResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get user permissions","tags":["Locations"]},"put":{"consumes":["application/json"],"description":"Update permissions for a user at a location. Only location owners can update permissions.","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"User ID","in":"path","name":"userId","required":true,"type":"string"},{"description":"Updated permissions","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.UpdateUserPermissionRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Permissions updated successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationActionResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - only owner can update permissions","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update user permissions","tags":["Locations"]}},"/locations/{id}/users/{userId}/permissions":{"get":{"description":"Get the permissions a specific user has at a location","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"User ID","in":"path","name":"userId","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"User permissions","schema":{"$ref":"#/definitions/podq-air_api-gateway_internal_handlers_locations.LocationUserPermissionsResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get a user's location permissions","tags":["Locations"]}},"/locations/{location_id}/airsense-grades":{"get":{"description":"Returns building performance grades (A+/A/B/C/D) for all indoor sensors at a location","parameters":[{"description":"Location ID","in":"path","name":"location_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"grades array with letter grades","schema":{"$ref":"#/definitions/internal_handlers.SensorGradesResponse"}},"400":{"description":"Invalid location ID","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get pre-calculated AirSense grades for a location","tags":["Sensors"]}},"/locations/{location_id}/detected-events":{"get":{"description":"List detected air-quality events for a location","parameters":[{"description":"Location ID","in":"path","name":"location_id","required":true,"type":"string"},{"description":"Filter by event type","in":"query","name":"type","type":"string"},{"default":7,"description":"Lookback window in days","in":"query","name":"days","type":"integer"},{"description":"Include hidden events","in":"query","name":"include_hidden","type":"boolean"}],"produces":["application/json"],"responses":{"200":{"description":"Detected events","schema":{"$ref":"#/definitions/internal_handlers.DetectedEventsListResponse"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"List detected events","tags":["Locations"]}},"/notifications/allow-one-more":{"post":{"consumes":["application/json"],"description":"Allow sending one additional message today beyond normal limits","parameters":[{"description":"Allow one more request","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_notifications.AllowOneMoreRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Success response","schema":{"$ref":"#/definitions/internal_handlers_notifications.AllowOneMoreResponse"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Service unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Allow one more message today","tags":["Notifications"]}},"/notifications/message-logs":{"get":{"description":"Get message delivery logs for a specific user and location","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"},{"description":"User ID","in":"query","name":"user_id","required":true,"type":"string"},{"default":50,"description":"Maximum number of logs to return","in":"query","name":"limit","type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Message logs","schema":{"$ref":"#/definitions/internal_handlers_notifications.MessageLogsResponse"}},"400":{"description":"Missing required parameters","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get message logs","tags":["Notifications"]}},"/notifications/messages/today":{"get":{"description":"Get list of messages sent today for the user and location","parameters":[{"description":"Location ID","in":"query","name":"location_id","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Today's messages","schema":{"$ref":"#/definitions/internal_handlers_notifications.TodaysMessagesResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Service unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get today's sent messages","tags":["Notifications"]}},"/notifications/sensor-alert-preferences":{"get":{"description":"List the authenticated user's sensor alert preferences, optionally filtered by location/metric","parameters":[{"description":"Filter by location ID","in":"query","name":"location_id","type":"string"},{"description":"Filter by metric type","in":"query","name":"metric_type","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Sensor alert preferences","schema":{"items":{"$ref":"#/definitions/internal_handlers_notifications.SensorAlertPreferenceJSON"},"type":"array"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"List sensor alert preferences","tags":["Notifications"]},"post":{"consumes":["application/json"],"description":"Create or update a sensor alert preference for the authenticated user","parameters":[{"description":"Sensor alert preference","in":"body","name":"request","required":true,"schema":{"additionalProperties":true,"type":"object"}}],"produces":["application/json"],"responses":{"200":{"description":"Saved preference","schema":{"$ref":"#/definitions/internal_handlers_notifications.SensorAlertPreferenceJSON"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Create or update a sensor alert preference","tags":["Notifications"]}},"/notifications/sensor-alert-preferences/{id}":{"delete":{"description":"Delete a sensor alert preference by ID for the authenticated user","parameters":[{"description":"Preference ID","in":"path","name":"id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Deletion result","schema":{"$ref":"#/definitions/pb.DeleteSensorAlertPreferencesResponse"}},"400":{"description":"Missing preference_id","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Delete a sensor alert preference","tags":["Notifications"]}},"/notifications/sensor-metrics":{"get":{"description":"List sensor metrics available for alerting, optionally filtered to a location","parameters":[{"description":"Filter metrics to a location","in":"query","name":"location_id","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Available sensor metrics","schema":{"items":{"$ref":"#/definitions/internal_handlers_notifications.SensorMetricInfoJSON"},"type":"array"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"List available sensor metrics","tags":["Notifications"]}},"/notifications/today":{"get":{"description":"Get today's notification message counts per type and channel for a location","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Today's message counts","schema":{"$ref":"#/definitions/internal_handlers_notifications.TodaysMessageCountsResponse"}},"400":{"description":"Missing location_id","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get today's notification counts","tags":["Notifications"]}},"/oauth2/authorize":{"get":{"description":"Initiate OAuth2 authorization flow. User must be authenticated before authorizing OAuth2 apps.","parameters":[{"description":"Response type (code)","in":"query","name":"response_type","required":true,"type":"string"},{"description":"Client ID","in":"query","name":"client_id","required":true,"type":"string"},{"description":"Redirect URI","in":"query","name":"redirect_uri","required":true,"type":"string"},{"description":"Requested scopes","in":"query","name":"scope","type":"string"},{"description":"State parameter","in":"query","name":"state","type":"string"},{"description":"PKCE code challenge","in":"query","name":"code_challenge","type":"string"},{"description":"PKCE challenge method","in":"query","name":"code_challenge_method","type":"string"}],"produces":["text/html"],"responses":{"302":{"description":"Redirect to client with authorization code","schema":{"type":"string"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"OAuth2 authorization endpoint","tags":["OAuth2"]}},"/oauth2/introspect":{"post":{"consumes":["application/x-www-form-urlencoded"],"description":"Introspect an OAuth2 token to get its metadata","parameters":[{"description":"Token to introspect","in":"formData","name":"token","required":true,"type":"string"},{"description":"Token type hint (access_token, refresh_token)","in":"formData","name":"token_type_hint","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Token metadata","schema":{"$ref":"#/definitions/pb.IntrospectTokenResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"OAuth2 token introspection endpoint","tags":["OAuth2"]}},"/oauth2/revoke":{"post":{"consumes":["application/x-www-form-urlencoded"],"description":"Revoke an OAuth2 access or refresh token","parameters":[{"description":"Token to revoke","in":"formData","name":"token","required":true,"type":"string"},{"description":"Token type hint (access_token, refresh_token)","in":"formData","name":"token_type_hint","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Token revoked (empty body on success)"},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"OAuth2 token revocation endpoint","tags":["OAuth2"]}},"/oauth2/token":{"post":{"consumes":["application/x-www-form-urlencoded"],"description":"Exchange authorization code for access token","parameters":[{"description":"Grant type (authorization_code, refresh_token)","in":"formData","name":"grant_type","required":true,"type":"string"},{"description":"Authorization code (for authorization_code grant)","in":"formData","name":"code","type":"string"},{"description":"Redirect URI","in":"formData","name":"redirect_uri","type":"string"},{"description":"Client ID","in":"formData","name":"client_id","required":true,"type":"string"},{"description":"Client secret","in":"formData","name":"client_secret","type":"string"},{"description":"Refresh token (for refresh_token grant)","in":"formData","name":"refresh_token","type":"string"},{"description":"PKCE code verifier","in":"formData","name":"code_verifier","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Access token response","schema":{"$ref":"#/definitions/pb.TokenResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Invalid client","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"OAuth2 token endpoint","tags":["OAuth2"]}},"/oauth2/userinfo":{"get":{"description":"Get information about the authenticated user (OpenID Connect UserInfo endpoint)","produces":["application/json"],"responses":{"200":{"description":"User information","schema":{"$ref":"#/definitions/pb.UserInfoResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"OAuth2 user info endpoint","tags":["OAuth2"]}},"/outdoor-aqi/current":{"get":{"description":"Get the current outdoor Air Quality Index for a location or public sensor","parameters":[{"description":"Location ID","in":"query","name":"location_id","type":"string"},{"description":"Public Sensor ID","in":"query","name":"sensor_id","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Current outdoor AQI","schema":{"$ref":"#/definitions/internal_handlers_environmental.OutdoorAQICurrentResponse"}},"400":{"description":"Missing location_id or sensor_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Sensor is not public","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Sensor not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get current outdoor AQI","tags":["Environmental"]}},"/outdoor-aqi/forecast":{"get":{"description":"Get hourly outdoor air quality forecast for a location","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"},{"default":24,"description":"Number of hours to forecast (max 100)","in":"query","name":"hours","type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Hourly forecast data","schema":{"items":{"$ref":"#/definitions/internal_handlers_environmental.OutdoorAQIForecastPoint"},"type":"array"}},"400":{"description":"Missing location_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Location not found or no city_id associated","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get outdoor AQI forecast","tags":["Environmental"]}},"/outdoor-aqi/history":{"get":{"description":"Get historical outdoor air quality data for a location. Use either hours parameter OR start_date/end_date range.","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"},{"description":"Number of hours of historical data (e.g., 24, 168 for 7 days, 720 for 30 days)","in":"query","name":"hours","type":"integer"},{"description":"Start date (YYYY-MM-DD or ISO8601 format)","in":"query","name":"start_date","type":"string"},{"description":"End date (YYYY-MM-DD or ISO8601 format)","in":"query","name":"end_date","type":"string"},{"default":"\"hour\"","description":"Data interval (5minute, 10minute, 1hour, 6hour, day)","in":"query","name":"interval","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Historical AQI data","schema":{"items":{"$ref":"#/definitions/internal_handlers_environmental.OutdoorAQIPoint"},"type":"array"}},"400":{"description":"Missing location_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Location not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get outdoor AQI history","tags":["Environmental"]}},"/outdoor-aqi/map":{"get":{"description":"Get outdoor AQI for sensors near a coordinate, for map display","parameters":[{"description":"Latitude","in":"query","name":"lat","required":true,"type":"number"},{"description":"Longitude","in":"query","name":"lon","required":true,"type":"number"},{"description":"Search radius in km","in":"query","name":"radius","required":true,"type":"number"},{"description":"Maximum number of sensors","in":"query","name":"limit","required":true,"type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Nearby outdoor AQI sensors","schema":{"$ref":"#/definitions/internal_handlers_environmental.nearbySensorsResponse"}},"400":{"description":"Missing lat/lon/radius/limit","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get nearby outdoor AQI for map","tags":["Environmental"]}},"/outdoor-aqi/metric-history":{"get":{"description":"Get historical data for outdoor AQI metrics with multi-sensor support","parameters":[{"description":"Location ID (required if sensor_id not provided)","in":"query","name":"location_id","type":"string"},{"description":"Metric type (aqi, pm25, pm10)","in":"query","name":"metric","required":true,"type":"string"},{"description":"Hours of history (default 24, max 168)","in":"query","name":"hours","type":"integer"},{"description":"Data interval (1minute, 5minute, 10minute, 1hour)","in":"query","name":"interval","type":"string"},{"description":"Sensor ID (required if location_id not provided)","in":"query","name":"sensor_id","type":"string"},{"description":"individual or aggregated (default aggregated)","in":"query","name":"breakdown","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Metric history data","schema":{"$ref":"#/definitions/internal_handlers_environmental.MetricHistoryResponse"}},"400":{"description":"Invalid parameters","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get outdoor AQI metric history","tags":["Environmental"]}},"/outdoor-aqi/nearby":{"get":{"description":"Get outdoor AQI for sensors near a coordinate (alias of /outdoor-aqi/map)","parameters":[{"description":"Latitude","in":"query","name":"lat","required":true,"type":"number"},{"description":"Longitude","in":"query","name":"lon","required":true,"type":"number"},{"description":"Search radius in km","in":"query","name":"radius","required":true,"type":"number"},{"description":"Maximum number of sensors","in":"query","name":"limit","required":true,"type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Nearby outdoor AQI sensors","schema":{"$ref":"#/definitions/internal_handlers_environmental.nearbySensorsResponse"}},"400":{"description":"Missing lat/lon/radius/limit","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get nearby outdoor AQI","tags":["Environmental"]}},"/outdoor-aqi/widget":{"get":{"description":"Lightweight endpoint returning minimal AQI data optimized for iOS widgets with user-configured bands","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Widget AQI data","schema":{"$ref":"#/definitions/internal_handlers_environmental.OutdoorAQIWidgetResponse"}},"400":{"description":"Missing location_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"No AQI data found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get outdoor AQI for iOS widget","tags":["Environmental"]}},"/outdoor/nearest":{"get":{"description":"Returns AQI, weather, and sensor info for the nearest outdoor sensor based on GPS coordinates","parameters":[{"description":"Latitude","in":"query","name":"lat","required":true,"type":"number"},{"description":"Longitude","in":"query","name":"lon","required":true,"type":"number"}],"produces":["application/json"],"responses":{"200":{"description":"Nearest sensor data","schema":{"$ref":"#/definitions/internal_handlers_environmental.NearestSensorResponse"}},"400":{"description":"Missing or invalid lat/lon parameters","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"No sensors found nearby","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get AQI and weather for nearest outdoor sensor","tags":["Environmental"]}},"/public/locations":{"get":{"description":"Get all publicly visible locations (no authentication required)","produces":["application/json"],"responses":{"200":{"description":"List of public locations","schema":{"$ref":"#/definitions/internal_handlers_locations.ListLocationsResponse"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"List public locations","tags":["Locations"]}},"/readings/aggregate":{"get":{"description":"Get aggregated sensor data by time period (hour, day, week, month) for a location","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"},{"description":"Sensor type filter","in":"query","name":"sensor_type","type":"string"},{"default":"hour","description":"Aggregation period: hour, day, week, month","in":"query","name":"period","type":"string"},{"description":"Start time (RFC3339 format)","in":"query","name":"start_time","type":"string"},{"description":"End time (RFC3339 format)","in":"query","name":"end_time","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Aggregated sensor data","schema":{"$ref":"#/definitions/internal_handlers_sensors.ReadingsAggregateResponse"}},"400":{"description":"Invalid parameters","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get aggregate sensor data","tags":["Sensors"]}},"/readings/history":{"get":{"description":"Get historical sensor readings for a time range","parameters":[{"description":"Location ID","in":"query","name":"location_id","type":"string"},{"description":"Sensor ID","in":"query","name":"sensor_id","type":"string"},{"description":"Start time (RFC3339 format)","in":"query","name":"start_time","type":"string"},{"description":"End time (RFC3339 format)","in":"query","name":"end_time","type":"string"},{"default":100,"description":"Maximum number of readings","in":"query","name":"limit","type":"integer"}],"produces":["application/json"],"responses":{"400":{"description":"Invalid parameters","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"501":{"description":"Not yet implemented","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get sensor readings history","tags":["Sensors"]}},"/readings/latest":{"get":{"description":"Get the most recent sensor readings for all user locations","produces":["application/json"],"responses":{"200":{"description":"Latest readings","schema":{"$ref":"#/definitions/LatestReadingsResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get latest readings (gRPC version)","tags":["Sensors"]}},"/ready":{"get":{"description":"Check if all service dependencies are ready and available","produces":["application/json"],"responses":{"200":{"description":"Service is ready","schema":{"$ref":"#/definitions/internal_handlers.ReadyResponse"}},"503":{"description":"Service not ready","schema":{"additionalProperties":true,"type":"object"}}},"summary":"Readiness check","tags":["Health"]}},"/sensor-data":{"post":{"consumes":["application/json"],"description":"Submit a single sensor reading with measurements (PM2.5, PM10, temperature, humidity, CO2, VOC, etc.)","parameters":[{"description":"Sensor reading data","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_sensors.V1SensorReading"}}],"produces":["application/json"],"responses":{"201":{"description":"Reading submitted successfully","schema":{"$ref":"#/definitions/internal_handlers_sensors.SensorDataSubmitResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Submit sensor reading","tags":["Sensors"]}},"/sensor-data/bulk":{"post":{"consumes":["application/json"],"description":"Submit multiple sensor readings in a single request for efficient data collection","parameters":[{"description":"Bulk sensor readings data","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_sensors.BulkSensorDataRequest"}}],"produces":["application/json"],"responses":{"201":{"description":"Readings submitted successfully","schema":{"$ref":"#/definitions/internal_handlers_sensors.BulkSensorDataResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Submit bulk sensor readings","tags":["Sensors"]}},"/sensors":{"post":{"consumes":["application/json"],"description":"Register a new sensor for a location. Requires permission to manage sensors for the location.","parameters":[{"description":"Sensor registration details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_sensors.RegisterSensorRequest"}}],"produces":["application/json"],"responses":{"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"501":{"description":"Not yet implemented","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Register new sensor","tags":["Sensors"]}},"/sensors/indoor/setup-credentials":{"post":{"consumes":["application/json"],"description":"Pre-register MQTT credentials for a BLE-configured indoor device before it connects","parameters":[{"description":"Device ID and password","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_sensors.SetupCredentialsRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Credentials stored","schema":{"$ref":"#/definitions/internal_handlers_sensors.MQTTSetupResponse"}},"400":{"description":"Validation failed","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Set up indoor sensor MQTT credentials","tags":["Sensors"]}},"/sensors/indoor/unclaimed":{"get":{"description":"List indoor sensors that are actively reporting but not yet assigned to any location","parameters":[{"default":24,"description":"Max hours since last reading (max 168)","in":"query","name":"hours","type":"integer"},{"default":20,"description":"Max results (max 50)","in":"query","name":"limit","type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Unclaimed sensors","schema":{"$ref":"#/definitions/internal_handlers_sensors.UnclaimedSensorsListResponse"}},"400":{"description":"Invalid query parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"List unclaimed indoor sensors","tags":["Sensors"]}},"/sensors/indoor/verify-credentials":{"post":{"consumes":["application/json"],"description":"Verify a password matches the stored hash for a device (detects BLE transmission errors)","parameters":[{"description":"Device ID and password to verify","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_sensors.VerifyCredentialsRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Verification result","schema":{"$ref":"#/definitions/internal_handlers_sensors.MQTTVerifyResponse"}},"400":{"description":"Validation failed","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Device not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Verify indoor sensor MQTT credentials","tags":["Sensors"]}},"/sensors/indoor/{device_id}/connection-status":{"get":{"description":"Check whether an indoor device has connected and reported data (used after BLE WiFi setup)","parameters":[{"description":"Device ID","in":"path","name":"device_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Connection status","schema":{"$ref":"#/definitions/internal_handlers_sensors.ConnectionStatusResponse"}},"400":{"description":"Validation failed","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get indoor sensor connection status","tags":["Sensors"]}},"/sensors/indoor/{device_id}/mqtt-status":{"get":{"description":"Check whether an indoor device has MQTT credentials configured","parameters":[{"description":"Device ID","in":"path","name":"device_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Credential status","schema":{"$ref":"#/definitions/internal_handlers_sensors.MQTTCredentialStatusResponse"}},"400":{"description":"Validation failed","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get indoor sensor MQTT credential status","tags":["Sensors"]}},"/sensors/list":{"get":{"description":"Returns all indoor and outdoor sensors with multipliers","produces":["application/json"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/pb.GetAllSensorsResponse"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get all sensors for correlation analysis","tags":["Sensors"]}},"/sensors/outdoor/public":{"get":{"description":"Get all public outdoor sensors available for user selection during onboarding","produces":["application/json"],"responses":{"200":{"description":"List of public outdoor sensors","schema":{"$ref":"#/definitions/PublicOutdoorSensorsResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get public outdoor sensors","tags":["Sensors"]}},"/sensors/public/nearby":{"get":{"description":"List public sensors within a radius of a coordinate, sorted by distance (no auth required)","parameters":[{"description":"Latitude","in":"query","name":"lat","required":true,"type":"number"},{"description":"Longitude","in":"query","name":"lon","required":true,"type":"number"},{"default":50,"description":"Search radius in km","in":"query","name":"radius","type":"number"},{"default":20,"description":"Max results (max 100)","in":"query","name":"limit","type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Nearby public sensors","schema":{"$ref":"#/definitions/internal_handlers_sensors.NearbySensorsListResponse"}},"400":{"description":"Missing lat/lon","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"List nearby public sensors","tags":["Sensors"]}},"/sensors/silence":{"post":{"consumes":["application/json"],"description":"Silence offline alerts for a single sensor","parameters":[{"description":"Silence request","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_sensors.SilenceSensorRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Silence result","schema":{"$ref":"#/definitions/internal_handlers_sensors.SensorActionResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Silence a sensor","tags":["Sensors"]}},"/sensors/silence-bulk":{"post":{"consumes":["application/json"],"description":"Silence offline alerts for multiple sensors at a location","parameters":[{"description":"Bulk silence request","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_sensors.BulkSilenceSensorsRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Bulk silence result","schema":{"$ref":"#/definitions/internal_handlers_sensors.BulkSilenceResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Silence sensors in bulk","tags":["Sensors"]}},"/sensors/unsilence":{"delete":{"description":"Remove an offline-alert silence for a sensor","parameters":[{"description":"Sensor ID","in":"query","name":"sensor_id","required":true,"type":"string"},{"description":"Sensor type","in":"query","name":"sensor_type","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Unsilence result","schema":{"$ref":"#/definitions/internal_handlers_sensors.SensorActionResponse"}},"400":{"description":"Missing sensor_id/sensor_type","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Unsilence a sensor","tags":["Sensors"]}},"/sensors/yf10x/events":{"post":{"consumes":["application/json"],"description":"Accepts vape or bullying detection events from YF10X sensors","parameters":[{"description":"Event data","in":"body","name":"event","required":true,"schema":{"$ref":"#/definitions/internal_handlers.YF10XEventRequest"}}],"produces":["application/json"],"responses":{"201":{"description":"Event recorded successfully","schema":{"$ref":"#/definitions/internal_handlers.YF10XEventResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Submit YF10X sensor event (vape/bullying detection)","tags":["Sensors"]}},"/sensors/yf10x/readings":{"post":{"consumes":["application/json"],"description":"Accepts YF10X sensor data from MQTT gateway and forwards to api-core","parameters":[{"description":"Sensor reading data","in":"body","name":"reading","required":true,"schema":{"$ref":"#/definitions/internal_handlers.YF10XReadingRequest"}}],"produces":["application/json"],"responses":{"201":{"description":"Reading submitted successfully","schema":{"$ref":"#/definitions/internal_handlers.YF10XReadingResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Submit YF10X sensor reading","tags":["Sensors"]}},"/sensors/{device_id}/ownership":{"get":{"description":"Check whether a device is claimed and whether the current user can claim/reconfigure it","parameters":[{"description":"Device ID","in":"path","name":"device_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Ownership status","schema":{"$ref":"#/definitions/internal_handlers_sensors.OwnershipResponse"}},"400":{"description":"Validation failed","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get sensor ownership status","tags":["Sensors"]}},"/sensors/{id}":{"delete":{"description":"Permanently delete a sensor. Requires permission to manage sensors for the location.","parameters":[{"description":"Sensor ID","in":"path","name":"id","required":true,"type":"string"}],"responses":{"400":{"description":"Invalid sensor ID","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Sensor not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"501":{"description":"Not yet implemented","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Delete sensor","tags":["Sensors"]},"get":{"description":"Get detailed information about a specific sensor","parameters":[{"description":"Sensor ID","in":"path","name":"id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"400":{"description":"Invalid sensor ID","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Sensor not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"501":{"description":"Not yet implemented","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get sensor by ID","tags":["Sensors"]},"put":{"consumes":["application/json"],"description":"Update sensor details. Requires permission to manage sensors for the location.","parameters":[{"description":"Sensor ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Updated sensor details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_sensors.UpdateSensorRequest"}}],"produces":["application/json"],"responses":{"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Sensor not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"501":{"description":"Not yet implemented","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update sensor","tags":["Sensors"]}},"/sensors/{sensor_id}/airsense-alerts":{"get":{"description":"List PodQ AirSense alerts for a sensor","parameters":[{"description":"Sensor ID","in":"path","name":"sensor_id","required":true,"type":"string"},{"description":"Include dismissed alerts","in":"query","name":"include_dismissed","type":"boolean"},{"description":"Lookback window in days","in":"query","name":"days","type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Sensor alerts","schema":{"$ref":"#/definitions/internal_handlers.SensorAlertsResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"List sensor alerts","tags":["Sensors"]}},"/sensors/{sensor_id}/airsense-history":{"get":{"description":"Returns daily grade snapshots across multiple timeframes (1d, 3d, 7d, 14d) for trend analysis","parameters":[{"description":"Indoor Sensor ID","in":"path","name":"sensor_id","required":true,"type":"string"},{"description":"Number of days of history to fetch (default 14, max 30)","in":"query","name":"days","type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"history array with daily snapshots","schema":{"$ref":"#/definitions/internal_handlers.SensorGradeHistoryResponse"}},"400":{"description":"Invalid sensor ID","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get historical AirSense grades for a sensor","tags":["Sensors"]}},"/sensors/{sensor_id}/airsense-interventions":{"get":{"description":"Retrieves all logged maintenance interventions for a sensor with before/after metrics","parameters":[{"description":"Indoor Sensor ID","in":"path","name":"sensor_id","required":true,"type":"string"},{"description":"Number of days of history (default 90)","in":"query","name":"days","type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/pb.GetInterventionsResponse"}},"400":{"description":"Invalid sensor ID","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get intervention history for a sensor","tags":["Sensors"]},"post":{"consumes":["application/json"],"description":"Records a maintenance action (filter change, HVAC repair, etc.) for a sensor","parameters":[{"description":"Indoor Sensor ID","in":"path","name":"sensor_id","required":true,"type":"string"},{"description":"Intervention details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers.LogInterventionRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/pb.LogInterventionResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Log a maintenance intervention","tags":["Sensors"]}},"/sensors/{sensor_id}/command":{"post":{"consumes":["application/json"],"description":"Sends a command to a YF10X sensor via MQTT (e.g., threshold configuration)","parameters":[{"description":"Sensor device ID (e.g., 2512010132)","in":"path","name":"sensor_id","required":true,"type":"string"},{"description":"Command to send","in":"body","name":"command","required":true,"schema":{"$ref":"#/definitions/internal_handlers_sensors.SensorCommandRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Command sent successfully","schema":{"$ref":"#/definitions/internal_handlers_sensors.SensorCommandResponse"}},"400":{"description":"Invalid request","schema":{"$ref":"#/definitions/internal_handlers_sensors.SensorCommandResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"MQTT gateway unavailable","schema":{"$ref":"#/definitions/internal_handlers_sensors.SensorCommandResponse"}}},"security":[{"BearerAuth":[]}],"summary":"Send command to YF10X sensor","tags":["Sensors"]}},"/settings/color-schemes":{"get":{"description":"Get all available color schemes","produces":["application/json"],"responses":{"200":{"description":"List of color schemes","schema":{"items":{"$ref":"#/definitions/internal_handlers_settings.ColorSchemeResponse"},"type":"array"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get color schemes","tags":["Settings"]}},"/settings/colors":{"get":{"description":"Get effective colors for the current context (user, location, or system)","parameters":[{"description":"Location ID","in":"query","name":"location_id","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Effective colors","schema":{"$ref":"#/definitions/internal_handlers_settings.EffectiveColorsResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get effective colors","tags":["Settings"]}},"/settings/colors/system":{"get":{"description":"Get system-level default colors (public endpoint)","produces":["application/json"],"responses":{"200":{"description":"System colors","schema":{"$ref":"#/definitions/internal_handlers_settings.EffectiveColorsResponse"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get system colors","tags":["Settings"]}},"/settings/location/{id}/colors":{"put":{"consumes":["application/json"],"description":"Update color settings for a specific location","parameters":[{"description":"Location ID","in":"path","name":"id","required":true,"type":"string"},{"description":"Color settings","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_settings.SettingsUpdateRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Updated colors","schema":{"$ref":"#/definitions/internal_handlers_settings.SettingsUpdateResult"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update location colors","tags":["Settings"]}},"/settings/system/colors":{"put":{"consumes":["application/json"],"description":"Update system-level default colors (admin only)","parameters":[{"description":"Color settings","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_settings.SettingsUpdateRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Updated colors","schema":{"$ref":"#/definitions/internal_handlers_settings.SettingsUpdateResult"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"403":{"description":"Forbidden - admin only","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update system colors","tags":["Settings"]}},"/settings/user/colors":{"put":{"consumes":["application/json"],"description":"Update color settings for the current user","parameters":[{"description":"Color settings","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_settings.SettingsUpdateRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Updated colors","schema":{"$ref":"#/definitions/internal_handlers_settings.SettingsUpdateResult"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update user colors","tags":["Settings"]}},"/timezone/location":{"get":{"description":"Get timezone information for geographic coordinates","parameters":[{"description":"Latitude","in":"query","name":"lat","required":true,"type":"number"},{"description":"Longitude","in":"query","name":"lng","required":true,"type":"number"}],"produces":["application/json"],"responses":{"200":{"description":"Timezone information","schema":{"$ref":"#/definitions/TimezoneResponse"}},"400":{"description":"Missing or invalid parameters","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get timezone for location","tags":["Utilities"]}},"/translations/languages":{"get":{"description":"Get list of all supported languages for i18n","produces":["application/json"],"responses":{"200":{"description":"Available languages","schema":{"$ref":"#/definitions/LanguagesResponse"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get available languages","tags":["Internationalization"]}},"/translations/namespaces":{"get":{"description":"Get list of all available translation namespaces","produces":["application/json"],"responses":{"200":{"description":"Available namespaces","schema":{"$ref":"#/definitions/NamespacesResponse"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get translation namespaces","tags":["Internationalization"]}},"/translations/{language}/{namespace}":{"get":{"description":"Get i18n translations for a specific language and namespace","parameters":[{"description":"Language code (en, es, fr, de, ja, etc.)","in":"path","name":"language","required":true,"type":"string"},{"description":"Translation namespace (common, dashboard, etc.)","in":"path","name":"namespace","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Translation strings","schema":{"$ref":"#/definitions/TranslationResponse"}},"400":{"description":"Invalid language or namespace","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get translations","tags":["Internationalization"]}},"/users/accept-invitation":{"post":{"consumes":["application/json"],"description":"Accept a pending invitation to join a location","parameters":[{"description":"Invitation ID or invitation token","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.AcceptInvitationRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Invitation accepted successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.InvitationAcceptedResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Invitation not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Accept location invitation","tags":["Users"]}},"/users/check-access":{"post":{"consumes":["application/json"],"description":"Check if a user exists, has an invitation, or needs to complete registration","parameters":[{"description":"User email","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_users.CheckAccessRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"User access status","schema":{"$ref":"#/definitions/internal_handlers_users.CheckAccessResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Check user access status","tags":["Users"]}},"/users/decline-invitation":{"post":{"consumes":["application/json"],"description":"Decline a pending invitation to join a location","parameters":[{"description":"Invitation ID","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_locations.DeclineInvitationRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Invitation declined successfully","schema":{"$ref":"#/definitions/internal_handlers_locations.LocationActionResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Invitation not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Decline location invitation","tags":["Users"]}},"/users/invitations":{"get":{"description":"Get all pending location invitations for the authenticated user","produces":["application/json"],"responses":{"200":{"description":"List of pending invitations","schema":{"$ref":"#/definitions/internal_handlers_users.PendingInvitationsResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get user's pending invitations","tags":["Users"]}},"/users/me":{"delete":{"consumes":["application/json"],"description":"Permanently delete the authenticated user's account. Requires password confirmation. This action cannot be undone.","parameters":[{"description":"Account deletion confirmation","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_users.DeleteAccountRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Account deleted successfully","schema":{"$ref":"#/definitions/internal_handlers_users.DeleteAccountResponse"}},"400":{"description":"Invalid request or missing confirmation","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized or incorrect password","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Delete user account","tags":["Users"]},"get":{"consumes":["application/json"],"description":"Get the authenticated user's complete profile information including ID, email, name, role, and account status.","produces":["application/json"],"responses":{"200":{"description":"User profile retrieved successfully","schema":{"$ref":"#/definitions/internal_handlers_users.GetMeResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get current user profile","tags":["Users"]},"put":{"consumes":["application/json"],"description":"Update the authenticated user's profile information. Email changes require verification (not yet implemented).","parameters":[{"description":"Profile update details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_users.UpdateProfileRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Profile updated successfully","schema":{"$ref":"#/definitions/internal_handlers_users.UpdateProfileResponse"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"409":{"description":"Email already in use","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update user profile","tags":["Users"]}},"/users/me/change-password":{"post":{"consumes":["application/json"],"description":"Change the authenticated user's password. Requires current password for verification.","parameters":[{"description":"Password change details","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_users.ChangePasswordRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Password changed successfully","schema":{"$ref":"#/definitions/internal_handlers_users.ChangePasswordResponse"}},"400":{"description":"Invalid request or validation error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized or current password incorrect","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Change password","tags":["Users"]}},"/users/me/device-preferences":{"delete":{"description":"Delete a device preference by its ID for the authenticated user","parameters":[{"description":"Preference ID","in":"query","name":"preference_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Deletion result","schema":{"$ref":"#/definitions/internal_handlers_users.AckResponse"}},"400":{"description":"Missing preference_id","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Delete a device preference","tags":["Users"]},"get":{"description":"List device preferences for the authenticated user, optionally filtered by device or location","parameters":[{"description":"Filter by device ID","in":"query","name":"device_id","type":"string"},{"description":"Filter by location ID","in":"query","name":"location_id","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Device preferences","schema":{"$ref":"#/definitions/internal_handlers_users.DevicePreferencesListResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"List device preferences","tags":["Users"]},"post":{"consumes":["application/json"],"description":"Set the per-device location/sensor/display preference for the authenticated user","parameters":[{"description":"Device preference (device_id, location_id, primary_sensor_id, display_mode, applies_to_all_devices)","in":"body","name":"request","required":true,"schema":{"additionalProperties":true,"type":"object"}}],"produces":["application/json"],"responses":{"201":{"description":"Saved preference","schema":{"$ref":"#/definitions/internal_handlers_users.DevicePreferenceMutationResponse"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Set a device preference","tags":["Users"]}},"/users/me/devices":{"get":{"description":"List all registered devices for the authenticated user","produces":["application/json"],"responses":{"200":{"description":"Registered devices","schema":{"$ref":"#/definitions/internal_handlers_users.DevicesListResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"List devices","tags":["Users"]},"post":{"consumes":["application/json"],"description":"Register a push/notification device for the authenticated user","parameters":[{"description":"Device registration (device_id, device_name, device_type)","in":"body","name":"request","required":true,"schema":{"additionalProperties":{"type":"string"},"type":"object"}}],"produces":["application/json"],"responses":{"201":{"description":"Registered device","schema":{"$ref":"#/definitions/internal_handlers_users.DeviceMutationResponse"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Register a device","tags":["Users"]}},"/users/me/devices/{device_id}":{"delete":{"description":"Remove a registered device for the authenticated user","parameters":[{"description":"Device ID","in":"path","name":"device_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Deletion result","schema":{"$ref":"#/definitions/internal_handlers_users.AckResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Delete a device","tags":["Users"]},"put":{"consumes":["application/json"],"description":"Update the display name of a registered device","parameters":[{"description":"Device ID","in":"path","name":"device_id","required":true,"type":"string"},{"description":"New device name (device_name)","in":"body","name":"request","required":true,"schema":{"additionalProperties":{"type":"string"},"type":"object"}}],"produces":["application/json"],"responses":{"200":{"description":"Updated device","schema":{"$ref":"#/definitions/internal_handlers_users.DeviceMutationResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Rename a device","tags":["Users"]}},"/users/me/devices/{device_id}/check-registration":{"get":{"description":"Check whether a device is still active or needs re-registration","parameters":[{"description":"Device ID","in":"path","name":"device_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Device registration status","schema":{"$ref":"#/definitions/internal_handlers_users.DeviceRegistrationStatus"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Check device registration","tags":["Users"]}},"/users/me/home-bootstrap":{"get":{"description":"Single best-effort payload with everything needed to render the Home screen","produces":["application/json"],"responses":{"200":{"description":"Home bootstrap payload (mixed-convention aggregate assembled from many services; see Getting Started)","schema":{"additionalProperties":true,"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Home screen bootstrap","tags":["Users"]}},"/users/me/preferences/onboarding-completed":{"get":{"description":"Get whether the authenticated user has completed onboarding","produces":["application/json"],"responses":{"200":{"description":"Onboarding completion status","schema":{"$ref":"#/definitions/internal_handlers.OnboardingCompletedResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get onboarding status","tags":["Users"]},"put":{"consumes":["application/json"],"description":"Set whether the authenticated user has completed onboarding","parameters":[{"description":"Onboarding status (completed)","in":"body","name":"request","required":true,"schema":{"additionalProperties":{"type":"boolean"},"type":"object"}}],"produces":["application/json"],"responses":{"200":{"description":"Onboarding completion status","schema":{"$ref":"#/definitions/internal_handlers.OnboardingCompletedResponse"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Set onboarding status","tags":["Users"]}},"/users/me/preferences/selected-location":{"get":{"description":"Get the authenticated user's currently selected location ID (null if unset)","produces":["application/json"],"responses":{"200":{"description":"Selected location ID","schema":{"$ref":"#/definitions/internal_handlers.SelectedLocationResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get selected location","tags":["Users"]},"put":{"consumes":["application/json"],"description":"Set the authenticated user's selected location ID","parameters":[{"description":"Selected location (location_id)","in":"body","name":"request","required":true,"schema":{"additionalProperties":{"type":"string"},"type":"object"}}],"produces":["application/json"],"responses":{"200":{"description":"Selected location ID","schema":{"$ref":"#/definitions/internal_handlers.SelectedLocationResponse"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Set selected location","tags":["Users"]}},"/users/me/preferences/widget-location":{"get":{"description":"Get the authenticated user's home-screen widget location ID (null if unset)","produces":["application/json"],"responses":{"200":{"description":"Widget location ID","schema":{"$ref":"#/definitions/internal_handlers.WidgetLocationResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get widget location","tags":["Users"]},"put":{"consumes":["application/json"],"description":"Set the authenticated user's home-screen widget location ID","parameters":[{"description":"Widget location (location_id)","in":"body","name":"request","required":true,"schema":{"additionalProperties":{"type":"string"},"type":"object"}}],"produces":["application/json"],"responses":{"200":{"description":"Widget location ID","schema":{"$ref":"#/definitions/internal_handlers.WidgetLocationResponse"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Set widget location","tags":["Users"]}},"/users/me/profile":{"put":{"consumes":["application/json"],"description":"Update user profile name (splits into first/last name)","parameters":[{"description":"Profile update","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_users.UpdateProfileV1Request"}}],"produces":["application/json"],"responses":{"200":{"description":"Profile updated successfully","schema":{"$ref":"#/definitions/internal_handlers_users.ProfileUpdateMessageResponse"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update user profile (v1 compatibility)","tags":["Users"]}},"/users/me/startup-bootstrap":{"get":{"description":"Single payload of the data the app needs at startup (selected location, onboarding status, pending invitations, server time)","produces":["application/json"],"responses":{"200":{"description":"Startup bootstrap payload","schema":{"$ref":"#/definitions/internal_handlers_users.startupBootstrapResponse"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"App startup bootstrap","tags":["Users"]}},"/users/notification-preferences/{location_id}":{"get":{"description":"Get user's notification preferences for a specific location","parameters":[{"description":"Location ID","in":"path","name":"location_id","required":true,"type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Notification preferences","schema":{"additionalProperties":true,"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Service unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Get notification preferences","tags":["Notifications"]},"put":{"consumes":["application/json"],"description":"Update user's notification preferences for a specific location","parameters":[{"description":"Location ID","in":"path","name":"location_id","required":true,"type":"string"},{"description":"Notification preferences","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_notifications.UpdateNotificationPreferencesRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"Updated preferences","schema":{"additionalProperties":true,"type":"object"}},"400":{"description":"Invalid request body","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"503":{"description":"Service unavailable","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update notification preferences","tags":["Notifications"]}},"/users/profile":{"patch":{"consumes":["application/json"],"description":"Update user's phone number (partial implementation)","parameters":[{"description":"Phone update","in":"body","name":"request","required":true,"schema":{"$ref":"#/definitions/internal_handlers_users.UpdateProfilePhoneRequest"}}],"produces":["application/json"],"responses":{"200":{"description":"User profile","schema":{"$ref":"#/definitions/internal_handlers_users.UserProfileV1"}},"400":{"description":"Invalid request","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"401":{"description":"Unauthorized","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"User not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"security":[{"BearerAuth":[]}],"summary":"Update user phone number","tags":["Users"]}},"/uvindex/history":{"get":{"description":"Get historical UV index readings for a location over a time range","parameters":[{"description":"Location ID","in":"query","name":"location_id","required":true,"type":"string"},{"description":"Range start (RFC3339)","in":"query","name":"start_date","type":"string"},{"description":"Range end (RFC3339)","in":"query","name":"end_date","type":"string"},{"default":"day","description":"Aggregation interval","in":"query","name":"interval","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"UV index history","schema":{"$ref":"#/definitions/internal_handlers_environmental.UVIndexHistoryResponse"}},"400":{"description":"Missing location_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get UV index history","tags":["Environmental"]}},"/weather/current":{"get":{"description":"Get current weather conditions for a location from Tomorrow.io","parameters":[{"description":"Location ID","in":"query","name":"location_id","type":"string"},{"description":"City ID (alternative to location_id)","in":"query","name":"city_id","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Current weather","schema":{"$ref":"#/definitions/internal_handlers_environmental.WeatherCurrentResponse"}},"400":{"description":"Missing location_id or city_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"No city found for location","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get current weather","tags":["Environmental"]}},"/weather/daily-forecast":{"get":{"description":"Get daily weather forecast summaries for a location","parameters":[{"description":"Location ID","in":"query","name":"location_id","type":"string"},{"description":"City ID (alternative to location_id)","in":"query","name":"city_id","type":"string"},{"default":4,"description":"Number of days to forecast (max 7)","in":"query","name":"days","type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Daily forecast data","schema":{"$ref":"#/definitions/internal_handlers_environmental.DailyForecastResponse"}},"400":{"description":"Missing location_id or city_id parameter","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Location not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get daily weather forecast","tags":["Environmental"]}},"/weather/forecast":{"get":{"description":"Get hourly weather forecast for a location","parameters":[{"description":"Location ID","in":"query","name":"location_id","type":"string"},{"description":"City ID (alternative to location_id)","in":"query","name":"city_id","type":"string"},{"default":24,"description":"Number of hours to forecast (max 120)","in":"query","name":"hours","type":"integer"}],"produces":["application/json"],"responses":{"200":{"description":"Weather forecast data","schema":{"additionalProperties":true,"type":"object"}},"400":{"description":"Missing location_id or city_id","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"404":{"description":"Location not found","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get weather forecast","tags":["Environmental"]}},"/weather/mappings":{"get":{"description":"Get weather code mappings with descriptions and icons for a language","parameters":[{"default":"en","description":"Language code","in":"query","name":"language","type":"string"}],"produces":["application/json"],"responses":{"200":{"description":"Weather code mappings","schema":{"$ref":"#/definitions/internal_handlers_environmental.WeatherMappingsResponse"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Get weather code mappings","tags":["Environmental"]}},"/webhooks/tyra":{"post":{"consumes":["application/json"],"description":"Proxy webhook requests from Tyra to the messaging service","parameters":[{"description":"Webhook payload from Tyra","in":"body","name":"payload","required":true,"schema":{"additionalProperties":true,"type":"object"}}],"produces":["application/json"],"responses":{"200":{"description":"Webhook processed successfully","schema":{"$ref":"#/definitions/internal_handlers_webhooks.TyraWebhookAck"}},"405":{"description":"Method not allowed","schema":{"additionalProperties":{"type":"string"},"type":"object"}},"500":{"description":"Internal server error","schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"summary":"Tyra webhook proxy","tags":["Webhooks"]}}},"schemes":["https"],"securityDefinitions":{"ApiKeyAuth":{"description":"API Key for service-to-service communication","in":"header","name":"X-API-Key","type":"apiKey"},"BearerAuth":{"description":"JWT token in format: Bearer {token}","in":"header","name":"Authorization","type":"apiKey"}},"swagger":"2.0","tags":[{"description":"Email/password, magic-link, and social login; access-token issue and refresh.","name":"Authentication"},{"description":"OAuth 2.0 authorization-code and token endpoints for third-party authorization.","name":"OAuth2"},{"description":"User profile, account management, and pending location invitations.","name":"Users"},{"description":"Registration of push-notification device tokens.","name":"Devices"},{"description":"Per-user application settings and color schemes.","name":"Settings"},{"description":"Location management, membership, invitations, and per-location air-quality data.","name":"Locations"},{"description":"Descriptive facts and context for a location.","name":"Location Profile"},{"description":"Sensor onboarding (MQTT), ownership, readings, silencing, and discovery.","name":"Sensors"},{"description":"Indoor and outdoor AQI, weather, UV index, and heat-index data and history.","name":"Environmental"},{"description":"User-configurable AQI/metric threshold bands, themes, and overrides.","name":"Environmental Bands"},{"description":"Detected air-quality events and AI-generated location insights.","name":"Insights"},{"description":"Notification preferences, message logs, and delivery counts.","name":"Notifications"},{"description":"Translations, supported languages, and timezone lookups.","name":"Internationalization"},{"description":"Miscellaneous helper endpoints.","name":"Utilities"},{"description":"In-app user feedback submission.","name":"Feedback"},{"description":"Inbound provider webhooks (e.g. Tyra).","name":"Webhooks"},{"description":"Service liveness and readiness probes.","name":"Health"}]}
