# About the API

5 endpoints. Base URL `https://princegeorgepulse.ca/api/v1`. Conventions (envelope, paging, fields, formats, limits) are on the [overview](https://princegeorgepulse.ca/developers/). [All endpoints](https://princegeorgepulse.ca/developers/reference/).

## GET /api/v1/ {#get}

API index: every endpoint with a link.

- Cache: 1 h
- Source: [PG Pulse](https://princegeorgepulse.ca)
- Licence: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

Also accepts `fields` (sparse fieldset) and `format` (`json`, `md`).

**Example request**

```bash
curl "https://princegeorgepulse.ca/api/v1/"
```

**Example response** (trimmed)

```json
{
  "data": {
    "name": "PG Pulse API",
    "version": "1.1.0",
    "description": "Free, open JSON API for everything on PG Pulse, the local guide to Prince George, British Columbia. No key, no sign-up.",
    "docs": "/developers/",
    "openapi": "/api/v1/openapi.json",
    "llms": "/llms.txt",
    "rateLimit": "120 requests per 60 seconds per IP",
    "endpoints": [
      {
        "path": "/api/v1/",
        "group": "About the API",
        "summary": "API index: every endpoint with a link",
        "docs": "/developers/reference/about-the-api/#get"
      },
      {
        "path": "/api/v1/meta",
        "group": "About the API",
        "summary": "Site facts, counts and data freshness",
        "docs": "/developers/reference/about-the-api/#get-meta"
      },
      "… 277 more"
    ]
  },
  "meta": {
    "source": "PG Pulse",
    "sourceUrl": "https://princegeorgepulse.ca",
    "updatedAt": null,
    "license": "CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)",
    "attribution": "PG Pulse (princegeorgepulse.ca)"
  },
  "links": {
    "self": "/api/v1/",
    "next": null,
    "docs": "/developers/reference/about-the-api/#get"
  }
}
```

## GET /api/v1/meta {#get-meta}

Site facts, counts and data freshness.

- Cache: 5 min
- Source: [PG Pulse](https://princegeorgepulse.ca)
- Licence: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

Also accepts `fields` (sparse fieldset) and `format` (`json`, `md`).

**Example request**

```bash
curl "https://princegeorgepulse.ca/api/v1/meta"
```

**Example response** (trimmed)

```json
{
  "data": {
    "site": {
      "name": "PG Pulse",
      "longName": "Prince George Pulse",
      "tagline": "The pulse of Prince George, British Columbia",
      "description": "The live local guide to Prince George, BC: news from every local source, live weather, air, rivers and wildfires, a local business directory and things to do.",
      "url": "https://princegeorgepulse.ca",
      "city": "Prince George",
      "region": "British Columbia",
      "country": "Canada",
      "lat": 53.9171,
      "lng": -122.7497,
      "timezone": "America/Vancouver",
      "contact": "hello@princegeorgepulse.ca"
    },
    "modules": [
      "core",
      "media",
      "… 74 more"
    ],
    "counts": {
      "businesses": 2484,
      "categories": 88,
      "neighbourhoods": 31,
      "newsItems": 871,
      "stories": 6,
      "upcomingEvents": 362,
      "djs": 80,
      "mapLayers": 50,
      "endpoints": 279
    },
    "updated": {
      "businesses": "2026-10-15T09:45:16.195Z",
      "news": "2026-10-16T10:45:31.669Z",
      "events": "2026-10-16T10:21:11.428Z"
    },
    "jobs": {
      "ok": true,
      "scheduler": {
        "alive": true,
        "at": "2026-10-16T10:48:14.290Z",
        "running": [
          "rebuild",
          "mailbox"
        ],
        "node": "v22.23.3",
        "timeZone": {
          "timezone": "America/Vancouver",
          "tzdata": "2026e",
          "ok": true,
          "checks": [
            "{…}"
          ]
        }
      },
      "stale": [],
      "failing": [],
      "jobs": {
        "feed": {
          "enabled": true,
          "dataAt": "2026-10-16T10:45:31.669Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "feed-trends": {
          "enabled": true,
          "dataAt": "2026-10-16T10:07:13.891Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "events": {
          "enabled": true,
          "dataAt": "2026-10-16T10:21:11.428Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "careers": {
          "enabled": true,
          "dataAt": "2026-10-16T08:37:16.644Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "live": {
          "enabled": true,
          "dataAt": "2026-10-16T10:43:26.141Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "city": {
          "enabled": true,
          "dataAt": "2026-10-12T09:10:01.912Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "businesses": {
          "enabled": true,
          "dataAt": "2026-10-15T09:45:11.669Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "maps": {
          "enabled": true,
          "dataAt": "2026-10-11T10:30:05.624Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "climate": {
          "enabled": true,
          "dataAt": "2026-10-16T07:31:29.793Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "stats": {
          "enabled": true,
          "dataAt": "2026-10-12T13:20:29.182Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "notable-people": {
          "enabled": true,
          "dataAt": "2026-10-05T12:50:25.112Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "gtfs": {
          "enabled": true,
          "dataAt": "2026-10-12T11:40:09.498Z",
          "stale": false,
          "lastOk": true,
          "running": false
        },
        "…": "46 more keys"
      }
    },
    "api": {
      "version": "1.1.0",
      "base": "/api/v1",
      "docs": "/developers/",
      "openapi": "/api/v1/openapi.json",
      "rateLimit": {
        "limit": 120,
        "windowSec": 60
      },
      "auth": "none",
      "cost": "free"
    }
  },
  "meta": {
    "source": "PG Pulse",
    "sourceUrl": "https://princegeorgepulse.ca",
    "updatedAt": null,
    "license": "CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)",
    "attribution": "PG Pulse (princegeorgepulse.ca)"
  },
  "links": {
    "self": "/api/v1/meta",
    "next": null,
    "docs": "/developers/reference/about-the-api/#get-meta"
  }
}
```

## GET /api/v1/pages {#get-pages}

Every public page on the site, with its Markdown version.

- Cache: 1 h
- Source: [PG Pulse](https://princegeorgepulse.ca)
- Licence: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
- Paginated: `limit` (default 500, max 5000) and `offset`; also `format=csv`

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `prefix` | query | string | Only paths starting with this. Example: `/dashboard/`. |
| `limit` | query | integer | Items per page (default 500). |
| `offset` | query | integer | Items to skip. |

Also accepts `fields` (sparse fieldset) and `format` (`json`, `md`, `csv`).

**Example request**

```bash
curl "https://princegeorgepulse.ca/api/v1/pages?prefix=/dashboard/"
```

**Example response** (trimmed)

```json
{
  "data": [
    {
      "path": "/dashboard/",
      "url": "https://princegeorgepulse.ca/dashboard/",
      "markdown": "https://princegeorgepulse.ca/dashboard.md"
    },
    {
      "path": "/dashboard/air-quality/",
      "url": "https://princegeorgepulse.ca/dashboard/air-quality/",
      "markdown": "https://princegeorgepulse.ca/dashboard/air-quality.md"
    },
    "… 13 more"
  ],
  "meta": {
    "source": "PG Pulse",
    "sourceUrl": "https://princegeorgepulse.ca",
    "updatedAt": null,
    "license": "CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)",
    "attribution": "PG Pulse (princegeorgepulse.ca)",
    "total": 15,
    "count": 15,
    "limit": 500,
    "offset": 0
  },
  "links": {
    "self": "/api/v1/pages?prefix=/dashboard/",
    "next": null,
    "docs": "/developers/reference/about-the-api/#get-pages"
  }
}
```

## GET /api/v1/search {#get-search}

Search across site pages, public places, awards categories, schools, outdoor places, businesses, categories, neighbourhoods (and subdivision names), answers, guides, stories, events, jobs, artists, map layers, resources, news, sports teams and clubs.

- Cache: 5 min
- Source: [PG Pulse](https://princegeorgepulse.ca)
- Licence: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `q` | query | string | Search text. Required. Example: `dog park`. |
| `per` | query | integer | Results per type (default 5, max 25). |

Also accepts `fields` (sparse fieldset) and `format` (`json`, `md`).

**Example request**

```bash
curl "https://princegeorgepulse.ca/api/v1/search?q=dog%20park"
```

**Example response** (trimmed)

```json
{
  "data": {
    "pages": [
      {
        "name": "Dog parks",
        "description": "Off-leash areas on a map",
        "url": "/maps/?layer=dog-parks"
      }
    ],
    "answers": [],
    "categories": [],
    "services": [],
    "awards": [],
    "schools": [],
    "outdoors": [],
    "businesses": [
      {
        "name": "Happy Tails Canine Adventures",
        "category": "Pets & vets",
        "area": "East Central Fort George",
        "url": "/business/happy-tails-canine-adventures/",
        "api": "/api/v1/businesses/happy-tails-canine-adventures"
      }
    ],
    "neighbourhoods": [],
    "guides": [
      {
        "name": "Parks and trails in Prince George, BC",
        "url": "/visit/parks-and-trails/",
        "markdown": "/visit/parks-and-trails.md"
      },
      {
        "name": "Bylaws, animal control and wildlife",
        "url": "/city/bylaws-and-animal-control/",
        "markdown": "/city/bylaws-and-animal-control.md"
      }
    ],
    "stories": [],
    "events": [],
    "…": "6 more keys"
  },
  "meta": {
    "source": "PG Pulse",
    "sourceUrl": "https://princegeorgepulse.ca",
    "updatedAt": null,
    "license": "CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)",
    "attribution": "PG Pulse (princegeorgepulse.ca)"
  },
  "links": {
    "self": "/api/v1/search?q=dog%20park",
    "next": null,
    "docs": "/developers/reference/about-the-api/#get-search"
  }
}
```

## GET /api/v1/changes {#get-changes}

Stable incremental discovery for businesses, events and jobs.

Use a full ISO since date-time with Z or an offset no older than 90 days, then follow cursor until complete. Keep watermark only after all pages finish. Removals are explicit tombstones. Fetch current entities from their API URL. Edits during paging appear in the next window. A cursor belongs to one city. An expired window returns 410 and requires a new full sync.

- Cache: 0 s
- Source: [PG Pulse](https://princegeorgepulse.ca)
- Licence: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `since` | query | string | UTC date-time, e.g. 2026-10-01T00:00:00.000Z (seconds, milliseconds or an offset like -07:00 are normalised) |
| `cursor` | query | string | Opaque cursor from the previous response; overrides since |
| `limit` | query | integer | Between 1 and 500 |

Also accepts `fields` (sparse fieldset) and `format` (`json`, `md`).

**Example request**

```bash
curl "https://princegeorgepulse.ca/api/v1/changes?since=2026-10-15T10:48:18.263Z"
```

**Example response** (trimmed)

```json
{
  "data": {
    "changes": [
      {
        "kind": "job",
        "id": "_0xWH8FUUBOO",
        "action": "remove",
        "updatedAt": "2026-10-15T13:35:04.405Z",
        "url": "/api/v1/jobs/_0xWH8FUUBOO"
      },
      {
        "kind": "job",
        "id": "9WBhuRzAVl3C",
        "action": "remove",
        "updatedAt": "2026-10-15T13:35:04.405Z",
        "url": "/api/v1/jobs/9WBhuRzAVl3C"
      },
      "… 98 more"
    ],
    "cursor": "eyJ2IjoxLCJjaXR5IjoicHJpbmNlLWdlb3JnZSIsInNpbmNlIjoiMjAyNi0xMC0xNVQxMDo0ODoxOC4yNjNaIiwidW50aWwiOiIyMDI2LTEwLTE2VDEwOjQ4OjE4LjI2NFoiLCJhZnRlciI6WyIyMDI2LTEwLTE2VDA3OjM1OjA5LjIxOFoiLCJqb2IiLCIzazVsVkdsSm1MTUEiXX0",
    "watermark": "2026-10-16T10:48:18.264Z",
    "complete": false
  },
  "meta": {
    "source": "PG Pulse",
    "sourceUrl": "https://princegeorgepulse.ca",
    "updatedAt": "2026-10-16T10:48:18.264Z",
    "ageSec": 0,
    "license": "CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)",
    "attribution": "PG Pulse (princegeorgepulse.ca)"
  },
  "links": {
    "self": "/api/v1/changes?since=2026-10-15T10:48:18.263Z",
    "next": null,
    "docs": "/developers/reference/about-the-api/#get-changes"
  }
}
```
