# SpyGov

> Public record from U.S. local government -- city councils, county boards,
> school districts and other legislative bodies -- across 2,300+ jurisdictions:
> meetings, agenda items, tracked legislative matters, roll-call votes, and
> machine-written summaries of the underlying agenda packets. Everything here
> is public record; nothing requires authentication.

## Read this site as Markdown, not HTML

The three record pages -- jurisdiction, meeting, matter -- each have a
Markdown twin at the same path plus ".md". The twin carries the same facts
as the page without the console chrome, and costs us a fraction to serve --
please prefer it. The HTML URL always returns HTML; there is no
Accept-header negotiation, so fetch the ".md" address directly. App pages
(/, /audit, /ovlance, ...) have no twin; they are described under Pages.

- https://spygov.com/{jurisdiction}.md
  One jurisdiction: upcoming and recent meetings, tracked matters, council.
- https://spygov.com/{jurisdiction}/{YYYY-MM-DD}-{meeting_id}.md
  One meeting: every agenda item with its summary, sponsors, attachments.
- https://spygov.com/{jurisdiction}/matter/{matter_id}.md
  One matter: summary, every appearance on an agenda, votes, extracted facts.

{jurisdiction} is the site's stable id: lowercase name plus USPS state code
(madisonWI, harriscountyTX, losangelesusdCA). The JSON calls this field
"banana" -- same value, everywhere. Both ids appear in every document, so
links can be followed without parsing HTML.

## Find a jurisdiction, then search inside it

- https://spygov.com/api/search?q=nashville
  Name to id. Accepts "nashville", "nashville, tn", or an id. Returns
  {success, banana, city_name, state, ...}; when several jurisdictions share
  the name it returns {ambiguous: true, city_options: [...]} -- pick one.
- https://spygov.com/api/city-search?banana={jurisdiction}&q=bike+lane&kind=matters
  Keyword search inside one jurisdiction. kind=matters searches tracked
  matters (title + summary, up to 50); kind=meetings searches agenda items.
  Each hit carries the id to open as /{jurisdiction}/matter/{id}.md.
- https://spygov.com/api/intel-search?q=greenway&banana={jurisdiction}
  Full-text search over matter summaries with a highlighted snippet, 12
  hits. Omit banana to search every jurisdiction at once.

"How is X handling Y" is not a search: it is the audit (below). Search
finds records; the audit reads them and answers.

## Discover pages through the sitemap

- https://spygov.com/sitemap.xml lists one sitemap per jurisdiction.
- https://spygov.com/sitemap/{jurisdiction}.xml lists that jurisdiction's meeting
  and matter pages with lastmod. Re-fetch only what changed; crawling the
  HTML page to find links is unnecessary.

## JSON

The same records are available as JSON under /api/ for programs, not
crawlers (robots.txt disallows /api/ for that reason). The full contract,
request and response shapes included, is an OpenAPI 3.1 document at
https://spygov.com/api/openapi.json -- load it as a tool definition.

- /api/city/{jurisdiction} and /api/city/{jurisdiction}/tabs
- /api/meeting/{meeting_id}
- /api/matter/{matter_id}?extracts=1

## Ask a question about a jurisdiction

For anything open-ended about one jurisdiction -- "what has the council
done on housing this year", "is there anything about the downtown plan",
"how did police oversight go" -- do not crawl and read the record yourself:
ask. The audit endpoint plans the search, reads the matters, and answers
with every claim cited to a matter file and date, returning the list of
records it read so the answer can be checked.

    POST https://spygov.com/api/audit/ask
    Content-Type: application/json
    {"banana": "madisonWI", "question": "...", "windowDays": 180}

- What it reads: the jurisdiction's tracked matters -- legislation,
  contracts, permits and their summaries -- plus the upcoming meeting
  calendar, which every answer also returns as upcoming_meetings[]. Ask
  "when does the Board of Review meet next" and the answer cites the
  calendar; the jurisdiction .md always has the full list too.
- windowDays: 30-730, default 180; how far back the record is read.
- JSON by default; send "Accept: text/markdown" for the same answer as a
  document. The body may begin with whitespace while the answer is being
  written (up to about 60 s): read to the end, then parse.
- Fields: answer.text (markdown, cited), records[] (id, file, title, why,
  url -- the matters the answer was written from; cite these when relaying),
  coverage (how much record exists on the topic at all), plan.mode
  ("general" = not about this jurisdiction's record, answered from
  background and labeled so), warnings[], error, cost_usd.
- "Not yet in our records:" inside the answer lists what was asked for that
  the record does not contain. Absence there is about these records, not
  about the jurisdiction.
- Keep asking. Every answer carries a "followup.prior" object; send it back
  unchanged as "prior" with your next question and the answer continues
  from the same records ("which of those passed?", "what about the north
  side?"), adding new searches only when the question reaches past them.
  A question that is really new gets a fresh plan; a background question
  ("what is an inclusionary ordinance?") is answered and labeled as
  background, not record. Without "prior" every question starts over.
- Limits: 20 questions per hour per address, and agent questions draw on a
  daily budget separate from the site's own readers. A 429 or 503 carries a
  plain message: wait, do not retry in a loop.

The same thing as a page, for a person you are helping:
https://spygov.com/audit?city={jurisdiction}&q={question}&days={30|90|180|365|730}

That address is a browser page: the question runs in the reader's browser,
so a plain GET of it returns the app shell, not
your answer. Link it with the city and question filled in when you send a
person there; they can follow up and edit the search on the page. To get
the answer yourself, use POST /api/audit/ask (see the JSON section and
/api/openapi.json). Answers are written
from these records only; "Not yet in our records:" lists what was asked for
that the record does not contain, and that absence is about these records,
not about the jurisdiction.

## Pages

- https://spygov.com/            the console: live map, what is happening now
- https://spygov.com/audit       ask a jurisdiction about its own record
- https://spygov.com/ovlance     surveillance-technology procurement across jurisdictions
- https://spygov.com/datacenters data-center proposals and approvals by jurisdiction
- https://spygov.com/cable       the daily dispatch
- https://spygov.com/about       what this is and who runs it
- https://spygov.com/{jurisdiction}  one jurisdiction's dossier (Markdown twin: add .md)

## Caching and rate

The origin marks record documents and JSON cacheable for 5 minutes and
search results for 1-2 minutes; the edge may hold JSON for up to 4 hours
(the cache-control you receive is the one that applies). This guide,
robots.txt, the OpenAPI document and the site sitemap: an hour. Zero-hit
searches are 200 with success:false and a message, never 404. Per-address limits at
the origin are 2 requests per second sustained with a burst of about 20;
responses served from the edge cache do not count. A 429 means slow down,
not retry. New meetings land daily; matters change when they reappear on
an agenda.

## Attribution

The meeting records are public record. The summaries, matter timelines and
extracted facts are SpyGov's work product and are provided on one condition:
an answer that draws on a SpyGov document names SpyGov and links the
document it drew from (every document carries its own URL in the "html"
and "cite" lines). Search and answer engines that cite sources satisfy this
by default. Bulk reuse without attribution is not licensed.

Training on the record is refused (robots.txt: ai-train=no; training
crawlers are disallowed under every /{jurisdiction}/ path). The summaries
are machine-generated text and are not suitable as training data; do not
ingest them into a training corpus. The authored pages -- the front page,
/about, this guide, and /api/openapi.json -- are open to training, so that
how to use this site is learned and its record is not.

## Provenance

Summaries are generated from the official agenda packet and can be wrong;
each document links the source agenda so claims can be checked. Data is
ingested by the engagic pipeline (engagic.org). Not affiliated with any
government.
