{
 "schema": "elsegundo.xyz/webhooks/v1",
 "title": "Webhooks: local events for your bot",
 "url": "https://elsegundo.xyz/webhooks/",
 "checked": "2026-10-27",
 "checked_at": "2026-10-27T15:50:00-07:00",
 "timezone": "America/Los_Angeles",
 "not_official": "Not an official City of El Segundo page or service. Not affiliated with rec.us, LA Metro, NWS, NOAA, AirNow, Heal the Bay or any city named here.",
 "candidatePolicy": "Election-neutral: no candidates, and ballot measures only as the official agenda wording.",
 "follow": "https://x.com/elsegundotoday",
 "corrections": "https://elsegundo.xyz/report/",
 "status": {
  "feeds": "live_on_merge",
  "deliveries": "needs_setup",
  "intake": "needs_setup",
  "summary": "The event feeds start on merge. Webhook deliveries open once a signing key is set. Partner intake is coming later."
 },
 "envelope": {
  "schema": "Always \"elsegundo.xyz/webhooks/v1\".",
  "id": "evt_ plus 24 hex characters. Deterministic: the same change always has the same id. Use it to de-duplicate.",
  "type": "One of the event types below.",
  "version": "Payload version for this type (1).",
  "created_at": "When we detected it, Pacific time with offset.",
  "summary": "One plain sentence.",
  "url": "The human page to open.",
  "source": "Where the data came from: name, url, retrieved_at, and credit where required.",
  "provenance": "label (fact, official record, schedule, reported, model, availability) and the sources behind it.",
  "data": "Type-specific fields (see examples).",
  "not_official": "The not-official note, on every event.",
  "follow": "https://x.com/elsegundotoday"
 },
 "signing": {
  "scheme": "Standard Webhooks v1, HMAC-SHA256",
  "headers": [
   "webhook-id",
   "webhook-timestamp",
   "webhook-signature"
  ],
  "signed_content": "{webhook-id}.{webhook-timestamp}.{raw request body}",
  "signature_format": "v1,<base64 HMAC-SHA256>",
  "secret_format": "whsec_<base64>, shown once when you subscribe",
  "tolerance_seconds": 300,
  "idempotency": "webhook-id equals the event id and stays the same across retries.",
  "extra_headers": [
   "x-elsegundo-event",
   "x-elsegundo-attempt",
   "x-elsegundo-subscription"
  ],
  "source_ids": [
   "std-webhooks"
  ],
  "verify_node": "import { createHmac, timingSafeEqual } from 'node:crypto';\nexport function verify(secret, h, rawBody) {\n  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');\n  const ts = Number(h['webhook-timestamp']);\n  if (Math.abs(Date.now() / 1000 - ts) > 300) return false;\n  const mac = createHmac('sha256', key).update(`${h['webhook-id']}.${ts}.${rawBody}`).digest();\n  return h['webhook-signature'].split(' ').some(s => {\n    const got = Buffer.from(s.replace(/^v1,/, ''), 'base64');\n    return got.length === mac.length && timingSafeEqual(got, mac);\n  });\n}"
 },
 "delivery": {
  "method": "POST",
  "content_type": "application/json",
  "timeout_seconds": 10,
  "success": "Any 2xx.",
  "gone": "Answer 410 Gone to unsubscribe yourself.",
  "retries_seconds": [
   60,
   300,
   1800,
   7200,
   21600,
   43200
  ],
  "retry_note": "Up to 7 attempts over about 21 hours, then the delivery is dropped to a dead-letter list.",
  "redirects": "Not followed.",
  "disable_after_failures": 50,
  "per_subscriber_per_run": 20,
  "order": "Best effort, not guaranteed. Use created_at and id.",
  "source_ids": [
   "std-webhooks",
   "owasp-ssrf"
  ]
 },
 "subscribe": {
  "endpoint": "https://elsegundo.xyz/api/v1/webhooks/subscribe",
  "request": {
   "url": "https://bot.example.org/hooks/elsegundo",
   "events": [
    "court.opened",
    "civic.agenda_posted"
   ],
   "filters": {
    "court.opened": {
     "dows": [
      "Sat",
      "Sun"
     ],
     "hour_from": 8,
     "hour_to": 12,
     "min_courts": 2
    }
   },
   "label": "Neighborhood bot"
  },
  "handshake": "We POST a signed {\"type\":\"webhook.verify\",\"challenge\":\"...\"} to your URL. Answer 2xx with {\"challenge\":\"<the same value>\"} within 8 seconds.",
  "response": {
   "ok": true,
   "subscription": {
    "id": "sub_<32 hex>",
    "status": "active"
   },
   "secret": "whsec_… (shown once)",
   "manage_token": "mt_… (shown once)"
  },
  "unsubscribe": {
   "endpoint": "https://elsegundo.xyz/api/v1/webhooks/unsubscribe",
   "request": {
    "id": "sub_…",
    "token": "mt_…"
   }
  },
  "status": {
   "endpoint": "https://elsegundo.xyz/api/v1/webhooks/subscription?id=sub_…",
   "auth": "Authorization: Bearer mt_…"
  },
  "url_rules": "Public https only, port 443, no user:password, no IP addresses, no localhost or internal names, no elsegundo.xyz or netlify.app. The host must resolve to public addresses, checked again before every delivery.",
  "limits": {
   "subscribe_requests": "6 per minute per IP",
   "active_per_host": 5,
   "total": 500
  },
  "privacy": "We store your URL, chosen events and filters, an optional label, and delivery status. No email, no IP log in our records. Secrets are derived, not stored."
 },
 "feeds": [
  {
   "url": "https://elsegundo.xyz/webhooks/feed.json",
   "format": "JSON Feed 1.1",
   "source_ids": [
    "jsonfeed"
   ]
  },
  {
   "url": "https://elsegundo.xyz/webhooks/feed.xml",
   "format": "RSS 2.0",
   "source_ids": [
    "rss"
   ]
  }
 ],
 "feed_note": "Both feeds carry the last 200 events with the full payload and accept ?type=<event type>. Cached 5 minutes. No signup.",
 "events": [
  {
   "type": "court.opened",
   "title": "A pickleball court opened up",
   "status": "ready_on_merge",
   "lead": true,
   "trigger": "A court hour at Rec Park that was booked in the previous /pickleball snapshot shows as open in the next one, for a future hour on a day both snapshots cover.",
   "cadence": "Checked at :20 past each hour; new snapshots arrive about 7, 10 AM, 1, 4 and 7 PM PT, so a typical day has up to 5 chances.",
   "source_ids": [
    "es-pickleball",
    "rec-us",
    "rec-us-tos"
   ],
   "filters": {
    "dows": "[\"Sat\",\"Sun\"]",
    "hour_from": "8 (24-hour PT)",
    "hour_to": "12 (exclusive)",
    "courts": "[1,2,3]",
    "min_courts": "2"
   },
   "rules": [
    "Reads the snapshot /pickleball already stores. Makes no extra calls to rec.us.",
    "Public availability only: open or not, per court per hour. No booker names or accounts are read or stored.",
    "Credits rec.us and links to rec.us to book. We never book or hold courts.",
    "Stops when the /pickleball kill switch is on (PICKLEBALL_LIVE=off or live_enabled:false), or when the snapshot is stale.",
    "Rec Park is the only court with live data today. Other South Bay courts are listed on /pickleball without live availability."
   ]
  },
  {
   "type": "today.published",
   "title": "Today’s issue is out",
   "status": "ready_on_merge",
   "trigger": "/today.json shows a new edition date and its freshness is \"fresh\".",
   "cadence": "Hourly check; at most one event per day.",
   "source_ids": [
    "es-today"
   ],
   "filters": null,
   "rules": [
    "Top five stories with their labels (fact, reported) and best source.",
    "Includes open \"For the bots\" asks once /asks.json is live."
   ]
  },
  {
   "type": "ask.opened",
   "title": "A new open ask",
   "status": "waits_for_asks_json",
   "trigger": "An ask in /asks.json (schema elsegundo.xyz/asks/v1) is newly open. Types: remind, contribute, opt-in, volunteer.",
   "cadence": "Hourly.",
   "source_ids": [],
   "filters": null,
   "rules": [
    "Starts sending once /asks.json ships (drafted separately, not published yet).",
    "Responses go through /report/ or the ask’s own link; nothing auto-publishes."
   ]
  },
  {
   "type": "transit.alert",
   "title": "Transit alert",
   "status": "ready_on_merge",
   "trigger": "An item in the /metro guide’s \"Right now\" list or project tracker is new, changes, or is cleared. Covers the C and K Lines, SkyLink and LAX connections.",
   "cadence": "Hourly. /metro.json changes by reviewed PR, so these follow our edits, not Metro’s live feed.",
   "source_ids": [
    "es-metro"
   ],
   "filters": {
    "kinds": "[\"service\",\"airport\",\"project\"]"
   },
   "rules": [
    "Each alert links the agency source it came from and keeps its fact or reported label.",
    "Live Metro alerts would need a Metro/Swiftly key; see blockers."
   ]
  },
  {
   "type": "civic.agenda_posted",
   "title": "A meeting agenda was posted",
   "status": "ready_on_merge",
   "trigger": "A new agenda appears in El Segundo’s or Redondo Beach’s Granicus agenda feed, or a Manhattan Beach Legistar meeting gets an agenda file.",
   "cadence": "5 times a day (7, 10 AM, 1, 4, 7 PM PT).",
   "source_ids": [
    "es-granicus",
    "redondo-granicus",
    "mb-legistar",
    "legistar-api"
   ],
   "filters": {
    "cities": "[\"El Segundo\",\"Manhattan Beach\",\"Redondo Beach\"]"
   },
   "rules": [
    "Meeting title, date and the official agenda link only. No summaries of items.",
    "Never names candidates. Ballot measures appear only in the city’s own agenda wording.",
    "Official record: the city’s feed is the source."
   ]
  },
  {
   "type": "conditions.threshold",
   "title": "Conditions crossed a threshold",
   "status": "partly_ready",
   "trigger": "A new NWS alert for El Segundo; an NWS daytime forecast high of 90°F or more; Santa Monica Bay buoy wave height crossing 1.8 m (about 6 ft); AQI crossing 101; a nearby beach water grade moving into or out of C/D/F.",
   "cadence": "Hourly (water grades twice a day).",
   "source_ids": [
    "es-conditions",
    "nws-api",
    "ndbc-46221",
    "airnow-api",
    "airnow-aqi",
    "htb-brc",
    "htb-grades"
   ],
   "filters": {
    "kinds": "[\"nws_alert\",\"heat\",\"surf\",\"aqi\",\"water_grade\"]"
   },
   "rules": [
    "NWS alerts, heat and surf work on merge. AQI needs a free AirNow key. Water grades wait for an OK on using Heal the Bay’s data feed.",
    "Surf uses offshore buoy wave height, not breaking surf at a given beach. Heat uses a forecast, labeled model."
   ]
  },
  {
   "type": "record.changed",
   "title": "A shared record was added or corrected",
   "status": "ready_on_merge",
   "trigger": "A resource, event or El Segundo business record is added, corrected (field by field, before and after) or removed.",
   "cadence": "Twice a day (6 AM and 6 PM PT).",
   "source_ids": [
    "es-resources",
    "es-events",
    "es-businesses",
    "osm-copyright"
   ],
   "filters": {
    "datasets": "[\"resources\",\"events\",\"businesses\"]"
   },
   "rules": [
    "Provenance on every event: the record’s own source link, its checked date and our dataset URL.",
    "Public, business and civic fields only. No people.",
    "Business data © OpenStreetMap contributors (ODbL)."
   ]
  }
 ],
 "intake": {
  "endpoint": "https://elsegundo.xyz/api/v1/intake",
  "status": "invite_only_not_open",
  "who": "Invited local businesses and broadcasters, each with their own key.",
  "fields": {
   "submissionId": "UUID v4 you generate (retries are safe)",
   "kind": "event | closure | correction | update",
   "title": "5–160 characters",
   "body": "20–4000 characters",
   "starts_at / ends_at": "ISO time with offset (starts_at required for events)",
   "place": "optional",
   "city": "required",
   "source_url": "required public https link",
   "rights_confirmed": "true: you have the right to share this",
   "media_label": "none | photo | ai-generated",
   "contact": "optional, kept private"
  },
  "review": "Every item lands as pending_review with published:false. A person checks it and its source before anything appears. Intake never sends a webhook or posts anywhere.",
  "limits": "16 KiB per item, 10 per minute per IP.",
  "public_tips": "https://elsegundo.xyz/report/"
 },
 "storage": {
  "store": "Netlify Blobs store \"webhooks\"",
  "keys": [
   "state/<source>",
   "status/<source>",
   "log/latest",
   "log/ids",
   "archive/YYYY-MM-DD",
   "subs/<id>",
   "queue/<due>-<id>",
   "dead/<id>",
   "intake/<id>"
  ],
  "source_ids": [
   "netlify-blobs"
  ]
 },
 "functions": [
  {
   "name": "webhooks-diff",
   "kind": "scheduled",
   "schedule": "20 * * * *",
   "does": "Reads sources, diffs, logs events, queues deliveries."
  },
  {
   "name": "webhooks-deliver",
   "kind": "scheduled",
   "schedule": "*/5 * * * *",
   "does": "Sends due deliveries, retries, dead-letters."
  },
  {
   "name": "webhooks-api",
   "kind": "http",
   "paths": [
    "/api/v1/webhooks/subscribe",
    "/api/v1/webhooks/unsubscribe",
    "/api/v1/webhooks/subscription"
   ],
   "does": "Subscribe with a URL handshake, check status, unsubscribe."
  },
  {
   "name": "webhooks-feed",
   "kind": "http",
   "paths": [
    "/webhooks/feed.json",
    "/webhooks/feed.xml"
   ],
   "does": "The event log as JSON Feed and RSS."
  },
  {
   "name": "webhooks-intake",
   "kind": "http",
   "paths": [
    "/api/v1/intake"
   ],
   "does": "Coming later. Not routed in this release."
  }
 ],
 "kill_switches": {
  "WEBHOOKS_LIVE=off": "Stops the diff job, deliveries, subscribe and intake. Feeds keep serving the last log.",
  "WEBHOOKS_OFF_TYPES": "Comma list of event types to stop, for example court.opened,conditions.threshold.",
  "PICKLEBALL_LIVE=off": "Also stops court.opened."
 },
 "blockers": [
  {
   "id": "signing-key",
   "need": "Set WEBHOOKS_SIGNING_KEY in Netlify (a random value of 32+ characters, Functions scope).",
   "unlocks": "Webhook subscriptions and signed deliveries. Until then subscribe answers 503 and the feeds are the way in.",
   "who": "Michael"
  },
  {
   "id": "intake-invite",
   "need": "OK a new intake path, then set WEBHOOKS_INTAKE_KEYS (JSON of partner id to the SHA-256 of that partner’s key) and WEBHOOKS_REVIEW_KEY_DIGEST.",
   "unlocks": "Partner intake is coming later. The route is not published in this release.",
   "who": "Michael"
  },
  {
   "id": "airnow-key",
   "need": "Register a free AirNow API key and set AIRNOW_API_KEY.",
   "unlocks": "AQI threshold events.",
   "who": "Michael"
  },
  {
   "id": "water-source",
   "need": "Decide whether to use Heal the Bay’s app data feed (undocumented; ask Heal the Bay or skip). Set WEBHOOKS_WATER_SOURCE=healthebay to turn it on.",
   "unlocks": "Beach water grade events.",
   "who": "Michael"
  },
  {
   "id": "asks-json",
   "need": "Ship /asks.json (drafted separately).",
   "unlocks": "ask.opened events and asks inside today.published.",
   "who": "Review of the asks draft"
  },
  {
   "id": "email",
   "need": "Email confirmation would need a sending provider and consent copy.",
   "unlocks": "Email subscribers. Not in v1: URL handshake plus feeds instead.",
   "who": "Michael, later"
  }
 ],
 "gaps": [
  "Hermosa Beach, Torrance, Hawthorne, Inglewood, LA City and LA County agendas are not wired yet; their public feeds weren’t found or answered with errors on Oct 27.",
  "Only Rec Park has live court data; other South Bay courts stay static.",
  "Transit alerts follow our /metro edits, not Metro’s live alert feed."
 ],
 "examples": {
  "court.opened": [
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_921135886c11c07ef2345bce",
    "type": "court.opened",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "3 court hours opened up at Rec Park since the last check; the first is Tue 5 PM (3 courts). Book on rec.us.",
    "url": "https://elsegundo.xyz/pickleball/",
    "source": {
     "name": "rec.us public court listing",
     "url": "https://www.rec.us/locations/7527b987-0f0b-473e-9680-0c90758f6dcd",
     "retrieved_at": "2026-10-27T13:05:11-07:00",
     "credit": "Availability from rec.us’s public court listing. Book on rec.us."
    },
    "provenance": {
     "label": "availability",
     "sources": [
      {
       "publisher": "rec.us",
       "url": "https://www.rec.us/locations/7527b987-0f0b-473e-9680-0c90758f6dcd"
      }
     ],
     "note": "Aggregate open/not-open per court per hour from rec.us’s public listing, read 5 times a day by /pickleball. No booker identity is read or stored. A slot may be taken by the time you look."
    },
    "data": {
     "location": {
      "id": "7527b987-0f0b-473e-9680-0c90758f6dcd",
      "name": "Recreation Park pickleball courts, El Segundo",
      "page": "https://elsegundo.xyz/pickleball/",
      "book_url": "https://www.rec.us/locations/7527b987-0f0b-473e-9680-0c90758f6dcd"
     },
     "detected_between": {
      "from": "2026-10-27T10:05:09-07:00",
      "to": "2026-10-27T13:05:11-07:00"
     },
     "openings": [
      {
       "date": "2026-10-27",
       "dow": "Tue",
       "hour": 17,
       "start": "2026-10-27T17:00:00-07:00",
       "end": "2026-10-27T18:00:00-07:00",
       "newly_open_courts": [
        6,
        7,
        8
       ],
       "court_names": [
        "Pickleball Court 6",
        "Pickleball Court 7",
        "Pickleball Court 8"
       ],
       "open_courts_total": 4,
       "book_url": "https://www.rec.us/locations/7527b987-0f0b-473e-9680-0c90758f6dcd?date=2026-10-27"
      },
      {
       "date": "2026-10-27",
       "dow": "Tue",
       "hour": 21,
       "start": "2026-10-27T21:00:00-07:00",
       "end": "2026-10-27T22:00:00-07:00",
       "newly_open_courts": [
        3,
        4,
        7
       ],
       "court_names": [
        "Pickleball Court 3",
        "Pickleball Court 4",
        "Pickleball Court 7"
       ],
       "open_courts_total": 5,
       "book_url": "https://www.rec.us/locations/7527b987-0f0b-473e-9680-0c90758f6dcd?date=2026-10-27"
      },
      {
       "date": "2026-10-28",
       "dow": "Wed",
       "hour": 17,
       "start": "2026-10-28T17:00:00-07:00",
       "end": "2026-10-28T18:00:00-07:00",
       "newly_open_courts": [
        3
       ],
       "court_names": [
        "Pickleball Court 3"
       ],
       "open_courts_total": 1,
       "book_url": "https://www.rec.us/locations/7527b987-0f0b-473e-9680-0c90758f6dcd?date=2026-10-28"
      }
     ],
     "credit": "Availability from rec.us’s public court listing. Book on rec.us."
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   }
  ],
  "today.published": [
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_37151b4fb25f2ea8a047db98",
    "type": "today.published",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "South Bay Today for 2026-10-27 is up: 5 top stories and 2 open asks for bots.",
    "url": "https://elsegundo.xyz/today/",
    "source": {
     "name": "South Bay Today (elsegundo.xyz)",
     "url": "https://elsegundo.xyz/today.json",
     "retrieved_at": "2026-10-27T22:00:50.699Z"
    },
    "provenance": {
     "label": "fact",
     "sources": [
      {
       "publisher": "elsegundo.xyz",
       "url": "https://elsegundo.xyz/today/"
      }
     ],
     "note": "Each story keeps its own label (fact, reported) and links its best source. Asks come from /asks.json."
    },
    "data": {
     "edition": "2026-10-27",
     "title": "South Bay Today",
     "page_url": "https://elsegundo.xyz/today/",
     "json_url": "https://elsegundo.xyz/today.json",
     "feed_url": "https://elsegundo.xyz/today/feed.json",
     "markdown_url": "https://elsegundo.xyz/today.md",
     "updated_at": "2026-10-27T15:00:50-07:00",
     "top_stories": [
      {
       "rank": 1,
       "headline": "El Segundo Reaches Deal With Chevron to Increase Payments to City",
       "label": "reported",
       "source": "MyNewsLA.com",
       "url": "https://news.google.com/rss/articles/CBMipgFBVV95cUxPZTBwRi1jRWQ5NzM2VWcxV2pVRGtBVTU1WXpuNnAyQW4wcGEzUnFWa21WWnozVEJmekZ6NDBoamo3RFM5bTQybzFZV05JXzRhZFowUmZLZTBHX1JVc3IyX3RaVURPMTdVcVNwN29HVEVsQi1yTWlWa3dWWklMUUFkY3FiYkU0Z25zQ0N6RFNyd0ROS2pWcDR5MWx0cmhPT25ROXJuemt3?oc=5"
      },
      {
       "rank": 2,
       "headline": "Epicenter Acquires Culver City Flex Industrial Site For $11M",
       "label": "reported",
       "source": "Bisnow",
       "url": "https://news.google.com/rss/articles/CBMiuwFBVV95cUxNZlF1VFVRSnBKSnhEQnhqZWgteXczTndNSl9raEZYWGduQS11eUpJZ1cxQlVRR0pRN08wc24tSWNuczluNVdodFNHQXRoSEFsd2wwdnFvaHF2bVZPc0RXLWFqYllwRHNPV0RMSnFrTVZUdDBzR245ai02enZVaGJON19DQUZiNEIta0x6ampjbDFBbC1vNkdKSHpqTS1YLTE0aGJ0WjB1MHprTHdNa1NmLTNZdjd1c2NWTG5Z?oc=5"
      },
      {
       "rank": 3,
       "headline": "Redondo Beach bar still open for business despite major flooding",
       "label": "reported",
       "source": "Jocelyn Fiset",
       "url": "https://ktla.com/news/local-news/redondo-beach-bar-still-open-major-flooding/"
      },
      {
       "rank": 4,
       "headline": "News | LA investor bets on defense demand with Culver City industrial purchase",
       "label": "reported",
       "source": "CoStar",
       "url": "https://news.google.com/rss/articles/CBMiswFBVV95cUxNZE9adjZ6U2ppZWRjVlJxSWN0YjdEd3NWYVhFaThfcFJFRzJEc2VxSmw1S1JzUWNzakgzVDQ1OFlxeWZHYWxoU1pxenpMV2ZiU01rQnlocUlvQ012b053cjZkTC03Uy10aDM3eV9ERkVXaXNOVmVGYWlyWXdvRUlEUHJWZ0pOODhqdzB5NUFaanR4WFVTVU9YZWNsWmoyeFg4M2tZTzBTUEE3N2tBSnNEcXlhVQ?oc=5"
      },
      {
       "rank": 5,
       "headline": "LAX’s embattled ‘People Mover’ malfunctions with massive arc flash during routine test of train",
       "label": "reported",
       "source": "New York Post",
       "url": "https://news.google.com/rss/articles/CBMirgFBVV95cUxOeWlqOUhiNFIyRkNOYWhHM0dHdVUzNEw0cjIxMDBBWTJBV0F2MVB2TEd4UnRJS1JHa2V5Y3dIYXdkV0xBQ1B1d3RaM05kcnRob1pkelJVX1NoRGFBYzZ1VU80eHRkUmlZWDhEQVpxSnQ2UUs1TGFyNGIydjNaVzM4U2xoRlF6OE0yZGNhbnA2RVpjN1dHR1ZGUXZsTU9yanhhajFFajBKQWpnQ2U2d3c?oc=5"
      }
     ],
     "asks": [
      {
       "id": "ask-court-hours-check",
       "type": "contribute",
       "title": "Confirm this weekend's hours at one South Bay pickleball court",
       "url": "https://elsegundo.xyz/pickleball/"
      },
      {
       "id": "ask-council-reminder",
       "type": "remind",
       "title": "Remind your people: El Segundo City Council meets Nov 3",
       "url": "https://elsegundo.granicus.com/ViewPublisher.php?view_id=10"
      }
     ],
     "asks_url": "https://elsegundo.xyz/asks.json"
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   }
  ],
  "ask.opened": [
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_71b2da75e8d11d35cf8b1686",
    "type": "ask.opened",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "New contribute ask: Confirm this weekend's hours at one South Bay pickleball court",
    "url": "https://elsegundo.xyz/pickleball/",
    "source": {
     "name": "elsegundo.xyz open asks",
     "url": "https://elsegundo.xyz/asks.json",
     "retrieved_at": null
    },
    "provenance": {
     "label": "fact",
     "sources": [
      {
       "publisher": "source",
       "url": "https://elsegundo.xyz/pickleball/"
      },
      {
       "publisher": "elsegundo.xyz",
       "url": "https://elsegundo.xyz/asks.json"
      }
     ]
    },
    "data": {
     "id": "ask-court-hours-check",
     "ask_type": "contribute",
     "title": "Confirm this weekend's hours at one South Bay pickleball court",
     "opens_at": "2026-10-27T15:00:00-07:00",
     "closes_at": "2026-10-31T18:00:00-07:00",
     "how_to_respond": "Send the court, the hours and the official page that lists them through /report/.",
     "url": "https://elsegundo.xyz/pickleball/"
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   }
  ],
  "transit.alert": [
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_57fb1af08ee666dc700c7f2a",
    "type": "transit.alert",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "New transit alert: D Line: three Wilshire stations closed until 4 AM Mon, Nov 2",
    "url": "https://elsegundo.xyz/metro/",
    "source": {
     "name": "elsegundo.xyz /metro guide",
     "url": "https://elsegundo.xyz/metro.json",
     "retrieved_at": "2026-10-27"
    },
    "provenance": {
     "label": "fact",
     "sources": [
      {
       "publisher": "The Source (LA Metro)",
       "url": "https://thesource.metro.net/three-d-line-stations-to-close-oct-23-through-nov-1-for-maintenance-work/",
       "title": "Three D Line stations to close Oct. 23 through Nov. 1 for maintenance work"
      }
     ]
    },
    "data": {
     "change": "new",
     "kind": "service",
     "id": "dline-closure",
     "title": "D Line: three Wilshire stations closed until 4 AM Mon, Nov 2",
     "text": "Wilshire/La Brea, Wilshire/Fairfax and Wilshire/La Cienega are closed for electrical work. D Line trains run Union Station to Wilshire/Western, with Line 855 shuttle buses every 10 minutes to the closed stations.",
     "status": null,
     "opening": null,
     "expires": "2026-11-02T04:00:00-08:00",
     "lines": []
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   },
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_7fee60082fc1f2f35cf13307",
    "type": "transit.alert",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "Updated LAX connection note: LAX SkyLink (Automated People Mover)",
    "url": "https://elsegundo.xyz/metro/",
    "source": {
     "name": "elsegundo.xyz /metro guide",
     "url": "https://elsegundo.xyz/metro.json",
     "retrieved_at": "2026-10-27"
    },
    "provenance": {
     "label": "fact",
     "sources": [
      {
       "publisher": "LAX (Los Angeles World Airports)",
       "url": "https://www.flylax.com/sites/lax/files/2026-10/LAX-SkyLink_Myth-vs-Fact_Web_0.pdf",
       "title": "LAX SkyLink Myth vs Fact (information current as of Oct 23, 2026)"
      },
      {
       "publisher": "LAX (Los Angeles World Airports)",
       "url": "https://www.flylax.com/skylink",
       "title": "SkyLink"
      },
      {
       "publisher": "The Source (LA Metro)",
       "url": "https://thesource.metro.net/how-to-use-the-lax-metro-transit-center-with-new-bus-and-rail-connections/",
       "title": "How to use the LAX/Metro Transit Center with new bus and rail connections"
      }
     ]
    },
    "data": {
     "change": "updated",
     "kind": "airport",
     "id": "skylink",
     "title": "LAX SkyLink (Automated People Mover)",
     "text": "Testing, not open. No confirmed date (LAX)",
     "status": "Testing, not open",
     "opening": "No confirmed date (LAX)",
     "expires": null,
     "lines": []
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   }
  ],
  "civic.agenda_posted": [
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_3a8853ddaf6e94bea69dca9b",
    "type": "civic.agenda_posted",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "El Segundo: agenda posted for Special Council Meeting (2026-10-26).",
    "url": "https://elsegundo.granicus.com/AgendaViewer.php?view_id=10&clip_id=5077",
    "source": {
     "name": "El Segundo City Council agenda feed (Granicus)",
     "url": "https://elsegundo.granicus.com/ViewPublisherRSS.php?view_id=10&mode=agendas",
     "retrieved_at": null
    },
    "provenance": {
     "label": "official record",
     "sources": [
      {
       "publisher": "City of El Segundo",
       "url": "https://elsegundo.granicus.com/AgendaViewer.php?view_id=10&clip_id=5077"
      }
     ]
    },
    "data": {
     "city": "El Segundo",
     "body": "City Council",
     "meeting_title": "Special Council Meeting",
     "meeting_date": "2026-10-26",
     "meeting_time": null,
     "agenda_url": "https://elsegundo.granicus.com/AgendaViewer.php?view_id=10&clip_id=5077",
     "detail_url": null,
     "neutrality": "Meeting title, date and the official agenda link only. We do not summarize items, and never mention candidates; ballot measures appear only as the official agenda wording."
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   }
  ],
  "conditions.threshold": [
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_f1574f5cbe01091a21ab6e88",
    "type": "conditions.threshold",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "NWS: Coastal Flood Advisory until Thu, Oct 29, 1:00 PM PT.",
    "url": "https://elsegundo.xyz/#conditions",
    "source": {
     "name": "National Weather Service active alerts",
     "url": "https://api.weather.gov/alerts/active?point=33.9192,-118.4165",
     "retrieved_at": null
    },
    "provenance": {
     "label": "fact",
     "sources": [
      {
       "publisher": "National Weather Service",
       "url": "https://api.weather.gov/alerts/active?point=33.9192,-118.4165"
      }
     ]
    },
    "data": {
     "kind": "nws_alert",
     "event": "Coastal Flood Advisory",
     "headline": "Coastal Flood Advisory issued October 27 at 6:54AM PDT until October 29 at 1:00PM PDT by NWS Los Angeles/Oxnard CA",
     "ends": "2026-10-29T13:00:00-07:00",
     "alert_url": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.37c31ccf7b9546160110964f4c4322180655466f.001.1"
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   },
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_9d9234b86ff9a343543a57ea",
    "type": "conditions.threshold",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "Heat: NWS forecasts a high near 92°F for Saturday.",
    "url": "https://elsegundo.xyz/#conditions",
    "source": {
     "name": "National Weather Service forecast (LOX/148,40)",
     "url": "https://api.weather.gov/gridpoints/LOX/148,40/forecast",
     "retrieved_at": null
    },
    "provenance": {
     "label": "model",
     "sources": [
      {
       "publisher": "National Weather Service",
       "url": "https://api.weather.gov/gridpoints/LOX/148,40/forecast"
      }
     ]
    },
    "data": {
     "kind": "heat",
     "date": "2026-10-31",
     "period": "Saturday",
     "forecast_high_f": 92,
     "threshold_f": 90
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   },
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_24e2acb463b9fbc5224d4bd3",
    "type": "conditions.threshold",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "Big surf: Santa Monica Bay buoy shows 2.1 m (6.9 ft) significant wave height.",
    "url": "https://elsegundo.xyz/#conditions",
    "source": {
     "name": "NDBC station 46221, Santa Monica Bay (CDIP)",
     "url": "https://www.ndbc.noaa.gov/data/realtime2/46221.txt",
     "retrieved_at": null
    },
    "provenance": {
     "label": "fact",
     "sources": [
      {
       "publisher": "NOAA National Data Buoy Center",
       "url": "https://www.ndbc.noaa.gov/data/realtime2/46221.txt"
      }
     ]
    },
    "data": {
     "kind": "surf",
     "station": "46221",
     "wave_height_m": 2.1,
     "wave_height_ft": 6.9,
     "dominant_period_s": 15,
     "observed_at": "2026-10-27T21:56:00Z",
     "threshold_m": 1.8,
     "note": "Offshore buoy wave height, not breaking surf at a given beach."
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   },
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_f0f31d2d70da1be170f00317",
    "type": "conditions.threshold",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "Air quality: AQI 112 (Unhealthy for Sensitive Groups) near El Segundo.",
    "url": "https://elsegundo.xyz/#conditions",
    "source": {
     "name": "AirNow current observations, ZIP 90245",
     "url": "https://docs.airnowapi.org/CurrentObservationsByZip/query",
     "retrieved_at": null
    },
    "provenance": {
     "label": "fact",
     "sources": [
      {
       "publisher": "U.S. EPA AirNow",
       "url": "https://docs.airnowapi.org/CurrentObservationsByZip/query"
      }
     ]
    },
    "data": {
     "kind": "aqi",
     "aqi": 112,
     "category": "Unhealthy for Sensitive Groups",
     "observed_at": "2026-10-27 15:00 PST",
     "threshold": 101
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   },
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_bf9d6d2eecb6594420aa9aaf",
    "type": "conditions.threshold",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "Beach water: Manhattan Beach Pier drain went from A to C (Heal the Bay).",
    "url": "https://elsegundo.xyz/#conditions",
    "source": {
     "name": "Heal the Bay Beach Report Card",
     "url": "https://brc.healthebay.org/",
     "retrieved_at": null
    },
    "provenance": {
     "label": "fact",
     "sources": [
      {
       "publisher": "Heal the Bay",
       "url": "https://brc.healthebay.org/"
      }
     ]
    },
    "data": {
     "kind": "water_grade",
     "beach_id": 44,
     "beach": "Manhattan Beach Pier drain",
     "grade": "C",
     "previous_grade": "A",
     "grade_updated": "2026-10-23T00:00:00.000Z",
     "direction": "worse"
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   }
  ],
  "record.changed": [
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_131cc84f5511a01a382a360a",
    "type": "record.changed",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "Resource corrected: Parks & Facilities Directory.",
    "url": "https://elsegundo.xyz/#directory",
    "source": {
     "name": "elsegundo.xyz resources",
     "url": "https://elsegundo.xyz/resources.json",
     "retrieved_at": null
    },
    "provenance": {
     "label": "fact",
     "sources": [
      {
       "publisher": "record source",
       "url": "https://www.elsegundorecparks.gov/parks-facilities/parks-facilities-directory"
      },
      {
       "publisher": "elsegundo.xyz",
       "url": "https://elsegundo.xyz/resources.json"
      }
     ],
     "record_source_date": "2026-10-24",
     "verification": "Official or operator source reviewed; some City pages reviewed via their search index"
    },
    "data": {
     "dataset": "resources",
     "record_id": "parks-facilities-directory",
     "change": "corrected",
     "changes": [
      {
       "field": "url",
       "before": "https://www.elsegundorecparks.gov/parks-facilities",
       "after": "https://www.elsegundorecparks.gov/parks-facilities/parks-facilities-directory"
      },
      {
       "field": "verified_date",
       "before": "2026-10-10",
       "after": "2026-10-24"
      }
     ],
     "record": {
      "title": "Parks & Facilities Directory",
      "category": "Play & parks",
      "url": "https://www.elsegundorecparks.gov/parks-facilities/parks-facilities-directory",
      "locality": "El Segundo",
      "verified_date": "2026-10-24"
     }
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   },
   {
    "schema": "elsegundo.xyz/webhooks/v1",
    "id": "evt_803e1efd5623d312fca26b13",
    "type": "record.changed",
    "version": 1,
    "created_at": "2026-10-27T15:20:00-07:00",
    "summary": "Business added: Citizens Business Bank.",
    "url": "https://elsegundo.xyz/businesses/",
    "source": {
     "name": "elsegundo.xyz businesses",
     "url": "https://elsegundo.xyz/api/v1/businesses?limit=100&city=El%20Segundo",
     "retrieved_at": null
    },
    "provenance": {
     "label": "fact",
     "sources": [
      {
       "publisher": "record source",
       "url": "https://www.openstreetmap.org/way/439602995"
      },
      {
       "publisher": "elsegundo.xyz",
       "url": "https://elsegundo.xyz/api/v1/businesses?limit=100&city=El%20Segundo"
      }
     ],
     "record_source_date": "2026-10-24",
     "verification": "Mapped listing; current operation not independently verified"
    },
    "data": {
     "dataset": "businesses",
     "record_id": "osm-way-439602995",
     "change": "added",
     "changes": null,
     "record": {
      "name": "Citizens Business Bank",
      "category": "Local services",
      "type": "bank",
      "address": "275 Main Street, El Segundo, 90245",
      "website": null,
      "sourceUrl": "https://www.openstreetmap.org/way/439602995",
      "sourceDate": "2026-10-24"
     }
    },
    "not_official": "Not an official City of El Segundo page or service. Independent local guide; check the linked source before acting.",
    "follow": "https://x.com/elsegundotoday"
   }
  ]
 },
 "events_count": 7,
 "example_notes": {
  "court.opened": "Real availability from the Oct 27, 1:05 PM PT snapshot, diffed against an edited copy standing in for the 10:05 AM one.",
  "today.published": "Real Oct 27 edition; the two asks are illustrative until /asks.json ships.",
  "ask.opened": "Illustrative ask; /asks.json is not published yet.",
  "transit.alert": "Real /metro items, marked new or updated against an edited earlier state.",
  "civic.agenda_posted": "Real El Segundo Granicus agenda item.",
  "conditions.threshold": "NWS alert and water grades are real readings; the surf, heat and AQI numbers are synthetic so each kind shows.",
  "record.changed": "Real records; the earlier state is edited so a correction and an addition show."
 },
 "sources": {
  "rec-us": {
   "publisher": "rec.us",
   "title": "El Segundo Recreation Park courts on rec.us (booking page)",
   "url": "https://www.rec.us/locations/7527b987-0f0b-473e-9680-0c90758f6dcd",
   "checked": "2026-10-27"
  },
  "rec-us-tos": {
   "publisher": "rec.us",
   "title": "rec.us Terms of Service",
   "url": "https://www.rec.us/tos",
   "checked": "2026-10-27"
  },
  "es-pickleball": {
   "publisher": "elsegundo.xyz",
   "title": "Pickleball near El Segundo (live court grid)",
   "url": "https://elsegundo.xyz/pickleball/",
   "checked": "2026-10-27"
  },
  "es-today": {
   "publisher": "elsegundo.xyz",
   "title": "South Bay Today",
   "url": "https://elsegundo.xyz/today/",
   "checked": "2026-10-27"
  },
  "es-metro": {
   "publisher": "elsegundo.xyz",
   "title": "Metro: South Bay transit guide (metro.json)",
   "url": "https://elsegundo.xyz/metro/",
   "checked": "2026-10-27"
  },
  "es-conditions": {
   "publisher": "elsegundo.xyz",
   "title": "Conditions Oracle API (NWS and NOAA data)",
   "url": "https://elsegundo.xyz/api/v1/conditions",
   "checked": "2026-10-27"
  },
  "es-resources": {
   "publisher": "elsegundo.xyz",
   "title": "Resource directory dataset",
   "url": "https://elsegundo.xyz/resources.json",
   "checked": "2026-10-27"
  },
  "es-events": {
   "publisher": "elsegundo.xyz",
   "title": "Events hub dataset",
   "url": "https://elsegundo.xyz/events.json",
   "checked": "2026-10-27"
  },
  "es-businesses": {
   "publisher": "elsegundo.xyz",
   "title": "Business map API (OpenStreetMap data)",
   "url": "https://elsegundo.xyz/api/v1/businesses",
   "checked": "2026-10-27"
  },
  "osm-copyright": {
   "publisher": "OpenStreetMap",
   "title": "OpenStreetMap copyright and license (ODbL)",
   "url": "https://www.openstreetmap.org/copyright",
   "checked": "2026-10-27"
  },
  "nws-api": {
   "publisher": "National Weather Service",
   "title": "API Web Service documentation",
   "url": "https://www.weather.gov/documentation/services-web-api",
   "checked": "2026-10-27"
  },
  "ndbc-46221": {
   "publisher": "NOAA National Data Buoy Center",
   "title": "Station 46221, Santa Monica Bay, CA (CDIP 028)",
   "url": "https://www.ndbc.noaa.gov/station_page.php?station=46221",
   "checked": "2026-10-27"
  },
  "airnow-api": {
   "publisher": "U.S. EPA AirNow",
   "title": "AirNow API: current observations by ZIP code (requires a free API key)",
   "url": "https://docs.airnowapi.org/CurrentObservationsByZip/query",
   "checked": "2026-10-27"
  },
  "airnow-aqi": {
   "publisher": "U.S. EPA AirNow",
   "title": "AQI basics (101 and up is unhealthy for sensitive groups)",
   "url": "https://www.airnow.gov/aqi/aqi-basics/",
   "checked": "2026-10-27"
  },
  "htb-brc": {
   "publisher": "Heal the Bay",
   "title": "Beach Report Card with NowCast",
   "url": "https://brc.healthebay.org/",
   "checked": "2026-10-27"
  },
  "htb-grades": {
   "publisher": "Heal the Bay",
   "title": "Annual Beach and River Report Cards 2025-2026 (what A to F grades mean)",
   "url": "https://healthebay.org/annual-beach-and-river-report-cards-2025-2026/",
   "checked": "2026-10-27"
  },
  "es-granicus": {
   "publisher": "City of El Segundo",
   "title": "El Segundo City Council meetings and agendas (Granicus)",
   "url": "https://elsegundo.granicus.com/ViewPublisher.php?view_id=10",
   "checked": "2026-10-27"
  },
  "redondo-granicus": {
   "publisher": "City of Redondo Beach",
   "title": "Redondo Beach meetings and agendas (Granicus)",
   "url": "https://redondo.granicus.com/ViewPublisher.php?view_id=3",
   "checked": "2026-10-27"
  },
  "mb-legistar": {
   "publisher": "City of Manhattan Beach",
   "title": "Manhattan Beach meeting calendar (Legistar)",
   "url": "https://manhattanbeach.legistar.com/Calendar.aspx",
   "checked": "2026-10-27"
  },
  "legistar-api": {
   "publisher": "Granicus",
   "title": "Legistar Web API help",
   "url": "https://webapi.legistar.com/Help",
   "checked": "2026-10-27"
  },
  "std-webhooks": {
   "publisher": "Standard Webhooks",
   "title": "Standard Webhooks specification (signing scheme and headers)",
   "url": "https://www.standardwebhooks.com/",
   "checked": "2026-10-27"
  },
  "jsonfeed": {
   "publisher": "JSON Feed",
   "title": "JSON Feed Version 1.1",
   "url": "https://www.jsonfeed.org/version/1.1/",
   "checked": "2026-10-27"
  },
  "rss": {
   "publisher": "RSS Advisory Board",
   "title": "RSS 2.0 Specification",
   "url": "https://www.rssboard.org/rss-specification",
   "checked": "2026-10-27"
  },
  "netlify-scheduled": {
   "publisher": "Netlify",
   "title": "Scheduled Functions (30-second limit; run only on published deploys)",
   "url": "https://docs.netlify.com/build/functions/scheduled-functions/",
   "checked": "2026-10-27"
  },
  "netlify-blobs": {
   "publisher": "Netlify",
   "title": "Netlify Blobs",
   "url": "https://docs.netlify.com/build/data-and-storage/netlify-blobs/",
   "checked": "2026-10-27"
  },
  "netlify-ratelimit": {
   "publisher": "Netlify",
   "title": "Rate limiting (function rateLimit config)",
   "url": "https://docs.netlify.com/manage/security/secure-access-to-sites/rate-limiting/",
   "checked": "2026-10-27"
  },
  "netlify-env": {
   "publisher": "Netlify",
   "title": "Environment variables overview",
   "url": "https://docs.netlify.com/build/environment-variables/overview/",
   "checked": "2026-10-27"
  },
  "owasp-ssrf": {
   "publisher": "OWASP",
   "title": "Server Side Request Forgery",
   "url": "https://owasp.org/www-community/attacks/Server_Side_Request_Forgery",
   "checked": "2026-10-27"
  }
 },
 "functions_source_ids": [
  "netlify-scheduled",
  "netlify-ratelimit",
  "netlify-env"
 ]
}
