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.
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.
| 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.aspxHTML — 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
-
Endpoint discovery. The Philadelphia tenant slug is
phila. The Events endpoint is:GET https://webapi.legistar.com/v1/phila/EventsNo API key, no cookies, no Referer required. Verified 2026-05-20 — Microsoft IIS/10.0 +
Granicusserver: gasmp-legapi1/2, ASP.NET WebAPI OData v3. -
Filter by year. OData v3
$filtersyntax — the field isEventDate(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=200URL-encode the
$as%24and the apostrophes/spaces as needed; literal+for spaces works inside$filter. Thedatetime'YYYY-MM-DD'literal is the supported OData v3 form (NOTdatetimeoffset'...'— that 400s). -
Filter by body. Each meeting carries
EventBodyId(integer) andEventBodyName(string). To restrict to "CITY COUNCIL" only (no committees), add:and EventBodyId eq 10Bodies 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 atGET https://webapi.legistar.com/v1/phila/Bodies(filter onBodyActiveFlag eq 1for currently-meeting bodies). -
Paginate. Default page size for the Events endpoint is large but you should still cap with
$topand step with$skipwhen iterating multi-year ranges:&$top=1000&$skip=0 # batch 1 &$top=1000&$skip=1000 # batch 22025 has 154 records (verified against the browser grid). A single
$top=1000covers a full year safely. -
Parse the response. Default content-type is XML (
application/xml; charset=utf-8) — the OData$formatquery option is rejected ("Query option 'Format' is not allowed"), and theAccept: application/jsonheader is silently ignored on this tenant (still returns XML). Parse the XML envelope<ArrayOfGranicusEvent>→<GranicusEvent>elements with these fields:XML element Type Maps to EventIdint Stable meeting ID (e.g. 6115)EventGuidUUID Alternate stable ID EventBodyIdint See body-id table above EventBodyNamestring Meeting body / committee name EventDateISO datetime (date-only, midnight) Meeting date EventTimestring Display time, e.g. "10:00 AM","2:00 PM"EventLocationstring e.g. "Room 400, City Hall"EventAgendaFileURL or nil Agenda PDF (may be i:nil="true")EventAgendaStatusNamestring "Final"/"Draft"EventMinutesFileURL or nil Minutes PDF EventMinutesStatusNamestring "Final"/"Draft"EventCommentstring or nil Free-text annotations (cancellations, tabled-until notes) EventInSiteURLURL Legistar detail page: https://phila.legistar.com/MeetingDetail.aspx?LEGID={EventId}&GID=30&G=...EventVideoStatusstring "Public"etc.EventVideoPathURL or nil Recorded-video URL -
Zod-validate. See the schema in
Expected Output. CoerceEventDatetoDate, parseEventTimeseparately, treat any element withi:nil="true"attribute asnull.
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.
-
Session setup. A bare session is sufficient — no Akamai, no Cloudflare.
--proxiesadds resilience if your egress is rate-limited;--verifiedis 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" -
Open the calendar.
browse open "https://phila.legistar.com/Calendar.aspx" --remote browse wait load --remote browse wait timeout 2000 --remoteDefault 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. -
Open the Year dropdown by clicking the
cell: selectnext to the year combobox (ref~117on 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 -
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. Usebrowse snapshotto locate thelistitemref for the target year, thenclick. The page does a full ASP.NET postback — wait forloadand an extra 3000ms before re-snapshotting. -
Extract the grid. Each grid row is a
[1-X] rowcontaining 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. -
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.
-
Body filter (optional). Same pattern as year: click the body combobox
selectcell, click the desired body in the dropdown list. The body dropdown is ~115 options long. -
Release the session.
browse cloud sessions update "$sid" --status REQUEST_RELEASE
Site-Specific Gotchas
- No JSON.
$format=jsonquery option is explicitly rejected by the Granicus WebAPI ("Query option 'Format' is not allowed"400). TheAccept: application/jsonheader is silently ignored. Parse XML. Don't waste time looking for a JSON toggle — there isn't one. $inlinecount=allpagesis rejected on Events. To get a total count, fetch with$top=1000and count entries in the response (or scrape the browser grid'smenuitem: NNN recordsheader). Verified 2026-05-20 —$inlinecountreturns 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. EventDateis date-only (midnight UTC). The actual meeting wall-clock time is inEventTimeas a display string ("10:00 AM"). To produce a single canonical timestamp, combineEventDate+EventTimein America/New_York (Philadelphia's timezone) — do NOT addEventTimetoEventDateas 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 tonullin 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 snapshotbefore eachbrowse clickin 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
Allor 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
RadComboBoxitemDataarray embedded in Calendar.aspx HTML; the WebAPIBodiesendpoint is the canonical list (filterBodyActiveFlag eq 1, BodyMeetFlag eq 1for 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. EventInSiteURLincludes session-bound parameters. TheG=A5947DFE-...GUID is a tenant-static identifier, NOT a per-user session token — safe to cache and reuse across runs. TheLEGID=parameter is the canonicalEventId.EventCommentcarries 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
textoverrides like"Joint Committees on Children & Youth and Education"for some joint bodies; the WebAPI returns the same string inEventBodyName. TreatEventBodyNameas 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 on Cursor
AI-first code editor with Composer
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
Execute installation command
Execute the skills CLI command in your project's root directory to begin installation:
The skills CLI fetches philly-council-events from GitHub repository phila.legistar.com/philly-council-events-omjf3s and configures it for Cursor.
Select Cursor when prompted
The CLI will show a list of available agents. Use arrow keys to navigate and space to select Cursor:
Verify installation
Confirm successful installation by checking the skill directory location:
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
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.Install skill using provided installation command
- 2.Test with simple use case relevant to your work
- 3.Evaluate output quality and relevance
- 4.Iterate on prompts to improve results
- 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▌
- 1Familiarize yourself with skill capabilities and limitations
- 2Start with low-risk, non-critical tasks
- 3Progress to more complex and valuable use cases
- 4Build expertise through regular use and experimentation
Discussion
Product Hunt–style comments (not star reviews)- No comments yet — start the thread.
Ratings
4.6★★★★★47 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