City Council & Public Meeting Transcripts
Pricing
from $8.00 / 1,000 transcribed meeting minutes
City Council & Public Meeting Transcripts
Timestamped transcripts of city council, county board and planning meetings from Granicus, Swagit, Legistar and YouTube, with agenda-item timestamps, official votes from Legistar or minutes, keyword alerts and new-meeting monitoring.
Pricing
from $8.00 / 1,000 transcribed meeting minutes
Rating
0.0
(0)
Developer
Catalyst Labs
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Share
Turn public meeting video into timestamped transcripts linked to the official agenda and votes. Works with city councils, county boards and regional bodies on Granicus, Swagit, Legistar and YouTube. Type a city name or paste meeting URLs.
Quick start
- Paste a meeting URL into Meeting URLs (the form is prefilled with a 17-minute Dallas meeting, about $0.14), or type a City or county such as
King County, WA. - Optional: turn on Agenda-item timestamps and add Keywords to find.
- Click Start. Each meeting becomes one dataset record; all transcripts are also saved as one Markdown file.
Not sure which meetings exist? Set Mode to List meetings only ($0.002 per meeting) first.
Input examples
{ "sources": ["https://dallastx.new.swagit.com/videos/404279"], "alignAgenda": true, "keywords": ["budget", "bond"] }
{ "city": "King County, WA", "bodyFilter": "Metropolitan King County Council", "daysBack": 14, "alignAgenda": true, "maxAudioMinutes": 300 }
{ "city": "Dallas, TX", "mode": "listOnly", "maxMeetingsPerSource": 5, "onlyNewMeetings": true, "monitorId": "dallas-weekly" }
Several places in one run (results are tagged with input, and OUTPUT.byInput summarizes each place):
{ "cities": ["Des Moines, IA", "Polk County, IA", "King County, WA"], "daysBack": 14, "maxMeetingsPerSource": 1, "alignAgenda": true, "generateDigest": true, "maxAudioMinutes": 400 }
YouTube-only city (Des Moines posts video only on YouTube; votes come from the city's minutes PDF). Wait up to 60 min inside the run if captions are not ready yet:
{ "sources": ["https://www.youtube.com/watch?v=03nYvOP_Mtk"], "alignAgenda": true, "generateDigest": true, "captionWaitMinutes": 60 }
County board with "Proceedings" minutes (Polk County, IA: official MP3 plus CivicClerk minutes):
{ "city": "Polk County, IA", "dateFrom": "2026-10-06", "dateTo": "2026-10-06", "alignAgenda": true, "generateDigest": true, "digestPoints": 5 }
Newsroom or research use, names as printed in the record:
{ "city": "Polk County, IA", "daysBack": 30, "generateDigest": true, "includeRecordNames": true }
Daily monitor with automatic upgrade
{ "cities": ["Des Moines, IA", "Polk County, IA"], "daysBack": 3, "onlyNewMeetings": true, "recheckPending": true,"monitorId": "my-newsroom", "alignAgenda": true, "generateDigest": true }
Schedule it daily in Apify Console (Schedules → Create → this Actor, saved input). New meetings get a preliminary digest the same week; each one is re-checked daily and upgraded to the verified record digest when the minutes appear.
Output example (shortened, from a real run)
{"status": "succeeded", "platform": "swagit", "title": "Finance Committee", "date": "2026-10-19T00:00:00","durationSeconds": 974, "transcriptSource": "speechToText", "wordCount": 1701, "agendaSource": "swagitIndex","agendaItems": [{ "title": "Call to Order", "start": 2, "end": 61, "timestamp": "00:00:02","url": "https://dallastx.new.swagit.com/play/404279/2","alignment": { "method": "official_video_index", "status": "matched", "confidence": "high" },"outcomeStatus": "not_in_official_record" }],"segments": [{ "start": 0.52, "end": 13.44, "text": "Is everybody ready? All right, committee. It is 1118 a.m. and I am calling this meeting ... to order.","url": "https://dallastx.new.swagit.com/play/404279/0", "confidence": 0.86, "lowConfidence": false }],"chargedEvents": { "audio-minute": 17, "agenda-alignment": 1 }}
Record-first digest (real output, Des Moines City Council, Aug 3 2026, 116-min meeting):
"digest": { "method": "record_first_rule_ranked", "verificationStatus": "verified", "deterministic": true, "contentHash": "4864710fd7a14d54","pointCount": 5, "recordedVotes": 74, "names": { "mode": "private_names_omitted", "privateNamesOmitted": 2 },"points": [{ "rank": 3, "headline": "On the proposed 38th Amendment to the Urban Renewal Plan for the Metro Center Urban Renewal Area","outcome": "Motion carried, 7-0.", "mover": "Mandelbaum", "seconder": "Gatto", "page": 11,"officialSourceUrl": "https://councildocs.dsm.city/minutes/as20260803.pdf#page=11","watchUrl": "https://www.youtube.com/watch?v=03nYvOP_Mtk&t=3572s", "timestamp": "00:59:32", "timestampKind": "item","rankSignals": ["public_hearing", "taxes_budget_fees"] }] },"consentAgenda": { "itemCount": 50, "timestamp": "00:21:42", "itemsWithOwnTimestamp": 3, "itemsSharingGroupTimestamp": 47 }
Failed records carry errorType, error, hint, retryable and chargedEvents: {}. When captions or the official recording are not posted yet, the record has status: "retry_later", a suggested retryAfter time and retryAfterReason, and is never charged.
Output fields at a glance
| Field | What it is |
|---|---|
status | succeeded, listed, retry_later or failed |
input | The city or URL the record came from (use it to split multi-city runs) |
text / segments / markdown / srt / vtt / llmText | Transcript in the formats you picked; segments have start, end, url, confidence |
agendaItems[] | Official title, start, timestamp, url, timestampKind (item or consent_group_shared), consentGroupId, alignment, and the official result, tally, mover, seconder, votes |
consentAgenda | The consent block: item count, shared timestamp, how many items have their own |
officialOutcomes[] | Every motion found in the minutes, verbatim quote with page |
digest | Opt-in digest (see below) |
transcriptSource, sourceChain | Where the transcript came from and which sources were tried |
chargedEvents | Exactly what this record was charged |
What you get per meeting
- Transcript. Text, segments, Markdown and SRT. Every segment links to the exact second of the video and carries a speech-to-text confidence score with a low-confidence flag.
- Agenda items. Each item has its official title, a start time and a deep link. Each placement states its method (clerk's video index, title match, or LLM placement with verified evidence) and its status: matched, uncertain or not_located.
- Official outcomes and votes. These come only from Legistar or the published minutes: result, mover, seconder, tally, and how each member voted (yes/no/abstain/recused/absent). If the official record doesn't say, the output is
not_in_official_record. We never infer a vote from the video. - Keyword hits with deep links, plus
listOnlymode,onlyNewMeetingsmonitors, and amaxAudioMinutescap. - Stable IDs (
schemaVersion1.0): jurisdiction (with OCD-ID and FIPS), body, meeting, agenda item, legislation, document (SHA-256), and official. - Optional digest (
generateDigest, $0.05 per meeting).- Record-first (default when the minutes or Legistar have votes): the top
digestPointsdecisions (default 5, exactly that many unless fewer qualify), ranked by fixed public rules: zoning and land use, ordinances, public hearings, taxes, budget and fees, contracts and property, dollar size, split votes, failed motions. Consent, routine items (minutes, claims, licenses, proclamations, receive-and-file) and set-a-date items are left out or ranked down. Every field comes from the official record with a page link, there is no AI-written text, and the same meeting always gives the same digest (contentHash). Status is alwaysverified. - No official votes yet (minutes usually follow 1–3 weeks later): a PRELIMINARY digest, always labeled (
digest.preliminary: true,digest.label). Choose it withpreliminaryDigest:agenda(default): rule-ranked official agenda titles with video links, no AI text and no outcomes. Deterministic. Not charged.ai: AI draft-and-verify from the transcript, each claim checked against the transcript (partially_verifiedorverified). Charged as a meeting digest.off: no digest.
- Automatic upgrade when the minutes post. Every preliminary meeting gets a
recheckblock and goes on a re-check list (key-value storecouncil-pending-rechecks, permonitorId). Schedule a daily run withrecheckPending: trueand the samemonitorId(it can be the same scheduled run as youronlyNewMeetingsmonitor). Each pending meeting is re-run with its cached transcript, so audio minutes are never charged twice. When the minutes are posted it gets the verified record digest (recheckResolved,digest.upgradedFrom); at most one digest is charged per meeting, so if the preliminary digest was already charged the upgrade is free. If the minutes are still missing, the record comes backminutes_pendingand nothing is charged. Meetings are dropped afterpendingMaxDays(default 45) asrecheck_expired. You can also re-check the meetings of one earlier run withrecheckDatasetId.
- Record-first (default when the minutes or Legistar have votes): the top
- Private individuals' names. By default digest headlines and text leave out names of private individuals (public commenters, applicants, company officers), for example "Members of the public made comments" or "(company officer)". Officials, staff, companies and places stay. Set
includeRecordNames: trueto keep names exactly as printed in the official record;digest.names.namedPeoplethen lists each person with a role (speaker,applicant,applicant_representative,official,named_in_record) and the source page. The raw transcript is never changed either way. - Several places per run with
cities, and a built-in wait for late captions withcaptionWaitMinutes. A meeting that still has no captions comes backretry_later(free) and is added to the same re-check list, so the scheduledrecheckPendingrun picks it up after itsretryAftertime. - Short headlines, full titles. Each digest point has a short
headline(at most 110 characters, cut only at a clause boundary, never with "…") and the fullofficialTitleexactly as printed. - Stable timestamps. Agenda-item times snap to the start of the transcript line that contains them (
timestampBasis: segment_start), whole seconds, and the alignment step is cached by transcript and agenda, so re-running the same meeting gives the same times.
Tested cities
Transcripts and votes checked by hand against the official record (October 2026):
- Des Moines, IA: YouTube captions; votes from the PrimeGov minutes. Oct 19 meeting: 52 of 52 item votes match the minutes (mover, seconder, tally, page), including two 6-1 votes.
- Polk County, IA: official MP3 recordings; "Proceedings" minutes. Oct 6 meeting: 38 of 38 item outcomes match, including one 3-0 vote with a member passing.
- King County, WA: Legistar votes. 4 meetings (Sep–Oct 2026): 39 of 39 recorded actions read match the Legistar record (mover, result and each member's vote); 3 untitled consent-agenda entries are not listed.
- Seattle, WA: Seattle Channel captions; Legistar actions. 4 meetings (Oct 2026): 32 of 32 recorded actions match.
- San Jose, CA: Granicus video; votes from the Legistar minutes. 5 meetings (Oct 2026): 30 of 30 actions read match the minutes (mover, seconder, result, tally); 1 procedural "orders of the day" vote is not listed. Outcomes from draft minutes say so.
Transcripts, agenda timestamps and keyword alerts work; votes are not read for these places yet:
- Dallas, TX, Houston, TX and Austin, TX (Swagit video and the clerk's video index)
Meeting lists work (List-only mode): Ames, Cedar Rapids and Davenport, IA. Phoenix, AZ lists meetings from Legistar, but its meetings have no linked video, so transcription fails for those meetings, without charge.
Not supported yet: Chicago, IL.
Other places on the same platforms often work but are not verified. YouTube-only cities need captions: YouTube often blocks audio downloads from cloud servers, so if a city posts video only on YouTube and has no other official recording, the transcript depends on captions being posted. Until they are, the meeting comes back retry_later and is not charged. Want your city tested? Open an issue on the Actor page.
How we verify
- Official data wins: votes and decisions come only from the Legistar API or verbatim minutes quotes (with page numbers).
- A minutes-vs-video check marks contradictions
disputedand withholds them. - Documents are fingerprinted. Revisions, such as minutes approved later, are written to a corrections log.
- Digest claims are checked one by one against the cited transcript span, plus a neutrality lint. Vote and approval wording and private commenters' names are removed. Quotes must be verbatim. Anything unverified is dropped, and posts are withheld with reasons.
Measured quality (October 2026)
- Votes: see Tested cities for hand checks against the minutes. Earlier checks: King County 132/132 fields and individual votes match the Legistar API.
- Agenda placement: King County 13 of 18 items placed by the clerk's index. Dallas 4/4. Ames 9/9. San Jose 6 matched, 1 uncertain, 3 not located.
- Speech-to-text: Whisper large-v3, about 2–3% word error rate on clean English. On council audio (Des Moines, Oct 2026) the transcript matched 92.3% of the words in the city's human-edited captions. Mean segment confidence was about 0.82 on the meetings tested.
Limits
- Consent items share one timestamp, on purpose. They are adopted by one motion without discussion, so they are marked
timestampKind: "consent_group_shared"with aconsentGroupId; items pulled for discussion keep their own timestamp. - Digests are fully verified only when official votes exist. Before the minutes are out, the digest is PRELIMINARY (AI-drafted and possibly
partially_verified, or agenda-only). Checkdigest.preliminaryanddigest.verificationStatusbefore republishing, and userecheckPendingto upgrade. - Transcript cache. To avoid charging twice, the transcript of a meeting with a preliminary digest is kept in your key-value store
council-transcript-cacheuntil it is re-checked. Delete that store if you don't want it kept. - Privacy guard limits. Names are detected by pattern: two capitalized words that are not an official, place or organization, plus single-word references next to a speech verb or title ("Doe said", "Ms. Roe"). A shared surname is not enough to be treated as an official: "Jane Sample" is removed even when a council member named John Sample is on the council. Lists of private property owners (for example tax-redemption petitions) are replaced by a count. Speakers' organizations are kept ("A speaker from the Drake Neighborhood Association"); vendors in contract titles are kept. Unusual name formats can still slip through, and an official whose first name is badly misheard may be replaced with "a speaker" (privacy wins). The raw transcript is the public recording, word for word, and is not redacted.
- YouTube-only cities may need captions to be ready. See below.
- YouTube from the cloud and the fallback chain. YouTube often blocks audio downloads from cloud servers. For a YouTube meeting the Actor tries, in order: human-made captions; the government's own recording for that date (for example Polk County's official MP3, or CivicClerk uploads for Davenport); auto-generated captions; the YouTube audio. Turn on
youtubeResidentialFallbackto add one last try through a US residential proxy. That try works only some of the time. Each record'ssourceChainlists the steps tried. If nothing works yet, the record comes back asretry_later(errorType: CAPTIONS_NOT_READY) with a suggestedretryAftertime and is not charged. SetcaptionWaitMinutesto have the run wait and retry on its own, or retry later, because captions and official recordings are often posted hours or days after the meeting. Des Moines posts its video only on YouTube. Its agendas and minutes come from PrimeGov, which has no recordings, so a Des Moines meeting's transcript depends on YouTube captions. - Votes are read only from official records. If a vote can't be matched to its item with certainty, the item is shown without an outcome rather than with a guess. Procedural motions such as adjournment are not attached to items.
- No speaker names (no diarization). Council word error rate is estimated against edited captions (about 92% word match), not a verbatim reference.
- Agenda platforms. The Actor reads meeting calendars from BoardDocs, CivicEngage Agenda Center, CivicWeb, PrimeGov and CivicClerk, plus official pages that list meetings with video links. A recording is used when the platform links one: YouTube, Swagit, Granicus, Vimeo, MP4 or MP3. When it links none, the meeting appears in List-only mode, and in transcribe mode it fails with
NO_VIDEO, not charged. For registry places, the agenda and minutes links are attached to the same-day video meeting. - Seattle Channel (seattlechannel.org, used by Seattle's Legistar): the city's own closed-caption file is used as the transcript (
transcriptSource: officialCaptions), the channel's agenda seek index places items, and votes come from Legistar. - Minutes posted only on the Legistar meeting page (e.g. San José "Action:" minutes): read from the page's minutes link. Outcomes from DRAFT minutes are labeled
digest.recordStatus: draft_minutes. - Not supported: Facebook video (login wall), and sites that refuse our requests (HTTP 403). These fail with a clear error and are not charged.
Pricing (pay per event)
| Event | Price | When |
|---|---|---|
| Transcribed meeting minute | $0.008 | Per minute of delivered transcript, rounded up once per meeting |
| Meeting listed | $0.002 | Per meeting in List-only mode |
| Agenda alignment | $0.05 | Per meeting, only when at least one item is located |
| Meeting digest | $0.05 | Per meeting when a digest is delivered, including digests labeled partially_verified (recordings under 3 minutes are skipped, not charged). At most once per meeting. Preliminary digests for meetings without minutes are free by default (preliminaryDigest: "agenda"); you pay $0.05 once, when the verified record digest is delivered. With preliminaryDigest: "ai" the AI preliminary digest is charged and the later upgrade is free. |
No start fee. Failed meetings are never charged. Example: a 2-hour meeting with agenda timestamps costs 120 × $0.008 + $0.05 = $1.01. Use Max audio minutes per run and the run's maximum charge to cap spending; the Actor stops before it would exceed either.