# NSW & Australian Council Planning Approvals & DA Tracker (`roomy_stove/aus-council-da-tracker`) Actor

Extracts NSW Development Application (DA) metadata from the official NSW Planning Portal Open Data API and enriches it with trade-lead classification.

- **URL**: https://apify.com/roomy\_stove/aus-council-da-tracker.md
- **Developed by:** [Wollemi](https://apify.com/roomy_stove) (community)
- **Categories:** Real estate, Lead generation, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $100.00 / 1,000 results

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## NSW & Australian Council Planning Approvals & DA Tracker

**Turn freshly lodged NSW Development Applications into trade-ready sales leads.** This Apify Actor pulls live Development Application (DA) metadata from the **official NSW Planning Portal Open Data API**, filters it to the councils, budgets and project types you care about, and tags every record with the **trades likely to win work** — pool builders, demolition crews, framers, concreters, electricians and more.

> Keywords: NSW Planning Portal API, Sydney Council DA Scraper, Development Applications, Property Leads, building leads, construction leads, DA tracking NSW.

***

### Why use it

When a DA is lodged, the clock starts. The builders, sub-contractors and buyers' agents who reach out first win the job. This actor answers one question for you every morning:

> *"Which development applications lodged near me this week need my trade — and are worth chasing?"*

- **Official source, daily fresh** — data comes straight from the keyless NSW Planning Portal Online DA Data API (DAs since 2018, updated daily).
- **Zero setup to first value** — no data API key required at all. It returns real, classified leads on the very first run.
- **Trade-lead classification** — every record includes `tradeOpportunities`, plus flags for pools, demolition and commercial work, and an explainable `lead_score`.
- **Pay only for what you use** — priced per dataset item, with a hard `maxResults` cap so a run can never surprise you.

***

### What you get (output schema)

Each dataset item looks like this:

```json
{
  "da_number": "PAN-664006",
  "council_lga": "Inner West Council",
  "lodgement_date": "2026-08-18",
  "status": "Additional Information Requested",
  "development_type": "Dwelling house, Alterations or additions to an existing building or structure",
  "location": {
    "street_address": "28A ROWNTREE STREET BALMAIN 2041",
    "suburb": "BALMAIN",
    "postcode": "2041",
    "lat": -33.855257425,
    "lng": 151.178232655
  },
  "valuation": { "estimated_cost_aud": 250000 },
  "raw_description": "Dwelling house; Alterations or additions to an existing building or structure; 1 new dwelling; 1 storey",
  "ai_structured_insights": {
    "projectCategory": "new dwelling",
    "isCommercial": false,
    "hasDemolition": false,
    "hasSwimmingPool": false,
    "tradeOpportunities": ["framing carpenter", "concreting", "bricklaying", "roofing", "electrical", "plumbing"],
    "classification_method": "rules",
    "confidence": 0.85
  },
  "lead_score": 74,
  "links": { "portal_url": "https://www.planningportal.nsw.gov.au" },
  "scraped_at": "2026-08-24T10:58:31.368Z"
}
```

***

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `councilLga` | string (enum) | `Inner West Council` | Council to track, or `ALL`. |
| `daysBack` | integer (1–90) | `7` | Lookback window from today. |
| `minCostAud` | integer | `100000` | Minimum estimated development cost. |
| `status` | string (enum) | `Any` | `Lodged`, `Under Assessment`, `Determined`, or `Any`. |
| `tradeFocus` | string (enum) | `Any` | Only return DAs relevant to one trade. |
| `enableLlmEnrichment` | boolean | `false` | Optional. Use `gpt-4o-mini` for trade inference on unrecognised development types. |
| `openaiApiKey` | string (secret) | — | Optional. Falls back to rules if absent. |
| `maxResults` | integer (≤1000) | `100` | Hard cap on billable items per run. |

#### How the classification works

The NSW open dataset is **structured** — it exposes a categorical `DevelopmentType` list and flags (new dwellings, subdivision, storeys) rather than free-text prose. So a **hybrid** pipeline keeps cost near zero while staying accurate and reliable:

1. **Structured fields** from the API (cost, development type, dwelling counts, subdivision flag) drive classification directly and deterministically.
2. **Deterministic rules** map those signals to a project category and a trade bundle, negation-aware for the rare cases prose appears.
3. **The LLM (`gpt-4o-mini`)** is optional and **off by default**. Because the data is already structured, rules do the heavy lifting; enable the LLM only if you want it to infer additional `tradeOpportunities` for `DevelopmentType` combinations the rules do not recognise. Every item records whether it was classified by `rules` or `llm`.

***

### Usage examples

#### cURL (Apify API)

```bash
curl -X POST "https://api.apify.com/v2/acts/roomy_stove~aus-council-da-tracker/run-sync-get-dataset-items?token=APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "councilLga": "Inner West Council",
    "daysBack": 7,
    "minCostAud": 100000,
    "tradeFocus": "Swimming Pool",
    "maxResults": 50
  }'
```

#### Python (requests)

```python
import requests

resp = requests.post(
    "https://api.apify.com/v2/acts/roomy_stove~aus-council-da-tracker/run-sync-get-dataset-items",
    params={"token": "APIFY_TOKEN"},
    json={
        "councilLga": "ALL",
        "daysBack": 14,
        "minCostAud": 500000,
        "tradeFocus": "Demolition",
        "maxResults": 100,
    },
    timeout=300,
)
resp.raise_for_status()
for da in resp.json():
    print(da["da_number"], da["lead_score"], da["ai_structured_insights"]["tradeOpportunities"])
```

#### Node.js (apify-client)

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

const client = new ApifyClient({ token: 'APIFY_TOKEN' });

const run = await client.actor('roomy_stove/aus-council-da-tracker').call({
    councilLga: 'City of Sydney',
    daysBack: 7,
    minCostAud: 250000,
    maxResults: 100,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(`Got ${items.length} DA leads`);
```

***

### Tips for recurring lead feeds

- **Schedule it.** Use Apify Schedules to run daily and stream new DAs into your CRM.
- **Integrate it.** Pipe the dataset into Zapier / Make / a webhook to auto-create leads.
- **Narrow with `tradeFocus`** so you only pay for leads you can act on.

***

### Legal, privacy & attribution

- **Data source & licence:** Data is sourced from the NSW Planning Portal Online DA Data API and is licensed **Creative Commons Attribution (CC-BY)**. Attribution: *"© State Government of NSW and NSW Department of Planning, Housing and Infrastructure."*
- **Privacy:** Individual home-owner names, personal phone numbers and private email addresses are stripped from output. Only public proposal metadata and corporate/business applicant entities are retained.
- **Copyright / IP:** This actor extracts **text and numeric metadata only**. It does **not** re-host or parse copyrighted architectural plans, engineering drawings or PDFs. Use the `portal_url` and `da_number` to view the official record.
- **Disclaimer:** This dataset is provided for **public informational metadata purposes only** and does not constitute legal, planning or financial advice. Always verify against the official NSW Planning Portal before acting.

# Actor input Schema

## `councilLga` (type: `string`):

Which NSW Local Government Area to track. Choose 'ALL' to scan every council in the lookback window.

## `daysBack` (type: `integer`):

How many days back from today to fetch lodged DAs.

## `minCostAud` (type: `integer`):

Only return DAs with an estimated development cost at or above this value. DAs with no cost figure are excluded when this is greater than 0.

## `status` (type: `string`):

Filter by application status. 'Any' returns all statuses.

## `tradeFocus` (type: `string`):

Only return DAs relevant to a specific trade, so you pay for leads you can actually act on. 'Any' returns everything.

## `enableLlmEnrichment` (type: `boolean`):

Optional, off by default. The NSW dataset is fully structured, so the deterministic rules classify it reliably without an LLM. Turn this on (and provide an OpenAI key) only if you want gpt-4o-mini to infer extra trade opportunities for development types the rules don't recognise. Falls back to rules-only if no key is provided.

## `openaiApiKey` (type: `string`):

Optional. Required only for LLM enrichment. Can also be provided via the OPENAI\_API\_KEY environment variable. Without it, the actor uses the deterministic rules classifier.

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

Hard cap on the number of billable dataset items returned per run. Protects against runaway costs.

## Actor input object example

```json
{
  "councilLga": "Inner West Council",
  "daysBack": 7,
  "minCostAud": 100000,
  "status": "Any",
  "tradeFocus": "Any",
  "enableLlmEnrichment": false,
  "maxResults": 100
}
```

# Actor output Schema

## `leads` (type: `string`):

Filtered, classified DA records in the default dataset.

## `runSummary` (type: `string`):

Fetched/filtered/charged counts and activation metrics.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("roomy_stove/aus-council-da-tracker").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("roomy_stove/aus-council-da-tracker").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 '{}' |
apify call roomy_stove/aus-council-da-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,roomy_stove/aus-council-da-tracker"
        }
    }
}

```

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/UQtoD6vsl34bfThhS/builds/AaPykYgWnvaGDXeJU/openapi.json
