# NSW Development Application Leads — New lodgements (`spider355/nsw-da-leads`) Actor

Filtered, contact-redacted extract of newly lodged NSW development applications by council, status, and trade. Not affiliated with or endorsed by the NSW Department of Planning, Housing and Infrastructure or the NSW Planning Portal. Exact CC BY 4.0 attribution is in the README.

- **URL**: https://apify.com/spider355/nsw-da-leads.md
- **Developed by:** [Mitchell](https://apify.com/spider355) (community)
- **Categories:** Lead generation, Real estate, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.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?

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

## NSW DA Leads (Apify Actor)

New-lodged monitor for development applications published through the NSW Planning Portal **DA Application Tracker**. It POSTs a fixed JSON body to the public tracker endpoint, then applies optional council and trade filters.

**Not affiliated with, and not endorsed by, the NSW Department of Planning, Housing and Infrastructure or the NSW Planning Portal.** Do not use NSW Government logos. Full application documents live on the [NSW Planning Portal](https://www.planningportal.nsw.gov.au/), not in this dataset.

**Data source (only):** `POST https://api.apps1.nsw.gov.au/eplanning/data/v0/DAApplicationTracker`

This Actor does **not** call `viewApplication` / OnlineDA (those paths were 404 or required unknown parameters). There is **no** `feedUrl` or other URL input.

### Output fields

Each dataset item is an allowlist. Applicant, owner, email, phone, contact, and person-name fields are never emitted (they are dropped if a future response adds them). Emails and obvious phone numbers inside free-text fields (`fullAddress`, development type, council, status, application type) are replaced with `[redacted]`. `CostofDevelopment`, dwelling counts, and storeys are not on this endpoint and are not output.

| Field | Description |
|-------|-------------|
| `planningPortalAppNumber` | `PLANNING_PORTAL_APP_NUMBER` |
| `councilName` | `COUNCIL_NAME` |
| `status` | `STATUS` |
| `typeOfDevelopment` | `TYPE_OF_DEVELOPMENT` |
| `applicationType` | `APPLICATION_TYPE` |
| `lodgementDate` | `LODGEMENT_DATE` |
| `determinationDate` | `DETERMINATION_DATE` when the tracker sends it, otherwise `null` |
| `fullAddress` | `FULL_ADDRESS` as published |
| `longitude` / `latitude` | GeoJSON Point coordinates (EPSG:4326) when `geometry` is present |
| `attribution` | `NSW Department of Planning, Housing and Infrastructure` |
| `licence` | `CC BY 4.0` |
| `source` | Dataset page on the Planning Portal open data site |

### Inputs

| Input | Notes |
|-------|--------|
| `council` | Optional substring(s) on council name. Multiple = OR. Resolved to **exact** `CouncilDisplayName` values (the API rejects a partial or a comma-separated list). |
| `status` | Optional tracker status string or list, sent as one comma-separated `ApplicationStatus`. **If omitted, this Actor sends `All`.** The API itself defaults to **On Exhibition** when the field is left out; that default is not used here. |
| `tradePreset` | Optional: `solar`, `demolition`, `pool`, `dwelling`, `commercial`, `subdivision`. Case-insensitive substring on `TYPE_OF_DEVELOPMENT`. Multiple = OR. |
| `keywords` | Optional free-text substrings, matched the same way. OR with `tradePreset`. |
| `days` | Lodgement window when dates are omitted. Default **7**: `LodgementDateFrom` = Australia/Sydney today minus `days`, `LodgementDateTo` = today. |
| `lodgedFrom` / `lodgedTo` | Optional `YYYY-MM-DD` pair. Overrides `days`. |
| `maxItems` | Default **200**, cap **2000**. |

Council and status narrow the tracker query. Trade presets and keywords are applied **locally** because the tracker ignored `DevelopmentType` on the probe. A run stops after `maxItems` matches, **5,000** scanned features, or **120** tracker POSTs (including the council-list probe), whichever comes first.

#### Example input

```json
{
  "council": "Sydney",
  "tradePreset": ["solar", "dwelling"],
  "keywords": "shed",
  "days": 7,
  "maxItems": 50
}
```

### Request body that worked

Probed 3 Oct 2026 (PT) against the fixed endpoint. A successful new-lodged body looks like:

```json
{
  "PageSize": 3,
  "PageNumber": 1,
  "ApplicationStatus": "All",
  "LodgementDateFrom": "2026-09-26",
  "LodgementDateTo": "2026-10-03"
}
```

`CouncilDisplayName` is added only when a council filter resolves to one exact display name (one series of pages per council). Example: `"CouncilDisplayName": "Council of the City of Sydney"`.

#### Fields the API refused or ignored

| Sent | Result |
|------|--------|
| `ApplicationStatus` string, including `All` and `"Under Assessment, Determined"` | Filters. `All` selects every status in `applicationStatusList`. |
| `ApplicationStatus` JSON array | **HTTP 400** |
| `ApplicationStatus` omitted | API default **On Exhibition** only (this Actor does not do that) |
| `ApplicationStatus` `""` | `TotalCount` 0 |
| `LodgementDateFrom` / `LodgementDateTo` | Filters `LODGEMENT_DATE` |
| `fromDate` / `toDate`, `lodgementFrom` / `lodgementTo` | Ignored |
| `CouncilDisplayName` exact | Filters that council |
| `CouncilDisplayName` comma-separated or partial `"Sydney"` | `TotalCount` 0, nothing selected |
| `CouncilName`, `council`, `councilName`, `COUNCIL_NAME`, `Council`, `SelectedCouncil` | Ignored |
| `APPLICATION_STATUS` | Ignored |
| `DevelopmentType` | Ignored (trade filter is local) |
| `ApplicationType` | Accepted by the API but **not an Actor input** |

`PageSize` and `PageNumber` page the `features` array. Responses also include large lookup lists (`councilList`, `developmentTypeList`, and others). Body reads are capped at **8 MiB** before parse. Non-JSON, non-200, and an unexpected shape fail the run.

### Local development

Requirements: Node.js 18+.

```bash
cd /team/projects/01-apify-scrapers/src/nsw-da-leads
npm install
npm test          # offline fixture tests (no network)
npm run smoke     # live tracker smoke, PageSize small, at most 3 redacted rows
```

`npm start` expects the Apify runtime. Prefer `npm test` and `npm run smoke` here. Do not `apify push` from this MVP unless the owner asks.

### Pricing (not set in this repo)

Target Store price **when published later**: pay-per-result **US$0.01 per result**. This MVP does not call the Apify pricing API and does not set a Store price.

### Privacy

Rows are public planning-tracker fields plus licence attribution. This Actor does not add applicant, owner, email, phone, or contact details. `fullAddress` is the portal's published address string. Processing on Apify is the owner's deployment choice. The creator does not sell inputs for unrelated purposes.

### Attribution

© State Government of NSW and NSW Department of Planning, Housing and Infrastructure 2021. This data is provided under a Creative Commons Attribution 4.0 licence http://creativecommons.org/licenses/by/4.0. Attribute NSW Department of Planning, Housing and Infrastructure.

This Actor emits a filtered, contact-redacted extract of tracker fields, not the original feed. It is **not affiliated with, and not endorsed by,** the NSW Department of Planning, Housing and Infrastructure or the NSW Planning Portal. Do not use NSW Government logos.

### Known limits

- Tracker fields only. No cost, dwelling count, storeys, or document download.
- Date filter is whatever `LodgementDateFrom` / `LodgementDateTo` do on the tracker (a probe row lodged inside the window was returned; inclusivity of the end date is the API's behaviour).
- Wide windows plus a rare trade keyword can hit the 5,000-feature scan cap before `maxItems`.
- Be polite: identifying User-Agent, timeout, modest page size.

# Actor input Schema

## `council` (type: `string`):

Optional. Case-insensitive substring matched to council display names (e.g. Sydney, Blacktown City Council). Comma-separated string or JSON array. Multiple = OR. Resolved to exact CouncilDisplayName values the tracker accepts.

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

Optional. One or more tracker statuses (comma-separated or JSON array), sent as a comma-separated ApplicationStatus string. If omitted, this Actor sends ApplicationStatus "All" so the date window is not limited to the API default (On Exhibition).

## `tradePreset` (type: `array`):

Optional. Substring match on TYPE\_OF\_DEVELOPMENT (case-insensitive). Multiple = OR. The tracker ignores DevelopmentType, so this filter runs locally.

## `keywords` (type: `string`):

Optional free-text substrings matched on TYPE\_OF\_DEVELOPMENT (case-insensitive), same as trade presets. Comma-separated or JSON array. OR with tradePreset.

## `days` (type: `integer`):

Used when lodgedFrom and lodgedTo are omitted. LodgementDateFrom = Australia/Sydney today minus this many days; LodgementDateTo = today. Default 7.

## `lodgedFrom` (type: `string`):

Optional inclusive lodgement start. Must be paired with lodgedTo. Overrides days.

## `lodgedTo` (type: `string`):

Optional inclusive lodgement end. Must be paired with lodgedFrom. Overrides days.

## `maxItems` (type: `integer`):

Cap on dataset items. Default 200, maximum 2000.

## Actor input object example

```json
{
  "days": 7,
  "maxItems": 200
}
```

# Actor output Schema

## `results` (type: `string`):

Filtered, contact-redacted NSW development application records written to the default dataset.

# 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("spider355/nsw-da-leads").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("spider355/nsw-da-leads").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 spider355/nsw-da-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,spider355/nsw-da-leads"
        }
    }
}
```

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/R3f7ZNutOWNLYOVMM/builds/hddB7dd1hqWSg5cHZ/openapi.json
