Skip to content

Publishers

CivicCatch API

Authenticate with a tenant API key and pull upcoming meetings from /api/v1/meetings and the documents an agency publishes between them from /api/v1/documents — official packet links plus a CivicCatch page for the detail. Built for a newsroom home page, not a scrape.

1. Create a key

  1. Sign in and finish onboarding so you own a preview or active tenant.
  2. Open your account and create an API key.
  3. Copy the secret once. CivicCatch stores only a hash.

2. Request meetings

GET https://www.civcatch.com/api/v1/meetings with Authorization: Bearer <key>. Only GET and OPTIONS are served. Responses carry Access-Control-Allow-Origin: * and Cache-Control: no-store: the key is the only thing gating the data, so never put a live key in browser JavaScript — call from your server. Optional ?window=month limits the list to the current Pacific calendar month (upcoming + already held). ?window=week is the same Pacific Sunday–Saturday week as the tenant this-week page. ?window=catalog is every upcoming meeting plus the last 90 days of past meetings — the same window as the tenant government page. Default window=upcoming returns every future meeting.

curl -sS "https://www.civcatch.com/api/v1/meetings?window=month" \
  -H "Authorization: Bearer cc_live_YOUR_KEY"

Query parameters

window is the only parameter. Every window is evaluated against the America/Los_Angeles calendar, matching the tenant site. An unrecognised value is not an error — it silently falls back to upcoming, so check your spelling rather than your error handler.

windowWhat comes back
upcoming (default)Every future meeting, through the end of its Pacific calendar day. recent is always [].
weekThe Pacific Sunday–Saturday week containing “now” — the same week as the tenant home page. Meetings already held this week land in recent.
monthThe current Pacific calendar month, split into upcoming and already held.
catalogEvery upcoming meeting plus the last 90 days of past meetings — the same window as the tenant government page.

There is no pagination and no cursor: each call returns the whole result set for the window. A tenant catalog is tens of meetings, not thousands.

3. Response

upcoming is sorted soonest first; recent most recent first. Both hold the same meeting object.

{
  "ok": true,
  "tenant": {
    "slug": "bigbear",
    "display_name": "Big Bear Valley",
    "site_url": "https://bigbear.civcatch.com"
  },
  "as_of": "2026-08-24T17:04:11.523Z",
  "window": "month",
  "upcoming": [
    {
      "id": "0f8f8a1c-1f2c-4a7d-9a1e-2c3b4d5e6f70",
      "starts_at": "2026-08-25T01:00:00.000Z",
      "has_meeting_time": true,
      "body": "BBLDWP",
      "body_label": "BBLDWP · Big Bear Lake Department of Water & Power",
      "agenda_url": "https://example.gov/agenda.pdf",
      "minutes_url": null,
      "highlights": ["Vote on water rates"],
      "highlights_es": ["Votación sobre las tarifas de agua"],
      "decisions": [],
      "decisions_es": [],
      "agenda_unparsed": false,
      "location": "City Hall, 39707 Big Bear Blvd",
      "href": "https://bigbear.civcatch.com/meetings/2026-08-24-bbldwp"
    }
  ],
  "recent": []
}

Top-level fields

  • ok — always true on 200. Error bodies carry ok: false and an error string instead.
  • tenant slug, display_name, and site_url (the tenant’s canonical subdomain origin) for the key’s tenant.
  • as_of — server time the payload was built, ISO 8601 UTC. Use it as the “last checked” stamp on your page.
  • window — the window actually applied, after the fallback described above. Echo it back rather than trusting your request.
  • upcoming / recent — arrays of meetings.

Meeting fields

  • id — row id, or null for a meeting we hold without one. Not a stable public identifier; key on href if you need one.
  • starts_at — ISO 8601 UTC instant. Read it together with has_meeting_time.
  • has_meeting_time — when true, the agency published a clock time and starts_at is a real instant: render it in America/Los_Angeles. When false, only the date is known and starts_at is midnight UTC on that date — render the date alone. Showing “12:00 AM” is the classic bug here.
  • body — the agency key as the source writes it (e.g. BBLDWP).
  • body_label — display label, expanded from the tenant’s body catalog when we have it; otherwise identical to body.
  • agenda_url / minutes_url — the official documents, or null when not posted. Always link these rather than us.
  • highlights — agenda items we extracted. Always an array: it is [] (never omitted, never null) when the packet has not been parsed yet.
  • highlights_es — the same agenda items in Spanish, written by the same model call. Always an array: [] (never omitted, never null) when the packet has not been parsed. Model-generated and unofficial — link agenda_url as the authority.
  • decisions — minutes-derived outcomes, also always an array. Empty until minutes are published and parsed; we never infer votes from an agenda.
  • decisions_es — the Spanish decisions, also always an array and empty on the same terms.
  • agenda_unparsed true when a packet exists but we could not read it, which is why highlights is empty.
  • location — free text from the agency notice, or null. Not geocoded, not normalized.
  • href — absolute URL of the CivicCatch meeting page on the tenant subdomain.

Cancellations are not exposed yet: CivicCatch tracks a meeting status internally and shows cancelled and rescheduled meetings on the tenant site, but /api/v1/meetings does not currently return a status field. Until it does, treat a listed meeting as posted-not-confirmed and link the official agenda.

4. Request documents

GET https://www.civcatch.com/api/v1/documents serves what an agency published between meetings — a rate study, a fee schedule, a public notice. Same Authorization: Bearer <key> header, same Access-Control-Allow-Origin: * and Cache-Control: no-store, same error bodies, and the same 60-requests-per-minute budget — that budget is shared across the whole v1 API, not counted per endpoint, so meetings and documents calls draw on one per-key allowance. There is no pagination here either.

curl -sS "https://www.civcatch.com/api/v1/documents?window=month&body=bbccsd" \
  -H "Authorization: Bearer cc_live_YOUR_KEY"

Query parameters

window takes week, month, or all, each evaluated against the America/Los_Angeles calendar. As on the meetings endpoint an unrecognised value is not an error — it silently falls back to all. Note the vocabulary differs from the meetings windows: a document has no future, so there is no upcoming.

windowWhat comes back
all (default)Every document we hold for the tenant, up to a year back — including undated ones.
weekDocuments published in the Pacific Sunday–Saturday week containing “now”.
monthDocuments published in the current Pacific calendar month.

Plenty of agency PDFs carry no publication date. An undated document — one whose published_at is null — is excluded from week and month entirely and appears only under all, sorted after every dated document. If you are filtering by window, expect to miss undated material.

?body=<slug> narrows the list to one agency by its body slug (e.g. bbccsd). An unknown slug is not an error: it returns an empty documents array.

Response

documents is sorted newest first by published_at.

{
  "ok": true,
  "tenant": {
    "slug": "bigbear",
    "display_name": "Big Bear Valley",
    "site_url": "https://bigbear.civcatch.com"
  },
  "as_of": "2026-08-24T17:04:11.523Z",
  "window": "month",
  "documents": [
    {
      "id": "4a1f0b2c-7d3e-4f51-9c62-8ab0d1e2f345",
      "body": "bbccsd",
      "body_label": "BBCCSD · Big Bear City Community Services District",
      "kind": "rate-study",
      "title": "2026 Water Rate Study",
      "summary": "The district proposes a 4% water rate increase beginning in January.",
      "summary_es": "El distrito propone un aumento del 4% en las tarifas de agua a partir de enero.",
      "highlights": ["Rates rise 4% in January"],
      "highlights_es": ["Las tarifas suben 4% en enero"],
      "tags": ["rate-change", "water"],
      "published_at": "2026-08-20T00:00:00.000Z",
      "source_url": "https://example.gov/rate-study.pdf",
      "href": "https://example.gov/rate-study.pdf"
    }
  ]
}

Document fields

  • id — row id, or null. Not a stable public identifier; key on source_url if you need one.
  • body — the agency slug the document belongs to, the same value ?body= takes. Empty string when the document is not attached to a body in the tenant’s catalog.
  • body_label — display label from the tenant’s body catalog.
  • kind — coarse genre, one of staff-report, public-notice, rate-study, consultant-report, press-release, or other. Topic lives in tags, so an annual budget is other tagged budget.
  • title — the document title as the agency publishes it.
  • summary / summary_es — a resident-readable summary in English and Spanish, or null when the document could not be read.
  • highlights / highlights_es — one to three key points in each language. Always arrays: [] (never omitted, never null) when the document could not be read.
  • tags — topic labels drawn from rate-change, fee-hearing, contract-award, public-comment, budget, water, wastewater, solid-waste, fire, and other. Always an array.
  • published_at — ISO 8601 UTC instant the agency published the document, or null when it printed no date. See the undated rule above before you filter on it.
  • source_url — the official document on the agency’s own site. This is the record; link it. It is null in the rare case the stored link is not a plain http(s) URL, and we never render such a value as a link.
  • href — the canonical URL a reader should open. Today it is identical to source_url, because no CivicCatch document page exists yet. The field is in the contract so the response shape does not change when that page ships and href starts pointing at it — read href for the link and source_url for the record.

Unofficial summary. summary, summary_es, highlights, and highlights_es are model-generated and unofficial — the same posture the tenant meeting pages take. They can be wrong, and they are never a substitute for the document itself: source_url is the authority. Publish them with the source link attached, and never present them as the agency’s own words.

Errors

Both endpoints return the same four errors. Every error body is { "ok": false, "error": "<code>" }.

  • 401 invalid_api_key — missing, malformed, unknown, or revoked key.
  • 403 tenant_not_ready — the key’s tenant is neither preview nor active (still draft, or suspended).
  • 429 rate_limited — 60 requests per minute per key, shared across every v1 endpoint (a request to /api/v1/documents spends the same allowance as one to /api/v1/meetings).
  • 500 query_failed — the key and tenant are fine, but the query behind the payload failed. Both endpoints return this rather than an empty 200, because an empty list is the honest answer for a quiet week and must never also mean “we could not read the data.” Transient; retry with backoff.
  • 503 tenant_unavailable — the key is valid but its tenant record could not be read. Transient; retry with backoff rather than treating the key as dead.