CivicRecap

CivicRecap for developers and agents

Last updated August 23, 2026

CivicRecap publishes a machine-readable record of California civic meetings — city councils, school boards, planning commissions and county boards. Everything on this page answers an anonymous caller: no key, no account, no sign-up.

The public API

The base URL is this origin. Responses are JSON, and a field with no value is omitted rather than sent as null.

EndpointAnswers
GET /apiThis API, described: what it is and where the spec is.
GET /api/placesEvery city and county with meetings on the record.
GET /api/places/{slug}One place, and the councils and boards that meet about it.
GET /api/agenciesEvery council, board and commission on the record.
GET /api/agencies/{slug}One council or board.
GET /api/in/{place}/{body}The same profile, addressed the way the site's URLs are.
GET /api/meetings/browseMeetings on the record, paged and filterable.
GET /api/meetings/{id}One meeting. Signed out this is the teaser — title, council or board, date, agenda items, published clip headlines — marked "gated": true.
GET /api/upcomingThe next meetings of a followed place or body, inside 30 days.
GET /api/search/locationsOne field: an address, a city, a county, a ZIP or the name of a body.
GET /api/versionThe build this API is running.
GET /healthLiveness, including the database.

The complete description is /openapi.json — OpenAPI 3, generated from the routes themselves, so it cannot drift from what is mapped. Public reads are rate-limited per client address (300 requests a minute at the default setting) and answer 429 past it.

curl -s https://www.civicrecap.com/api/meetings/browse?page=1

Errors are one shape, on every route:

{
  "error": "not_found",
  "message": "No meeting with that id or slug.",
  "hint": "Find one with GET /api/meetings/browse, or read /sitemap.xml."
}

What needs an account

Finding a meeting is public; reading one is not. GET /api/meetings/{id} answers an anonymous caller with a redacted teaser — the title, the council or board, the date, the agenda items and the published clip headlines — marked "gated": true. The recording, the written briefing and the transcript need either a session cookie (cr_session, minted by signing in) or a share grant: ?st= on a link a signed-in reader passed on, good for that one meeting for 14 days.

The spec declares this where a machine can read it: every gated operation carries security against the named schemes in components.securitySchemessession, share_grant, media_signature and email_graphic (what a figure carries when it was mailed rather than rendered on a page). A fifth, agent_token, is not part of this surface at all: it is a bearer token an operator mints by hand for the read-only operational tier at /api/agent/*, and nothing here issues one. There is no OAuth authorization server and no token endpoint. An operation declaring no security answers an anonymous caller in full.

There are no downloads, for anyone. Posters are open, so a share card can be unfurled; a rendered clip, the web rendition, our copy of the agenda and a briefing figure are signed per visit and expire, and the signature is minted by GET /api/meetings/{id}/clips, /video, /agenda and /graphics rather than assembled from a key. The recording stays with the council or board that made it.

Reading the site as markdown

Every public page answers Accept: text/markdown with a prose version of itself, and the same document has an address of its own at /md plus the page's path — /md/browse, /md/developers (this page). Ask for the /md address when a cache sits between us: the markdown answer carries Vary: Accept, but a shared cache that ignores Vary cannot tell the two representations of a page URL apart.

Pages are addressed place-first: /in/{place}, the councils and boards that serve it at /in/{place}/{body}, and a meeting at /in/{place}/{body}/{date}-{id}. The older flat spellings (/places/{slug}, /agencies/{slug}, /meetings/{slug}) are answered forever and redirect here, so a link of either shape resolves. A URL naming nothing answers a real 404, never a 200 carrying an app shell.

Machine-readable files

  • /auth.mdAuth.md agent-registration discovery: the credentials, how each is obtained, and why there is no OAuth to negotiate.
  • /.well-known/api-catalogThe API catalog (RFC 9727): a linkset pointing at the spec, these docs and the health endpoint.
  • /openapi.jsonThe full OpenAPI 3 description of the public read surface, generated from the routes themselves.
  • /llms.txtWhat this site is, what is public and where everything machine-readable lives.
  • /sitemap.xmlEvery public URL: places, councils and boards, meetings.
  • /robots.txtCrawling rules. Everything public is allowed.

Using the record

The meetings are public records and the briefings and clips are made from them by machine. Every generated sentence is traced back to what it rests on before it is published, and anything that cannot be is dropped — but a briefing is a summary, not a transcript, and the terms say what may be relied on. Questions, corrections and anything that looks wrong: [email protected].

Home · Browse every council and board · Privacy