{"openapi": "3.1.0", "info": {"title": "OpenPublica API", "version": "1.0.0", "summary": "Search US local-government meetings and adopted planning documents.", "description": "OpenPublica makes US city-council and board meetings searchable: it transcribes each meeting, generates a structured AI summary, and indexes the adopted general-plan and budget documents those meetings act on.\n\nPublic and unauthenticated. Every route is `GET` and returns a `{data, meta}` envelope, except `/v1/governments/{id}/plan` (raw markdown) and this document.\n\n**LLM tools should prefer the MCP server** at `https://api.openpublica.com/mcp` \u2014 it exposes the same corpus as typed tools with usage guidance built in.\n\n**Please send a descriptive `User-Agent`.** Requests are logged by route, status, and hashed IP so we can report how much use comes from agents rather than the website; a real User-Agent is the easiest way to be counted as something better than \"unknown\".\n\n**Send `Accept-Encoding: gzip`.** Listings are mostly prose and compress ~10x.", "contact": {"name": "OpenPublica", "url": "https://www.openpublica.com"}}, "servers": [{"url": "https://api.openpublica.com", "description": "Production"}], "externalDocs": {"description": "Prose API reference", "url": "https://www.openpublica.com/api-docs"}, "tags": [{"name": "Meetings", "description": "Browse, search, and read meetings."}, {"name": "Governments", "description": "Resolve and profile tracked governments."}, {"name": "Documents", "description": "Adopted general-plan and budget documents."}, {"name": "Corpus", "description": "Coverage and production figures."}], "paths": {"/v1": {"get": {"operationId": "getIndex", "tags": ["Corpus"], "summary": "Route discovery index", "description": "Lists every v1 route. `GET /v1/openapi.json` returns this document.", "responses": {"200": {"description": "The route index", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"type": "object", "properties": {"version": {"type": "string"}, "routes": {"type": "array", "items": {"type": "string"}}}}, "meta": {"type": "object", "additionalProperties": true}}}}}}}}}, "/v1/openapi.json": {"get": {"operationId": "getOpenapi", "tags": ["Corpus"], "summary": "This OpenAPI document", "description": "Served raw \u2014 a deliberate exception to the {data, meta} envelope, because tooling expects a bare OpenAPI document.", "responses": {"200": {"description": "The OpenAPI 3.1 document", "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}}}}}, "/v1/meetings": {"get": {"operationId": "listMeetings", "tags": ["Meetings"], "summary": "Browse meetings newest-first", "description": "Only explicitly public meetings (visible=true). Visibility is filtered before pagination and rechecked before response. Sparse fieldsets keep their requested shape. Responses use Cache-Control: no-store.", "parameters": [{"$ref": "#/components/parameters/governmentIdQuery"}, {"$ref": "#/components/parameters/categoryTag"}, {"$ref": "#/components/parameters/topic"}, {"$ref": "#/components/parameters/startDate"}, {"$ref": "#/components/parameters/endDate"}, {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 10}}, {"$ref": "#/components/parameters/page"}, {"name": "fields", "in": "query", "description": "Comma-separated sparse fieldset. A full meeting is ~7 KB, so a caller enumerating the corpus for a few scalars should say so \u2014 it is the difference between a 700 KB page and a 20 KB one. `transcription` and `summary_embeddings` are never selectable; use `GET /v1/meetings/{id}`.", "schema": {"type": "string"}, "example": "meeting_id,government_id,epoch_timestamp"}], "responses": {"200": {"description": "A page of meetings", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Meeting"}}, "meta": {"type": "object", "additionalProperties": true, "properties": {"count": {"type": "integer"}, "limit": {"type": "integer"}, "page": {"type": "integer"}, "has_more": {"type": "boolean"}}}}}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/meetings/search": {"get": {"operationId": "searchMeetings", "tags": ["Meetings"], "summary": "Search meeting summaries by meaning", "description": "Semantic summary search remains separate from unified stored-text search. Only current explicitly public meetings are returned after live parent checks. Each page reruns a ranked 60-result window; this is not a corpus-wide total or snapshot. mode=keyword and mode=hybrid return 410; use /v1/meetings/content-search for keyword/transcript discovery. Responses use Cache-Control: no-store.", "parameters": [{"name": "q", "in": "query", "required": true, "schema": {"type": "string", "minLength": 1, "maxLength": 2000}, "example": "police budget", "description": "Nonblank semantic query."}, {"name": "mode", "in": "query", "schema": {"type": "string", "enum": ["semantic", "keyword", "hybrid"], "default": "semantic"}, "description": "semantic is the active contract. keyword and hybrid are retained only to return an explicit 410 migration error."}, {"$ref": "#/components/parameters/governmentIdQuery"}, {"$ref": "#/components/parameters/governmentIdsQuery"}, {"$ref": "#/components/parameters/categoryTag"}, {"$ref": "#/components/parameters/topic"}, {"name": "min_score", "in": "query", "description": "Semantic score floor.", "schema": {"type": "number", "minimum": 0, "maximum": 1}}, {"$ref": "#/components/parameters/startDate"}, {"$ref": "#/components/parameters/endDate"}, {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 25}}, {"name": "page", "in": "query", "description": "Page within the first 60 ranked results. (page - 1) * limit must be below 60; otherwise 400. Pages are not a snapshot.", "schema": {"type": "integer", "minimum": 1, "maximum": 60, "default": 1}}], "responses": {"200": {"description": "Ranked matches", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/MeetingMatch"}}, "meta": {"type": "object", "additionalProperties": true, "properties": {"count": {"type": "integer"}, "match_type": {"type": "string", "enum": ["semantic", "keyword", "hybrid"]}, "degraded_to_vector": {"type": "boolean", "description": "True when a hybrid keyword query failed and results use semantic matching only."}, "page": {"type": "integer", "minimum": 1}, "limit": {"type": "integer", "minimum": 1, "maximum": 25}, "has_more": {"type": "boolean", "description": "More results remain within this ranked window; false at its ceiling."}, "result_window": {"type": "integer", "enum": [60]}, "window_full": {"type": "boolean", "description": "The ranked window filled. This does not prove additional matches exist."}, "coverage": {"type": "object", "properties": {"searched_fields": {"type": "array", "items": {"type": "string", "enum": ["title", "summary", "topics.topic"]}}, "transcripts": {"type": "boolean", "enum": [false]}, "documents": {"type": "boolean", "enum": [false]}}}}}}}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}, "503": {"description": "Keyword query temporarily unavailable. No semantic or paid embedding fallback.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "410": {"description": "Retired query route or mode. Use /v1/meetings/content-search (source=transcripts for stored transcript text). Responses use Cache-Control: no-store.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/v1/meetings/{id}": {"get": {"operationId": "getMeeting", "tags": ["Meetings"], "summary": "One meeting in full", "description": "Only explicitly public meetings. Missing, hidden or invalidly marked parents return 404. Visibility is checked again after loading the record. Responses use Cache-Control: no-store.", "parameters": [{"$ref": "#/components/parameters/meetingIdPath"}], "responses": {"200": {"description": "The meeting", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"$ref": "#/components/schemas/Meeting"}, "meta": {"type": "object", "additionalProperties": true}}}}}}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/vocabulary": {"get": {"operationId": "getVocabulary", "tags": ["Meetings"], "summary": "Valid category and topic filter values", "description": "Read this before filtering \u2014 `category_tag`s are opaque UUIDs you cannot guess.", "parameters": [{"$ref": "#/components/parameters/governmentIdQuery"}, {"$ref": "#/components/parameters/governmentIdsQuery"}, {"$ref": "#/components/parameters/startDate"}, {"$ref": "#/components/parameters/endDate"}, {"name": "limit", "in": "query", "description": "Topics returned.", "schema": {"type": "integer", "minimum": 1, "maximum": 200}}], "responses": {"200": {"description": "Categories and topics with counts", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"$ref": "#/components/schemas/Vocabulary"}, "meta": {"type": "object", "additionalProperties": true, "properties": {"default_window_days": {"type": "integer"}}}}}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/governments": {"get": {"operationId": "findGovernments", "tags": ["Governments"], "summary": "Find tracked governments", "description": "Provide AT MOST ONE selector. None returns an alphabetical page.", "parameters": [{"name": "q", "in": "query", "description": "Name/typeahead search.", "schema": {"type": "string"}, "example": "sacramento"}, {"name": "near", "in": "query", "description": "`lon,lat` \u2014 nearest to a coordinate.", "schema": {"type": "string"}, "example": "-121.4944,38.5816"}, {"name": "postal_code", "in": "query", "description": "Nearest to a ZIP. Unknown ZIP returns 404.", "schema": {"type": "string"}, "example": "95814"}, {"name": "country_code", "in": "query", "schema": {"type": "string", "default": "US"}}, {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 50}}], "responses": {"200": {"description": "Matching governments", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Government"}}, "meta": {"type": "object", "additionalProperties": true, "properties": {"count": {"type": "integer"}, "mode": {"type": "string", "enum": ["name", "near", "postal", "all"]}, "resolved_location": {"type": "object", "description": "Echoes the geocoded place when postal_code was used."}}}}}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/governments/{id}": {"get": {"operationId": "getGovernment", "tags": ["Governments"], "summary": "A government's profile", "parameters": [{"$ref": "#/components/parameters/governmentIdPath"}, {"name": "include_weekly_summary", "in": "query", "description": "Include the AI weekly digest.", "schema": {"type": "boolean", "default": false}}], "responses": {"200": {"description": "The government", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"$ref": "#/components/schemas/Government"}, "meta": {"type": "object", "additionalProperties": true}}}}}}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/governments/{id}/stats": {"get": {"operationId": "getGovernmentStats", "tags": ["Governments"], "summary": "Meeting activity over a window", "description": "Use `all` as the id for corpus-wide stats.", "parameters": [{"$ref": "#/components/parameters/governmentIdPath"}, {"name": "days", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 365, "default": 30}}], "responses": {"200": {"description": "Activity stats", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"$ref": "#/components/schemas/GovernmentStats"}, "meta": {"type": "object", "additionalProperties": true}}}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/governments/{id}/documents": {"get": {"operationId": "listGovernmentDocuments", "tags": ["Documents"], "summary": "Adopted plan and budget documents", "parameters": [{"$ref": "#/components/parameters/governmentIdPath"}, {"name": "include_superseded", "in": "query", "schema": {"type": "boolean", "default": false}}, {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 50}}], "responses": {"200": {"description": "Held documents. An empty list with coverage `not_reviewed` is a statement about OpenPublica, not about the government.", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Document"}}, "meta": {"type": "object", "additionalProperties": true, "properties": {"count": {"type": "integer"}, "coverage": {"$ref": "#/components/schemas/DocumentCoverage"}}}}}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/governments/{id}/plan": {"get": {"operationId": "getGovernmentPlan", "tags": ["Documents"], "summary": "The whole general plan as one markdown file", "description": "Returns raw `text/markdown` \u2014 a deliberate exception to the envelope, because the point of the route is to hand you a file. Sections are ordered as a plan is bound (Gov Code \u00a765302), not alphabetically. Budgets are not included.\n\nNOT suitable for a model context window: 27k\u2013179k tokens depending on the city. The read path for an agent is `/v1/documents/search` then `/v1/documents/{id}/text`.", "parameters": [{"$ref": "#/components/parameters/governmentIdPath"}], "responses": {"200": {"description": "The assembled plan", "content": {"text/markdown": {"schema": {"type": "string"}}}}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/documents/search": {"get": {"operationId": "searchDocuments", "tags": ["Documents"], "summary": "Word search over page-cited document text", "description": "`$text` matching, NOT semantic search. Retired and unreadable documents are not searchable, so an empty result is not evidence of absence \u2014 check the listing.", "parameters": [{"name": "q", "in": "query", "required": true, "schema": {"type": "string"}}, {"$ref": "#/components/parameters/governmentIdQuery"}, {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 50, "default": 10}}], "responses": {"200": {"description": "Page-cited excerpts", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/DocumentExcerpt"}}, "meta": {"type": "object", "additionalProperties": true, "properties": {"count": {"type": "integer"}, "limit": {"type": "integer"}}}}}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/documents/{id}": {"get": {"operationId": "getDocument", "tags": ["Documents"], "summary": "One document's metadata", "parameters": [{"$ref": "#/components/parameters/documentIdPath"}], "responses": {"200": {"description": "The document", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"$ref": "#/components/schemas/Document"}, "meta": {"type": "object", "additionalProperties": true}}}}}}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/documents/{id}/text": {"get": {"operationId": "getDocumentText", "tags": ["Documents"], "summary": "Read a page range as markdown", "description": "Bounded to 40 pages and 120k characters, CLAMPED rather than rejected. Text is assembled from the page windows overlapping the range, so `covered` may differ from `requested` \u2014 cite `covered`.\n\nPages are transcribed from rendered pixels, not the PDF text layer, so scanned tables come through as text and an unreadable page is a marked hole, never a silent omission.", "parameters": [{"$ref": "#/components/parameters/documentIdPath"}, {"name": "first_page", "in": "query", "schema": {"type": "integer", "minimum": 1, "default": 1}}, {"name": "last_page", "in": "query", "description": "Defaults to first_page + 39.", "schema": {"type": "integer", "minimum": 1}}], "responses": {"200": {"description": "The markdown for the covered range", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"type": "object", "properties": {"text": {"type": "string"}, "requested": {"type": "object", "properties": {"first_page": {"type": "integer"}, "last_page": {"type": "integer"}}}, "covered": {"type": "object", "properties": {"first_page": {"type": "integer"}, "last_page": {"type": "integer"}}}}}, "meta": {"type": "object", "additionalProperties": true, "properties": {"max_page_span": {"type": "integer"}}}}}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/stats": {"get": {"operationId": "getCorpusStats", "tags": ["Corpus"], "summary": "Corpus totals", "responses": {"200": {"description": "Totals", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"$ref": "#/components/schemas/CorpusStats"}, "meta": {"type": "object", "additionalProperties": true}}}}}}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/operations": {"get": {"operationId": "getOperations", "tags": ["Corpus"], "summary": "What the pipeline produced over the last 30 complete UTC days", "description": "Throughput only: meetings and documents published in the window, and the minutes of meeting behind them. Read `minutes` with `meetings_measured` beside it \u2014 the corpus predates the duration field, so it is a total over that many meetings and not necessarily over all of them.", "responses": {"200": {"description": "Production figures", "headers": {"Cache-Control": {"schema": {"type": "string"}, "description": "public, max-age=300, stale-while-revalidate=3600"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"$ref": "#/components/schemas/Operations"}, "meta": {"type": "object", "additionalProperties": true}}}}}}, "429": {"$ref": "#/components/responses/RateLimited"}, "500": {"$ref": "#/components/responses/ServerError"}}}}, "/v1/transcripts/search": {"get": {"operationId": "searchTranscriptPhrases", "tags": ["Meetings"], "summary": "Retired transcript passage query", "description": "Returns 410. Use GET /v1/meetings/content-search?source=transcripts for one current public meeting result per match with native next/back pagination and optional independently verified timed evidence. No legacy query flag or catalog query fallback remains.", "parameters": [], "responses": {"410": {"description": "Retired query route or mode. Use /v1/meetings/content-search (source=transcripts for stored transcript text). Responses use Cache-Control: no-store.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "deprecated": true}}, "/v1/transcripts/catalog-search": {"get": {"operationId": "searchPublishedTranscriptCatalog", "tags": ["Meetings"], "summary": "Retired transcript passage query", "description": "Returns 410. Use GET /v1/meetings/content-search?source=transcripts for one current public meeting result per match with native next/back pagination and optional independently verified timed evidence. No legacy query flag or catalog query fallback remains.", "parameters": [], "responses": {"410": {"description": "Retired query route or mode. Use /v1/meetings/content-search (source=transcripts for stored transcript text). Responses use Cache-Control: no-store.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}, "deprecated": true}}, "/v1/meetings/content-search": {"get": {"operationId": "searchMeetingContent", "tags": ["Meetings"], "summary": "Search metadata and stored transcript text together", "description": "One compound Atlas ranking over title, summary, topics and existing full transcript text, with live score/ObjectId cursor pagination. Transcript-only results require current nonempty stored text and a searchable flag that is absent or the scalar Boolean true; old catalog publication is not required. Whole quoted queries are analyzed phrases in the flat stored text, not exact-byte or run-bounded quotations. Timestamped excerpts independently verify current immutable timed evidence and may be unavailable. All-source candidates retain current metadata without evidence. Scope checks remain live despite index lag. Follow non-null cursors even on empty pages. No total or complete transcript coverage is claimed. Cache-Control: no-store. Public scope accepts either government_id or government_ids (comma-separated, at most 100 entries). meeting_id applies one exact canonical BSON meeting ID within the same publication and government scope. Private /v1/me/meetings/content-search derives current saved-city scope, rejects government overrides, and binds cursors to the verified account. Old query flags and legacy keyword/transcript serving paths are removed.", "parameters": [{"name": "q", "in": "query", "required": true, "schema": {"type": "string", "minLength": 1, "maxLength": 160}, "description": "Analyzed words; whole double-quoted queries use analyzed phrase matching."}, {"name": "source", "in": "query", "schema": {"type": "string", "enum": ["all", "metadata", "transcripts"], "default": "all"}}, {"$ref": "#/components/parameters/governmentIdQuery"}, {"name": "government_ids", "in": "query", "schema": {"type": "string"}, "description": "Comma-separated 1\u2013100 city IDs; mutually exclusive with government_id. Whitespace around CSV entries is ignored."}, {"name": "meeting_id", "in": "query", "schema": {"type": "string", "pattern": "^[0-9a-f]{24}$"}, "description": "Exact canonical BSON meeting ID, still restricted by all other filters and current publication scope."}, {"name": "body_id", "in": "query", "schema": {"type": "string", "maxLength": 128}}, {"$ref": "#/components/parameters/topic"}, {"$ref": "#/components/parameters/startDate"}, {"$ref": "#/components/parameters/endDate"}, {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 20, "default": 10}}, {"name": "cursor", "in": "query", "schema": {"type": "string"}, "description": "Opaque next_cursor or previous_cursor from the same query and filters; not a snapshot."}], "responses": {"200": {"description": "One ranked result per public meeting.", "content": {"application/json": {"schema": {"type": "object", "required": ["data", "meta"], "properties": {"data": {"type": "array", "items": {"allOf": [{"$ref": "#/components/schemas/Meeting"}, {"type": "object", "required": ["search"], "properties": {"search": {"type": "object", "required": ["matched_fields", "transcript_excerpt"], "properties": {"matched_fields": {"type": "array", "description": "Current locally verified evidence only; analyzer-only or lagged index candidates may have no verified fields.", "items": {"type": "string", "enum": ["title", "summary", "topics.topic", "transcripts"]}}, "transcript_excerpt": {"type": ["object", "null"], "description": "Verified immutable transcript-search.v2 hit with excerpt/match codepoint offsets, revision, run/segment locators and citation URLs/context timestamps; null when unavailable.", "additionalProperties": true}}}}}]}}, "meta": {"type": "object", "required": ["schema_version", "next_cursor", "previous_cursor", "has_more", "order", "coverage"], "properties": {"schema_version": {"const": "meeting-content-search.v1"}, "next_cursor": {"type": ["string", "null"]}, "previous_cursor": {"type": ["string", "null"]}, "has_more": {"type": "boolean"}, "order": {"const": "relevance"}, "coverage": {"type": "object", "additionalProperties": true}}}}}}}}, "400": {"$ref": "#/components/responses/BadRequest"}, "409": {"description": "Query, account scope or selected content changed; restart search."}, "429": {"$ref": "#/components/responses/RateLimited"}, "503": {"description": "Disabled or temporarily unavailable; no embedding fallback."}}}}}, "components": {"parameters": {"limit": {"name": "limit", "in": "query", "description": "Maximum records to return.", "schema": {"type": "integer", "minimum": 1}}, "page": {"name": "page", "in": "query", "description": "1-based page number.", "schema": {"type": "integer", "minimum": 1, "default": 1}}, "governmentIdQuery": {"name": "government_id", "in": "query", "description": "Scope to one tracked government, e.g. `sacramento-california`.", "schema": {"type": "string"}, "example": "sacramento-california"}, "governmentIdsQuery": {"name": "government_ids", "in": "query", "description": "Comma-separated government ids. One of the few keys parsed as a list.", "schema": {"type": "string"}, "example": "sacramento-california,oakland-california"}, "categoryTag": {"name": "category_tag", "in": "query", "description": "Opaque category UUID. Never guess it \u2014 read it from `GET /v1/vocabulary`.", "schema": {"type": "string", "format": "uuid"}}, "topic": {"name": "topic", "in": "query", "description": "Topic name, as listed by `GET /v1/vocabulary`.", "schema": {"type": "string"}}, "startDate": {"name": "start_date", "in": "query", "description": "Inclusive lower day bound.", "schema": {"type": "string", "format": "date"}}, "endDate": {"name": "end_date", "in": "query", "description": "Inclusive upper day bound.", "schema": {"type": "string", "format": "date"}}, "governmentIdPath": {"name": "id", "in": "path", "required": true, "description": "Government id, e.g. `sacramento-california`.", "schema": {"type": "string"}}, "meetingIdPath": {"name": "id", "in": "path", "required": true, "description": "Meeting id.", "schema": {"type": "string"}}, "documentIdPath": {"name": "id", "in": "path", "required": true, "description": "Document id.", "schema": {"type": "string"}}}, "schemas": {"Error": {"type": "object", "required": ["error"], "properties": {"error": {"type": "object", "required": ["message"], "properties": {"message": {"type": "string"}}}}}, "Topic": {"type": "object", "properties": {"topic": {"type": "string"}, "conversation_percentage": {"type": ["number", "null"]}}}, "Meeting": {"type": "object", "description": "A published meeting. `video_url` is null until the deferred Internet Archive upload lands; `source_url` (the government's own link) is the interim fallback. `duration_seconds` is absent rather than null when a meeting's runtime has not been measured.", "properties": {"meeting_id": {"type": "string"}, "title": {"type": "string"}, "date": {"type": "string", "format": "date"}, "epoch_timestamp": {"type": "integer", "description": "Unix seconds."}, "duration_seconds": {"type": "number", "description": "How long the meeting ran, in seconds, derived from the transcript's own timings. ABSENT when unknown \u2014 never null and never 0. A meeting published before the pipeline began recording runtime carries none until a backfill reaches it, and a few have no usable transcript to measure, so treat a missing key as 'not measured' rather than as a zero-length meeting. Selectable via `fields`. Corpus totals and their coverage: GET /v1/operations."}, "government_id": {"type": "string"}, "category_name": {"type": ["string", "null"]}, "category_tag": {"type": ["string", "null"]}, "summary": {"type": "string"}, "topics": {"type": "array", "items": {"$ref": "#/components/schemas/Topic"}}, "video_url": {"type": ["string", "null"], "format": "uri"}, "source_url": {"type": ["string", "null"], "format": "uri"}, "thumbnail_url": {"type": ["string", "null"], "format": "uri"}, "transcript_url": {"type": ["string", "null"], "format": "uri"}}}, "MeetingMatch": {"allOf": [{"$ref": "#/components/schemas/Meeting"}, {"type": "object", "properties": {"relevance_score": {"type": ["number", "null"], "minimum": 0, "maximum": 1, "description": "Semantic score. Present on semantic/hybrid results."}, "keyword_score": {"type": ["number", "null"], "description": "BM25 score, unbounded. Keyword/hybrid only."}, "highlights": {"type": "array", "items": {"type": "string"}, "description": "Matched keyword snippets. Keyword/hybrid only."}}}]}, "Government": {"type": "object", "properties": {"government_id": {"type": "string"}, "name": {"type": "string"}, "city": {"type": ["string", "null"]}, "state": {"type": ["string", "null"]}, "location": {"type": "object", "properties": {"coordinates": {"type": "array", "items": {"type": "number"}, "minItems": 2, "maxItems": 2, "description": "[longitude, latitude]"}}}, "categories": {"type": "array", "items": {"type": "object", "properties": {"name": {"type": "string"}, "category_tag": {"type": "string"}}}}}}, "Vocabulary": {"type": "object", "properties": {"categories": {"type": "array", "items": {"type": "object", "properties": {"name": {"type": "string"}, "category_tag": {"type": "string"}, "count": {"type": "integer"}}}}, "topics": {"type": "array", "items": {"type": "object", "properties": {"topic": {"type": "string"}, "count": {"type": "integer"}}}}}}, "DocumentAvailability": {"type": "object", "description": "Present ONLY when something is wrong \u2014 a clean document omits it, so its presence is the signal. Read it before quoting figures.", "properties": {"complete": {"type": "boolean"}, "reason": {"type": "string", "description": "Drawn from a fixed caller-safe set."}, "problems": {"type": "array", "items": {"type": "string"}}}}, "Document": {"type": "object", "properties": {"document_id": {"type": "string"}, "government_id": {"type": "string"}, "title": {"type": "string"}, "status": {"type": "string", "enum": ["converted", "superseded", "failed"], "description": "`converted` is served and searchable. `superseded` is retired by a newer edition, stored but not searchable. `failed` means WE HOLD THIS PIN AND COULD NOT READ IT \u2014 not that the government published nothing."}, "element": {"type": ["string", "null"], "description": "The slot whose pin acquired this document."}, "covers": {"type": "array", "items": {"type": "string"}, "description": "Every slot the document answers for. A city binding one general plan pins it as `combined` and it answers for all eight elements \u2014 reading `element` alone makes the rest look unpublished."}, "page_count": {"type": ["integer", "null"]}, "source_url": {"type": "string", "format": "uri", "description": "Always the government's own copy. Cite it, especially on a failed entry."}, "markdown_url": {"type": ["string", "null"], "format": "uri"}, "availability": {"$ref": "#/components/schemas/DocumentAvailability"}}}, "DocumentCoverage": {"type": ["object", "null"], "description": "What the registry knows about this government \u2014 it is what makes an empty `data` readable. `null` means the government was added since the projection last ran, which is not the same claim as `not_reviewed`.", "properties": {"state": {"type": "string", "enum": ["pinned", "reviewed_none_pinned", "unavailable", "not_reviewed"]}, "reason": {"type": ["string", "null"]}}}, "DocumentExcerpt": {"type": "object", "properties": {"document_id": {"type": "string"}, "government_id": {"type": "string"}, "title": {"type": "string"}, "text": {"type": "string", "description": "A short passage around the match joined with `\u2026`, not the whole page window."}, "first_page": {"type": "integer"}, "last_page": {"type": "integer"}, "source_url": {"type": "string", "format": "uri"}, "document_complete": {"type": "boolean"}}}, "CorpusStats": {"type": "object", "properties": {"total_governments": {"type": "integer"}, "total_states": {"type": "integer", "maximum": 50, "description": "Distinct states with at least one tracked government. At most 50."}, "includes_dc": {"type": "boolean", "description": "DC is tracked like a state but is not one. Render the pair as '50 states and DC' rather than adding them into an ambiguous 51."}, "total_meetings": {"type": "integer"}}}, "GovernmentStats": {"type": "object", "properties": {"total_meetings": {"type": "integer"}, "activity_by_day": {"type": "object", "additionalProperties": {"type": "integer"}, "description": "Keyed by ISO date, on the meeting-date clock."}, "categories": {"type": "array", "items": {"type": "object", "properties": {"name": {"type": "string"}, "count": {"type": "integer"}}}}}}, "Window": {"type": "object", "properties": {"start": {"type": "string", "format": "date"}, "end": {"type": "string", "format": "date"}, "days": {"type": "integer"}}}, "Operations": {"type": "object", "properties": {"window": {"$ref": "#/components/schemas/Window"}, "corpus": {"type": "object", "properties": {"meetings": {"type": "integer"}, "documents": {"type": "integer"}, "governments": {"type": "integer"}, "states": {"type": "integer", "maximum": 50, "description": "Distinct states with at least one tracked government. At most 50."}, "includes_dc": {"type": "boolean", "description": "DC is tracked like a state but is not one. Render the pair as '50 states and DC' rather than adding them into an ambiguous 51."}, "minutes": {"type": "integer", "description": "Minutes of meeting the corpus holds. Read it with meetings_measured: the corpus predates the duration field, so this is a total over that many meetings, not over all of them."}, "meetings_measured": {"type": "integer", "description": "How many meetings `minutes` was summed over. Equal to `meetings` once every meeting has a measured duration; lower while the backfill is still running."}}}, "published": {"type": "object", "properties": {"meetings": {"type": "integer"}, "documents": {"type": "integer"}, "minutes": {"type": "integer", "description": "Minutes of meeting published in the window. Read it with meetings_measured, as above."}, "meetings_measured": {"type": "integer", "description": "How many of the window's meetings `minutes` was summed over."}}}, "daily": {"type": "array", "items": {"type": "object", "properties": {"date": {"type": "string", "format": "date"}, "meetings": {"type": "integer", "description": "Counted by when we PUBLISHED, not when the meeting was held."}, "documents": {"type": "integer"}, "cumulative_meetings": {"type": "integer", "description": "Anchored to the corpus total and walked forward, so the last entry equals corpus.meetings rather than a window subtotal."}}}}}}}, "responses": {"BadRequest": {"description": "Malformed or invalid parameters.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "NotFound": {"description": "No such record.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "RateLimited": {"description": "Per-IP rate limit exceeded.", "headers": {"Retry-After": {"schema": {"type": "integer"}, "description": "Seconds to wait."}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "ServerError": {"description": "Internal server error.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}