DEV Community

How to Get Canadian Charity Data by API (2026)

Canada has about 85,000 registered charities, and the Canada Revenue Agency (CRA) publishes the whole list as open data - legal names, mailing addresses, designations, categories, websites, and the directors/officers of each one. It's a huge, clean, national dataset that's perfect for prospecting, sector research, or due diligence on a specific charity.

The only friction: it ships as a set of separate CSVs you have to join yourself, keyed by Business Number (BN). This post shows how to pull it programmatically in 2026 - the files, the join, a query API, and a hosted option you can call from code or an AI agent.

What's in the data

The CRA "List of charities" open data lives on Canada's Open Government Portal under the Open Government Licence. It's compiled from each charity's annual T3010 return and refreshed yearly. It comes as ~20 CSVs; the ones that matter for a contact directory are three:

  • Identification - BN, legal name, operating name, full mailing address, city, province, postal code, designation, category (~84k rows).
  • Charity Contact Web Addresses - website URL by BN (~34k rows).
  • Directors/Officers - every director/officer by BN, with position and start/end dates (~570k rows).

Two small code lists you'll want to decode:

  • Designation: A = Public foundation, B = Private foundation, C = Charitable organization.
  • Category: 4-digit codes like 0210 (Foundations), 0100 (Core health care), 0180 (Animal welfare) - the full map is in the CRA "Codes lists" PDF on the same page.

Option 1: query without downloading (CKAN datastore API)

The portal runs CKAN, so each resource is queryable over HTTP - handy for a quick lookup without pulling the whole file. Every resource has an ID (find it on the dataset page):

import requests

IDENT = "694fdc72-eae4-4ee0-83eb-832ab7b230e3"  # 2024 Identification resource
r = requests.get(
    "https://open.canada.ca/data/api/action/datastore_search",
    params={"resource_id": IDENT, "q": "foundation", "limit": 5},
    timeout=60,
)
for row in r.json()["result"]["records"]:
    print(row["Legal Name"], "|", row["City"], row["Province"])

Great for spot lookups. For a full dataset build, download the CSVs instead.

Option 2: download and join by BN

Grab the three CSVs and join them on BN - identification is the backbone, the other two enrich it:

import pandas as pd

BASE = "https://open.canada.ca/data/dataset/80c00cdb-1358-415c-bb8b-0de7f12675b8/resource"
ident = pd.read_csv(f"{BASE}/694fdc72-eae4-4ee0-83eb-832ab7b230e3/download/ident_2024_updated.csv")
web = pd.read_csv(f"{BASE}/e3567bb5-5d98-44d0-b0e8-9cdd3732c9e4/download/weburl_2024_updated.csv")

DESIG = {"A": "Public foundation", "B": "Private foundation", "C": "Charitable organization"}
ident["designation"] = ident["Designation"].map(DESIG)

# attach website (column is "BN/NE" in the web file)
web = web.rename(columns={"BN/NE": "BN"})
out = ident.merge(web[["BN", "Contact URL"]], on="BN", how="left")

on = out[(out["Province"] == "ON") & out["Legal Name"].str.contains("FOUNDATION", na=False)]
print(on[["Legal Name", "City", "Address Line 1", "Contact URL", "designation"]].head())

Add the directors file the same way (group by BN into a list, and keep only rows where End Date is blank for current officials).

A few things to watch for:

  • The website file's key column is BN/NE, not BN.
  • The directors file is large (~570k rows) - skip it if you only need a mailing list.
  • The data is refreshed annually, so treat it as a comprehensive snapshot, not a live feed.
  • Designation and category are codes - decode them with the CRA codes list for readable output.

Option 3: one call, already joined

If you'd rather not maintain the download-and-join pipeline (and the yearly URL changes), I built an Apify Actor that resolves the latest release, joins the files, decodes the codes, and returns one clean record per charity:

{
  "provinces": ["ON"],
  "keywords": ["foundation"],
  "includeDirectors": true,
  "maxResults": 100
}

Output - one record per charity:

{
  "businessNumber": "119033702RR0001",
  "charityName": "MARMORA HISTORICAL FOUNDATION INC.",
  "designationLabel": "Charitable organization",
  "categoryLabel": "Public amenities",
  "address": "2619 ANCHOR LANE",
  "city": "KINGSTON",
  "province": "ON",
  "postalCode": "K7L0C2",
  "website": "WWW.MARMORAHISTORY.CA",
  "directors": [{ "name": "Jane Smith", "position": "Director" }],
  "id": "cra-119033702RR0001"
}

Turn includeDirectors off for a faster mailing-list-only run; filter by province, category code, or designation. Pricing is per record ($6 per 1,000).

Call it from an AI agent (MCP)

The Actor is exposed over the Model Context Protocol, so an LLM agent can pull charity data as a tool:

https://mcp.apify.com?tools=truenorthdata/canada-charities

Wire it into a Claude / MCP client and ask "registered animal-welfare charities in BC with a website" - the agent runs the Actor and filters for you.

FAQ

Is this legal? The CRA publishes this under the Open Government Licence โ€“ Canada, for reuse with attribution.

How current is it? It's refreshed annually from T3010 returns - a full national snapshot rather than a live feed.

Does it include financials? The open data has separate T3010 financial files (revenue, expenditures, assets) keyed by BN - those columns are numeric line codes you decode with the data dictionary. A good v2 addition.

Contact details? Full mailing address for essentially every charity; websites for many; personal emails/phones are not published.

Building with Canadian public data - charities, permits, tenders, licences? I write about turning open registries into products. Questions welcome in the comments.

Comments

No comments yet. Start the discussion.