# ACNC Charity Register Delta (`titan_coder/acnc-charity-register-delta`) Actor

Watches the official ACNC Australian charity register by state and reports only real changes: new registrations and confirmed deregistrations. Weekly cadence, not continuous. For compliance and donor due-diligence teams. Free when nothing changes.

- **URL**: https://apify.com/titan\_coder/acnc-charity-register-delta.md
- **Developed by:** [Radu Furtuna](https://apify.com/titan_coder) (community)
- **Categories:** Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 charity registration change delivereds

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

## ACNC Charity Register Delta

Watches the official **ACNC Register of Australian charities** (data.gov.au, package `acnc-register`) by
state/territory and reports only genuine changes since your last check: a new charity registration, or a
confirmed deregistration. Free when nothing changes — you pay only for delivered events.

Built for compliance teams, grant-makers and donors who need to know when a charity in their book of
business gets newly registered — or drops off the register — without re-downloading and diffing a
14+ MB CSV every week themselves.

### Honest limits — read this before you rely on the signal

- **The source itself updates weekly**, not continuously. This is not a real-time feed.
- **The register file carries no status field at all.** ACNC does not publish "revoked"/"active" — a
  deregistered charity simply stops appearing in the next weekly file. This actor's entire signal is
  therefore *presence/absence of an ABN between two snapshots*, not a stated reason for the change. There
  is no "reason for deregistration" field to report, because the source does not provide one.
- **~0.9% of rows have no usable ABN** (mostly Private Ancillary Funds that are legitimately not required
  to publish one). Those rows have no stable identity and are excluded from delta detection entirely —
  they never appear as `appeared`/`disappeared`, in any watch, including `state: "ALL"`.
- **The source's own `State` field is free text, not a clean enum**: alongside the 8 canonical
  state/territory codes it contains inconsistent case, stray spaces, and outright garbage entries, plus
  \~10% blank values. Only rows whose `State` normalizes exactly to one of NSW/VIC/QLD/WA/SA/TAS/ACT/NT are
  matched by a state-specific watch; everything else (including blanks) is only visible under
  `state: "ALL"`.
- Deregistration (`disappeared`) detection needs **two consecutive complete weekly snapshots** without
  the ABN (two-strike) — a single missing week only produces an unpaid `removal_candidate` row. Given the
  weekly cadence, a confirmed `disappeared` event lags the real-world change by roughly two weeks.
- If this run's CSV download looks truncated, corrupted, or suspiciously short (`coverage.
  registerSnapshotIncompleteCount > 0`), **no baseline is initialized and no deregistration detection
  runs at all** for the affected watches — a partial snapshot never gets silently treated as "nothing
  changed" or "everyone disappeared." Nothing is billed in that case. Completeness is checked two ways:
  the downloaded byte count against the response's own `Content-Length`, **and**, independently, against
  the file `size`/`md5` that data.gov.au's CKAN metadata API (`package_show`) reports for this resource
  *before* the download starts — a cleanly-truncated file can otherwise report its own correct
  `Content-Length` for a partial body and pass the first check alone.
- This is an **informational monitor of a public register**, not a real-time charity-status verification
  service. Always confirm directly with ACNC (acnc.gov.au/charity) before making a business decision.

### How it works

1. First run per watch (`monitorId` + `watchId`) records a private baseline of every ABN currently
   matching your `state`/`charitySize` filter — no rows, no charge. A baseline is only recorded from a
   **complete** weekly snapshot (see above).
2. Every later run re-downloads the register and classifies each ABN in your filter:
   - **`appeared`** — a new ABN registration matching your filter appeared;
   - **`removal_candidate`** — an ABN you'd seen before is missing from this run's snapshot for the
     first time in a row. Delivered as an informational row with `billed: false` — not charged, and not
     proof of anything (a single missing point is indistinguishable from a transient download hiccup);
   - **`disappeared`** — the same ABN is missing for the **second consecutive** complete weekly snapshot
     **and is also absent from the full register**, not just from your filter. Billed: the charity's
     registration was very likely cancelled.
   - **`left_filter`** — the ABN is still present in the **full** ACNC register this run, but no longer
     matches *this watch's* `state`/`charitySize` filter (typically the source's own `State` or
     `Charity_Size` field changed). This is **not** a deregistration signal — the organisation did not
     leave ACNC, it just left your filter's slice of it. Delivered immediately (no two-strike wait) as an
     informational row with `billed: false` — never charged, and never confused with `disappeared`. If the
     same ABN later moves back into your filter (e.g. `State` changes back), that return is symmetric and
     **also free**: no row, no charge — it never left the full register, so there is nothing new to report.
3. Only `appeared`/`disappeared` are billed, one PPE event each (`charity-registration-changed`).
   `removal_candidate` and `left_filter` are always informational, never billed.
4. **Attribute changes on an ABN that stays registered and stays inside your filter are never reported as
   an event.** This actor is a discovery/membership monitor, not an attribute-change tracker: if a
   charity's name, town, or other field changes while its ABN keeps matching your filter, no row is
   produced for that change (the source itself carries no revision history for it either). Only presence/
   absence relative to your filter and to the full register is ever reported.

### Input

- `monitorId` — name of this monitor's durable history (`a-z0-9-`, up to 40 chars). Re-run with the same
  `monitorId` to continue the same history; a new value starts a fresh baseline.
- `watches` — 1-20 objects `{"watchId", "state", "charitySize"?}`. `state` is one of NSW/VIC/QLD/WA/SA/
  TAS/ACT/NT, or `ALL` for the whole register with no state filter. `charitySize` is an optional exact
  match against the register's own `Charity_Size` field (`Small`/`Medium`/`Large`) — leave unset to watch
  every size. Once a `watchId` is used, its `state`/`charitySize` are locked — reusing the id with a
  different filter fails the run instead of silently mixing histories. Add new `watchId`s any time under
  the same `monitorId`.
- `notifyOn` / `webhookUrl` — optional HTTPS webhook receiving a JSON digest of billed changes.

### Output

- **Dataset** — every row this run produced (`watchId`, `abn`, `status`, `charityLegalName`,
  `stateRaw`/`stateNorm`, `postcode`, `country`, `charitySize`, `registrationDate`, `townCity`,
  `eventId`, `billed`). `status` is one of `appeared`/`disappeared`/`removal_candidate`/`left_filter`. A
  durable named dataset holds the append-only history of everything ever delivered under this
  `monitorId`; the default (run) dataset mirrors what this run delivered plus a `run_summary` row.
  - `billed` on a `removal_candidate` or `left_filter` row is always `false` — never charged, by design.
  - `billed` on an `appeared`/`disappeared` row is written as `null`. The row is saved to the dataset
    *before* its charge is attempted (we never charge before delivery is confirmed — see "Guarantee"
    below), so the final charge outcome isn't known yet at write time. To find out whether one specific
    row was actually charged, match its `eventId` against this run's Pay-Per-Event charged-events ledger
    in the Apify Console. For the run-level total, use `recordsDelivered`/`recordsBilled` in `coverage`
    and the unbilled count in `digest`.
- **Key-value store** — `coverage` (per-watch status/reason/matchedCount/candidateCount, requested/
  attempted/succeeded/failed, records delivered/billed, total register rows seen and how many had no
  usable ABN this run; `registerSnapshotIncompleteCount > 0` means the weekly download was rejected as
  incomplete for the affected watches — see "Honest limits") and `digest` (a short human-readable
  summary, which also carries this partial-coverage warning even when no billable change occurred).

### Guarantee

**At-most-once delivery and billing, not exactly-once.** A crash right after a right-to-bill is granted
can lose that one event (it will not be redelivered), but you will never be charged twice for the same
underlying change. See `ROADMAP.md` for the full claim protocol and its boundaries.

### Durable dataset vs. default dataset

The **durable named dataset** is a journal of delivery attempts across every run of this `monitorId` — it
is not itself proof of payment (a row can in rare crash scenarios be written without a confirmed charge).
The **default (per-run) dataset** mirrors this run's rows plus one `run_summary` record and is what you
see in the Apify Console run view. Reconcile billing against `coverage`, not row count alone.

# Actor input Schema

## `monitorId` (type: `string`):

Name of this monitor's durable history (a-z, 0-9, dash; up to 40 chars).

## `watches` (type: `array`):

1-20 objects: {"watchId": "act-charities", "state": "ACT", "charitySize": "Small" (optional)}. state is one of NSW, VIC, QLD, WA, SA, TAS, ACT, NT, or ALL (the whole register, no state filter - includes the ~10% of rows where the source's own State field is blank or free-text noise). charitySize is an optional exact match against the register's own Charity\_Size field (Small, Medium or Large) - leave empty to watch every size, including charities where the source leaves this field blank. New watches can be added later under the same monitorId.

## `notifyOn` (type: `string`):

new\_alerts — post the webhook only when new billed changes were delivered; always — post it every run; never — do not call webhookUrl at all.

## `webhookUrl` (type: `string`):

Optional. Receives a digest of delivered (billed) charity registration/deregistration changes as JSON. HTTPS only.

## Actor input object example

```json
{
  "monitorId": "my-acnc-watch",
  "watches": [
    {
      "watchId": "act-charities",
      "state": "ACT"
    }
  ],
  "notifyOn": "new_alerts"
}
```

# Actor output Schema

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

Every row this run produced. Key fields: watchId, abn (source-native id, 11 digits), status (appeared|disappeared|removal\_candidate|left\_filter), charityLegalName, stateRaw/stateNorm, postcode, country, charitySize, registrationDate, townCity. removal\_candidate rows carry billed:false (unpaid early signal from a single missing weekly snapshot, not yet confirmed - confirmed only after two consecutive weekly snapshots miss the ABN AND it is also absent from the full register). left\_filter rows also carry billed:false: the ABN is still present in the full ACNC register this run, it just stopped matching this watch's state/charitySize filter - not a deregistration signal. Informational monitor of a public register - not a real-time charity-status verification service, and not an attribute-change tracker (name/address edits on a charity that stays registered and stays inside your filter are never reported).

## `coverage` (type: `string`):

What this run actually covered and what it charged for: per-watch status/reason/matchedCount/candidateCount, records delivered and records billed, requested/attempted/succeeded/failed watch counts, total register rows seen and how many had no usable ABN this run. registerSnapshotIncompleteCount > 0 means the weekly CSV download looked truncated or corrupted this run - no baseline and no deregistration detection happened for the affected watches, and nothing was billed for them.

## `digest` (type: `string`):

A short human-readable summary of what this run found, written every run.

# 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 = {
    "monitorId": "my-acnc-watch",
    "watches": [
        {
            "watchId": "act-charities",
            "state": "ACT"
        }
    ],
    "notifyOn": "new_alerts"
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/acnc-charity-register-delta").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 = {
    "monitorId": "my-acnc-watch",
    "watches": [{
            "watchId": "act-charities",
            "state": "ACT",
        }],
    "notifyOn": "new_alerts",
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/acnc-charity-register-delta").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 '{
  "monitorId": "my-acnc-watch",
  "watches": [
    {
      "watchId": "act-charities",
      "state": "ACT"
    }
  ],
  "notifyOn": "new_alerts"
}' |
apify call titan_coder/acnc-charity-register-delta --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,titan_coder/acnc-charity-register-delta"
        }
    }
}
```

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/s0rdivqqz3rIUkfG4/builds/HI7fiZ8ebDOP9K8fQ/openapi.json
