philly-council-events

phila.legistar.com/philly-council-events-omjf3s · updated May 21, 2026

MDX-style export adds YAML metadata + attribution linking explainx.ai and this canonical listing URL.

$browse install phila.legistar.com/philly-council-events-omjf3s
0 commentsdiscussion
summary

Extract Philadelphia City Council and committee meeting events from the Legistar calendar (phila.legistar.com), filterable by year and body. Returns a Zod-validated array of events with name, date, time, location, and agenda/minutes URLs. Read-only.

skill.md
name
philly-council-events
title
Philadelphia City Council Events
description
>- Extract Philadelphia City Council and committee meeting events from the Legistar calendar (phila.legistar.com), filterable by year and body. Returns a Zod-validated array of events with name, date, time, location, and agenda/minutes URLs. Read-only.
website
phila.legistar.com
category
government
tags
- government - legislative - philadelphia - legistar - calendar - civic-data
source
'browserbase: agent-runtime 2026-05-20'
updated
'2026-05-20'
recommended_method
api
alternative_methods
- method: browser rationale: >- When the public Legistar WebAPI (webapi.legistar.com) is unreachable at the network layer, navigate phila.legistar.com/Calendar.aspx, change the Telerik year dropdown to the target year, and scrape the resulting RadGrid. Costs ~10x more turns and depends on unstable a11y refs across ASP.NET postbacks.
verified
false
proxies
true

Philadelphia City Council Events — Browser Skill

Purpose

Extract Philadelphia City Council meeting events (City Council + all standing/joint committees) from the Legistar calendar — returning each event's name (meeting body), date, time, location, agenda/minutes status, and Legistar detail-page URL. Filterable by year and/or by specific body. Read-only — never opens agenda PDFs in write-mode, never modifies state.

The output is a Zod-validated array of event records. A reference Zod schema is included in Expected Output.

When to Use

  • Building a roster of upcoming or historical City Council and committee meetings for a given year.
  • Backfilling a database of Philadelphia legislative meetings (the catalog goes back to 2000).
  • Cross-referencing agenda packets / minutes URLs with a specific meeting date + body.
  • Anywhere you'd otherwise scrape phila.legistar.com/Calendar.aspx HTML — the public Granicus Legistar WebAPI is faster, cheaper, paginatable, and structurally more reliable.

Workflow

The phila.legistar.com Calendar.aspx page is a Telerik RadGrid built on top of an unauthenticated, public Granicus Legistar WebAPI at https://webapi.legistar.com/v1/phila/. The browser UI is a thin client over this API — every record visible in the grid is queryable directly via OData. Lead with the API; the browser flow is the fallback for when the API is unreachable from your network sandbox (no auth, no anti-bot — but some sandboxes block raw DNS for webapi.legistar.com).

Recommended — Legistar WebAPI

  1. Endpoint discovery. The Philadelphia tenant slug is phila. The Events endpoint is:

    GET https://webapi.legistar.com/v1/phila/Events
    

    No API key, no cookies, no Referer required. Verified 2026-05-20 — Microsoft IIS/10.0 + Granicusserver: gasmp-legapi1/2, ASP.NET WebAPI OData v3.

  2. Filter by year. OData v3 $filter syntax — the field is EventDate (datetime, midnight UTC):

    GET https://webapi.legistar.com/v1/phila/Events
        ?$filter=EventDate ge datetime'2025-01-01' and EventDate lt datetime'2026-01-01'
        &$orderby=EventDate
        &$top=200
    

    URL-encode the $ as %24 and the apostrophes/spaces as needed; literal + for spaces works inside $filter. The datetime'YYYY-MM-DD' literal is the supported OData v3 form (NOT datetimeoffset'...' — that 400s).

  3. Filter by body. Each meeting carries EventBodyId (integer) and EventBodyName (string). To restrict to "CITY COUNCIL" only (no committees), add:

    and EventBodyId eq 10
    

    Bodies discovered 2026-05-20 (sample): BodyId=10 → "CITY COUNCIL", 4 → "Committee on Public Health and Human Services", 39 → "Committee of the Whole", 44 → "Committee on Legislative Oversight", 50 → "Committee on Law and Government". The full body list is at GET https://webapi.legistar.com/v1/phila/Bodies (filter on BodyActiveFlag eq 1 for currently-meeting bodies).

  4. Paginate. Default page size for the Events endpoint is large but you should still cap with $top and step with $skip when iterating multi-year ranges:

    &$top=1000&$skip=0     # batch 1
    &$top=1000&$skip=1000  # batch 2
    

    2025 has 154 records (verified against the browser grid). A single $top=1000 covers a full year safely.

  5. Parse the response. Default content-type is XML (application/xml; charset=utf-8) — the OData $format query option is rejected ("Query option 'Format' is not allowed"), and the Accept: application/json header is silently ignored on this tenant (still returns XML). Parse the XML envelope <ArrayOfGranicusEvent><GranicusEvent> elements with these fields:

    XML elementTypeMaps to
    EventIdintStable meeting ID (e.g. 6115)
    EventGuidUUIDAlternate stable ID
    EventBodyIdintSee body-id table above
    EventBodyNamestringMeeting body / committee name
    EventDateISO datetime (date-only, midnight)Meeting date
    EventTimestringDisplay time, e.g. "10:00 AM", "2:00 PM"
    EventLocationstringe.g. "Room 400, City Hall"
    EventAgendaFileURL or nilAgenda PDF (may be i:nil="true")
    EventAgendaStatusNamestring"Final" / "Draft"
    EventMinutesFileURL or nilMinutes PDF
    EventMinutesStatusNamestring"Final" / "Draft"
    EventCommentstring or nilFree-text annotations (cancellations, tabled-until notes)
    EventInSiteURLURLLegistar detail page: https://phila.legistar.com/MeetingDetail.aspx?LEGID={EventId}&GID=30&G=...
    EventVideoStatusstring"Public" etc.
    EventVideoPathURL or nilRecorded-video URL
  6. Zod-validate. See the schema in Expected Output. Coerce EventDate to Date, parse EventTime separately, treat any element with i:nil="true" attribute as null.

Browser fallback

Only use this when the WebAPI is blocked at the network layer (e.g. some sandboxes refuse outbound DNS for webapi.legistar.com). The same browse cloud fetch proxy path that works for phila.legistar.com also works for webapi.legistar.com — so 99% of the time the API path is reachable.

  1. Session setup. A bare session is sufficient — no Akamai, no Cloudflare. --proxies adds resilience if your egress is rate-limited; --verified is not required.

    sid=$(browse cloud sessions create --keep-alive --proxies | node -e "let s='';process.stdin.on('data',c=>s+=c).on('end',()=>process.stdout.write(JSON.parse(s).id))")
    export BROWSE_SESSION="$sid"
    
  2. Open the calendar.

    browse open "https://phila.legistar.com/Calendar.aspx" --remote
    browse wait load --remote
    browse wait timeout 2000 --remote
    

    Default state: Calendar Year = "This Month", Body = "City Council and All Committees". The page is a Telerik RadGrid (ctl00_ContentPlaceHolder1_gridCalendar) inside an ASP.NET WebForms postback model.

  3. Open the Year dropdown by clicking the cell: select next to the year combobox (ref ~117 on a fresh snapshot — refs are NOT stable across postbacks, always re-snapshot):

    browse snapshot --remote
    browse click "@<select-cell-ref-next-to-year>" --remote
    browse wait timeout 1500 --remote
    
  4. Click the target year. Options: All Years, 2026, 2025, ..., 2000, Last Year, Last Month, Last Week, This Year, This Month, This Week, Today, Next Week, Next Month, Next Year. Use browse snapshot to locate the listitem ref for the target year, then click. The page does a full ASP.NET postback — wait for load and an extra 3000ms before re-snapshotting.

  5. Extract the grid. Each grid row is a [1-X] row containing 11 cells in order: BodyName, MeetingDate, ExportToCalendar (iCal), MeetingTime, MeetingLocation, MeetingDetails (link), Agenda (link or "Not available"), AccessibleAgendaHTML, AgendaPacket, Minutes, AccessibleMinutesHTML. Iterate [1-X] cell: children inside each row. Read the record-count header (menuitem: NNN records) to validate completeness before parsing.

  6. Paginate. The grid defaults to 100 rows per page. If the record count > 100, click the pager's "Next page" button at the bottom and re-snapshot. 2025 = 154 records = 2 pages.

  7. Body filter (optional). Same pattern as year: click the body combobox select cell, click the desired body in the dropdown list. The body dropdown is ~115 options long.

  8. Release the session.

    browse cloud sessions update "$sid" --status REQUEST_RELEASE
    

Site-Specific Gotchas

  • No JSON. $format=json query option is explicitly rejected by the Granicus WebAPI ("Query option 'Format' is not allowed" 400). The Accept: application/json header is silently ignored. Parse XML. Don't waste time looking for a JSON toggle — there isn't one.
  • $inlinecount=allpages is rejected on Events. To get a total count, fetch with $top=1000 and count entries in the response (or scrape the browser grid's menuitem: NNN records header). Verified 2026-05-20 — $inlinecount returns 200 but the count metadata is not present in the XML output.
  • OData v3 datetime literal form. Use datetime'2025-01-01' (no time portion, no Z, no offset). datetimeoffset'...' 400s. ISO-8601 raw strings 400.
  • EventDate is date-only (midnight UTC). The actual meeting wall-clock time is in EventTime as a display string ("10:00 AM"). To produce a single canonical timestamp, combine EventDate + EventTime in America/New_York (Philadelphia's timezone) — do NOT add EventTime to EventDate as UTC.
  • i:nil="true" attribute = null. Any GranicusEvent field can be empty; the API marks empties with <EventAgendaFile i:nil="true" /> rather than omitting the element. Map to null in your Zod schema.
  • Browser refs are NOT stable across postbacks. Every Telerik RadComboBox interaction is a full ASP.NET __doPostBack, which regenerates the entire a11y tree (the grid re-snapshot grew from [1-940] to [1-5306] after a single year-filter click). Always re-browse snapshot before each browse click in the fallback flow.
  • Default page state is "This Month". First page load returns 2 records (the current week's meetings). Don't conclude "the calendar is empty" — change the year filter to All or a specific year first.
  • Body dropdown is ~115 entries including standing committees, joint committees ("Joint Committees on X and Y"), and special committees. The full enum is in the RadComboBox itemData array embedded in Calendar.aspx HTML; the WebAPI Bodies endpoint is the canonical list (filter BodyActiveFlag eq 1, BodyMeetFlag eq 1 for currently-meeting bodies).
  • Cookie-based settings. Calendar.aspx persists filter state in cookies (Setting-30-Calendar Year, Setting-30-Calendar Body, Setting-30-Calendar Options). These travel across page reloads in the same session. Useful if you want to lock a year selection without re-clicking the dropdown — but irrelevant when using the WebAPI.
  • EventInSiteURL includes session-bound parameters. The G=A5947DFE-... GUID is a tenant-static identifier, NOT a per-user session token — safe to cache and reuse across runs. The LEGID= parameter is the canonical EventId.
  • EventComment carries meeting-state metadata. Look for strings like "No Calendar for Today", "Council President tabled meeting until ...", "CANCELLED". The grid UI displays these as inline notes. Surface them in the output schema so consumers can distinguish a scheduled-but-cancelled meeting from one that actually occurred.
  • Joint committees have free-text names in the dropdown but normalized names in the API. The Calendar.aspx itemData has explicit text overrides like "Joint Committees on Children & Youth and Education" for some joint bodies; the WebAPI returns the same string in EventBodyName. Treat EventBodyName as the source of truth.
  • Catalog depth. Years 2000 through 2026 are queryable. Pre-2000 events return empty.

Expected Output

A Zod-validated array of event records:

import { z } from "zod";

export const PhilaCouncilEventSchema = z.object({
  eventId: z.number().int(),
  eventGuid: z.string().uuid(),
  bodyId: z.number().int(),
  bodyName: z.string(),                    // e.g. "CITY COUNCIL", "Committee on Law and Government"
  date: z.coerce.date(),                   // EventDate, midnight UTC
  time: z.string(),                        // EventTime display string, e.g. "10:00 AM"
  location: z.string(),                    // e.g. "Room 400, City Hall"
  agendaFile: z.string().url().nullable(),
  agendaStatus: z.enum(["Draft", "Final"]),
  minutesFile: z.string().url().nullable(),
  minutesStatus: z.enum(["Draft", "Final"]),
  comment: z.string().nullable(),          // e.g. "CANCELLED", "tabled until ..."
  videoStatus: z.string(),                 // e.g. "Public"
  videoPath: z.string().url().nullable(),
  detailUrl: z.string().url(),             // EventInSiteURL
});

export const PhilaCouncilEventsSchema = z.array(PhilaCouncilEventSchema);

Example output (2 records from year=2025):

[
  {
    "eventId": 6115,
    "eventGuid": "0C3EC3DE-5220-4E73-8D14-47FA1D2C4EFA",
    "bodyId": 50,
    "bodyName": "Committee on Law and Government",
    "date": "2025-01-22T00:00:00.000Z",
    "time": "10:00 AM",
    "location": "Room 400, City Hall",
    "agendaFile": "https://philadelphia.legistar1.com/philadelphia/meetings/2025/1/6115_A_Committee_on_Law_and_Government_25-01-22_Public_Hearing_Notice.pdf",
    "agendaStatus": "Final",
    "minutesFile": null,
    "minutesStatus": "Draft",
    "comment": null,
    "videoStatus": "Public",
    "videoPath": null,
    "detailUrl": "https://phila.legistar.com/MeetingDetail.aspx?LEGID=6115&GID=30&G=A5947DFE-5A17-435B-A57D-5F0923C2343D"
  },
  {
    "eventId": 6093,
    "eventGuid": "BCAFB815-DC0D-4423-AAFE-44150A03BBFA",
    "bodyId": 10,
    "bodyName": "CITY COUNCIL",
    "date": "2025-01-23T00:00:00.000Z",
    "time": "10:00 AM",
    "location": "Room 400, City Hall",
    "agendaFile": "https://philadelphia.legistar1.com/philadelphia/meetings/2025/1/6093_A_CITY_COUNCIL_25-01-23_City_Council_Calendar.pdf",
    "agendaStatus": "Final",
    "minutesFile": "https://philadelphia.legistar1.com/philadelphia/meetings/2025/1/6093_M_CITY_COUNCIL_25-01-23_Meeting_Minutes_%28Long%29.pdf",
    "minutesStatus": "Final",
    "comment": null,
    "videoStatus": "Public",
    "videoPath": null,
    "detailUrl": "https://phila.legistar.com/MeetingDetail.aspx?LEGID=6093&GID=30&G=A5947DFE-5A17-435B-A57D-5F0923C2343D"
  }
]

If the requested year has no events (e.g., a year before 2000), return [].

how to use philly-council-events

How to use philly-council-events on Cursor

AI-first code editor with Composer

1

Prerequisites

Before installing skills in Cursor, ensure your development environment meets these requirements:

  • Cursor installed and configured on your development machine
  • Node.js version 16.0+ with npm package manager (verify with node --version)
  • Active project directory or workspace where you want to add philly-council-events
2

Execute installation command

Execute the skills CLI command in your project's root directory to begin installation:

$browse install phila.legistar.com/philly-council-events-omjf3s

The skills CLI fetches philly-council-events from GitHub repository phila.legistar.com/philly-council-events-omjf3s and configures it for Cursor.

3

Select Cursor when prompted

The CLI will show a list of available agents. Use arrow keys to navigate and space to select Cursor:

◆ Which agents do you want to install to?
│ ── Universal (.agents/skills) ── always included ────
│ • Amp
│ • Antigravity
│ • Cline
│ • Codex
│ ●Cursor(selected)
│ • Cursor
│ • Windsurf
4

Verify installation

Confirm successful installation by checking the skill directory location:

.cursor/skills/philly-council-events

Reload or restart Cursor to activate philly-council-events. Access the skill through slash commands (e.g., /philly-council-events) or your agent's skill management interface.

Security & Verification Notice

We perform automated surface-level scans (Gen AI Scanner, Socket, Snyk) during installation. These checks detect common vulnerabilities but do not guarantee complete security. Always review skill source code and verify the publisher's reputation before production use.

Skills execute code in your development environment. Always verify the publisher's identity, review recent commits, and test in isolated environments before production deployment.

List & Monetize Your Skill

Submit your Claude Code skill and start earning

GET_STARTED →

Use Cases

Task Automation & Efficiency

Automate repetitive workflows and reduce manual effort

Example

Generate reports, summarize documents, draft communications

Save 3-5 hours per week on routine tasks

Knowledge Enhancement

Learn new skills, understand complex topics, get expert guidance

Example

Explain concepts, provide examples, suggest learning resources

Accelerate learning and skill development by 2x

Quality Improvement

Enhance output quality through reviews, suggestions, and refinements

Example

Review drafts, suggest improvements, catch errors

Improve work quality by 30-40% with less effort

Implementation Guide

Prerequisites

  • Claude Desktop or compatible AI client with skill support
  • Clear understanding of task or problem to solve
  • Willingness to iterate and refine outputs

Time Estimate

15-45 minutes depending on use case complexity

Installation Steps

  1. 1.Install skill using provided installation command
  2. 2.Test with simple use case relevant to your work
  3. 3.Evaluate output quality and relevance
  4. 4.Iterate on prompts to improve results
  5. 5.Integrate into regular workflow if valuable

Common Pitfalls

  • Expecting perfect results without iteration
  • Not providing enough context in prompts
  • Using skill for tasks outside its intended scope
  • Accepting outputs without review and validation

Best Practices

✓ Do

  • +Start with clear, specific prompts
  • +Provide relevant context and constraints
  • +Review and refine all outputs before using
  • +Iterate to improve output quality
  • +Document successful prompt patterns

✗ Don't

  • Don't use without understanding skill limitations
  • Don't skip validation of outputs
  • Don't share sensitive information in prompts
  • Don't expect skill to replace human judgment

💡 Pro Tips

  • Be specific about desired format and style
  • Ask for multiple options to choose from
  • Request explanations to understand reasoning
  • Combine AI efficiency with human expertise

When to Use This

✓ Use When

Use when skill capabilities match your task, clear ROI on time saved, and you can validate outputs. Best for repetitive tasks, learning, and quality improvement.

✗ Avoid When

Avoid when task requires deep expertise you can't validate, involves sensitive decisions, or when learning process is more valuable than speed of completion.

Learning Path

  1. 1Familiarize yourself with skill capabilities and limitations
  2. 2Start with low-risk, non-critical tasks
  3. 3Progress to more complex and valuable use cases
  4. 4Build expertise through regular use and experimentation

Discussion

Product Hunt–style comments (not star reviews)
  • No comments yet — start the thread.
general reviews

Ratings

4.647 reviews
  • Diya Bansal· Dec 20, 2024

    philly-council-events reduced setup friction for our internal harness; good balance of opinion and flexibility.

  • Jin White· Dec 16, 2024

    philly-council-events is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.

  • Zaid Verma· Dec 12, 2024

    Registry listing for philly-council-events matched our evaluation — installs cleanly and behaves as described in the markdown.

  • Maya Abebe· Dec 4, 2024

    philly-council-events has been reliable in day-to-day use. Documentation quality is above average for community skills.

  • Yash Thakker· Nov 27, 2024

    Useful defaults in philly-council-events — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.

  • Lucas Wang· Nov 23, 2024

    philly-council-events reduced setup friction for our internal harness; good balance of opinion and flexibility.

  • Hassan Nasser· Nov 11, 2024

    philly-council-events has been reliable in day-to-day use. Documentation quality is above average for community skills.

  • Jin Kim· Nov 7, 2024

    Keeps context tight: philly-council-events is the kind of skill you can hand to a new teammate without a long onboarding doc.

  • Jin Mensah· Oct 26, 2024

    I recommend philly-council-events for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.

  • Dhruvi Jain· Oct 18, 2024

    Registry listing for philly-council-events matched our evaluation — installs cleanly and behaves as described in the markdown.

showing 1-10 of 47

1 / 5