Skip to main content

libmuni

Unofficial read-only Python clients for municipal government software platforms. A municipality's public record is spread across several vendor platforms, and libmuni provides one small, typed, well-behaved client per platform. The clients share no cross-vendor data model — what unifies them is the jurisdiction rather than a schema.

The first platform is CivicClerk (libmuni.civicclerk), a meeting-portal system used by many municipalities to publish meeting calendars, agendas, minutes, and supporting documents. Those portals are backed by a public, unauthenticated API at https://{tenant}.api.civicclerk.com/v1; the client reads that data — meeting calendars, attendance details, structured agendas, and document downloads — without reverse-engineering the API yourself.

Install

pip install libmuni

Requires Python 3.11+. The only runtime dependency is httpx.

Quickstart

The tenant name is the subdomain of a municipality's CivicClerk portal — the exampletown in exampletown.portal.civicclerk.com.

import datetime as dt
from pathlib import Path

from libmuni.civicclerk import CivicClerkClient

with CivicClerkClient("exampletown") as client:
    # Boards and committees
    for category in client.get_event_categories():
        print(category.id, category.name)

    # This month's meetings for one board
    events = list(
        client.iter_events(
            category_id=12,
            from_date=dt.date.today(),
            to_date=dt.date.today() + dt.timedelta(days=31),
        )
    )
    for event in events:
        print(event.starts_at, event.name)
        print("  attend:", event.location, event.virtual_meeting_url)
        print("  recording:", event.recording_url)

    # The structured agenda of the next meeting that has one
    event = next(e for e in events if e.agenda_id)
    agenda = client.get_agenda(event.agenda_id)
    for item in agenda.walk_items():
        print(("  " if item.is_section else "    ") + (item.name or ""))
        for attachment in item.attachments:
            client.download_attachment(attachment, Path(attachment.file_name or "file.pdf"))

    # Meeting-level documents (agenda PDF, packet, minutes)
    for file in event.published_files:
        client.download_file(file.file_id, Path(f"{file.file_id}.pdf"))
        text = client.get_file_text(file.file_id)  # best-effort, may be None

Every model keeps the raw API object in .raw, so fields the library doesn't model yet are still reachable.

Command line

The package installs a muni-cli command that routes to a subcommand per platform. muni-cli civicclerk demo exampletown narrates a tour of the whole CivicClerk surface, and the other subcommands (categories, events, agenda, text, download) mirror the client methods, with --json exposing raw payloads.

Datetimes are local wall-clock time

The API stamps a Z (UTC) suffix on datetimes that are actually local wall-clock times, and nothing in the payload declares the tenant's timezone. The library therefore returns naive datetimes carrying the local reading (a 7 pm meeting is 19:00, whatever the timezone). If you need aware datetimes, attach the municipality's timezone yourself.

Errors

Every operational error subclasses CivicClerkError:

  • UnknownTenantError — the tenant subdomain doesn't exist (typo?)
  • NotFoundError — an id-parameterized lookup (agenda, file) has no data
  • APIStatusError — any other HTTP error status
  • NetworkError — DNS/connection/timeout failures
  • PayloadError — a response body the library can't use

Two Python built-ins stay outside that hierarchy by design: ValueError for an invalid tenant or argument, and OSError for a failed local file write.

Absent optional data is never an error: an event with no published materials has an empty published_files, not an exception.

Documentation

The cheat sheet is the fastest way into the full documentation — the whole public surface, the traps, and what the library deliberately does not do.

Using an LLM or coding agent? Hand it llms-full.txt, which is the entire documentation set — signatures included — as a single file. An llms.txt index is served alongside it in the llms.txt format.

Versioning

Semantic versioning covers this library's surface only. The upstream CivicClerk API is undocumented and makes no compatibility promises; every model keeps the raw response payload reachable so upstream additions are usable immediately, and upstream drift is addressed in library releases.

Drift is detected by an opt-in live contract suite: set CIVICCLERK_TEST_TENANT=<tenant> and run pytest -m live (deselected by default; suitable for a scheduled CI job).

Development

uv sync                  # install with dev tools
uv run pytest            # tests (live suite excluded)
uvx prek run --all-files # lint, format, type check (what CI enforces)

uv run --group docs mkdocs serve   # preview the docs site locally

Documentation lives at chapinb.com/libmuni. Pushing a v* tag deploys that version's docs with a version selector (GitHub Pages must be set to serve the gh-pages branch once).

Disclaimer

This project is not affiliated with, endorsed by, or supported by CivicPlus or the CivicClerk product. "CivicClerk" is used only to describe the API this library talks to. The library reads publicly available government records from small municipal servers, and paces its requests politely by default (at most ~1 request/second; see min_request_interval to tune or disable). Any redistribution of downloaded documents is the responsibility of downstream applications.

Acceptable use. The portals are governed by the platform vendor's and each jurisdiction's terms of use, which bind anyone accessing them and can change at any time — this library neither reproduces nor tracks them. They commonly restrict automated access and cap total requests over a window; the default pacing bounds the instantaneous rate but not total volume. Read the terms for the portals you query and keep your usage within them. See Acceptable use for details.

License

MIT

Metadata

Release files for libmuni 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for libmuni 0.1.0
File Size Uploaded
libmuni-0.1.0.tar.gz 25.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for libmuni 0.1.0
File Interpreter ABI Platform
libmuni-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 56.1 kB

Release files / libmuni-0.1.0.tar.gz

Download URL libmuni-0.1.0.tar.gz
Size 25.6 kB
Tags Source
SHA-256 checksum
How to use checksums
18d6fd5c787caaccbcce6aa163a7a02e58d2e4590d4ab95bc757074b07bf3938
BLAKE2b-256 checksum
How to use checksums
41fe3293985341058ef924f3f38a8292af394dac333530e37a200184748bafd6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 18, 2026.

Transparency log

Release files / libmuni-0.1.0-py3-none-any.whl

Download URL libmuni-0.1.0-py3-none-any.whl
Size 30.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
21cf39a35ec2d69cb49f8eb18251e24d2e9ec46e465ccedaf43c94d3bcf414a2
BLAKE2b-256 checksum
How to use checksums
f4679d51a8bc7376be0656356f40ede309f2528463d498cc5f737ee24d7a7d6a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page