# MyJobMag, Jobberman, BrighterMonday & Careers24 Jobs Scraper (`openharvest/africa-jobs-scraper`) Actor

Scrape jobs from MyJobMag, Jobberman, BrighterMonday and Careers24 in Nigeria, Kenya, South Africa, Ghana and Uganda: title, company, location, salary, job type, dates and full description in one schema. Filter by keyword and date, get only new jobs.

- **URL**: https://apify.com/openharvest/africa-jobs-scraper.md
- **Developed by:** [Enes Bulanık](https://apify.com/openharvest) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 job postings

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

## MyJobMag, Jobberman, BrighterMonday & Careers24 Jobs Scraper

Scrape job postings from **MyJobMag, Jobberman, BrighterMonday and Careers24**, 8 African job boards in **Nigeria, Kenya, South Africa, Ghana and Uganda**, into one format. You get title, company, location, salary, job type, posting and closing dates, and the full description. Filter by keyword and date, and on a schedule get only the jobs that are new since your last run.

**Price: $1.50 per 1,000 jobs** (+ $1.00 per 1,000 if you want full job details). There is no start fee, and boards that fail cost nothing.

### What does Africa Jobs Scraper do?

It reads the public job listings of these boards (the same pages anyone can open without an account) and returns one row per job in a **single unified schema**:

| Board | Country | Board id |
|---|---|---|
| MyJobMag | Nigeria | `myjobmag-ng` |
| MyJobMag | Kenya | `myjobmag-ke` |
| MyJobMag | South Africa | `myjobmag-za` |
| Jobberman | Nigeria | `jobberman-ng` |
| Jobberman | Ghana | `jobberman-gh` |
| BrighterMonday | Kenya | `brightermonday-ke` |
| BrighterMonday | Uganda | `brightermonday-ug` |
| Careers24 | South Africa | `careers24-za` |

For every job you get:

- **Job**: title, company, location, job type (full time, contract, internship...), work mode (onsite / remote / hybrid), job function and industry
- **Salary**: the text shown on the board (e.g. `NGN 150,000 - 250,000`, `Market Related`) plus parsed `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryPeriod` (MONTH / YEAR)
- **Dates**: `postedDate` and closing date (`validThrough`) as `YYYY-MM-DD`
- **Requirements** (with details): education, experience level, years of experience
- **Full description** as clean plain text, with bullet points kept
- Job URL, apply URL, company page and logo, plus `featured` and `easyApply` flags

#### What makes it different

- **One schema across 8 boards**: no need to stitch four different scrapers together.
- **Grouped MyJobMag posts handled**: "Jobs at Company X" cards with 10+ positions become one row per position.
- **Structured data first**: dates, salary ranges and requirements come from the boards' own schema.org `JobPosting` data, not from guessing in the HTML.
- **Duplicate removal across boards**: the same job posted on MyJobMag and Jobberman is returned once, and you are not charged for the duplicate.
- **"Only new jobs" mode for schedules**: the scraper remembers what it already gave you (for 120 days) and returns only new postings.
- **Privacy by default**: e-mail addresses and phone numbers of recruiters are removed from all text. Rows where this happened have `contactsRedacted: true`.

### Use cases

- **Job aggregators and job alert apps**: feed fresh Nigerian, Kenyan, South African, Ghanaian and Ugandan jobs into your site or WhatsApp/Telegram alert bot every day.
- **Recruiters and staffing agencies**: see who is hiring for which roles, in which city, and at what salary.
- **Labour-market and salary research**: track demand by job function, industry, location and salary range across 5 countries.
- **HR-tech and AI products**: build datasets for job classification, salary prediction or skills extraction.
- **Sales prospecting**: build a list of companies with open roles in a city or industry, then research them from the company name and job page (no personal contact data is collected).

### How to scrape African job boards

1. Click **Try for free** or **Start**.
2. Choose the **Job boards** (leave them all selected for every country).
3. Optional: type a **Keyword** (e.g. `accountant`), set **Posted within (days)** (e.g. `7`) and **Max jobs per board**.
4. Click **Start** and download the results as JSON, CSV, Excel or HTML, or connect them to Google Sheets, Make, Zapier, n8n, webhooks or the Apify API.

### Input

| Field | Type | Description |
|---|---|---|
| `boards` | array | Board ids from the table above, e.g. `["jobberman-ng", "myjobmag-ng"]`. Empty = all 8 boards. |
| `keyword` | string | Only jobs whose title (or job function) contains all these words. |
| `postedWithinDays` | integer | Only jobs posted in the last N days. `0` = any date. |
| `maxJobsPerBoard` | integer | Max jobs saved per board, newest first. Default `20`. `0` = no limit. |
| `includeDetails` | boolean | Open each job page for the full description, dates, salary period and requirements. Default `true`. |
| `dedupeAcrossBoards` | boolean | Skip the same job (title + company + country) found on another board. Default `true`. |
| `onlyNewJobs` | boolean | Return only jobs not returned by earlier runs with the same `stateKey`. Default `false`. |
| `stateKey` | string | Name of the "only new jobs" memory, e.g. `nigeria-daily`. |
| `proxyConfiguration` | object | Optional. Not needed in normal use. |

Example: accountant jobs in Nigeria and Kenya from the last 14 days.

```json
{
    "boards": ["myjobmag-ng", "jobberman-ng", "myjobmag-ke", "brightermonday-ke"],
    "keyword": "accountant",
    "postedWithinDays": 14,
    "maxJobsPerBoard": 50,
    "includeDetails": true
}
```

Example: daily schedule with only new jobs from every board.

```json
{
    "maxJobsPerBoard": 200,
    "postedWithinDays": 3,
    "onlyNewJobs": true,
    "stateKey": "all-boards-daily"
}
```

### Output

Real sample (description shortened):

```json
{
    "type": "job",
    "board": "jobberman-ng",
    "site": "Jobberman",
    "country": "Nigeria",
    "countryCode": "NG",
    "jobId": "accountant-7j0nrg",
    "title": "Accountant",
    "company": "The Bliss Caregivers",
    "location": "Lagos",
    "employmentType": "Full Time",
    "workMode": null,
    "salaryText": "NGN 150,000 - 250,000",
    "salaryMin": 150000,
    "salaryMax": 250000,
    "salaryCurrency": "NGN",
    "salaryPeriod": "MONTH",
    "jobFunction": "Accounting, Auditing & Finance",
    "industry": "Healthcare",
    "experienceLevel": "Senior level",
    "experienceText": null,
    "minExperienceMonths": 60,
    "education": "HND",
    "postedDate": "2026-10-07",
    "postedText": "Yesterday",
    "validThrough": "2027-01-05",
    "featured": true,
    "easyApply": true,
    "snippet": "Responsibilities will include recording and categorizing expenses, and preparing financial reports; ...",
    "description": "Responsibilities:\n\n- Ensure that proper books of accounts are being kept accurately and timely.\n- Institute and ensure controls in accounting transactions.\n...",
    "contactsRedacted": false,
    "url": "https://www.jobberman.com/listings/accountant-7j0nrg",
    "applyUrl": "https://www.jobberman.com/listings/accountant-7j0nrg",
    "companyUrl": null,
    "companyLogoUrl": "https://i.roamcdn.net/kazi/ng/hq/0XZOzjwnh4h-iMg2R9zIMA/-/_1Q6tq89LxV7VGRgamhgYA/adv220510_1652223324.jpg",
    "detailsFetched": true,
    "scrapedAt": "2026-10-08T18:25:53.733Z"
}
```

Which fields are filled depends on what each board publishes. Jobberman and BrighterMonday show salary ranges on most jobs, MyJobMag shows experience in years and education, and Careers24 shows salary as text (e.g. "Market Related"). Missing values are `null`, never invented.

If a board cannot be reached, you get a free row with `type: "error"` and an `errorCode` (`BOARD_FAILED`, `NO_RESULTS`, `INVALID_INPUT`) instead of a failed run.

The dataset has ready-made views in Apify Console: **Overview**, **Full details** and **Status & errors**. A `RUN_SUMMARY` record in the key-value store shows pages, jobs and skipped duplicates per board.

### Pricing

This Actor uses **pay-per-event** pricing: you only pay for the jobs you get. Platform usage is included.

| Event | Price | When it is charged |
|---|---|---|
| `job` | **$0.0015** per job ($1.50 / 1,000) | Each job saved to the dataset |
| `job-details` | **$0.001** per job ($1.00 / 1,000) | Only when *Include full job details* is on and the job page was read successfully |
| Errors, duplicates, already-seen jobs | **Free** | |

Examples:

| Job | Cost |
|---|---|
| 20 jobs from each of the 8 boards with details (160 jobs) | 160 × $0.0025 = **$0.40** |
| 1,000 jobs, listing data only | **$1.50** |
| 1,000 jobs with full details | **$2.50** |

There is no start fee. Set **Max cost per run** in the run options and the scraper stops cleanly when your budget is reached.

### Use with AI agents (MCP)

AI agents can call this Actor through the [Apify MCP server](https://mcp.apify.com) as `openharvest/africa-jobs-scraper`. It is a good fit for questions such as "Which companies in Lagos are hiring accountants this week and what do they pay?" Example input an agent can send:

```json
{
    "boards": ["jobberman-ng", "myjobmag-ng"],
    "keyword": "accountant",
    "postedWithinDays": 7,
    "maxJobsPerBoard": 25,
    "includeDetails": false
}
```

Use `includeDetails: false` for quick, cheap overviews, and `true` when the agent needs the full description or the closing date.

### FAQ

**Do I need an account on these job boards?**
No. The scraper reads only public job listings that anyone can open without logging in.

**How many jobs can I get?**
MyJobMag and Careers24 can be paged through deeply. For Jobberman and BrighterMonday the scraper follows the pages that the sites allow robots to visit: the newest about 160 jobs per board, then the same lists by job function, industry, location and job type. That covers most of the open jobs on each board. Use `maxJobsPerBoard: 0` for no limit.

**How fresh is the data?**
Live: every run reads the boards at that moment. For a daily feed, schedule the Actor with `onlyNewJobs: true` and `postedWithinDays: 3`.

**Why are some jobs older than my date filter?**
They are not: `postedWithinDays` removes them. Without the filter, MyJobMag sometimes pins old posts to the top of a list, and those are returned as they are shown.

**How does the keyword filter work?**
All words must appear in the job title or job function (`accountant` matches "Senior Accountant"). On Careers24 the site's own search is used.

**Why is `location` just the country on some BrighterMonday jobs?**
That is how the employer posted it. We do not guess a city.

**Do you return recruiters' e-mails or phone numbers?**
No. E-mail addresses and phone numbers are removed from all text fields (`contactsRedacted` shows where this happened). To apply, use `applyUrl`.

**Is Pnet or MyJobMag Ghana supported?**
Not yet. MyJobMag Ghana has posted almost no new jobs since 2024, so Ghana is covered through Jobberman Ghana. Tell us in the Issues tab if you need another board.

### Is it legal to scrape these job boards?

This Actor only collects **publicly available job advertisements** that the boards publish on the open web without login. It follows the sites' robots.txt page limits, sends requests at a moderate rate, and removes personal contact details of recruiters. Job ads may still contain copyrighted text and company information, so make sure your use complies with applicable laws (e.g. Nigeria's NDPA, Kenya's Data Protection Act, South Africa's POPIA, GDPR) and with the boards' terms. **Do not use the data for spam or unsolicited messages.** If you are unsure, consult a lawyer.

### Related actors

Other actors by the same developer:

- **Greenhouse, Lever, Ashby & Workday Jobs Scraper – 10 ATS**: jobs straight from company career pages
- **Google Sheets Import & Export – Read, Append & Dedupe Rows**: send your daily job feed to a spreadsheet
- **TED, SAM.gov, UK & Brazil Tenders Scraper – Public Procurement**: public contracts and tenders
- **Website Tech Stack Detector – Bulk CMS, Analytics & Frameworks**: research the companies that are hiring

### Support & feedback

Found a bug, a missing field or a board you need? Open an issue in the **Issues** tab.

# Actor input Schema

## `boards` (type: `array`):

Which job boards to scrape. Leave empty for all 8 boards (Nigeria, Kenya, South Africa, Ghana, Uganda).

## `keyword` (type: `string`):

Optional. Only jobs whose title (or job function) contains all these words, e.g. <code>accountant</code>, <code>nurse</code> or <code>software engineer</code>. Careers24 uses its own site search.

## `postedWithinDays` (type: `integer`):

Optional. Only jobs posted in the last N days (0 = any date). Paging stops as soon as older jobs are reached.

## `maxJobsPerBoard` (type: `integer`):

Maximum number of jobs to save from each board (newest first). 0 = no limit. You pay per job, so this also caps your cost.

## `includeDetails` (type: `boolean`):

Open each job page to add the full description, exact posting and closing dates, salary range and period, industry, required education and experience (adds a small 'job-details' charge per job). Turn off for the fastest, cheapest listing-only run.

## `dedupeAcrossBoards` (type: `boolean`):

Skip a job when the same title from the same company in the same country was already saved from another board in this run. Skipped duplicates are free.

## `onlyNewJobs` (type: `boolean`):

Remember the jobs saved by earlier runs (for 120 days) and return only jobs you have not received before. Ideal for daily schedules. Uses the state key below.

## `stateKey` (type: `string`):

Name of the memory used by 'Only new jobs'. Use a different key for each schedule or task, e.g. <code>nigeria-daily</code>.

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

Usually not needed - all boards work without a proxy. Enable Apify Proxy only if you see repeated HTTP 403/429 warnings.

## Actor input object example

```json
{
  "boards": [
    "myjobmag-ng",
    "jobberman-ng",
    "brightermonday-ke"
  ],
  "keyword": "accountant",
  "postedWithinDays": 7,
  "maxJobsPerBoard": 100,
  "includeDetails": true,
  "dedupeAcrossBoards": true,
  "onlyNewJobs": false,
  "stateKey": "nigeria-daily",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All scraped job postings in the default dataset (JSON, CSV, Excel and more via the dataset export).

## `files` (type: `string`):

Records saved in the default key-value store, e.g. the run summary (RUN_SUMMARY).

# 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 = {
    "boards": [
        "myjobmag-ng",
        "myjobmag-ke",
        "myjobmag-za",
        "jobberman-ng",
        "jobberman-gh",
        "brightermonday-ke",
        "brightermonday-ug",
        "careers24-za"
    ],
    "maxJobsPerBoard": 5,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("openharvest/africa-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 = {
    "boards": [
        "myjobmag-ng",
        "myjobmag-ke",
        "myjobmag-za",
        "jobberman-ng",
        "jobberman-gh",
        "brightermonday-ke",
        "brightermonday-ug",
        "careers24-za",
    ],
    "maxJobsPerBoard": 5,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("openharvest/africa-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 '{
  "boards": [
    "myjobmag-ng",
    "myjobmag-ke",
    "myjobmag-za",
    "jobberman-ng",
    "jobberman-gh",
    "brightermonday-ke",
    "brightermonday-ug",
    "careers24-za"
  ],
  "maxJobsPerBoard": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call openharvest/africa-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,openharvest/africa-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/8n7u7mASjQcmfP8LC/builds/wETlwuy2Qp9kWKJUK/openapi.json
