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¶
Open tier (30 req/min, limited tools): No key required for city_pulse, get_voice_counts, and a few other discovery tools.
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": "builder", "email": "you@example.com"}'
Returns a Stripe checkout URL. After payment, your API key is provisioned automatically. Key delivery is currently manual — an admin will send it to the email you provide. Automated delivery is planned.
| Tier | Rate Limit | Use Case | Tools |
|---|---|---|---|
| open | 30 req/min | Discovery, dashboards | 6 tools (city_pulse, get_started, voice counts, relays, initiatives) |
| free | 60 req/min | Personal use, exploration | + All read-only civic data (~30 tools) |
| builder | 300 req/min | Application integrations, civic tech | + Participation tools (testimony, comment drafting, meeting prep) |
| organization | 300 req/min | Nonprofits, civic orgs | Same as builder |
| city | 600 req/min | Municipal staff, officials | Same as builder |
| admin | 1,000 req/min | Platform operations | + Admin tools (data status, cost dashboard, key management) |
All tiers have a 10,000 req/hour ceiling. LLM-powered endpoints (conversation, chat routing) are limited to 30 req/min regardless of tier. See MCP Tool Access by Tier for the complete tool-to-tier mapping.
Billing Webhooks¶
Stripe webhook events (checkout.session.completed, customer.subscription.deleted, invoice.payment_failed) are handled at POST /api/billing/webhook. On successful checkout, the key is auto-provisioned with the correct tier. Subscription changes (cancellations, failures) update key status automatically.
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¶
The API uses a sliding-window rate limiter with per-minute and per-hour windows, plus a burst token bucket for spike protection. Rate limits are tracked per API key (or per IP for unauthenticated requests).
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.
Note: Rate limit counters are in-memory and reset on server restart. This is acceptable for pilot scale but means brief windows of unrestricted access after deploys.
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)¶
Read endpoints (voice counts, initiative listings) are available via the REST API. Write endpoints (casting voices, creating initiatives, committing to actions) are served exclusively by the relay and require Nostr-signed requests. Write tools are not available through the MCP server or API key tiers.
Write requests are subject to the relay's acceptance policy — rate limiting per public key, with unlimited access for attested or paying users.
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).
v2 Query Interface (Recommended)¶
The v2 interface provides server-side composition across corpora and jurisdictions. All queries go through 5 verbs at /api/v2/civic/:
# Multi-corpus search
curl -X POST "$BASE/api/v2/civic/search" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"query": "housing", "corpus": ["decisions", "legislation", "meetings"]}'
# Cross-jurisdiction search (parents = county, state, federal)
curl -X POST "$BASE/api/v2/civic/search" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"query": "housing", "corpus": ["decisions"], "include_parents": true}'
# Cross-jurisdiction with siblings (other cities in same county)
curl -X POST "$BASE/api/v2/civic/search" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"query": "housing", "corpus": ["decisions"], "include_parents": true, "include_siblings": true}'
civic.search¶
POST /api/v2/civic/search
| Field | Type | Description |
|---|---|---|
query | string | Natural language query (required) |
corpus | string[] | Corpus types to search (required): decisions, testimony, legislation, meetings, issues, budget, municipal_code, packets, rules, orders |
jurisdiction | string | Override jurisdiction (default: server's jurisdiction) |
since | string | Date range start (ISO) |
until | string | Date range end (ISO) |
location | string | Geographic filter |
limit | int | Max results (1-100, default 10) |
depth | string | minimal, standard (default), deep |
mode | string | search (default), aggregate, trend, diff, intersect |
include_parents | bool | Include parent jurisdictions (county, state, federal) |
include_siblings | bool | Include sibling jurisdictions (same parent county) |
Response:
{
"results": [
{
"type": "decision",
"ref": "decision:city-san-rafael:proudcity-city-san-rafael-city-council-january-20-2026-tuesday:03",
"title": "Approve Housing Element Update",
"date": "2026-02-15",
"summary": "Approved 4-1 — City Council",
"relevance": 0.95,
"jurisdiction": "city-san-rafael",
"details": {"outcome": "Approved 4-1", "vote_summary": "4-1"}
}
],
"jurisdiction_results": {
"city-san-rafael": [...],
"county-marin": [...]
},
"meta": {
"schema_version": "2025.1",
"query_time_ms": 245,
"total_results": 12,
"corpus_counts": {"decisions": 5, "legislation": 7}
}
}
jurisdiction_results is only present in cross-jurisdiction queries. Each CivicResult includes a jurisdiction field.
civic.upcoming¶
POST /api/v2/civic/upcoming
| Field | Type | Description |
|---|---|---|
types | string[] | Event types: meetings, hearings, comment_periods, legislation, elections |
days | int | Look-ahead window (1-365, default 14) |
actionable_only | bool | Only events with participation opportunities |
civic.context¶
POST /api/v2/civic/context
| Field | Type | Description |
|---|---|---|
ref | string | Opaque ref from a search/upcoming result |
concept | string | Civic term to look up (e.g., "conditional use permit") |
depth | string | minimal, standard, deep |
Provide either ref (item context) or concept (jargon lookup), not both.
civic.act¶
POST /api/v2/civic/act
| Field | Type | Description |
|---|---|---|
action | string | Action name: prepare_comment, comment_template, etc. |
ref | string | Item reference for context-dependent actions |
params | object | Action-specific parameters |
civic.explore¶
POST /api/v2/civic/explore
| Field | Type | Description |
|---|---|---|
what | string | jurisdictions, corpora, corpus_schema:{name}, actions, capabilities, schema_version |
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 (as of March 2026)¶
| 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.