# CharityJob Scraper — UK Charity Jobs, Volunteers & Leads (`scrapersdelight/charityjob-uk-jobs-scraper`) Actor

$1 per 1,000 jobs, $0 to start. Scrape CharityJob.co.uk paid AND volunteer roles by keyword, location, cause, salary, workplace and date posted, then add each charity's own website, LinkedIn, HQ and open-role counts. Saved-search monitor with Slack/webhook alerts. No login, no API key.

- **URL**: https://apify.com/scrapersdelight/charityjob-uk-jobs-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Jobs, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 job returneds

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

## 💜 CharityJob Scraper — UK Charity Jobs, Volunteer Roles & Charity Leads

**Scrape every live advert on [CharityJob.co.uk](https://www.charityjob.co.uk) — the UK's largest
charity-sector job board — and get the hiring charity's own website, LinkedIn and HQ attached to
each row.** Paid roles *and* the 1,622-advert volunteer corpus, with every filter the site itself
offers wired to a real input.

**$1 per 1,000 jobs. $0 to start a run.** No login, no API key, no cookies.

***

### 🧾 What does this Actor do?

CharityJob's search page is a two-panel UI that pre-renders the **full detail** of all 15 results in
the same HTML response. That means one request returns 15 *complete* job records — full description,
salary line, contract, hours, causes, tags, closing date and the charity's profile link — with no
per-job fetch and no browser. This Actor walks that pagination, applies the site's own server-side
filters, then optionally enriches each row twice:

- **`enrichOrganisation`** (on by default) — fetches `/organisation/<slug>` for each charity and adds
  its **own website URL**, LinkedIn / Facebook / X / YouTube / Instagram / TikTok, **HQ postcode**,
  mission, about text, and how many paid and volunteer roles it currently has open. Each charity is
  fetched **once per run**, however many adverts it has in your result set — so you are never
  double-charged for the same organisation.
- **`enrichStructuredSalary`** (off by default) — fetches each job page for its schema.org
  `JobPosting` block: numeric `salaryMin` / `salaryMax` / currency / period, ISO `datePosted` and
  `validThrough`, the `employmentType` enum, and every listed locality.

Add `monitorMode` and a schedule and it becomes a saved-search watcher that returns only adverts
that are new since the previous run, with optional Slack / webhook / email alerts.

***

### 🎯 Who buys this data

| Buyer | What they do with a row |
|---|---|
| **Fundraising / charity-tech vendors** (CRM, donation platforms, payroll, DBS checking) | A charity that is *hiring a fundraiser* is a charity with budget and a live pain. The row names the charity, its size band, its own website and its LinkedIn — a ready outbound record. |
| **Charity-sector recruitment agencies** | Which charities are hiring directly right now, and which roles agencies already hold (`hideAgencies`, `applyLabel`). |
| **Sector researchers & funders** | Salary bands, cause mix, workplace mix and hiring volume across the whole UK charity sector, refreshed daily. |
| **Volunteer-management platforms** | The 1,622-advert volunteer corpus that no rival CharityJob scraper covers at all. |
| **Job aggregators** | A clean, deduplicated feed of ~98 new paid adverts a day (measured 2026-09-05, run `yrqe2Ct8glpQWnfAm`: the site reported **686** paid adverts posted in the last 7 days). |

***

### 📦 What you get — measured, not estimated

Every number below comes from **one run on build 0.1.9, 2026-09-05**: run `NI8sMc1CRWmghfm1l`,
`jobType: "both"` with **both** enrichments on, **120 rows / 120 unique `jobId` (60 paid + 60
volunteer) in 140.7 seconds** across 203 HTTP requests. Fill = the field is present and non-empty.

**Always there, 100% of 120 rows** — `jobId` · `jobUrl` · `jobSlug` · `title` · `organisation` ·
`organisationSlug` · `organisationUrl` · `organisationType` · `partnerId` · `logoUrl` · `location` ·
`locationName` · `workplace` · `salaryRaw` · `causes` · `tags` · `closingDate` ·
`listedOrRefreshedOn` · `postedRelative` · `applyLabel` · `resultPosition` · `descriptionText` ·
`orgPaidJobCount` · `orgVolunteerJobCount` · `orgProfileId` · `sourceUrl` · `scrapedAt`

| Field | Fill | Why it is not 100% |
|---|---:|---|
| `contractType` | 99% (119/120) | One advert stated no contract type |
| `organisationSize` | 81% (97/120) | Not every charity states a staff band |
| `postedOn` | 61% (73/120) | Refreshed adverts show "Refreshed on" instead — `listedOrRefreshedOn` merges the two and is 100% |
| `hours` | 50% (60/120) | Volunteer adverts carry no hours field at all — **100% (60/60) across the paid half** |
| `refreshedOn` | 39% (47/120) | Shown only when the advert has been refreshed |
| `shortDescription` | 26% (31/120) | A card-only field the site shows selectively |
| `jobReference` | 22% (26/120) | Only some recruiters publish one |
| `closingBadge` | 10% (12/120) | Shown only when a deadline is close |

Booleans are always present; these are their **true** rates in the same 120 rows:
`isVolunteerRole` 50% (the deliberate 60/60 split), `hasOrganisationProfile` **100%**,
`activelyInterviewing` 66%, `isTopJob` 18% (the sponsored promos — see honest limit 2),
`isExpiredJob` 0%, `isFutureJob` 0%.

**With `enrichStructuredSalary` on** (same run):

| Field | Fill | Note |
|---|---:|---|
| `datePosted`, `validThrough`, `employmentType` | 100% | ISO dates + the schema.org enum |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` | 50% (60/120) | Volunteer roles have no salary — **100% (60/60) across the paid half** |
| `jobRegions` / `jobLocalities` | 72% / 71% | Multi-site adverts list several |
| `jobLocationType` | 28% | Only set (to `TELECOMMUTE`) for remote roles |

**With `enrichOrganisation` on — the lead payload** (same run; all 120 rows had a charity profile
page, so nothing below is diluted by missing profiles):

| Field | Fill |
|---|---:|
| `orgHeadquarters` (postcode + country) | 78% (94/120) |
| `orgAboutText` | 78% (94/120) |
| **`charityWebsiteUrl`** | **68% (82/120)** |
| `linkedinUrl` | 58% (69/120) |
| `facebookUrl` | 49% (59/120) |
| `orgMission` | 48% (57/120) |
| `twitterUrl` | 38% (46/120) |
| `instagramUrl` / `youtubeUrl` / `tiktokUrl` | 22% / 8% / 2% |

#### ⚠️ Ten columns are empty unless you turn `enrichStructuredSalary` on

`enrichStructuredSalary` ships **off**, because it costs an extra HTTP fetch *and* an extra
$0.0005 charge per job. With the shipped defaults every row still carries all **62 columns**, but
ten of them are null on **every** row:

`salaryMin` · `salaryMax` · `salaryCurrency` · `salaryPeriod` · `datePosted` · `validThrough` ·
`employmentType` · `jobLocationType` · `jobLocalities` · `jobRegions`

Measured, defaults untouched: run `5fILHhLj0oYhBeyN9` (25 rows) and run `g2i3Rtd3upc2DD1DJ`
(25 rows) — those ten fields were null on 50 of 50 rows. Turn the flag on and they fill (100%
`datePosted` / `validThrough` / `employmentType`, and 100% numeric salary across paid roles, per the
table above). `salaryRaw` is 100% filled either way and carries whatever the charity actually wrote.

#### The `lead` output shape

Set `outputShape: "lead"` and you get a **23-column projection of the same record — the same field
names, never renamed**, so both dataset views ("Overview" and "Charity leads") and any downstream
mapping keep working when you switch shape:

`organisation` · `charityWebsiteUrl` · `linkedinUrl` · `organisationType` · `organisationSize` ·
`orgHeadquarters` · `orgPaidJobCount` · `orgVolunteerJobCount` · `title` · `salaryRaw` ·
`salaryMin` · `salaryMax` · `contractType` · `hours` · `locationName` · `workplace` · `causes` ·
`listedOrRefreshedOn` · `closingDate` · `jobId` · `jobUrl` · `organisationUrl` · `scrapedAt`

Verified on run `2aP61qySUDcGNCYES` (build 0.1.9, 10 rows): the "Charity leads" CSV export came back
with **12 of 12 columns populated** and the "Overview" export with **10 of 10**.

***

### 🔍 Every filter the site has, wired and tested

All of these are the site's **own server-side** parameters — each one was verified live on
2026-09-04 by confirming CharityJob's own result counter moved:

| Input | Live check on 2026-09-04 |
|---|---|
| `keywords` | `fundraising` → 619 results · `chief executive` → 387 |
| `location` + `radius` | `manchester` r5 → 235 · r80 → 456 |
| `cause` (21 values, repeatable) | `education` + `health` → 501 |
| `contractType` (repeatable) | `contract` + `temporary` → 532 |
| `workplace` (on-site / hybrid / remote) | `hybrid` → 935 · `remote` → 179 |
| `minSalary` / `maxSalary` | £40k–£60k → 609 |
| `postedWithinDays` | Re-measured 2026-09-05 on build 0.1.9: 3 days → **351** (`iRVhHbCIbbauu1d1v`) · 7 days → **686** (`yrqe2Ct8glpQWnfAm`) · 14 days → **1,225** (`2SpeTyiXZmG1JtMKe`). The 1-day bucket is *calendar today*, so it reads low late in the UTC day — **27** at 23:00 UTC (`zekT3x2hm9N41d6wB`). |
| `sortBy` | `latest` (newest) vs `closing` (soonest) — different first rows |
| `entryLevelOnly` | → 143 |
| `applyNowOnly` | → 525 (excludes external-ATS redirects) |
| `hideAgencies` | → 1,454 (direct charity employers only) |
| `jobType` | Re-measured 2026-09-05 (run `NI8sMc1CRWmghfm1l`): paid → **1,652** · volunteer → **1,622** · `both` splits your cap fairly between the two (that run returned exactly 60 paid + 60 volunteer) |

Plus client-side controls: `excludeTopJobs`, `excludeExpired`, `maxItems`, `maxPages`,
`deduplicateBy`, `includeDescriptionHtml`, `outputShape`, `maxConcurrency`, `maxRequestRetries`,
`proxyConfiguration`, and `startUrls` (paste any CharityJob search **or** single job URL — the URL's
own querystring wins, the fields above are merged in only where it is silent).

***

### 💵 Pricing

Pay per event. **There is no charge to start a run**, and both of Apify's auto-events are removed.

| Event | Price | When it fires |
|---|---|---|
| **Job returned** | **$0.001** (**$1 per 1,000**) | Once per advert delivered to your dataset. Rows dropped by your filters, by deduplication, or by the monitor's seen-set are never charged. |
| Charity profile enriched | $0.0005 | Once per **unique** charity profile fetched. A charity with ten adverts costs one charge, not ten. |
| Structured salary fetched | $0.0005 | Per job page fetched for numeric salary + ISO dates. Opt-in, off by default. |
| Monitoring run | $0.01 | Once per scheduled monitor run. |
| New advert detected | $0.002 | Per advert that is new since the previous monitor run. |

**What a full scrape costs you today.** The site reported **1,652 live paid adverts** on
2026-09-05 (run `NI8sMc1CRWmghfm1l`). Rival prices below were read from the Apify store API the
same day, not from memory:

| | Rate | 1,652 paid adverts |
|---|---|---|
| **This Actor** | $0.001/row, **no start charge** | **$1.65** |
| memo23/charityjob-scraper | $0.0015/row + $0.05 start | $2.53 |
| getdataforme/charityjob-urls-scraper | $0.009/row + $0.05 start | $14.92 |

Charity enrichment adds $0.0005 per **unique** charity, not per row. Measured on the 120-row run
above: 75 unique charities behind 120 rows → **+$0.0375** (charged 75, verified against 75 distinct
`organisationSlug` values). Over a whole-corpus scrape the worst case is one unique charity per row,
so an all-1,652 enriched scrape is **$1.65 + at most $0.83**.

**What `maxTotalChargeUsd` actually does here.** Before anything is written, the run prices every
row it is holding against the budget you set — the row itself *plus* the charity-profile and
structured-salary events that row will owe — and delivers only the rows the budget can pay for in
full. A row that cannot be paid for completely is **withheld, not shipped**, so you never pay an
enrichment charge for a row you did not receive, and the run total never crosses your cap. Measured
2026-09-05 on build 0.1.9: at a **$0.0100** cap with both enrichments on (run `JQTpnBgabykQAzAah`)
the run stored **5** rows and billed **5** rows + 4 charity profiles + 5 salary fetches =
**$0.0095**; at a **$0.0200** cap with charity enrichment on (run `N1fBkYE01Ra1gkCnA`) it stored
**14** rows and billed 14 rows + 12 profiles = **$0.0200** exactly. Rows stored equals rows billed
on both, and both numbers reproduced exactly on the shipped build (runs `ia03DJsdRGwXvCd3Z` and
`02tgk8WO2gYcfTkDt`). (On build 0.1.8 the same $0.0100 cap billed $0.0105 — over the cap — and asked to charge
17 profiles and 20 salary fetches behind 10 delivered rows; run `kZrf2EPW56a8H6OcX`. That is fixed.)

***

### 🧪 Reliability, measured

- **Transport:** plain HTTP + cheerio through an Apify **datacenter** proxy. The escalation ladder
  (direct / datacenter / residential-GB) returned **byte-identical** 570,104-byte responses on the
  2026-09-04 transport probe, so the cheapest rung is the default. No browser, no residential,
  512 MB. Every run listed on this page went through that default datacenter proxy and returned
  200s with zero blocks.
- **Sustained load** (2026-09-04 probe, single attempt, no retry, all through an Apify proxy):
  datacenter sequential **39/40 (97.5%)**, concurrency 5 → **10/10**, concurrency 10 → 19/20.
  Across 90 proxied requests CharityJob returned **zero 403, zero 429, zero CAPTCHA, zero
  soft-block** — both failures were Apify-proxy-side `595 ECONNRESET`, which the default 3 retries
  (each on a fresh exit IP) absorb entirely.
- **Speed (re-measured 2026-09-05 on build 0.1.9):** 120 rows with **both** enrichments —
  8 listing pages + 75 charity profiles + 120 job pages = **203 HTTP requests — in 140.7 seconds**
  (run `NI8sMc1CRWmghfm1l`). Unenriched is far faster: 25 rows with charity enrichment only in
  **8.1 s** (`5fILHhLj0oYhBeyN9`), and a 2-row counter probe in **6.3 s** (`NAUMgCJDav9hZr7nb`).
- **Pagination is bounded and self-describing:** the page reports its own total and page count, and
  the crawl stops at the last page. 15 results per page is fixed by the site (`?pagesize=100` is
  ignored).

***

### ⚠️ Honest limits — read these before you buy

1. **There is no apply link, and this Actor does not invent one.** CharityJob's apply button is a
   JavaScript-only trigger through `track.charityjob.co.uk`; no destination URL exists anywhere in
   the page HTML. What you get instead is `applyLabel` — `"Apply Now"` (apply on CharityJob) or
   `"Redirect to recruiter"` (external ATS) — which is honest and 100% filled.
2. **Sponsored "Top job" promos are injected into EVERY result set, regardless of your filters.**
   Measured: advert 1082124, located "Remote", appeared under both `cause=education` and
   `location=london`. That is the site's behaviour, not a bug in the filter. Set
   `excludeTopJobs: true` to drop them (they were **18%** — 22 of the 120 rows in run
   `NI8sMc1CRWmghfm1l`).
3. **A salary filter silently drops adverts with no stated salary.** That is how the site works.
4. **`orgHeadquarters` is a postcode + country**, read from the charity's own map embed — not a full
   street address.
5. **Some charities have no CharityJob profile page.** Their name still comes through;
   `hasOrganisationProfile` is `false` and the enrichment fields stay null. Across the 170 rows I
   measured on 2026-09-05 (runs `NI8sMc1CRWmghfm1l`, `5fILHhLj0oYhBeyN9`, `g2i3Rtd3upc2DD1DJ`) every
   single row *did* have a profile — so check `hasOrganisationProfile` rather than assuming a rate.
6. **Ten columns are null unless you opt in.** With the shipped defaults `enrichStructuredSalary`
   is off, and `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `datePosted`,
   `validThrough`, `employmentType`, `jobLocationType`, `jobLocalities` and `jobRegions` are null on
   every row (measured: 50 of 50 rows across runs `5fILHhLj0oYhBeyN9` and `g2i3Rtd3upc2DD1DJ`). They
   are opt-in because each one costs an extra fetch and an extra $0.0005 per job. `salaryRaw` is
   100% filled either way.
7. **This corpus is modest and it churns.** **1,652 paid + 1,622 volunteer** adverts live on
   2026-09-05 (run `NI8sMc1CRWmghfm1l`), with **686** paid adverts posted in the previous 7 days
   (~98/day, run `yrqe2Ct8glpQWnfAm`) — so the whole live paid corpus turns over in roughly 17 days.
   The counter also moves while you read it: the same paid search reported **1,659** eight minutes
   earlier the same evening (run `g2i3Rtd3upc2DD1DJ`). If you want a one-off list, it is a small one;
   the value is in watching the flow.
8. **`refreshedOn` vs `postedOn`** are mutually exclusive on the site. Use `listedOrRefreshedOn`,
   which merges them and is 100% filled (`postedOn` alone was 61% — 73 of 120 — and `refreshedOn`
   39% — 47 of 120 — in run `NI8sMc1CRWmghfm1l`).

***

### ❓ FAQ

**Do I need a CharityJob account or an API key?**
No. Everything this Actor reads is a public, server-rendered page.

**Does it work without a proxy?**
It runs, but use the default Apify proxy. Datacenter is measured sufficient — residential is *not*
required, though you can pin `apifyProxyGroups: ["RESIDENTIAL"], countryCode: "GB"` if you prefer.

**How many jobs are there in total?**
The site reported **1,652 paid** and **1,622 volunteer** adverts on 2026-09-05 (run
`NI8sMc1CRWmghfm1l`). The Actor reads the live figure from the page and logs it every run, so you
always know the size of your query before you commit to it.

**Can I scrape the whole site?**
Yes — set `maxItems: 0` and `jobType: "both"`. On 2026-09-05 that was **3,274 rows, $3.27**
unenriched (1,652 + 1,622, run `NI8sMc1CRWmghfm1l`).

**How do I get only new jobs?**
`monitorMode: true` on a schedule. The seen-set lives in a **named** key-value store so it survives
between runs. Verified 2026-09-05 on build 0.1.9: round 1 (`ueZLsTzyegaJsyr26`) returned **10 new of 10** and
charged `job-scraped: 10`, `new-job-detected: 10`, `monitor-run-completed: 1`; round 2 over the
identical search a minute later (`fSs1lDG9XasfEmi0p`) returned **1 new of 10** — one genuinely fresh
advert had been listed in between — and charged 1 row, 1 `new-job-detected` and the one
`monitor-run-completed`. The nine already-seen adverts were suppressed and cost nothing. Pair it with
`postedWithinDays: 1` to keep runs tiny.

**Will I be charged twice for the same job?**
No. `deduplicateBy: "jobId"` is the default and it collapses the sponsored promos that repeat across
pages. A/B verified 2026-09-05 with the *same* search URL listed twice in `startUrls`, `maxItems`
30: with the default `"jobId"` (run `cZajAeBeLeehAXwIc`, build 0.1.9) → **30 rows, 30 unique, max
repeat 1**, and the crawler simply pages on to fill the cap with genuinely new adverts; with
`"none"` (run `ead3ode2CCsmJNAJj`) → **30 rows but only 15 unique, every id twice**, billed 30. The control
double-charges; the shipped default does not.

**Will I be charged twice for the same charity?**
No. Organisation enrichment deduplicates by `organisationSlug`. Verified on run
`NI8sMc1CRWmghfm1l`: 120 rows contained **75 distinct `organisationSlug` values**, 75 profiles were
fetched, and `chargedEventCounts` shows `organisation-enriched: 75` — not 120.

**What if my search returns nothing?**
The run exits **cleanly** with zero rows and charges nothing. The Actor distinguishes a genuinely
empty search (the site's counter reads 0) from a broken parse (the counter is missing, or says >0
while no rows parsed) — the latter fails the run loudly rather than handing you a silently empty
dataset.

**Can I paste a URL from my browser?**
Yes. Put any `charityjob.co.uk/jobs?…` or `/volunteer-jobs?…` search URL in `startUrls` and its
querystring becomes the filter set. A single job URL works too and returns that one advert.

**What's the difference between `full` and `lead` output?**
`full` is every field — **62 columns** with the shipped defaults. Counted on every row of runs
`5fILHhLj0oYhBeyN9` (25 rows), `g2i3Rtd3upc2DD1DJ` (25) and `NI8sMc1CRWmghfm1l` (120): exactly 62
keys, no row short. Two options each append one more column — `includeDescriptionHtml` adds
`descriptionHtml`, and `monitorMode` adds `isNew` (measured on run `ueZLsTzyegaJsyr26`: the
62-column full shape came back with 63, `isNew` present on all 10 rows).
`lead` is a **23-column projection of those same fields, under the same names** — organisation,
charityWebsiteUrl, linkedinUrl, type, size, orgHeadquarters, open-role counts, title, salary,
locationName, workplace, causes, closingDate and the URLs. Because the names do not change, both
dataset views work in either shape. **Note:** two of those 23 — `salaryMin` and `salaryMax` — are
part of the opt-in set in honest limit 6, so with the shipped defaults they arrive empty in the
lead shape too (measured on run `2aP61qySUDcGNCYES`: 23 columns, `salaryMin`/`salaryMax` 0 of 10,
every other column 4–10 of 10). Turn `enrichStructuredSalary` on if your CRM needs numeric pay.

**Does it cover volunteer roles?**
Yes — `/volunteer-jobs` is the site's own second tab over the same search grammar, 1,622 adverts,
parsed by the same code into the same fields. Set `jobType` to `volunteer` or `both`.

**Why is `salaryMin` empty?**
Most often because `enrichStructuredSalary` is **off by default** — with the shipped defaults
`salaryMin` and nine sibling columns are null on every row (see honest limit 6). Turn it on and it
filled 100% of paid rows (60/60) in run `NI8sMc1CRWmghfm1l`. It stays empty on volunteer adverts
(no salary exists) and on adverts that state pay only as free text. `salaryRaw` is 100% filled
either way and carries whatever the charity actually wrote.

***

### ⚖️ Legal & fair use

CharityJob's `robots.txt` (fetched 2026-09-04) opens with, verbatim:

```
User-agent: *
Allow: /
```

and disallows only `/feeds/`, `/Invoice/` and `/attachments/` — **none of which this Actor
requests**. `/jobs`, `/volunteer-jobs` and `/organisation` are explicitly allowed and are listed in
the site's own `jobsitemap.xml`. The file additionally names a list of AI/SEO crawlers (Bytespider,
GPTBot, Diffbot, Amazonbot, ClaudeBot, CCBot, PerplexityBot and others) as blocked or crawl-delayed;
this Actor is none of them and does not impersonate them.

This Actor reads only ordinary public pages. It does **not** log in, does not solve CAPTCHAs, does
not forge authentication, and does not fire the site's tracked apply endpoint.

Job adverts can contain personal information (named hiring contacts inside a description). **You are
the data controller for anything you collect**, and you are responsible for complying with
CharityJob's Terms of Service, UK GDPR, and any applicable marketing rules (PECR) before contacting
anyone. Use the data for market research, lead generation and job aggregation — not for
unsolicited bulk email to named individuals.

***

*Built by [ScrapersDelight](https://apify.com/scrapersdelight). Not affiliated with, endorsed by, or
connected to CharityJob or CharityJob Ltd.*

# Actor input Schema

## `startUrls` (type: `array`):

Paste any CharityJob search URL (https://www.charityjob.co.uk/jobs?keywords=fundraising\&location=london) or any single job URL (https://www.charityjob.co.uk/jobs/org/role/1082796). A pasted URL's own querystring IS the filter set — the fields below are merged in only where the URL is silent, so your URL always wins. Leave empty to build the search from the fields below.

## `jobType` (type: `string`):

Which of the site's two corpora to crawl. 'paid' = /jobs. 'volunteer' = /volunteer-jobs — the same search grammar and the same fields. 'both' crawls each in turn. Measured on the live site 2026-09-05 (run NI8sMc1CRWmghfm1l): 1,652 live paid adverts, 1,622 volunteer. The Actor logs the site's own counter every run, so you always see today's figure.

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

Free-text search across job titles and descriptions, e.g. 'fundraising', 'database manager', 'trusts and foundations'. Maps to the site's own ?keywords= parameter.

## `location` (type: `string`):

Town, city or postcode, e.g. 'london', 'manchester', 'BS1'. Maps to ?location= and is combined with the radius below.

## `radius` (type: `string`):

Distance around the location. Only these radii exist on the site; anything else is rounded down. Ignored when no location is set.

## `cause` (type: `array`):

Charity cause areas, repeatable — selecting several returns the union. These are the site's own 21 cause slugs.

## `contractType` (type: `array`):

Repeatable contract filter. Maps to the site's ?contracttype= parameter.

## `workplace` (type: `array`):

On-site, hybrid or remote. Repeatable. Maps to ?workplace=.

## `minSalary` (type: `integer`):

Only roles advertised at or above this annual salary. Maps to ?minsalary=. Note that a salary filter drops every advert with no stated salary.

## `maxSalary` (type: `integer`):

Only roles advertised at or below this annual salary. Maps to ?maxsalary=.

## `postedWithinDays` (type: `string`):

Only adverts listed in the last N days — the key filter for a monitoring run. Measured on the live site 2026-09-05: 3 days = 351 paid adverts (run iRVhHbCIbbauu1d1v), 7 days = 686 (yrqe2Ct8glpQWnfAm), 14 days = 1,225 (2SpeTyiXZmG1JtMKe). The 1-day bucket is calendar-today, so it reads low late in the UTC day — 27 at 23:00 UTC (zekT3x2hm9N41d6wB).

## `sortBy` (type: `string`):

Result order. 'latest' is the site default (newest first) and is what makes contiguous pagination stable; 'closing' sorts by closing date.

## `entryLevelOnly` (type: `boolean`):

Only roles flagged entry level. Maps to ?joblevel=entry\_level.

## `applyNowOnly` (type: `boolean`):

Exclude adverts that redirect to an external recruiter ATS. Maps to ?applytype=online.

## `hideAgencies` (type: `boolean`):

Exclude recruitment agencies so every row is a charity you can actually sell to. Maps to ?employerType=direct-employer.

## `excludeTopJobs` (type: `boolean`):

CharityJob injects sponsored 'Top job' adverts into EVERY result set regardless of your filters — a 'Remote' promo shows up under location=london. Turn this on to drop them, at the cost of a few genuinely matching sponsored roles.

## `excludeExpired` (type: `boolean`):

Drop rows the site marks is-expired-job or is-future-job.

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

Hard cap on rows returned — this is also your billing cap, since you are charged per row delivered. Set 0 for no cap (measured 2026-09-05, run NI8sMc1CRWmghfm1l: the whole paid corpus is 1,652 rows, volunteer 1,622 — 3,274 for jobType 'both').

## `maxPages` (type: `integer`):

Alternative cap, in pages of 15 results. 0 = no page cap. Whichever of maxItems / maxPages is reached first stops the crawl.

## `enrichOrganisation` (type: `boolean`):

Fetch /organisation/<slug> for each charity to add its own website URL, LinkedIn / Facebook / X / YouTube / Instagram / TikTok, HQ postcode, mission, about text and current open-role counts. Each charity is fetched — and billed — ONCE per run, even when it has ten adverts in your result set.

## `enrichStructuredSalary` (type: `boolean`):

Fetch each job page for its schema.org JobPosting block: numeric salaryMin / salaryMax / currency / period, ISO datePosted and validThrough, the employmentType enum and every listed locality. Off by default because it costs one extra request per job.

## `includeDescriptionHtml` (type: `boolean`):

Add descriptionHtml alongside the plain-text description. Off by default because it roughly triples the size of each row.

## `outputShape` (type: `string`):

'full' returns every field (62 columns). 'lead' returns 23 of those SAME fields under the SAME names — the compact charity-centric row a CRM import wants: organisation, charityWebsiteUrl, linkedinUrl, organisationType/Size, orgHeadquarters, open-role counts, title, salary, locationName, workplace, causes, closingDate, URLs. Both dataset views work in either shape. Two of the 23, salaryMin and salaryMax, fill only when enrichStructuredSalary is on: run 2aP61qySUDcGNCYES (2026-09-05, shipped defaults) returned 0 of 10, salaryRaw 10 of 10.

## `deduplicateBy` (type: `string`):

'jobId' collapses the same advert appearing twice (sponsored 'Top job' promos repeat across pages) so you are never charged twice for one job. 'none' returns the raw stream.

## `monitorMode` (type: `boolean`):

Remembers every job ID it has already returned for this exact search (in a named key-value store that survives between runs) and outputs only the new ones. Pair it with a schedule and postedWithinDays to watch a niche. Billed as one monitor-run-completed plus new-job-detected per new advert, on top of the per-row charge.

## `alertOnNewJob` (type: `boolean`):

Only applies in monitor mode. Alerts are free — there is no alert charge event.

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

POST a JSON payload for each new job found in monitor mode.

## `slackWebhookUrl` (type: `string`):

POST a formatted Slack card for each new job found in monitor mode.

## `emailRecipients` (type: `array`):

Email addresses to notify (via apify/send-mail) when monitor mode finds new jobs.

## `proxyConfiguration` (type: `object`):

MEASURED: an Apify DATACENTER proxy is sufficient — 97.5% clean on 40 sequential requests, with zero blocks from the site itself. Residential is not needed, but you can pin it here (apifyProxyGroups: \['RESIDENTIAL'], countryCode: 'GB') if you prefer.

## `maxConcurrency` (type: `integer`):

MEASURED on the live site: 5 concurrent -> 10/10 clean, 10 concurrent -> 19/20. 5 is the recommended setting.

## `maxRequestRetries` (type: `integer`):

Each retry uses a fresh proxy exit IP. The only failures observed on this target were Apify-proxy-side 595 ECONNRESET transients, which 3 retries absorb entirely.

## Actor input object example

```json
{
  "startUrls": [],
  "jobType": "paid",
  "keywords": "fundraising",
  "location": "london",
  "radius": "20",
  "cause": [],
  "contractType": [],
  "workplace": [],
  "postedWithinDays": "0",
  "sortBy": "latest",
  "entryLevelOnly": false,
  "applyNowOnly": false,
  "hideAgencies": false,
  "excludeTopJobs": false,
  "excludeExpired": true,
  "maxItems": 25,
  "maxPages": 0,
  "enrichOrganisation": true,
  "enrichStructuredSalary": false,
  "includeDescriptionHtml": false,
  "outputShape": "full",
  "deduplicateBy": "jobId",
  "monitorMode": false,
  "alertOnNewJob": true,
  "webhookUrl": "",
  "emailRecipients": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 5,
  "maxRequestRetries": 3
}
```

# Actor output Schema

## `items` (type: `string`):

The dataset of scraped CharityJob.co.uk roles (one advert per row), with the charity's contact profile attached.

# 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 = {
    "jobType": "paid",
    "maxItems": 25,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/charityjob-uk-jobs-scraper").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 = {
    "jobType": "paid",
    "maxItems": 25,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/charityjob-uk-jobs-scraper").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 '{
  "jobType": "paid",
  "maxItems": 25,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapersdelight/charityjob-uk-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/charityjob-uk-jobs-scraper"
        }
    }
}

```

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/6MwRFnYnHFSrCRbvx/builds/JXVjcgREkaTQYRJFS/openapi.json
