sfbos.info

PUBLIC · READ-ONLY · CORS ENABLED

Search API

Unified search across legislative files, votes, public comments, and page text. Natural-language questions are routed to action-aware evidence, and every response links to the official record.

GET /api/query

Send a complete natural-language question to receive one compact evidence bundle drawn from legislative files, page text, recorded votes, and relevant public comments.

GET /api/query.md?q=How+many+housing+units+has+Connie+Chan+voted+against%3F+Which+addresses%3F

GET /api/search

q

Required. Web-style search query, 2–300 characters.

year

Optional. Calendar year from 1996 through 2026.

from / to

Optional inclusive year range from 1996 through 2026.

kind

Optional. agenda or minutes.

type

Optional. all, legislation, votes, comments, or pages.

supervisor

Optional supervisor surname. Natural-language questions can infer it.

position

Optional. aye, no, absent, or excused.

final

Optional boolean. Restricts vote records to likely final actions.

limit

Optional. 1–50 results; defaults to 20.

mode

Optional. lexical or hybrid, the default. Hybrid retrieves independent semantic candidates and reports a lexical fallback when embeddings are unavailable.

format

Optional. json or md.

Legislative files are grouped and ranked ahead of repeated PDF-page matches. Results identify their type and can include the matched action, recorded position, roll call, extracted addresses, unit counts, amounts, and parties. transcriptUrl targets the structured record row; officialUrl targets the authoritative City PDF.

GET /api/aggregates/*

Deterministic, action-aware aggregation over reconciled supervisor identities. Use /api/aggregates/votes for recorded positions and /api/aggregates/housing for unit mentions and addresses. Each response states its grouping rule and interpretation limits.

GET /api/aggregates/housing?voter=Chan&position=no&from=2021&to=2026

Bulk data and changes

/api/snapshots lists cursor-paginated JSON and NDJSON feeds. /api/changes is an append-only change feed. Both publish a schema version.

GET /api/snapshots/recorded-positions?format=ndjson&limit=1000
GET /api/changes?cursor=0&limit=250

POST /api/mcp

Stateless MCP Streamable HTTP endpoint with provider-neutral, read-only tools for search, document evidence, aggregation, and the change feed.

GET /api/items

Search complete legislative-file blocks from meeting minutes. Results preserve each roll call and the action immediately before it, preventing an Aye or No from being interpreted without its motion.

q

Required. Searches the item and related files for the same matter.

voter

Optional. Recorded surname or full name; for example, Chan.

position

Optional. aye, no, absent, or excused. Requires voter.

final

Optional boolean. Restrict matches to roll calls classified as final actions.

groupBy

Optional. none, file, or matter to collapse repeated readings and companion records.

from / to

Optional inclusive year range, 1996–2026.

limit

Optional. 1–50 results; defaults to 20.

format

Optional. json or md.

GET /api/items.md?q=housing+production&voter=Chan&position=no&final=true&groupBy=file&from=2021&to=2026
Accept: text/markdown

Use /api/items.md for Markdown. A position is not automatically a stance: an Aye on “disapprove” opposes the underlying project, while an Aye on “table disapproval” supports it. The returned action text makes that distinction explicit.

GET /api/comments

Search clerk-written public-comment summaries as individual speaker statements instead of mixed page snippets.

q

Required. Topic or phrase to search.

speaker

Optional speaker-name filter.

from / to

Optional inclusive year range, 1996–2026.

limit

Optional. 1–50 results; defaults to 20.

GET /api/comments.md?q=Great+Highway&from=2021&to=2021
Accept: text/markdown

JSON

GET /api/search?q=affordable+housing&from=2018&to=2020&type=legislation
Accept: application/json

Markdown for any model

GET /api/search.md?q=who+voted+against+housing&supervisor=Chan&position=no&type=votes
Accept: text/markdown

The interface is provider-neutral. Markdown can also be requested from /api/search using Accept: text/markdown or ?format=md.

Discovery