Pagination

GET/v1/outages and GET/v1/suburbs return a page at a time. Every page ends with a pagination object:

200GET /v1/suburbs?q=cranb&limit=2
{
  "data": [
    {
      "id": 7904,
      "name": "Cranbourne",
      "postcode": "3977",
      "state": "vic",
      "url": "https://aussieoutagetracker.com.au/locations/vic/power/cranbourne-3977/",
      "networks": [
        "ausnet"
      ]
    },
    {
      "id": 7903,
      "name": "Cranbourne East",
      "postcode": "3977",
      "state": "vic",
      "url": "https://aussieoutagetracker.com.au/locations/vic/power/cranbourne-east-3977/",
      "networks": [
        "ausnet"
      ]
    }
  ],
  "pagination": {
    "has_more": true,
    "next_cursor": "eyJzIjoibmFtZSIsInYiOiJjcmFuYm91cm5lIGVhc3QiLCJpIjo3OTAzfQ",
    "limit": 2
  },
  "meta": {
    "data_as_of": "2026-10-20T02:28:26.234Z"
  }
}

A real response, captured from the API. Arrays are shortened to two items.

  • has_more is true when there is at least one more page.
  • next_cursor is what you send as cursor to get it. It is null on the last page.
  • limit is the page size in use: 100 unless you ask, and at most 500.

Walking to the end

Send the same filters and sort with each page's cursor, and stop when has_more is false:

let cursor;
const all = [];
do {
  const params = new URLSearchParams({ state: 'nsw', from: '2026-10-01T00:00:00+10:00', limit: '100' });
  if (cursor) params.set('cursor', cursor);
  const res = await fetch(`https://api.aussieoutagetracker.com.au/v1/outages?${params}`, {
    headers: { Authorization: `Bearer ${process.env.AOT_API_KEY}` },
  });
  const page = await res.json();
  all.push(...page.data);
  cursor = page.pagination.next_cursor;
} while (cursor);

Each page is a call, so a larger limit walks the same list in fewer calls.

Longer than a month

One query of GET/v1/outages reads at most 31 days of outage start times, so the archive is read a month at a time rather than in one sweep:

  • from and to may be at most 31 days apart;
  • from alone reads the 31 days after it, and to alone the 31 days before it;
  • with neither, the window is the last 31 days.

There is no window for active_at (one instant), for changed_since (which itself reaches back at most 31 days), or for a status of only active or scheduled, which are live. A window that is too wide is refused with 400 invalid_parameter, naming the parameter.

No date is out of reach. To read further back, walk one window to its end, then the next. For years at once, or a whole state, request a bulk export in Settings › API: we quote it and send the file.

Cursors, not page numbers

The cursor says "the rows after this one", by the sort value and id of the last row you received. That is a different promise from page=7, and a better one for a program:

  • It costs the same on page 400 as on page 1. An offset makes the database re-read every row it skips, so deep pages get slower and slower. A cursor is a single index seek.
  • It is stable while the archive grows. New outages are recorded every few minutes. With offsets, a row inserted at the head shifts everything after it, so a walk sees one outage twice and silently misses another. A cursor carries on from exactly where it stopped.
  • There is no total count. Counting a filtered archive is the expensive half of any query, and a program walking to the end does not need it. For totals, use GET/v1/stats/outages.

Rules

  • A cursor is opaque. It is base64url text; do not build, edit or store meaning in one.
  • A cursor belongs to its sort. One issued for sort=-start and sent with sort=updated is refused with 400 invalid_parameter rather than silently paging in the wrong order. Start again without a cursor when you change the sort.
  • Keep the filters the same. The cursor records where you are in the sort, not which filters you used. Change a filter mid-walk and the remaining pages answer the new question from the old position.
  • Outages arriving during a walk. A walk newest-first (-start) will not see an outage that starts after it began. To pick up everything that changed, follow up with changed_since — see Keeping a copy in sync.