Skip to Content
Hey Town Hall For Businessdocs

Recipes

Applications and hearings near a project

For a builder or developer: the development applications on a project's street, their stage, and the public hearings and notices that mention it.

View as Markdown

After this recipe you can hand your own language model the applications on your project's street and the hearings and notices that mention it, each with its link, and ask what could affect your schedule.

For: a builder or developer watching the blocks around a project.

Uses: GET /v1/jurisdictions/at, GET /v1/applications, GET /v1/meetings and GET /v1/search. Four requests: three records requests and one search.

Steps

  1. Find the town that governs the project from its coordinates.
  2. List the town's development applications and keep the ones on the project's street. Each has its folder number, kind and stage.
  3. List the town's public hearings since a date you choose.
  4. Search the town's agenda items and notices for the street, which finds the hearing notices that name it.
  5. Number each record with its link and hand the list to your language model.

The example is a project on Harbour Road in Port Ellery, a sandbox town. Its meetings run from May to October 2026, so SINCE is set inside that range; in production, use a date a few months back.

The code

Python 3.11 or later
import os

import requests

BASE = "https://data.heytownhall.com"
HEADERS = {"Authorization": f"Bearer {os.environ['HTD_KEY']}"}
PROJECT = {"lat": "49.05", "lng": "-124.1"}
STREET = "Harbour Road"
SINCE = "2026-07-01"
QUESTION = "What is proposed on Harbour Road, at what stage, and which hearings could affect a project there?"


def get(path, params):
    res = requests.get(f"{BASE}{path}", params=params, headers=HEADERS, timeout=30)
    body = res.json()
    if not res.ok:
        raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
    return body["results"]


town = next(j for j in get("/v1/jurisdictions/at", PROJECT) if j["kind"] == "municipality")
applications = [a for a in get("/v1/applications", {"jurisdiction": town["id"], "limit": 100}) if STREET.lower() in (a["address"] or "").lower()]
hearings = get("/v1/meetings", {"jurisdiction": town["id"], "kind": "public_hearing", "from": SINCE})
mentions = get("/v1/search", {"q": f'"{STREET}"', "jurisdiction": town["id"], "in": "agenda_items,posts", "from": SINCE, "limit": 20})

records = [(f"Application {a['folder_number']}, {a['type_label']} at {a['address']}: stage {a['stage']} since {a['stage_since']}", a["source_url"]) for a in applications]
records += [(f"Public hearing on {m['meeting_date']}: {m['title']}, {m['location']}", m["source_url"]) for m in hearings]
records += [(f"{h['date']}, {h['family'].replace('_', ' ')}: {h['title']}", h["source_url"]) for h in mentions]

prompt = "\n".join([
    QUESTION,
    f"Answer only from these records of {town['name']}. Cite each claim by its number and keep its link. If the records do not answer the question, say so.",
    "",
    *(f"[{i}] {text} ({url})" for i, (text, url) in enumerate(records, 1)),
])
print(prompt)  # Send this to your own language model.
JavaScript (Node 18 or later)
const BASE = 'https://data.heytownhall.com';
const headers = { Authorization: `Bearer ${process.env.HTD_KEY}` };
const PROJECT = { lat: '49.05', lng: '-124.1' };
const STREET = 'Harbour Road';
const SINCE = '2026-07-01';
const QUESTION = 'What is proposed on Harbour Road, at what stage, and which hearings could affect a project there?';

async function get(path, params) {
  const url = new URL(path, BASE);
  for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
  const res = await fetch(url, { headers });
  const body = await res.json();
  if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
  return body.results;
}

const town = (await get('/v1/jurisdictions/at', PROJECT)).find((j) => j.kind === 'municipality');
const applications = (await get('/v1/applications', { jurisdiction: town.id, limit: 100 })).filter((a) => (a.address ?? '').toLowerCase().includes(STREET.toLowerCase()));
const hearings = await get('/v1/meetings', { jurisdiction: town.id, kind: 'public_hearing', from: SINCE });
const mentions = await get('/v1/search', { q: `"${STREET}"`, jurisdiction: town.id, in: 'agenda_items,posts', from: SINCE, limit: 20 });

const records = [
  ...applications.map((a) => [`Application ${a.folder_number}, ${a.type_label} at ${a.address}: stage ${a.stage} since ${a.stage_since}`, a.source_url]),
  ...hearings.map((m) => [`Public hearing on ${m.meeting_date}: ${m.title}, ${m.location}`, m.source_url]),
  ...mentions.map((h) => [`${h.date}, ${h.family.replaceAll('_', ' ')}: ${h.title}`, h.source_url]),
];

const prompt = [
  QUESTION,
  `Answer only from these records of ${town.name}. Cite each claim by its number and keep its link. If the records do not answer the question, say so.`,
  '',
  ...records.map(([text, url], i) => `[${i + 1}] ${text} (${url})`),
].join('\n');
console.log(prompt); // Send this to your own language model.

What the sandbox prints

The prompt from the sandbox
What is proposed on Harbour Road, at what stage, and which hearings could affect a project there?
Answer only from these records of City of Port Ellery. Cite each claim by its number and keep its link. If the records do not answer the question, say so.

[1] Application REZ26-001, Rezoning at 412 Harbour Road: stage referral since 2026-07-01 (https://tracker.example/port-ellery/100012)
[2] Public hearing on 2026-10-04: Public Hearing, Council Chambers, City Hall (https://port-ellery.civicweb.example/meetings/900007)
[3] Public hearing on 2026-07-24: Public Hearing, Council Chambers, City Hall (https://port-ellery.civicweb.example/meetings/900003)
[4] 2026-10-06, post: Notice of public hearing: 412 Harbour Road (https://portellery.example/news/100001)
[5] 2026-10-04, agenda item: Rezoning application for 412 Harbour Road to permit a six-storey residential building (https://port-ellery.civicweb.example/meetings/900007)
[6] 2026-09-21, agenda item: Rezoning application for 412 Harbour Road to permit a six-storey residential building (https://port-ellery.civicweb.example/meetings/900006)
[7] 2026-08-07, agenda item: Rezoning application for 412 Harbour Road to permit a six-storey residential building (https://port-ellery.civicweb.example/meetings/900004)
[8] 2026-07-24, agenda item: Rezoning application for 412 Harbour Road to permit a six-storey residential building (https://port-ellery.civicweb.example/meetings/900003)

Going further

  • Road and utility work on the same street: GET /v1/works lists announced work with its days and streets.
  • A new application on the street: run this weekly and compare folder numbers with the last run.
  • More than one page of applications: follow next_cursor, as in Pagination and filters.

On this page