Inspiration
Democracies will rot without participation. And of all the groups, young people struggle the most with voting. Around half of all voters aged 18 to 25 don't participate. According to a 2025 article from Tufts, people either lack information, miss deadlines, or don't believe it is important. Without our duty to uphold our representation and keep politicians accountable, our republic would inevitably slide.
We believe the best way is to get involved locally. Politics should never be abstract, but a tangible, spirited relationship. However, national politics is too distant and difficult to engage with across the nation. Our local representatives remain one of the easiest points of access to the political system. The problem is that many people are simply unable to find the channels to connect.
We decided to build an app that gets young people into voting. It tells them exactly where (location) and when (time) they can show up to physically interact with their representative, because there is something special about face-to-face interaction that is missing from modern politics.
What it does
You tell CivicSignal what you care about in plain English and pick your cities. It reads every upcoming agenda item across Bay Area city councils (Berkeley, Oakland, San José, Alameda, Mountain View, Sunnyvale, or any of the hundreds of cities on Legistar) and returns only the items that affect you, ranked by relevance. For each item you get:
- a one-sentence plain-English summary and why it matters to you
- whether it's a vote, a public hearing, a consent-calendar item or a discussion
- the meeting date and a link to the staff report
From there you can:
- draft a public comment with Claude, in your own words, ready to email or read aloud at the meeting
- add the meetings to your calendar so you actually show up
- start a weekly watch that emails you new matches
- ask the agent questions like "is anything about bike lanes being voted on soon?" and get a cited answer
How we built it
Apify is the backbone.
- The CivicSignal Actor is written in Python with httpx and BeautifulSoup. It has two platform adapters, the Legistar Web API and Berkeley's eAgenda HTML pages, that normalize every agenda item into one schema. Claude scores each item against the user's interests in batches with structured outputs, returning a relevance score, the matched interest, a plain summary and the action type.
- Datasets and a key-value store. Each match is a dataset row with a table view in Console. A Markdown digest goes in the
OUTPUTrecord so an agent can read the whole result in one call. - Pay-per-event pricing. The Actor charges
agenda-item-scannedandrelevant-item-found, which also makes it payable by agents per call. - Standby mode with its own MCP server.
usesStandbyModepluswebServerMcpPath: /mcpturn the Actor into an always-on MCP server withfind_agenda_itemsandlist_jurisdictionstools, plus a plainGET /scanAPI. - Apify's hosted MCP server. The in-app agent is Claude using the Anthropic MCP connector against
mcp.apify.com. It runs CivicSignal as a tool, then opens staff reports withapify/web-fetchto answer follow-up questions. - Tasks and Schedules. "Keep watching for me" saves the input as an Apify Task and attaches a weekly Schedule. Scheduled runs email their digest through the official
apify/send-mailActor. - Web app. Next.js 15 and Tailwind, talking to Apify through
apify-client.
Challenges we ran into
- Every city is different. Berkeley publishes agendas as Drupal pages, Oakland and San José use Legistar, and San Francisco's Legistar API currently returns errors on SF's side. The adapter layer and a shared schema contain that mess.
- School boards are blocked. BoardDocs, which most school districts use, blocks automated access. We chose to respect that rather than work around it, and listed it as a limitation.
- Agendas are full of boilerplate. Roll calls, ADA notices and approval-of-minutes items had to be filtered out before scoring so that a relevance score means something.
- Making one Actor work three ways. A batch run, an always-on HTTP API and a stateless MCP server share the same scan code but have different lifecycles and billing moments. Getting Standby mode to charge per match without double-charging took a few iterations.
Accomplishments that we're proud of
- Real data from real meetings: one scan reads 200+ live agenda items across four cities and comes back with a handful that actually matter to you.
- One Actor that works three ways: as a batch job on Apify, as an always-on API, and as an MCP tool any agent can call.
- The agent loop works end to end: Claude calls the Actor, reads its dataset, opens the staff report and answers with the specific item, date and link.
- A Store-ready Actor with input and dataset schemas, a README, pay-per-event pricing and adapter tests.
What we learned
- Standby mode and MCP fit together well. A Standby Actor is already a long-lived web server, so adding a
/mcproute makes it an MCP server with no cold start and no extra infrastructure. - Apify Store quality signals (schemas, README, pricing events) aren't paperwork. Writing the dataset schema first forced us to decide what a "normalized agenda item" is before we wrote either adapter.
- Local government data is more open than we expected, and more fragmented. Legistar alone covers hundreds of cities, but each one configures it differently.
- Relevance is a judgment call. We learned to show Claude's reason alongside its score and always link the source, so the user checks the document rather than trusting us.
What's next for CivicSignal
- More platforms: Granicus/PrimeGov, CivicPlus, and school boards where access is allowed.
- Where to show up: meeting location and public-comment instructions pulled from each agenda, so the "when" becomes a "where" too.
- Vote outcomes: follow items through to the minutes and tell users how it went, and how each council member voted.
- x402 payments, so any agent can pay per call without an Apify account.
- Beyond the Bay: the Legistar adapter already covers hundreds of US cities. Presets for more of them are a config change.
Built With
- aiohttp
- anthropic
- apify
- apify-client
- apify-sdk
- beautiful-soup
- claude
- docker
- httpx
- legistar-api
- mcp
- next.js
- pydantic
- python
- react
- tailwindcss
- typescript
- vercel
Log in or sign up for Devpost to join the conversation.