Developer API
CityAlert.live normalizes live public-safety incidents — police open-data feeds, earthquake agencies, wildfire services, weather warnings, disaster alerts, geocoded media reports and community submissions — into one GeoJSON stream. The same endpoints that power the map are public and free to use with attribution.
Machine-readable spec: /api/openapi.json (OpenAPI 3.1). For AI agents, see llms.txt, agents.txt and the MCP server below. What changed recently: /changelog (Atom).
Live endpoints need no key. For history, higher limits or webhooks: get an API key ↓
Public endpoints
- GET
/api/incidentsMerged live incident stream as a GeoJSON FeatureCollection
Filter with ?region=, ?category=, ?source=, ?bbox=minLon,minLat,maxLon,maxLat, ?days=, ?limit= (max 10000), ?id=. Use ?format=json for a plain array. ?refresh=1 forces an upstream re-fetch. History beyond ?days=7 requires a paid API key.
- GET
/api/archiveHistorical incident archive — every incident every feed has ever returned (paid)
Requires an x-api-key header on a Developer or Pro plan. Filter with ?source=, ?region=, ?category=, ?bbox=minLon,minLat,maxLon,maxLat, ?from= & ?to= (ISO 8601, on the incident's occurred time), ?limit= (default 500, max 5000). Returns {incidents, count, from, to} newest-first.
- GET
/api/regionsFocus-region list — key, label, center, zoom, bbox
- GET
/api/trendsHistorical aggregates — crime YoY, earthquakes by region, GDACS by year
- GET
/api/crime-year?year=YYYY®ion=<key>Full-year incident GeoJSON + neighbourhood ranking
Vancouver VPD history goes back to 2003; other regions via police portals or the media log.
- GET
/api/newsRegional safety news items
?place=<name> for ad-hoc places outside the focus regions.
- GET
/cities/<slug>/feed.xmlAtom feed of a city's 20 most recent incidents
Advertised via <link rel="alternate"> on every city page.
- GET
/api/reportsCommunity-submitted incident reports
- POST
/api/reportsSubmit a community report
Body: {title, lat, lon, category?, severity?, description?}. Rate-limited to 5/hour per IP; reports are community-verified before going active.
- POST
/api/reports/<id>/voteVote on a report — confirm, resolve or flag
Body: {kind: "confirm"|"resolve"|"flag"}. 30 votes/hour per IP.
- GET
/api/dashboards?id=<id>Fetch a shared dashboard config
- POST
/api/dashboardsCreate a shareable dashboard — returns {id, token}
The token is the write credential; PUT /api/dashboards updates with {id, token, config}.
- POST
/api/mcpMCP (Model Context Protocol) server for AI assistants
JSON-RPC 2.0 — initialize, tools/list, tools/call over the same incident data. Historical tools (get_trends, get_crime_year) require a Pro-plan x-api-key.
Incident object
Every incident — in GeoJSON properties or JSON array items — carries:
id stable source-prefixed identifier source feed key (e.g. "usgs", "vpd", "zwaailicht") category crime | fire | disaster | weather | unrest | health | infra | report type source-specific type (e.g. "earthquake", "Break and Enter") title headline description optional detail (plain text, max ~160 chars) lat, lon WGS84 coordinates time reported time, ISO 8601 severity low | medium | high | critical url upstream source link region focus-region key when inside one nhood neighbourhood label when the feed provides one status community-report state: pending | active | resolved | hidden
API keys & plans
Paid endpoints authenticate with an x-api-key: ca_… header. Keys are self-serve: sign in and create one under Account → API keys — free keys are instant, paid tiers upgrade through Stripe on the same page. Usage is metered per UTC day across all endpoints; over-limit requests return 429 with a Retry-After hint.
Free
60 requests/day — shared ~15-minute snapshot (responses carry meta.delayed); real-time refresh, archive, deep history and MCP history tools require a paid plan.
Developer — $99/mo
5,000 requests/day — GET /api/archive; days>7 on /api/incidents; real-time refresh.
Pro — $499/mo
50,000 requests/day — everything in Developer; get_trends + get_crime_year MCP history tools; outbound webhook delivery.
To upgrade, sign in and pick a paid tier under Account → API keys — Stripe handles checkout. The full comparison, including consumer and municipal plans, is on the pricing page. Missing or invalid keys get 401; a valid key on too-low a plan gets 403.
Outbound webhooks
Pro-plan keys can register HTTPS endpoints under Account → API keys instead of polling. Each dispatch tick (~15 min) POSTs a batch of newly-seen incidents matching the endpoint's category/region/severity filters:
POST <your-endpoint>
content-type: application/json
x-cityalert-signature: t=<unix>,v1=<hmac-sha256>
{"type":"incidents.batch","created":"<iso>","incidents":[
{"id","source","category","type","title","description",
"lat","lon","time","severity","url","region"} ]}Add a bbox ([minLat, minLon, maxLat, maxLon]) to geo-fence an endpoint, and cluster_threshold (integer 2–1000) to switch it from per-batch delivery to a single incidents.cluster post when that many matching incidents sit inside the bbox within the trailing hour — it refires only when new incidents grow the cluster:
{"type":"incidents.cluster","created":"<iso>",
"cluster":{"threshold":5,"window_minutes":60,
"count":7,"bbox":[49.6,-119.5,50.1,-118.8]},
"incidents":[ …same item shape as incidents.batch… ]}Verify x-cityalert-signature exactly like a Stripe webhook: compute HMAC-SHA256(secret, t + "." + raw_body) with the signing secret shown once at registration and compare to v1. Endpoints are at-most-once per tick — a failed POST is logged but not retried, so re-pull /api/incidents to fill any gap. Endpoints that fail 25 consecutive ticks are auto-disabled (re-enable from your account).
MCP server
POST /api/mcp speaks the Model Context Protocol — point any MCP client (Claude, ChatGPT, open-source agents) at it to query incidents, regions and trends as tools instead of raw HTTP.
In Claude or ChatGPT, add https://cityalert.live/api/mcp as a custom connector / MCP server (streamable HTTP, no auth needed for the free tools). Clients that only send Accept: application/json get plain JSON-RPC responses — SSE is optional. Free and anonymous calls get the shared ~15-minute delayed snapshot; a paid x-api-key header unlocks real-time refresh, andPro-plan keys add get_trends and get_crime_year. Anonymous calls to those two return a tool-level error naming the key requirement — the MCP session itself stays healthy (no HTTP 401 mid-handshake).
Per-city Atom feeds
Each of the 157 city pages publishes an Atom feed of its 20 most recent incidents at /cities/<slug>/feed.xml. City pages themselves are ISR-rendered every ~5 minutes with live incident lists — safe to crawl and cite. Community reports drop out of rendered pages immediately when they're resolved or hidden (the status flip purges the page — no waiting on the revalidate window).
Embed widget
/embed renders a compact, map-free incident summary that can be iframed into any page — scoped by ?region=<key> (see /api/regions) or ?city=<slug> (any /cities/ slug):
<iframe src="https://cityalert.live/embed?city=vancouver-bc" width="100%" height="360" style="border:0" loading="lazy" title="CityAlert incident summary"></iframe>
The widget refreshes every ~10 minutes and links back to the live map. Keep the CityAlert.live attribution link intact. Self-serve: Embed a widget generates a ready-to-paste snippet for any region.
Usage, rate limits & attribution
- Live read endpoints are public; deep history (/api/archive, days>7, MCP history tools) requires a paid API key. Community write endpoints are IP rate-limited.
- Attribution: cite CityAlert.live and the underlying source listed in each incident's
sourcefield. - Media-sourced incidents are approximate block-level locations — preserve that caveat when republishing.
- Commercial use, redistribution and team delivery are covered on the data licensing page.
- This is an aggregation/awareness tool. For emergencies defer to official authorities.
- Data is provided under CC BY 4.0 for our normalization layer; underlying feeds carry their own licenses — see Sources & licenses.