Skip to Content
Hey Town Hall Data docs

Start

Quickstart

Your first request against the sandbox, by REST and by MCP, and what changes when you go live.

View as Markdown

1. Get a sandbox key

Sign-ups are not open yet. Keys come from the dashboard once sign-ups open. Until then you can read these guides and the reference, and plan your build against the sandbox shapes.

A sandbox key starts with htd_test_. It reads the sandbox: Port Ellery, Marlow County and Tolland Township, three fictional governments in the same shapes as the live data. It is free, and it takes 10 requests a minute.

The dashboard shows a key once, when you create it. Keep it in your secret store, not in your code. The examples below read it from an environment variable, HTD_KEY.

2. Make a REST request

Ask for the open bids and tenders of Port Ellery, a sandbox town:

curl
curl "https://data.heytownhall.com/v1/bids?jurisdiction=port-ellery" \
  -H "Authorization: Bearer $HTD_KEY"
JavaScript
const res = await fetch('https://data.heytownhall.com/v1/bids?jurisdiction=port-ellery', {
  headers: { Authorization: `Bearer ${process.env.HTD_KEY}` },
});
const page = await res.json();
if (!res.ok) throw new Error(`${page.error.code}: ${page.error.message}`);
for (const bid of page.results) console.log(bid.title, bid.closes_at, bid.source_url);
Python
import os
import requests

res = requests.get(
    "https://data.heytownhall.com/v1/bids",
    params={"jurisdiction": "port-ellery"},
    headers={"Authorization": f"Bearer {os.environ['HTD_KEY']}"},
    timeout=30,
)
page = res.json()
if not res.ok:
    raise RuntimeError(f"{page['error']['code']}: {page['error']['message']}")
for bid in page["results"]:
    print(bid["title"], bid["closes_at"], bid["source_url"])

The sandbox answers with a page like this one. Port Ellery is fictional, and its links are under .example. as_of is when the API answered.

Sandbox answer (fictional)
{
  "query": { "jurisdiction": "port-ellery", "region": null, "country": null, "from": null, "to": null, "since": null, "limit": 25 },
  "as_of": "2026-11-13T18:00:00.000Z",
  "results": [
    {
      "id": "100009",
      "jurisdiction_id": "port-ellery",
      "country": "CA",
      "region": "BC",
      "title": "ITT 2026-06: Snow clearing services 2026-2028",
      "summary": "Sealed proposals are invited.",
      "published_at": "2026-10-11T16:00:00.000Z",
      "closes_at": "2026-11-21T22:00:00.000Z",
      "categories": ["procurement"],
      "first_seen_at": "2026-10-11T16:00:00.000Z",
      "source_url": "https://bids.example/port-ellery/100009",
      "read_at": "2026-10-30T16:00:00.000Z"
    },
    {
      "id": "100006",
      "jurisdiction_id": "port-ellery",
      "country": "CA",
      "region": "BC",
      "title": "RFP 2026-00: Eastside water main replacement",
      "summary": "Sealed proposals are invited.",
      "published_at": "2026-10-05T16:00:00.000Z",
      "closes_at": "2026-11-20T22:00:00.000Z",
      "categories": ["procurement"],
      "first_seen_at": "2026-10-05T16:00:00.000Z",
      "source_url": "https://bids.example/port-ellery/100006",
      "read_at": "2026-10-30T16:00:00.000Z"
    }
  ],
  "next_cursor": null,
  "environment": "sandbox"
}

Every list answers in this envelope. next_cursor is null because this is the last page. Pagination and filters explains the rest.

3. Connect your AI client

Give your AI client the MCP server's address and your key. Most clients that take a remote MCP server accept a block like this:

MCP settings
{
  "mcpServers": {
    "hey-town-hall-data": {
      "url": "https://data.heytownhall.com/mcp",
      "headers": { "Authorization": "Bearer htd_test_..." }
    }
  }
}

Then ask it a question about the sandbox, such as "Which tenders are open in Port Ellery?". It calls list_bids with jurisdiction set to port-ellery and reads the same two records. Connect the MCP server lists every tool.

4. Go live

Live keys are not open yet. They open once the rates are final. Until then a card can be saved in the dashboard, and a request with a live key answers 403 with the code live_not_open. Sandbox keys work now.

When your build works against the sandbox:

  1. Add a payment method in the dashboard.
  2. Create a live key. It starts with htd_live_.
  3. Swap it in for the sandbox key.

The paths, the parameters and the shapes stay the same. Answers say "environment": "live" and carry the real records. Live requests are billed at a flat rate per 1,000, and a failed request is never billed. Set a monthly spending cap before you start.

Coverage today is British Columbia. The United States is being added.

On this page