Skip to content
Documentation menu

Concepts

Pagination

List endpoints return a page of results and a cursor for the next page:

{
  "data": [ ... ],
  "next_cursor": "cur_5RbJ8uYc3XeN1qGh6tZkWo"
}
  • Set the page size with limit (1 to 100, default 20; portfolio sites allow up to 500).
  • Pass next_cursor back unchanged as cursor, with the same other parameters, to get the next page.
  • next_cursor is null on the last page.
  • Cursors are opaque (cur_ followed by 22 characters). Do not build or edit them: an altered cursor returns 400 bad_request. They do not expire.
  • Results are in the order records were added to Planning Signal, oldest first. The order never changes, so you will not skip or repeat records while paging, even as new ones arrive (they join the end).
  • /api/v1/applications/:id/conditions returns up to 100 conditions in one response and /api/v1/authorities returns every council at once; neither is paginated.
  • If your allowance runs out part-way through a page, the page is shorter than limit and next_cursor continues from the last record returned.

A complete paging loop

const KEY = process.env.PLANNING_SIGNAL_API_KEY;

async function* allApplications(params) {
  let cursor = null;
  do {
    const url = new URL("https://planningsignal.co.uk/api/v1/applications");
    for (const [k, v] of Object.entries({ ...params, limit: 100 })) url.searchParams.set(k, v);
    if (cursor) url.searchParams.set("cursor", cursor);
    const res = await fetch(url, { headers: { Authorization: `Bearer ${KEY}` } });
    const body = await res.json();
    if (!res.ok) throw new Error(`${res.status} ${body.code}`);
    yield* body.data;
    cursor = body.next_cursor;
  } while (cursor);
}

for await (const app of allApplications({ authority: "Camden", since: "2026-09-01" })) {
  console.log(app.id, app.reference);
}

Questions? Contact us, or see the help centre.