Room change watcher

A free image-change trigger with an explicit interpretation boundary.

Documentation revision 1 · Contract reference API 54 · Installed readiness unknown

Preview offline card · Complete HTML interaction body · Machine-readable recipe

What runs where

Controller integration

Only explicit decision-model/weather requests leave the phone; direct or relay transport still requires a running reachable phone.

Boundaries

Lifecycle

start

POST /v1/watches with the start example; retain the returned watch_id. Then render/post the card with resource_id.

update

GET state/tally first. To change the definition, export required history, DELETE old watch, POST revised definition. No watch PATCH exists.

pause

POST /v1/watches/{id}/pause with {"paused":true}; resume with false. Confirm user_paused from the returned metadata. Expiry is unchanged.

stop

DELETE /v1/watches/{id}; confirm deleted equals the saved id and watch.ended reason deleted. Archive needed tally BEFORE deletion. Cancel the interaction separately.

Failures and costs

Complete synthetic contract examples

These fixtures demonstrate contracts. They are not observations from a phone. Replace image/resource IDs with returned authorized IDs. Load request.json as the complete body of POST /v1/interactions; it contains the full offline HTML.

start

{
  "id": "start",
  "request": {
    "method": "POST",
    "path": "/v1/watches",
    "body": {
      "label": "Room image changes",
      "data": [
        "camera"
      ],
      "camera": "rear",
      "model": "none",
      "interval_s": 30,
      "window": 1,
      "min_readings": 1,
      "expires_in_s": 3600,
      "repeat": true,
      "cooldown_s": 120,
      "local": {
        "change_check": true,
        "sensitivity": "normal",
        "max_reuse_s": 300
      }
    }
  },
  "response": {
    "status": 201,
    "body": {
      "watch_id": "w0123456789ab",
      "label": "Room image changes",
      "user_paused": false,
      "created_at": "2026-11-04T10:00:00Z",
      "expires_at": "2026-11-04T11:00:00Z",
      "interval_s": 30,
      "data": [
        "camera"
      ],
      "checks": 0,
      "matches": 0,
      "last_match_at": null,
      "readings_held": 0,
      "last_check": null,
      "usage": {
        "model": "none",
        "answered_locally": 0,
        "paid_calls": 0,
        "paid_cost_usd": 0,
        "paid_calls_last_hour": 0,
        "max_paid_per_hour": null,
        "skipped_over_paid_cap": 0,
        "change_check": true
      },
      "spec": {
        "label": "Room image changes",
        "data": [
          "camera"
        ],
        "camera": "rear",
        "model": "none",
        "interval_s": 30,
        "window": 1,
        "min_readings": 1,
        "expires_in_s": 3600,
        "repeat": true,
        "cooldown_s": 120,
        "local": {
          "change_check": true,
          "sensitivity": "normal",
          "max_reuse_s": 300
        }
      },
      "callback_secret": null,
      "first_check_in_s": 0,
      "delivery": "watch.matched and watch.ended go to your registered callback if you have one, and to GET /v1/events (both need the interactions permission)"
    }
  },
  "evidence": "Synthetic contract fixture, not a device observation."
}

stop

{
  "id": "stop",
  "request": {
    "method": "DELETE",
    "path": "/v1/watches/w0123456789ab"
  },
  "response": {
    "status": 200,
    "body": {
      "deleted": "w0123456789ab"
    }
  },
  "evidence": "Synthetic contract fixture, not a device observation."
}

Extensions

Source evidence