City Council & Public Meeting Transcripts avatar

City Council & Public Meeting Transcripts

Pricing

from $8.00 / 1,000 transcribed meeting minutes

Go to Apify Store
City Council & Public Meeting Transcripts

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

Catalyst Labs

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

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

  1. 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.
  2. Optional: turn on Agenda-item timestamps and add Keywords to find.
  3. 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

FieldWhat it is
statussucceeded, listed, retry_later or failed
inputThe city or URL the record came from (use it to split multi-city runs)
text / segments / markdown / srt / vtt / llmTextTranscript 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
consentAgendaThe consent block: item count, shared timestamp, how many items have their own
officialOutcomes[]Every motion found in the minutes, verbatim quote with page
digestOpt-in digest (see below)
transcriptSource, sourceChainWhere the transcript came from and which sources were tried
chargedEventsExactly 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 listOnly mode, onlyNewMeetings monitors, and a maxAudioMinutes cap.
  • Stable IDs (schemaVersion 1.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 digestPoints decisions (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 always verified.
    • No official votes yet (minutes usually follow 1–3 weeks later): a PRELIMINARY digest, always labeled (digest.preliminary: true, digest.label). Choose it with preliminaryDigest:
      • 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_verified or verified). Charged as a meeting digest.
      • off: no digest.
    • Automatic upgrade when the minutes post. Every preliminary meeting gets a recheck block and goes on a re-check list (key-value store council-pending-rechecks, per monitorId). Schedule a daily run with recheckPending: true and the same monitorId (it can be the same scheduled run as your onlyNewMeetings monitor). 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 back minutes_pending and nothing is charged. Meetings are dropped after pendingMaxDays (default 45) as recheck_expired. You can also re-check the meetings of one earlier run with recheckDatasetId.
  • 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: true to keep names exactly as printed in the official record; digest.names.namedPeople then 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 with captionWaitMinutes. A meeting that still has no captions comes back retry_later (free) and is added to the same re-check list, so the scheduled recheckPending run picks it up after its retryAfter time.
  • 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 full officialTitle exactly 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 disputed and 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 a consentGroupId; 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). Check digest.preliminary and digest.verificationStatus before republishing, and use recheckPending to 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-cache until 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 youtubeResidentialFallback to add one last try through a US residential proxy. That try works only some of the time. Each record's sourceChain lists the steps tried. If nothing works yet, the record comes back as retry_later (errorType: CAPTIONS_NOT_READY) with a suggested retryAfter time and is not charged. Set captionWaitMinutes to 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)

EventPriceWhen
Transcribed meeting minute$0.008Per minute of delivered transcript, rounded up once per meeting
Meeting listed$0.002Per meeting in List-only mode
Agenda alignment$0.05Per meeting, only when at least one item is located
Meeting digest$0.05Per 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.