CivicOS API Reference¶
CivicOS provides two interfaces: a REST API for HTTP clients (curl, JavaScript, etc.) and a Python SDK for direct integration.
Quick Start (REST API)¶
# Base URL (San Rafael pilot)
BASE=https://san-rafael.civicosproject.org
# Upcoming meetings
curl -H "Authorization: Bearer YOUR_API_KEY" \
"$BASE/api/events/search?topics=housing&date_range=next+month"
# Past decisions
curl -H "Authorization: Bearer YOUR_API_KEY" \
"$BASE/api/events/search?q=housing"
# Legislation
curl -H "Authorization: Bearer YOUR_API_KEY" \
"$BASE/api/legislation/state/housing"
# Interactive docs (no auth required)
open "$BASE/docs"
Getting an API Key¶
Free tier (60 req/min): Contact us via GitHub Issues.
Paid tiers: Visit the billing endpoint to provision a key:
curl -X POST "$BASE/api/billing/checkout" \
-H "Content-Type: application/json" \
-d '{"tier": "journalist", "email": "you@example.com"}'
Returns a Stripe checkout URL. After payment, your API key is emailed.
| Tier | Rate Limit | Use Case |
|---|---|---|
| free | 60 req/min | Personal use, exploration |
| journalist | 120 req/min | Reporting, investigations |
| organization | 300 req/min | Nonprofits, civic tech |
| city | 600 req/min | Municipal staff, officials |
| api | 1,000 req/min | Application integrations |
All tiers have a 10,000 req/hour ceiling. LLM-powered endpoints (conversation, chat routing) are limited to 30 req/min regardless of tier.
Authentication¶
All API requests require a Bearer token in the Authorization header:
Key format: cvk_live_ followed by 32 hex characters.
Unauthenticated requests return 401:
{
"detail": {
"error": "Authentication required",
"message": "Include Bearer token in Authorization header",
"example": "Authorization: Bearer <your_api_key>"
}
}
Rate Limiting¶
Rate limit status is returned in response headers:
X-RateLimit-Limit-Minute: 120
X-RateLimit-Remaining-Minute: 118
X-RateLimit-Limit-Hour: 10000
X-RateLimit-Remaining-Hour: 9998
X-RateLimit-Burst-Remaining: 20
When rate limited (429):
{
"detail": {
"error": "Rate limit exceeded",
"message": "Too many requests. Limit: 120 per minute",
"retry_after": 45
}
}
The Retry-After header tells you how many seconds to wait.
Error Responses¶
| Status | Meaning |
|---|---|
| 400 | Bad request — invalid parameters |
| 401 | Unauthorized — missing or invalid API key |
| 404 | Not found — resource doesn't exist |
| 422 | Validation error — malformed request body |
| 429 | Rate limited — see Retry-After header |
| 500 | Internal error |
| 503 | Service unavailable — downstream dependency down |
Validation errors (422) return field-level details:
{
"detail": [
{
"loc": ["body", "title"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
REST Endpoints¶
Meetings & Events¶
GET /api/events/search¶
Search upcoming and past meetings.
| Parameter | Type | Description |
|---|---|---|
jurisdiction | string | Jurisdiction ID (default: city-san-rafael) |
topics | string | Comma-separated topic filter |
q | string | Free-text search query |
date_range | string | "this week", "next month", or month name |
itemCountMin | int | Minimum agenda items |
curl -H "Authorization: Bearer $KEY" \
"$BASE/api/events/search?topics=housing,transportation&date_range=next+month"
Response:
{
"events": [
{
"id": "mtg-2026-03-10-cc",
"title": "City Council Regular Meeting",
"date": "2026-03-10T19:00:00",
"body": "City Council",
"location": "City Hall, 1400 Fifth Ave",
"agenda_items": [...]
}
],
"count": 5,
"query": {"topics": ["housing", "transportation"]},
"jurisdictions_searched": ["city-san-rafael"]
}
GET /api/events/{event_id}¶
Get a single meeting with full agenda.
GET /api/events¶
List all events, optionally filtered by jurisdiction, project_type, or start_date.
Legislation¶
GET /api/legislation/state/{topic}¶
State bills relevant to a topic.
| Parameter | Type | Description |
|---|---|---|
jurisdiction | string | Filter by jurisdiction |
status | string | Bill status filter |
limit | int | Max results (default: 50) |
Response:
{
"topic": "housing",
"level": "state",
"bills": [
{
"bill_id": "ca-sb123",
"bill_number": "SB 123",
"bill_name": "Housing Accountability Act",
"status": "3",
"summary": "...",
"leverage_point": "Comment period open until March 15"
}
],
"count": 10
}
GET /api/legislation/federal/{topic}¶
Federal bills relevant to a topic. Same parameters as state.
Voting Records¶
GET /api/voting-record/{official}/{jurisdiction}¶
Voting statistics for an elected official.
Issues (SeeClickFix / 311)¶
GET /api/operational-issues/{jurisdiction_id}¶
SeeClickFix community issues.
| Parameter | Type | Description |
|---|---|---|
limit | int | Max results (default: 50) |
status | string | Filter by status |
GET /api/issues/search¶
Search user-created issues with filters for category, jurisdiction, status, and free-text q.
Context Assembly¶
GET /api/context/{item_type}/{item_id}¶
Assemble a rich context bundle for any civic item. This is the primary endpoint for getting comprehensive background on a meeting, decision, or issue.
| Parameter | Type | Description |
|---|---|---|
item_type | string | "decision", "meeting", "issue", "event", "legislation", "initiative" |
item_id | string | Entity identifier |
jurisdiction | string | Required. e.g., "city-san-rafael" |
sections | string | Comma-separated: history, regulatory, financial, testimony, participation |
depth | string | "minimal", "standard" (default), "deep" |
curl -H "Authorization: Bearer $KEY" \
"$BASE/api/context/decision/dec-123?jurisdiction=city-san-rafael§ions=history,regulatory"
Coordination (Nostr-based)¶
Voice, subscription, and initiative endpoints use Nostr-signed requests instead of API key auth. See Coordination docs.
GET /api/coordination/voice/counts/{entity}¶
Public endpoint (no auth). Returns voice counts for an entity.
{
"entity": "city-san-rafael:mtg-2026-03-10-cc:item-5",
"support": 42,
"oppose": 12,
"watching": 8,
"total": 62
}
Admin & Diagnostics¶
GET /api/tools/data-provenance¶
Public endpoint. Returns data coverage and freshness for the jurisdiction.
GET /api/status¶
Public endpoint. Health check with database, vector, and service status.
GET /api/admin/cost-dashboard¶
Operating cost breakdown (admin auth required).
Python SDK¶
For direct integration without HTTP overhead.
from dotenv import load_dotenv
load_dotenv() # Loads DATABASE_URL from .env
from civicos import CivicOS
c = CivicOS("city-san-rafael")
Query Methods¶
what_happened(query, since=None) -> List[Decision]¶
Search past council decisions by topic.
Decision fields: id, title, date (datetime), outcome, body, votes (dict)
Outcomes: approved, denied, continued, withdrawn, received, adopted, other
what_happened_full_context(query, since=None, top_k=5) -> List[DecisionWithContext]¶
Decisions with linked transcript excerpts and public comment context.
DecisionWithContext fields: decision (Decision), transcript_links (List[TranscriptLink]), link_confidence (float), link_type (str)
Properties: has_transcript, public_comments, staff_discussion, council_discussion
whats_next(topics=None, days=30, include_elections=False) -> List[Meeting]¶
Upcoming meetings, optionally filtered by topic.
meetings = c.whats_next(["transportation"], days=14)
for m in meetings:
print(m.title, m.date, len(m.agenda_items), "agenda items")
Meeting fields: id, title, date (datetime), body, agenda_items (list), location
what_applies(topic, location=None, ranking_mode="auto") -> RegulatoryStack¶
Federal, state, and local regulations relevant to a topic.
regs = c.what_applies("housing", ranking_mode="section_first")
print(len(regs.federal), "federal")
print(len(regs.state), "state")
print(len(regs.local), "local")
RegulatoryStack fields: topic, jurisdiction, federal (list), state (list), local (list), retrieved_at (datetime)
what_was_said(query, top_k=10) -> List[TranscriptExcerpt]¶
Search meeting transcripts by topic.
TranscriptExcerpt fields: id, text, speaker, speaker_role, video_id, start_timestamp, end_timestamp, is_public_comment, score
Property: video_url — YouTube link with timestamp
get_public_testimony(topic, top_k=10) -> List[TranscriptExcerpt]¶
Public comment excerpts only (filters to public comment segments).
what_happened_with_discussion(query, top_k=10, agenda_item=None) -> List[HybridSearchResult]¶
Combined PDF + transcript search. Each result has source_type ("pdf" or "transcript").
Budget & Finance¶
budget(department=None, fund=None, fiscal_year=None) -> List[BudgetItem]¶
items = c.budget(department="Fire")
for item in items:
print(item.department, item.line_item, f"${item.budgeted_dollars:,.0f}")
BudgetItem fields: id, fund, department, program, line_item, budgeted_dollars (float), fiscal_year, revised_dollars, actual_dollars, source_url, source_page, notes
budget_summary(fiscal_year=None, group_by="department") -> List[BudgetSummary]¶
Aggregated budget by department or fund.
BudgetSummary fields: name, budgeted_dollars (float), item_count (int), revised_dollars, actual_dollars
federal_expenditures(cfda_number=None, audit_year=None) -> List[FederalExpenditure]¶
Audited federal spending from the Single Audit (Federal Audit Clearinghouse data).
exps = c.federal_expenditures()
for e in exps[:5]:
print(f"{e.federal_program_name}: ${e.amount_expended_dollars:,.0f}")
FederalExpenditure fields: report_id, cfda_number, audit_year (int), amount_expended_dollars (float), federal_program_total_dollars, cluster_total_dollars, federal_program_name, cluster_name, federal_agency_prefix, is_major (bool), is_passthrough (bool), source_url
federal_expenditures_summary(audit_year=None) -> dict¶
Aggregated federal spending summary.
summary = c.federal_expenditures_summary()
print(f"Total: ${summary['total_dollars']:,.0f} across {len(summary['programs'])} programs")
Returns: {"total_dollars": float, "audit_year": int, "programs": [...]}
intergovernmental_revenue(fiscal_year=None, source=None) -> IntergovernmentalRevenueSummary¶
Revenue from federal, state, and county sources (CA State Controller data).
rev = c.intergovernmental_revenue()
print(f"Federal: ${rev.federal_total_dollars:,.0f}")
print(f"State: ${rev.state_total_dollars:,.0f}")
print(f"County: ${rev.county_total_dollars:,.0f}")
print(f"Total: ${rev.total_dollars:,.0f}")
IntergovernmentalRevenueSummary fields: fiscal_year (int), entity_name, federal_total_dollars (float), state_total_dollars (float), county_total_dollars (float), undetermined_total_dollars (float), total_dollars (float), details (List[IntergovernmentalRevenue])
IntergovernmentalRevenue fields: fiscal_year, form_table (SCO form code), source ("federal"/"state"/"county"), amount_dollars (float), category, subcategory, line_description, entity_name
funding_flow(program=None, cfda_number=None) -> List[FundingFlow]¶
Trace federal-to-state-to-city funding paths. Links federal awards to state pass-throughs to city budget items.
Note: This feature requires funding flow linkage data, which is not yet available for all jurisdictions. Returns empty results where linkage data hasn't been ingested.
FundingFlow fields: budget_item_id, budget_description, budget_dollars (float), department, fund, fiscal_year, federal_award_id, federal_cfda_number, federal_program_name, federal_agency, federal_dollars, match_type, match_confidence (float), reconciliation_status
funding_flow_impact(program=None, cfda_number=None, cut_percentage=0.20) -> FundingFlowImpact¶
Model the impact of hypothetical funding cuts on local budget items.
Note: Depends on funding flow linkage data (see above).
FundingFlowImpact fields: program_name, cfda_number, cut_percentage (float), total_current_dollars (float), total_impact_dollars (float), affected_items (List[FundingFlow])
Voting Records¶
get_voting_record(official_name, topic=None, since=None, until=None) -> VotingRecord¶
Voting statistics for an elected official, optionally filtered by topic.
VotingRecord fields: official_id, official_name, topic, total_votes (int), yes_votes (int), no_votes (int), abstain_votes (int), decisions (list of dicts)
Properties: yes_percentage, no_percentage, abstain_percentage
AI Actions¶
draft_action(action_type, topic, description, target=None, template=None) -> ActionDraft¶
AI-generated civic action draft (public comment, letter, etc.).
ActionDraft fields: draft (str), description, citations (list)
Data Coverage¶
All amounts are in dollars (not cents). Dates are ISO 8601 datetimes.
See Data Dictionary for complete field-level schemas of all return types.
San Rafael Pilot (current data)¶
| Corpus | Records | Source |
|---|---|---|
| Meetings | ~98 | ProudCity |
| Decisions | ~44 | Minutes extraction |
| Transcripts | ~19 | YouTube + AssemblyAI |
| Agenda packets | ~5,084 chunks | City agenda PDFs |
| Municipal code | ~16,175 sections | Municode |
| Community issues | ~1,730 | SeeClickFix (311) |
| Budget items | ~58 | FY25-26 budget |
| Legislation | ~17,719 | LegiScan (CA + federal) |
| Federal expenditures | 52 | FAC Single Audit |
| Intergovernmental revenue | 10 line items | CA State Controller |
Known Limitations¶
- Funding flow linkage (
funding_flow(),funding_flow_impact()) — linkage data not yet ingested. These methods return empty results. - Transcript search — requires vector embeddings. Available via the hosted API but not in local SQLite mode.
- Data freshness — meetings and agendas update daily. Legislation updates weekly. Budget data is per fiscal year.