# Quickstart

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

## 1. Get a sandbox key

**Sign-ups are not open yet.** Keys come from the [dashboard](https://data.heytownhall.com/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:

```bash
curl "https://data.heytownhall.com/v1/bids?jurisdiction=port-ellery" \
  -H "Authorization: Bearer $HTD_KEY"
```

```js
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.

```json
{
  "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](https://data.heytownhall.com/docs/pagination) 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:

```json
{
  "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](https://data.heytownhall.com/docs/mcp) 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](https://data.heytownhall.com/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](https://data.heytownhall.com/docs/billing) before you start.

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