> ## Documentation Index
> Fetch the complete documentation index at: https://docs.machinefi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Snow Alert for Pittsburgh

> Build a weather alerting system that texts you when it starts snowing

Let's build a system that monitors a Pittsburgh webcam and sends you an SMS via Twilio when snow is detected.

<Warning>
  **Remember**: Jobs are time-bounded (default 10 minutes) and can also stop when
  `max_triggers` is reached. For continuous monitoring, your webhook should restart the job.
</Warning>

## How to use examples

Use this recipe as a template:

* Replace the stream URL with your own live source.
* Replace the condition with your domain-specific event.
* Keep the webhook + restart pattern for long-running monitoring.

## The Setup

We'll use:

* **Trio** to monitor a Pittsburgh traffic cam
* **Twilio** to send SMS alerts
* A simple **webhook receiver** that restarts jobs when they expire

## Step 1: Find a Live Stream

Pittsburgh has several public traffic cams. Search YouTube for "pittsburgh live cam" or "traffic cam live".

<Note>
  The URL must be an **active live stream**. Regular videos and past streams
  will be rejected with a 400 error.
</Note>

## Step 2: Create the Webhook Receiver

Set these environment variables in your host:

* `TRIO_API_KEY` (from the Trio console)
* `WEBHOOK_HOST` (your public base URL, e.g., `https://your-webhook-server.com`)
* `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`
* `TWILIO_PHONE`, `MY_PHONE`

```python webhook_server.py theme={null}
from fastapi import FastAPI, Request, BackgroundTasks
from twilio.rest import Client
import httpx
import os

app = FastAPI()

twilio_client = Client(
    os.environ["TWILIO_ACCOUNT_SID"],
    os.environ["TWILIO_AUTH_TOKEN"]
)

TRIO_API_BASE = "https://trio.machinefi.com/api"
TRIO_API_KEY = os.environ["TRIO_API_KEY"]

# Track current job
current_job_id = None
STREAM_URL = "https://youtube.com/watch?v=PITTSBURGH_LIVE_CAM"
CONDITION = "Is it snowing? Look for falling snowflakes or white accumulation."

async def start_live_monitor_job():
    """Start or restart the live monitor job."""
    global current_job_id

    async with httpx.AsyncClient() as client:
        response = await client.post(
            f"{TRIO_API_BASE}/live-monitor",
            json={
                "stream_url": STREAM_URL,
                "condition": CONDITION,
                "webhook_url": f"{os.environ['WEBHOOK_HOST']}/snow-alert",
                "interval_seconds": 30,
            },
            headers={"Authorization": f"Bearer {TRIO_API_KEY}"},
        )

        if response.status_code == 200:
            current_job_id = response.json()["job_id"]
            print(f"Started job: {current_job_id}")
        else:
            print(f"Failed to start job: {response.text}")

@app.on_event("startup")
async def startup():
    """Start monitoring on server startup."""
    await start_live_monitor_job()

@app.post("/snow-alert")
async def snow_alert(request: Request, background_tasks: BackgroundTasks):
    payload = await request.json()

    # Handle trigger events
    if payload.get("type") == "watch_triggered":
        data = payload.get("data", {})

        # Send SMS alert
        twilio_client.messages.create(
            body=f"It's snowing in Pittsburgh!\n\n{data.get('explanation', '')}",
            from_=os.environ["TWILIO_PHONE"],
            to=os.environ["MY_PHONE"],
        )
        print("Snow alert sent!")

    # Handle job status events (auto-stop by duration or trigger limit)
    elif payload.get("type") == "job_stopped":
        data = payload.get("data", {})

        # Restart for continuous coverage when the job auto-stops
        if data.get("auto_stopped"):
            reason = data.get("reason", "unknown")
            print(f"Job auto-stopped ({reason}), restarting...")
            background_tasks.add_task(start_live_monitor_job)
        else:
            print("Job was manually stopped")

    return {"status": "ok"}
```

Deploy this to Railway, Vercel, or any hosting provider.

## Step 3: Start Monitoring

The webhook server automatically starts the job on startup. If you need to manually start:

```bash theme={null}
curl -X POST https://trio.machinefi.com/api/live-monitor \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "stream_url": "https://youtube.com/watch?v=PITTSBURGH_LIVE_CAM",
    "condition": "Is it snowing? Look for falling snowflakes or white accumulation.",
    "webhook_url": "https://your-webhook-server.com/snow-alert",
    "interval_seconds": 30
  }'
```

## What Happens

1. Trio validates the URL is a live stream
2. Captures a frame every 30 seconds
3. Pre-filter checks for motion (skips static frames)
4. If motion detected, VLM analyzes the frame
5. If snow is detected, webhook fires with `watch_triggered`
6. Your server sends SMS via Twilio
7. After duration limit or `max_triggers` limit, job stops and sends `job_stopped`
8. Your server restarts the job automatically for continuous coverage

## Webhook Payloads

### Watch Trigger

```json theme={null}
{
  "type": "watch_triggered",
  "timestamp": "2024-01-26T15:30:00Z",
  "source_url": "https://youtube.com/watch?v=PITTSBURGH_LIVE_CAM",
  "data": {
    "condition": "Is it snowing?",
    "triggered": true,
    "explanation": "Light snow is falling, visible as white particles against the dark buildings.",
    "prefilter_skipped": false,
    "frame_b64": "base64-encoded-image..."
  }
}
```

### Job Auto-Stop (duration limit)

```json theme={null}
{
  "type": "job_stopped",
  "timestamp": "2024-01-26T15:40:00Z",
  "source_url": "https://youtube.com/watch?v=PITTSBURGH_LIVE_CAM",
  "data": {
    "job_id": "abc123...",
    "status": "stopped",
    "checks_performed": 20,
    "triggers_fired": 0,
    "frames_skipped": 80,
    "auto_stopped": true,
    "reason": "max_duration_reached",
    "watch_duration_seconds": 600.0
  }
}
```

## Cost Analysis

With 10-minute job cycles and 30-second intervals:

| Metric                      | Per Job (10 min) | Per Hour  | Per Day     |
| --------------------------- | ---------------- | --------- | ----------- |
| Frames captured             | 20               | 120       | 2,880       |
| VLM calls (with pre-filter) | \~5-10           | \~30-60   | \~720-1,440 |
| VLM cost                    | \~\$0.001        | \~\$0.006 | \~\$0.14    |

<Tip>
  The pre-filter typically saves 50-70% on API costs by skipping static frames.
</Tip>

## Monitoring Dashboard

Check your job status and metrics:

```bash theme={null}
# Current jobs
curl https://trio.machinefi.com/api/jobs \
  -H "Authorization: Bearer YOUR_API_KEY"

# API usage metrics (internal endpoint)
curl https://trio.machinefi.com/api/metrics \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Trio-Internal: true"

# Recent logs (internal endpoint)
curl "https://trio.machinefi.com/api/logs?level=INFO&limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Trio-Internal: true"
```

## Wrapping Up

You now have a production-ready snow alerting system that:

* Automatically restarts after 10-minute job limits
* Sends SMS alerts via Twilio
* Costs less than \$0.15/day with pre-filtering

The same pattern works for:

* Rain detection
* Fog/visibility monitoring
* Traffic incident detection
* Crowd density monitoring

<Card title="Full API Reference" icon="code" href="/api-reference/live-monitor">
  Explore all live monitor endpoint options
</Card>
