All endpoints are JSON over HTTPS. Inputs validated; errors use standard HTTP codes and a structured error body:
{ "code":"BAD_REQUEST", "message":"reason", "details": {...} }
POST /v1/subscribe/{city}Purpose: Subscribe (or update) a user for city notifications.
Path params
city — canonical city identifier or slug (server resolves name → canonical id).Request body
{
"user_id": "string", // client user id
"push_token": "string", // token for push provider
"platform": "ios|android|web",
"notify_level": "ALL|CRISIS_ONLY",
"channels": ["push","email"] // optional
}
Responses
201 Created — subscription created (returns subscription resource).200 OK — subscription updated.400 Bad Request — validation error.404 Not Found — unknown city.409 Conflict — duplicate when required by semantics.Notes
DELETE /v1/unsubscribe/{city}Purpose: Remove user subscription.
Request body (or auth context)
{ "user_id": "string" }
Responses
200 OK — unsubscribed.404 Not Found — subscription not found.GET /v1/weather/{city}/{YYYY-MM-DD}Purpose: Return forecast for a city on a specific date.
Response (200 OK)
{
"city_id":"city:2643743",
"city_name":"London",
"date":"2025-12-08",
"forecast": {
"temp_min_c": 4.0,
"temp_max_c": 8.0,
"temp_avg_c": 6.0,
"humidity_percent": 78,
"precip_probability": 0.6,
"wind_kph": 40,
"sunny": false
},
"hazards":[{"type":"HIGH_WIND","level":"SEVERE","confidence":0.9}],
"generated_at":"2025-12-07T23:00:00Z",
"source":["providerA","providerB"]
}
Errors
404 Not Found — city or date unknown400 Bad Request — invalid date format500 Internal Server Error — server failureNotes
event_id. Use Redis SET / bloom filter with TTL to dedupe within a short window.city_id + hazard_type + time_window (e.g., 15 minutes) to avoid notification storms; escalate if severity increases.weather::city::<id>::date::<YYYY-MM-DD>) and cached subscriber sets.Purpose: Turn heterogeneous provider inputs into a canonical, enriched event and identify hazards.
Core responsibilities
urgency (LOW | NORMAL | HIGH).Scaling
Algorithms / Data structures
event_id (TTL 5–15m) or content-hash bloom filter for fuzzy duplicate detection.Tradeoffs
Purpose: Store and serve subscriptions for fast fan-out.
Data model (NoSQL recommended)
city#<city_id>user#<user_id>push_token, platform, notify_level, channels, status, created_at, last_invalidated_atWhy NoSQL (DynamoDB/Cassandra)
city_id → subscribers fan-out pattern.Caching
subscribers::city::<city_id>::shard::<n> storing serialized arrays/sets of subscriber objects (TTL 1–5 minutes).Scaling & sharding
city_id. For hot cities, use shard suffix: city#<id>#shard-<0..k-1> where k grows with subscribers.Algorithms / data structures
Tradeoffs
Purpose: Reliable, prioritized fan-out of events to subscribers.
Exchange & queue model
weather.city.<city_id>.normalweather.city.<city_id>.urgentdeliver.shard.<n>.normal and deliver.shard.<n>.urgentshard = hash(city_id) % N_SHARDSPriority & ordering
Delivery worker steps
city_id → shard then fetch subscribers from Redis (or DB fallback).notify_level and user preferences.Scaling
N_SHARDS and worker count to raise throughput. Autoscale worker pool based on queue length and delivery latency.Algorithms / data structures
delivered::<event_id>::<user_id> with TTL.Capacity example (simple)
Tradeoffs
Purpose: Low-latency reads for forecasts and subscriber caches.
Keys
weather::city::<city_id>::date::<YYYY-MM-DD>subscribers::city::<city_id>::shard::<n>Strategy
Tradeoffs
Purpose: Interface with Airship / FCM / APNs (or custom push gateway) for final delivery.
Considerations
POST /v1/subscribe/{city} → Subscription DB + Redis cache.urgent vs normal).GET /v1/weather/{city}/{date} → Redis (cache-aside) → fallback to canonical store if needed.