Skip to content
PG Pulse
PG Pulse

Try searching for

Local knowledge for your AI tools

Connect Cursor, VS Code, Codex or Claude Code. Free MCP access, no key needed.

Connect your AI assistant
View as MarkdownFree, no key needed

PG Pulse API

A free, open, read-only API for everything on PG Pulse, the live local guide to Prince George, British Columbia, Canada. No key, no sign-up, no cost. CORS is open, so you can call it straight from a browser.

Quickstart #

bash
curl --fail-with-body "https://princegeorgepulse.ca/api/v1/pages?limit=5"
curl --fail-with-body "https://princegeorgepulse.ca/api/v1/search?q=parks&per=3"
js
const response = await fetch('https://princegeorgepulse.ca/api/v1/pages?limit=5', { signal: AbortSignal.timeout(15000) })
if (!response.ok) throw new Error(`API returned HTTP ${response.status}`)
const { data, meta } = await response.json()
console.log(data, 'Source:', meta.attribution)

More in the quickstart.

Assistant #

Ask a local question from your application with POST https://princegeorgepulse.ca/api/chat. This is a separate, rate-limited AI endpoint, with JSON responses by default and no saved conversation unless you request one. It supports server-to-server calls; browser requests must come from our own site. No API key is needed.

bash
curl 'https://princegeorgepulse.ca/api/chat' -H 'Content-Type: application/json' --data '{"question":"What’s on this weekend?","store":false}'

The response contains answer (Markdown), links (title and URL), and tools (source lookups used). Treat the answer as guidance and check the linked sources. To continue, send messages with user/assistant history ending in a user question, or request store: true and reuse the returned thread id with store: true. Thread ids grant access to the conversation: keep them private. Never send sensitive information.

For streaming, send Accept: text/event-stream. Handle text, retract (clear earlier text), links, status, thread, done and error. A stream ending without done is incomplete. Limits are shared with the website chat; honour Retry-After on 429/503 and do not retry in a tight loop. See current availability and limits and the chat OpenAPI contract.

Response format #

Connect an agent #

Connect your agent with one-click links, copyable configurations and troubleshooting. Use https://princegeorgepulse.ca/mcp as a Streamable HTTP MCP server. It covers every enabled public API endpoint, including custom city modules and OpenAPI. Focused tools for search, the directory, events, news, jobs, conditions, weather, address zoning, garbage day and transit are convenient starting points; the server also offers a usage-guide and API-catalogue resource and two prompts (local briefing, address report). For other topics, use discover_api to find an endpoint, describe_api for its parameters, and query_api to read it. batch_api combines up to four independent queries, with two running at a time and duplicate queries sharing work. Query filters and sparse fields keep responses small. Large answers return explicit partial-view notices, JSON Pointers and continuation instructions; follow these before assuming a result is complete. No model is called. Tool availability follows enabled city modules. Requests and distinct data lookups share the public API rate limit; respect Retry-After. Server-to-server clients need no key. Cross-site browser requests and persistent subscriptions are not supported. Cite the returned sources and use local listing URLs.

Every JSON response has the same envelope:

json
{
  "data": [ ... ],
  "meta": {
    "source": "Who the data comes from",
    "sourceUrl": "https://…",
    "updatedAt": "2026-09-30T21:00:00.000Z",
    "license": "The licence the data is under",
    "attribution": "The credit line to show",
    "total": 129, "count": 50, "limit": 50, "offset": 0
  },
  "links": { "self": "/api/v1/…", "next": "/api/v1/…&offset=50", "docs": "/developers/reference/…" }
}

meta.stale: true means the upstream source did not answer and you are getting the last good copy. updatedAt is when we last fetched or built the data.

Dates and times #

Dates are YYYY-MM-DD. Every date-time is ISO 8601 with a UTC offset: Z for moments we record (updatedAt, firstSeen), the local offset (such as -07:00) for local times like event starts, forecast hours, sunrise and meetings. Parse them as offsets, never as floating local times.

Pagination #

List endpoints take limit and offset. Follow links.next (also sent as an HTTP Link: <…>; rel="next" header) until it is null. meta.total is the number of matching items.

Filtering, search and fields #

  • Most lists take q (text search, every word must match) plus their own filters, listed on each endpoint.
  • fields=slug,name,lat,lng keeps only those keys in each item (dot paths work through nested objects and arrays, such as hourly.temp).
  • Location filters use near=lat,lng (and radius in km where offered), for example /businesses?near=53.9171,-122.7497&radius=1.

Output formats #

formatContent typeWhere
json (default)application/jsoneverywhere; add pretty for indented output
mdtext/markdowneverywhere; also Accept: text/markdown
csvtext/csvlist endpoints
geojsonapplication/geo+jsonmap endpoints (neighbourhoods, map layers, crime points, transit shapes, garbage zones)

Caching and ETags #

Responses carry Cache-Control, a weak ETag and, when the data has a timestamp, Last-Modified (the same moment as meta.updatedAt). Send the ETag back as If-None-Match and you get 304 Not Modified with no body when the data has not changed. Revalidate with the ETag rather than If-Modified-Since: many lists also depend on the current time (upcoming events, next pickup day). Live sources are cached on our server for minutes (weather, outages, transit) to hours (maps, census), so polling faster than an endpoint's cache time gains nothing. Responses are compressed (brotli or gzip) when you send Accept-Encoding.

Rate limits #

120 requests per 60 seconds per IP address. Every response includes RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds). Over the limit you get 429 Too Many Requests with a Retry-After header. Need more? Cache responses, use ETags, or email hello@princegeorgepulse.ca.

Errors #

Errors use HTTP status codes and a JSON body:

json
{ "error": { "status": 404, "code": "not_found", "message": "Business \"nope\" not found" }, "links": { "docs": "/developers/" } }

Codes: bad_request (400), not_found (404), method_not_allowed (405), sync_expired (410, a /changes cursor older than the sync window: start a full sync), rate_limited (429), upstream_unavailable (503, retry later). Branch on code, not on message. Unknown paths answer 404 with didYouMean suggestions. When an upstream fails we serve the last good copy with meta.stale: true instead of an error where we can.

Versioning #

/api/v1 only grows: new endpoints, fields and parameters can appear at any time, so ignore fields you do not know. Nothing is removed, renamed or changed in meaning within v1; a breaking change would ship as /api/v2 with at least 90 days of overlap and a note in the changelog. Current version: 1.1.0 (info.version in OpenAPI).

Discovery, status and tools #

Attribution and licences #

The API is free for any use, including commercial. Please credit PG Pulse (princegeorgepulse.ca) and the original source named in each response's meta.attribution. PG Pulse's own content and compiled data are CC BY 4.0 with credit to PG Pulse. Third-party data stays under its source's licence (meta.license). News items are headlines and links only: follow url to read the story on the publisher's site. Details: attribution and licences.

Markdown for every page #

Every page on PG Pulse has a Markdown version with its data attached:

  • Add .md to the path: https://princegeorgepulse.ca/about.md, https://princegeorgepulse.ca/index.md
  • Or prefix /md: https://princegeorgepulse.ca/md/about/
  • Or send Accept: text/markdown to the normal URL

Pages also advertise it with <link rel="alternate" type="text/markdown">.

Endpoint groups #

  • About the API (5): /, /meta, /pages, /search, …
  • Directory (12): /businesses, /businesses/changes, /businesses/new, /businesses/{slug}, …
  • Neighbourhoods (7): /neighbourhoods, /neighbourhoods/geojson, /neighbourhoods/lookup, /neighbourhoods/aliases, …
  • Food & nightlife (9): /food/places, /food/cuisines, /food/late-night, /nightlife, …
  • Events (5): /events, /events/months, /events/categories, /events/sources, …
  • Sports (8): /sports/teams, /sports/teams/{id}, /sports/games, /sports/standings, …
  • Local artists (6): /artists, /artists/taxonomy, /artists/{slug}, /djs, …
  • Awards (4): /awards/seasons, /awards/seasons/{season}, /awards/seasons/{season}/{category}, /awards/honours
  • News & stories (12): /news, /news/archive, /news/archive/{year}, /news/archive/{year}/{month}, …
  • Guides (15): /guides/sections, /guides, /guides/{section}/{slug}, /resources, …
  • Live conditions (6): /now, /home, /happening, /happening/ended, …
  • Weather (14): /weather, /weather/now, /weather/official, /weather/observations, …
  • Climate (4): /climate, /climate/this-day, /climate/normals, /climate/records
  • Air & water (8): /air, /rivers, /rivers/stations, /rivers/history, …
  • Hazards (8): /wildfire, /earthquakes, /roads, /aurora, …
  • Power (3): /power/outages, /power/rates, /power/outages/history
  • Safety (4): /crime, /crime/points, /crime/occurrences, /crime/severity
  • People & economy (13): /markets, /people, /census, /economy, …
  • Jobs (8): /jobs, /jobs/categories, /jobs/employers, /jobs/top-employers, …
  • City data (20): /permits, /bylaws, /bylaws/stats, /bylaws/{number}, …
  • Civic (9): /council/votes, /council/votes/summary, /civic, /civic/representatives, …
  • Indigenous Prince George (1): /indigenous
  • Maps (2): /maps/layers, /maps/layers/{id}
  • Transit (10): /transit/routes, /transit/stops, /transit/stops/{id}/departures, /transit/shapes, …
  • Garbage & recycling (5): /garbage/lookup, /garbage/suggest, /garbage/schedule, /garbage/zones, …
  • Datasets (6): /datasets, /datasets/{id}, /datasets/{id}/records, /datasets/{id}/series, …
  • History (3): /history/series, /history, /live/sources
  • Key services (6): /services/places, /services/places/{id}, /services/needs, /services/sources, …
  • Emergency (8): /emergency/contacts, /emergency/care, /emergency/prepare, /emergency/alerts, …
  • Taxes & fees (2): /taxes, /taxes/estimate
  • Getting around (10): /airport/info, /intercity, /taxis, /roads/highways, …
  • Advertising (2): /ads, /ads/featured-listings
  • Community (19): /community, /community/worship, /community/charities, /community/child-care, …
  • Living (4): /costs/household, /costs/rates, /internet/providers, /internet/neighbourhoods
  • City (3): /holidays, /holidays/next, /holidays/calendar
  • Outdoors (10): /fishing, /fishing/species, /fishing/guide, /fishing/{slug}, …
  • City spending (8): /spending, /spending/suppliers, /spending/grants, /spending/contracts, …

Every endpoint by group: https://princegeorgepulse.ca/developers/reference/ (Markdown).

Changelog #

  • 1.1.0 (2026-10-05): New endpoints: /events/months and a month=YYYY-MM filter on /events (the site’s month pages); /news/archive/{year}; /zoning/addresses (civic address suggestions) and /zoning/small-scale-housing. /maps/layers/{id} takes bbox and near. meta.updatedAt is always a date-time with Z or an offset: sources dated only to a day, month or year give the start of that period in the city’s time zone, with meta.updatedAtPrecision. Transit departures add ISO scheduledAt/expectedAt next to the epoch-millisecond values; air stations add pm25Avg24h (same value as pm25_24). Every OpenAPI operation has a response example (hand-written or a placeholder generated from its schema). MCP: focused tools for news, jobs, weather, address zoning, garbage day and bus stops; event month and date filters; tool titles; two resources (usage guide, API catalogue) and two prompts (local briefing, address report); schema violations return the same structured invalid_arguments error as other failures; HTTP refusals use the API error shape. The server card lists the tools, resources, prompts and protocol version.
  • 1.0.2 (2026-10-02): Responses carry Last-Modified (from meta.updatedAt) and the full RateLimit-* set is declared in OpenAPI; ETags now ignore meta.ageSec, so If-None-Match returns 304 whenever the data is unchanged. Discovery from the domain alone: /.well-known/api-catalog (RFC 9727), an MCP server card at /.well-known/mcp/server-card.json, and /.well-known/security.txt. A Postman collection at /developers/postman.json. OpenAPI documents error codes, the If-None-Match header and the versioning policy.
  • 1.0.1 (2026-10-01): Full public API coverage through MCP discovery, filtered queries and bounded batches, with explicit partial results and actionable retry errors. Client setup and downloadable configurations at /developers/mcp/. City-aware examples, safer pagination, nested-array field selection and accessible code copying.
  • 1.0.0 (2026-09-30): First public release: every module of PG Pulse as read-only JSON under /api/v1, with OpenAPI 3.1, Markdown, CSV and GeoJSON output, ETags and generous per-IP rate limits. Markdown versions of every page (/<page>.md, /md/<page>, or Accept: text/markdown), plus /llms.txt and /llms-full.txt.

Add PG Pulse to your home screen

Your browser installs apps from its Share menu.

  1. Tap Share in the browser toolbar (at the bottom of the screen on iPhone, at the top on iPad).
  2. Scroll down and tap Add to Home Screen.
  3. Tap Add. PG Pulse opens from its icon like any other app.