Skip to Content
Hey Town Hall For Businessdocs

Recipes

What councils will decide on an issue

For an advocacy group or membership platform: the agenda items on one issue across a province, when each comes up, and who sits on each council.

View as Markdown

After this recipe you can hand your own language model every agenda item on one issue across a province, with the meeting date and the members of each council, each with its link, and ask it to write a briefing for your members.

For: an advocacy group, an industry association or a membership platform.

Uses: GET /v1/agenda-items and GET /v1/officials. One records request for the agenda items, and one for each town's officials.

Steps

  1. List the agenda items tagged with your issue's topic, across the province, from a date on. GET /v1/topics lists the topic ids.
  2. For each town in the results, list its elected officials.
  3. Number each record with its link and hand the list to your language model.

An agenda is posted a few days before its meeting, so in production set FROM to today's date to see what is coming. The sandbox's meetings end in October 2026, so the example uses 2026-10-01.

The code

Python 3.11 or later
import os

import requests

BASE = "https://data.heytownhall.com"
HEADERS = {"Authorization": f"Bearer {os.environ['HTD_KEY']}"}
TOPIC = "short_term_rentals"
FROM = "2026-10-01"
QUESTION = "For our members who rent out homes: which councils will discuss short-term rentals, when, and who sits on each council?"


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"]


items = get("/v1/agenda-items", {"topic": TOPIC, "region": "BC", "from": FROM, "limit": 100})
records = [(f"{i['meeting_date']}, {i['jurisdiction_id']}, item {i['number']} {i['title']}. {i['plain_summary'] or i['recommendation']}", i["source_url"]) for i in items]
for town in sorted({i["jurisdiction_id"] for i in items}):
    members = get("/v1/officials", {"jurisdiction": town, "limit": 100})
    names = ", ".join(f"{m['title']} {m['name']}" for m in members)
    records.append((f"Elected officials of {town}: {names}", members[0]["source_url"] if members else ""))

prompt = "\n".join([
    QUESTION,
    "Answer only from these records. Cite each claim by its number and keep its link. If the records do not answer the question, say so.",
    "",
    *(f"[{n}] {text} ({url})" for n, (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 TOPIC = 'short_term_rentals';
const FROM = '2026-10-01';
const QUESTION = 'For our members who rent out homes: which councils will discuss short-term rentals, when, and who sits on each council?';

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 items = await get('/v1/agenda-items', { topic: TOPIC, region: 'BC', from: FROM, limit: 100 });
const records = items.map((i) => [`${i.meeting_date}, ${i.jurisdiction_id}, item ${i.number} ${i.title}. ${i.plain_summary ?? i.recommendation}`, i.source_url]);
for (const town of [...new Set(items.map((i) => i.jurisdiction_id))].sort()) {
  const members = await get('/v1/officials', { jurisdiction: town, limit: 100 });
  const names = members.map((m) => `${m.title} ${m.name}`).join(', ');
  records.push([`Elected officials of ${town}: ${names}`, members[0]?.source_url ?? '']);
}

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

What the sandbox prints

The prompt from the sandbox
For our members who rent out homes: which councils will discuss short-term rentals, when, and who sits on each council?
Answer only from these records. Cite each claim by its number and keep its link. If the records do not answer the question, say so.

[1] 2026-10-10, tolland-township, item 4. Short-term rental licensing bylaw amendments. Staff ask council to consider: short-term rental licensing bylaw amendments. (https://tolland-township.primegov.example/meetings/900023)
[2] 2026-10-07, marlow-county, item 5. Short-term rental licensing bylaw amendments. Staff ask the board to consider: short-term rental licensing bylaw amendments. (https://marlow-county.escribe.example/meetings/900015)
[3] Elected officials of marlow-county: Chair Helena Brightwater, Director Alice Penhallow, Director Owen Castellan, Director Priya Vantongeren, Director Tomas Ferrell (https://marlowcounty.example/council)
[4] Elected officials of tolland-township: Councillor Bea Lindqvist, Councillor Hal Moreton, Councillor Nora Quillfeather, Councillor Sam Okonkwo-Reyes, Mayor Gil Ashdown (https://tolland.example/council)

Going further

On this page