# Building Permits for 8 Canadian and US Cities (`openpermits/building-permits-canada-us`) Actor

Daily-refreshed building permits from eight official city open-data portals in one schema. Personal names removed. Includes a new-since-date mode for alert feeds.

- **URL**: https://apify.com/openpermits/building-permits-canada-us.md
- **Developed by:** [OpenPermits](https://apify.com/openpermits) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 permit records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Building Permits: Calgary, Edmonton, Toronto, Vancouver, Seattle, San Jose, Washington DC, Minneapolis

OpenPermits brings together building permits from eight official city open-data portals in one consistent schema. Records are prepared ahead of time, so a run starts fast and never depends on a city's website being up. Personal names are removed.

### What you get

One record per permit: permit number, type, work type, work tags (our own labels, see below), status, applied, issued and completed dates, site address, coordinates, declared project value and currency, housing units, neighbourhood, and the business name of the contractor or applicant when it is clearly a business. Every record carries `snapshot_updated_at` (when it was last refreshed from the city), `first_seen_at`, `last_changed_at`, the city's dataset link and the credit line its licence requires.

### Coverage and freshness

| City | Country | Currency | Source |
| --- | --- | --- | --- |
| Calgary | CA | CAD | City of Calgary open data |
| Edmonton | CA | CAD | City of Edmonton open data |
| Toronto | CA | CAD | City of Toronto open data |
| Vancouver | CA | CAD | City of Vancouver open data |
| Seattle | US | USD | City of Seattle open data |
| San Jose | US | USD | City of San José open data |
| Washington DC | US | USD | DC open data |
| Minneapolis | US | USD | City of Minneapolis open data |

- **Refreshed once a day.** Every record the city still returns is confirmed on each refresh, so `snapshot_updated_at` is the time of the latest refresh that confirmed the record, the same for all records in a refresh. `last_changed_at` moves only when the permit itself changes (status, dates, value and so on).
- The history kept is the last 365 days of permit activity; the exact window start is in each snapshot.
- Coverage starts on the Actor's first refresh date. Cities publish at their own pace, so a permit can appear a day or more after the city issued it.
- Which permits a city includes differs (Toronto: active permits; San Jose: active permits; others: all building permits the city publishes). Fields a city does not publish are `null`.
- **Edmonton permit numbers:** Edmonton's open data leaves its own `permit_number` column empty, so for Edmonton `permit_number` is the city's row identifier (for example `1-253374239`). If the city starts filling its column, that value is used instead.

### Work tags

`work_tags` is a list of labels from a fixed set: `new_building`, `addition`, `interior_renovation`, `demolition`, `solar`, `pool`, `signage`, `change_of_use`, `roofing`, `deck_or_porch`, `basement`, `fence_or_wall`, `hvac`, `electrical`, `plumbing`, `fire_protection`. They are worked out from each city's permit type, work type and work description by keyword rules, and only our own labels are output, never the city's text. A permit can have several tags or none (an empty list means no rule matched, not that nothing was built). The rules are keyword-based and approximate. In a hand-checked random sample of 104 permits across the eight cities, about 90% had exactly the right set of tags; about 93% of the tags given were right and about 98% of the tags that applied were found. Accuracy varies by city (11 to 13 of 13 sampled permits exactly right in every city), and `interior_renovation` is the most often over-applied, because cities use "alteration" for exterior and site work too. Treat tags as a search aid, not a verified classification; the city's own `permit_type` and `work_type` remain in every record.

### Alert mode: new since a date

Set **Changed or added since** to a date and the run returns only permits added to the data, or changed, on or after that date. Schedule the Actor daily with yesterday's date to receive a feed of new permits. When a permit is first seen, its change date is its own latest permit date, so a first run returns recent permits, not the whole backlog.

### Input

- **Cities**: one or more of the eight above (default: Calgary, which publishes status, value, coordinates and, for many permits, the contractor).
- **Issued or applied on or after / on or before**: date range on the permit's issued date (applied date if not yet issued). Default: the last 30 days.
- **Changed or added since**: alert mode, see above. When set, the date range above is ignored.
- **Contractor present:** keep only permits that show a contractor business. A contractor that could be a person is withheld, so those permits are dropped by this filter.
- **Minimum declared value**, **Text contains** (matches permit type, work type, work tags and business names; for example "solar" or "demolition").
- **Maximum records**: stop after this many. **Free plan: at most 100 records per run**, whatever you enter. The run still finishes successfully, and the status message says the cap applied.

### Pricing

**Pricing:** US$3.50 per 1,000 permits. For example, 500 permits cost US$1.75. Use "changed since" mode to pay only for new or updated permits.

Pay per event: a small charge each time a run starts, plus a charge for each permit record written to the dataset. On the pricing line, Apify shows the event title "Permit record" in its plural form, "Permit records".

### Changelog

- Oct 2026: price lowered from US$10 to US$3.50 per 1,000 results.

### Personal data

City permit files can include owner, applicant and contractor names, some of them people. This Actor never outputs owner names, contact details, or applicant and contractor addresses. A contractor or applicant name is shown only when it carries positive evidence of a business (for example a company suffix or a trade word) and shows no sign of a personal name. Otherwise it is withheld and counted in `party_names_withheld`. Free-text work descriptions are not published, because they often name engineers, architects, inspectors and owners and cannot be scrubbed reliably; use permit type and work type instead. Site addresses are shown as each city publishes them. Some trade names that begin with a family name will pass; if you spot a personal name, report it in the Issues tab and the affected city's data will be reviewed.

### Data licences and credits

Each city publishes its data under its own open licence. The required credit line is in every record's `source_attribution` field, with the licence link in `licence_url`. Examples: "Contains information licensed under the Open Government Licence – Vancouver." and "Contains information licensed under the Open Government Licence – City of Calgary." Please keep these credits if you republish the data. The data comes with no warranty from the cities or from this Actor.

### Output example

```json
{
  "permit_id": "calgary:BP2026-19782",
  "city": "calgary",
  "permit_number": "BP2026-19782",
  "work_tags": ["new_building"],
  "issued_date": "2026-10-15",
  "address": "298 CORNER GLEN GD NE",
  "declared_value": 451819.65,
  "currency": "CAD",
  "contractor_business": "WAVE HOMES",
  "snapshot_updated_at": "2026-10-18T21:41:33Z"
}
```

### If the live feed is unavailable

The Actor then serves a bundled baseline snapshot instead of failing, and the status message says so and shows its `snapshot_updated_at`.

# Actor input Schema

## `cities` (type: `array`):

Cities to read. Choose from calgary, edmonton, san_jose, seattle, toronto, vancouver, washington_dc, minneapolis.

## `issuedFrom` (type: `string`):

Earliest permit date (issued date, or applied date when not yet issued), as YYYY-MM-DD. Default: the last 30 days. Ignored when 'Changed or added since' is set.

## `issuedTo` (type: `string`):

Latest permit date, as YYYY-MM-DD. Optional.

## `sinceDate` (type: `string`):

Return only permits added to the data, or changed, on or after this date (YYYY-MM-DD). Schedule the Actor daily with yesterday's date to get an alert feed. Retained history is up to 365 days.

## `minDeclaredValue` (type: `integer`):

Only permits with a declared value at or above this amount, in the city's currency.

## `contractorPresent` (type: `boolean`):

Only permits that show a contractor business name. Names that could belong to a person are withheld from every record, so a permit whose contractor is a person counts as having none. Some cities publish no contractor at all (see the coverage table).

## `textContains` (type: `string`):

Keep permits whose permit type, work type, work tag or business name contains this text (case-insensitive), for example 'solar' or 'demolition'.

## `maxResults` (type: `integer`):

Stop after this many records. Free plan: at most 100 per run, whatever you enter.

## Actor input object example

```json
{
  "cities": [
    "calgary"
  ],
  "contractorPresent": false,
  "maxResults": 10
}
```

# Actor output Schema

## `permits` (type: `string`):

Permit records returned by this run, in the Permits table view.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "cities": [
        "calgary"
    ],
    "maxResults": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("openpermits/building-permits-canada-us").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "cities": ["calgary"],
    "maxResults": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("openpermits/building-permits-canada-us").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "cities": [
    "calgary"
  ],
  "maxResults": 10
}' |
apify call openpermits/building-permits-canada-us --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,openpermits/building-permits-canada-us"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/MsjHBD5NPUq4Z5wiu/builds/6ozp5b7l3nXN8kXgE/openapi.json
