# MyCareersFuture Scraper + Hiring Company Leads (Singapore) (`snappytools/mycareersfuture-scraper`) Actor

Search MyCareersFuture, Singapore's government job portal. One row per job with salary, plus one row per hiring company with UEN, industry, address, website, emails and phones.

- **URL**: https://apify.com/snappytools/mycareersfuture-scraper.md
- **Developed by:** [Snappy](https://apify.com/snappytools) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 jobs

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

## MyCareersFuture Scraper + Hiring Company Leads (Singapore)

Search MyCareersFuture (mycareersfuture.gov.sg), Singapore's government job portal, the same way you would on the website, and get every matching job as a clean table. Employers on MyCareersFuture have to state a salary range, so jobs come with real monthly figures: in a 300-job test, every one had a salary.

It can also give you **one row per hiring company**: its UEN, official industry, registered address, how many jobs it has open and how many people have applied, and (if you turn it on) the emails, phone numbers and social links from its website and job ads.

Put it on a schedule with **Only new jobs since my last run** and each run returns only jobs you haven't seen before.

### Why this one

- **Data from the official source.** Jobs come from MyCareersFuture's own public data, the same data its website shows. No login, no browser, nothing to break when the page design changes.
- **Hiring companies as leads, not just job rows.** Twenty jobs from the same company become one row with its UEN, website, emails, phone numbers and socials. In a real test (30 accountant jobs, 17 Oct 2026), 17 of the 25 hiring companies came back with an email or phone number (13 of the 14 agencies, 4 of the 11 direct employers), and every one had its registered address.
- **Agencies filtered by their official industry code.** Every company on MyCareersFuture carries its SSIC code. Code 78 means an employment or recruitment agency, so **Leave out recruitment agencies** is exact, not a guess from the name.
- **The real employer behind agency jobs.** When an agency posts a job and names its client, the client becomes the company row, and the agency is listed separately. The agency's own recruiter emails aren't passed off as the client's.
- **Applications count on every job**, and a filter for jobs with few applicants (an employer struggling to fill a role is a good prospect for a recruiter).
- **Pay once per lead.** "Only companies I haven't had before" skips companies an earlier run already gave you.
- **$1 per 1,000 jobs.** No charge for empty searches or status rows.

### Quick start: a lead list in one run

Paste this into the Input tab (JSON view) and click Start.

```json
{
    "searches": ["accountant"],
    "output": "companies",
    "findCompanyContacts": true,
    "skipRecruiters": true,
    "postedWithinDays": "14",
    "maxJobsPerSearch": 200
}
```

You get one row per company that posted an accountant job in the last two weeks, with their contact details, and no recruitment agencies. Turn on **Only companies I haven't had before** and schedule it weekly to get only new companies each time.

### What you can use it for

| Who | What for |
| --- | --- |
| B2B sales teams | A company that's hiring is growing or changing. Find Singapore businesses hiring for roles your product supports (accounts, IT, drivers, F\&B staff) and contact them while it's relevant |
| Recruitment agencies | Find employers advertising directly, and spot jobs with few applicants where your help is most needed |
| Job boards and aggregators | A daily feed of new Singapore jobs in your niche |
| Salary research | Advertised monthly salaries by role, category, position level and years of experience |
| Market and workforce research | Hiring demand by category, industry (SSIC) and region |

### How to use it

1. Click **Try for free**.
2. In **Searches**, add one search per line, using the same keywords you'd type on the site:
   - `accountant`
   - `software engineer`
   - `accountant | Central` (one region: Central, East, North, North-East, West or Islandwide)
   - `*` for every job on the site
   - Or paste the address of a MyCareersFuture search results page. Its keywords and filters are used as they are.
3. Choose **What to return**: Jobs, Jobs and hiring companies, or Hiring companies only.
4. Optionally set filters: posted within N days, category, employment type, position level, minimum monthly salary, region, maximum applications.
5. Click **Start**.

For a daily feed, go to **Schedules**, use the same input, and tick **Only new jobs since my last run**.

### Input example

```json
{
    "searches": [
        "data analyst",
        "https://www.mycareersfuture.gov.sg/search?search=accountant&salary=5000&employmentType=Full%20Time&sortBy=new_posting_date&page=0"
    ],
    "output": "jobsAndCompanies",
    "findCompanyContacts": true,
    "skipRecruiters": true,
    "maxJobsPerSearch": 300,
    "postedWithinDays": "7",
    "positionLevels": ["Executive", "Senior Executive"],
    "maxApplications": 30
}
```

### What data you get

The Output has four tabs: **Jobs**, **Salaries**, **Hiring companies** and **Run status**.

#### Job rows

| Field | What it is | Example |
| --- | --- | --- |
| `jobId`, `uuid`, `title`, `jobUrl` | MyCareersFuture job post ID, internal ID, title and link | `MCF-2026-1403856` |
| `companyName`, `companyUen` | The employer and its Unique Entity Number | `CULTIVAR ASIA PTE. LTD.`, `201715475K` |
| `companyIndustry`, `companySsicCode` | Official industry (SSIC 2020) | `Employment agencies (except domestic worker employment placement agencies)`, `78104` |
| `companyEmployeeCount`, `companyWebsite` | As the employer listed them. Most employers leave the employee count empty | `10`, `https://www.cultivar.asia/` |
| `isRecruitmentAgency` | `true` when the employer's SSIC code is in division 78 (employment activities) | `true` |
| `postedOnBehalf`, `postedBy`, `postedByUen` | When an agency posted the job for a client: the client is the company, and the agency is `postedBy` | |
| `clientNameHidden` | `true` when an agency posted the job and the client chose to stay anonymous | `false` |
| `isConfidential` | `true` when the employer hid its name | `false` |
| `salaryMin`, `salaryMax`, `salaryType` | The advertised range and period | `4000`, `6500`, `Monthly` |
| `salaryMonthlyMin`, `salaryMonthlyMax`, `salaryAnnualMin`, `salaryAnnualMax` | Converted to monthly and yearly figures, in SGD | `4000`, `6500`, `48000`, `78000` |
| `salaryHidden` | `true` when the employer chose not to show the salary | `false` |
| `employmentTypes`, `positionLevels`, `categories` | As on the site | `["Permanent"]`, `["Executive"]`, `["Accounting / Auditing / Taxation"]` |
| `skills`, `keySkills` | Skills listed on the job | `["Monthly Closing", "MYOB"]` |
| `vacancies`, `minimumYearsExperience` | | `5`, `1` |
| `applications`, `views` | How many people have applied and viewed so far | `168`, `1249` |
| `regions`, `areas`, `address`, `postalCode`, `latitude`, `longitude` | Where the job is, when the employer shows it | `["Islandwide"]` |
| `postedAt`, `originalPostedAt`, `expiresAt`, `daysAgo`, `repostCount` | Dates. `postedAt` is the latest posting date (reposts move it forward) | `2026-09-29`, `2026-08-14`, `2026-10-29` |
| `status` | `Open` or `Re-open` | `Re-open` |
| `isResponsiveEmployer` | MyCareersFuture's "responsive employer" badge for the poster | `true` |
| `flexibleWorkArrangements`, `schemes`, `ssocCode`, `occupationId` | Extra details from the job | `33131` |
| `logoUrl`, `search`, `scrapedAt` | | |
| `isNew` | Only with "Only new jobs since my last run" | `true` |

With **Include full job descriptions** you also get `description` (the full text) and `adEmails`, `adPhones` and `adWebsites` (contact details written in it).

Salary conversion: a year is 12 months (a 13th-month bonus isn't added, because most jobs don't say whether there is one). Hourly rates use 40 hours × 52 weeks.

#### Hiring company rows

One row per company (by UEN) across all your searches. Jobs whose employer hid its name can't be linked to a company, so they're left out.

| Field | What it is |
| --- | --- |
| `companyName`, `uen` | The company and its UEN |
| `employeeCount`, `industry`, `ssicCode`, `isRecruitmentAgency` | From its MyCareersFuture profile (the employee count is often empty) |
| `registeredAddress`, `postalCode` | Its registered address, from its MyCareersFuture company record |
| `openJobs`, `jobTitles`, `totalVacancies`, `categories`, `positionLevels`, `employmentTypes`, `regions` | What it's hiring for in this run's results |
| `totalApplications`, `fewestApplications` | How many applications its jobs have, and the lowest for any one job |
| `salaryMonthlyMin`, `salaryMonthlyMax`, `newestPostingDate` | Its salary range and most recent posting |
| `hiresThroughAgencies` | Agencies that posted jobs on its behalf |
| `website`, `websiteSource` | Its website, and where it came from: its MyCareersFuture profile, a web address in its job ad, the domain of an email in its job ad, or (with contact finding on) an address made from the company name whose homepage shows that name |
| `emails`, `phones`, `socials`, `contactPageUrl` | With **Find company emails and phone numbers** on: found on its homepage and contact page, plus any in its own job ads. Own-domain emails come first. Phones are in +65 format, including WhatsApp links |
| `jobAdEmails`, `jobAdPhones` | Contact details written in its own job ads (often the hiring manager) |
| `contactStatus` | `found`, `no website found`, `none found on website`, `website unreachable (...)`, or `not requested` |
| `description`, `logoUrl`, `jobUrls` | Its profile text, logo and links to its jobs |

Example from a real run (search `accountant`, 17 Oct 2026, trimmed):

```json
{
    "type": "company",
    "companyName": "TAN LEE ET FOOD INDUSTRY PTE. LTD.",
    "uen": "201017591N",
    "website": "https://www.tlefood.com/",
    "emails": ["sales@tlefood.com"],
    "phones": ["+65 6268 1135"],
    "contactStatus": "found",
    "openJobs": 1,
    "jobTitles": ["Accountant"],
    "industry": "Manufacture of coffee powder",
    "ssicCode": "10763",
    "isRecruitmentAgency": false,
    "salaryMonthlyMin": 3500,
    "salaryMonthlyMax": 4800,
    "registeredAddress": "12 CHIN BEE CRESCENT, Singapore 619896",
    "websiteSource": "MyCareersFuture company profile"
}
```

#### Run status

One free row per search saying what happened, for example (from a real run): "Delivered 50 jobs. MyCareersFuture shows 750 for this search. Stopped at your limit of 50. 47 left out because the company is registered as an employment agency." Searches with no results and links that can't be used each get their own row, and you aren't charged for jobs from them. There's one more row for the hiring companies. A run summary is saved as `OUTPUT` in the key-value store.

### Pricing

You pay per result, nothing for empty searches or status rows.

| Event | Price |
| --- | --- |
| Job | $1.00 per 1,000 jobs |
| Full job description (only if you turn it on) | $0.50 per 1,000 jobs |
| Hiring company row | $0.002 per company |
| Contact details found for a company (only if you turn it on, and only when at least one email or phone number is found) | $0.008 per company |

So a company lead with contact details costs $0.01, and one where nothing was found costs $0.002. With **Hiring companies only**, you pay for the company rows, not for the jobs used to find them. For example:

| Run | Cost |
| --- | --- |
| 1,000 jobs | $1.00 |
| 1,000 jobs with full descriptions | $1.50 |
| 100 hiring companies only, with contacts found for 60 | $0.68 |

If you set a maximum cost per run, the run stops cleanly at that limit and the status row says so.

### Good to know

- **Filters that MyCareersFuture applies itself** (keywords, category, employment type, position level, minimum salary, sort order) work exactly like the site's. The minimum salary filter keeps jobs whose range reaches that amount, as the site does.
- **Filters this tool applies** (posted within, region, applications, agencies) are checked job by job, so a narrow filter on a broad search may need many pages. The status row says how many jobs each filter left out.
- **Recruitment agencies post a lot of jobs on MyCareersFuture.** In the test above, 14 of the 25 companies were agencies. Agencies also list their contact details more often, so if you want direct employers only, turn on "Leave out recruitment agencies".
- **Agencies** are the companies registered under SSIC division 78 (781xx employment and recruitment agencies, 782xx temporary staffing, 783xx other human resources provision). A company without a code is judged by its name.
- **Hidden details stay hidden.** If an employer hides its name, salary, address or the client it's recruiting for, this tool doesn't show them either.
- **Only new jobs** remembers job IDs per search and filter combination, for about four months. Changing a search or its filters starts fresh. **Only companies I haven't had before** works the same way for companies.
- **Contact details** come only from public pages: the company's own website and its job ads. Nothing behind a login is accessed. Use them in line with Singapore's PDPA and Spam Control Act, and the Do Not Call rules for phone numbers.
- This tool isn't affiliated with or endorsed by MyCareersFuture, Workforce Singapore or the Singapore Government.

### Getting the data out

Download results as Excel, CSV or JSON from the Output tab, or send them on automatically: Google Sheets, Slack, Zapier, Make and webhooks are all in the **Integrations** tab, and every run is available through the Apify API.

### Questions or problems

Open an issue on the **Issues** tab and include the run link. I reply within one business day.

# Actor input Schema

## `searches` (type: `array`):

One search per line, using the same keywords you would type on mycareersfuture.gov.sg, for example "accountant", "software engineer" or "nurse". Add a | and a region to search one part of Singapore: "accountant | Central". Use "\*" for every job on the site. You can also paste the address of a MyCareersFuture search results page; its keywords and filters are kept.

## `output` (type: `string`):

Jobs only, jobs plus one row per hiring company, or just the hiring companies (for lead lists).

## `findCompanyContacts` (type: `boolean`):

For each hiring company, take its website (from MyCareersFuture, its job ads, or its name) and check the homepage and contact page for emails, phone numbers (including WhatsApp links) and social links. Needs "Jobs and hiring companies" or "Hiring companies only". Slower: allow about 1 to 3 seconds per company.

## `maxJobsPerSearch` (type: `integer`):

The most jobs to collect for each search line.

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

Only jobs posted (or reposted) in this many days.

## `categories` (type: `array`):

Only jobs in these MyCareersFuture categories. Leave empty for all.

## `employmentTypes` (type: `array`):

Leave empty for all.

## `positionLevels` (type: `array`):

Leave empty for all.

## `salaryMin` (type: `integer`):

Only jobs whose advertised range reaches at least this much a month, the same way the site's salary filter works. Leave at 0 for no minimum.

## `regions` (type: `array`):

Only jobs in these parts of Singapore. "Islandwide" is what employers pick when there's no fixed worksite. Leave empty for all.

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

Newest first is best for schedules.

## `skipRecruiters` (type: `boolean`):

Leave out companies registered under SSIC code 78 (employment and recruitment agencies, temp staffing, HR provision), using the official industry code on MyCareersFuture. When an agency posts a job and names its client, the job is kept with the client as the company.

## `maxApplications` (type: `integer`):

MyCareersFuture shows how many people have applied. A job with few applicants is one the employer may need help filling. Leave empty for no limit.

## `newCompaniesOnly` (type: `boolean`):

Skip hiring companies that an earlier run already delivered (with the same searches, or the same watch name). Stops you paying twice for the same lead.

## `includeDescriptions` (type: `boolean`):

Adds the full job description as plain text, plus any emails, phone numbers and websites written in it. Costs a little extra per job.

## `onlyNewSinceLastRun` (type: `boolean`):

For schedules. Remembers the jobs each search has already returned and skips them next time. The first run returns everything, as usual.

## `watchName` (type: `string`):

Optional. Runs with the same searches share their memory automatically. Give a name to keep two schedules separate, or to share one company memory across different searches.

## Actor input object example

```json
{
  "searches": [
    "accountant"
  ],
  "output": "jobs",
  "findCompanyContacts": false,
  "maxJobsPerSearch": 20,
  "postedWithinDays": "any",
  "categories": [],
  "employmentTypes": [],
  "positionLevels": [],
  "salaryMin": 0,
  "regions": [],
  "sortBy": "newest",
  "skipRecruiters": false,
  "newCompaniesOnly": false,
  "includeDescriptions": false,
  "onlyNewSinceLastRun": false
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

## `salaries` (type: `string`):

No description

## `companies` (type: `string`):

No description

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

No description

## `allResults` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# 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 = {
    "searches": [
        "accountant"
    ],
    "output": "jobs",
    "maxJobsPerSearch": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("snappytools/mycareersfuture-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 = {
    "searches": ["accountant"],
    "output": "jobs",
    "maxJobsPerSearch": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("snappytools/mycareersfuture-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 '{
  "searches": [
    "accountant"
  ],
  "output": "jobs",
  "maxJobsPerSearch": 20
}' |
apify call snappytools/mycareersfuture-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,snappytools/mycareersfuture-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/DDgjoZ3NRY3VMOmIJ/builds/ncmd6DldidZ30dvNM/openapi.json
