# JobStreet Scraper — SG, MY, PH & ID Jobs with Salary (`unfenced-group/jobstreet-scraper`) Actor

Extract job listings from JobStreet across Singapore, Malaysia, Philippines and Indonesia. Keyword search, work type filter, structured salary fields, company logo, and full descriptions (with details enabled). Incremental mode for scheduled monitoring. No API key required.

- **URL**: https://apify.com/unfenced-group/jobstreet-scraper.md
- **Developed by:** [Unfenced Group](https://apify.com/unfenced-group) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Stats:** 14 total users, 2 monthly users, 96.3% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.79 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

## JobStreet Scraper

![JobStreet Scraper](https://api.apify.com/v2/key-value-stores/lJlifu6C8YQfZMPKE/records/jobstreet-scraper)

Extract job listings from JobStreet across Singapore, Malaysia, the Philippines and Indonesia. Full descriptions, structured salary, company names, precise location and change tracking. No API key required.

***

### Why this scraper?

#### 🎯 Built for Southeast Asia

Four JobStreet markets in one actor: Singapore, Malaysia, the Philippines and Indonesia. Salary parsing and currency handling are tuned per market (SGD, MYR, PHP, IDR).

#### 📄 Complete job data

Job title, full description in HTML, plain text and Markdown, apply URL, publish date and work arrangement. Turn on full details for the complete description and company data.

#### 💶 Structured salary

Minimum, maximum, pay period and currency, parsed from each listing. Fields are always present and returned as `null` when the employer doesn't publish a figure.

#### 🔁 Incremental monitoring

Run on a schedule and receive only what changed. Every result carries a change status of new, updated or reappeared. Unchanged reposts are skipped and never charged.

#### 🔎 Multi-query batches

Search several keywords in a single run. Results are merged and de-duplicated, so one scheduled run can cover an entire hiring watchlist.

#### 🤖 Agent-ready output

A compact output mode returns a reduced set of core fields for AI agents, MCP clients and automated pipelines.

***

### Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `country` | string | `"SG"` | Target market: `SG`, `MY`, `PH` or `ID`. |
| `searchQuery` | string | `"developer"` | Job title, skill or keyword. Leave empty to fetch all recent listings. |
| `searchQueries` | array | (empty) | Run several keyword searches in one run. Overrides `searchQuery` when set. |
| `location` | string | (empty) | City or region, e.g. `Singapore`, `Kuala Lumpur`, `Manila`. Empty means all locations. |
| `workType` | string | (empty) | Filter by `Full time`, `Part time`, `Contract/Temp` or `Casual/Vacation`. |
| `classificationId` | integer | (empty) | Optional category ID to filter by job category. |
| `salaryMin` | integer | (empty) | Only return listings paying at or above this amount (server-side). Units set by `salaryType`. |
| `salaryMax` | integer | (empty) | Only return listings paying at or below this amount (server-side). Units set by `salaryType`. |
| `salaryType` | string | `monthly` | Period for the salary filter: `monthly`, `annual` or `hourly`. |
| `daysOld` | integer | (empty) | Only return listings posted within this many days. |
| `includeKeywords` | array | (empty) | Keep only listings whose title, company or description contains one of these words. |
| `excludeKeywords` | array | (empty) | Drop listings whose title, company or description contains one of these words. |
| `maxAgeMinutes` | integer | (empty) | Drop listings older than this many minutes. For near-real-time monitoring. |
| `fetchDetails` | boolean | `false` | Fetch the full description and company data. Off returns basic listing info only. |
| `incrementalMode` | boolean | `false` | Return only new or changed listings since the previous run. |
| `skipReposts` | boolean | `false` | Skip listings already seen in previous runs. Superseded by incremental mode. |
| `compactOutput` | boolean | `false` | Return a reduced set of core fields for agents and pipelines. |
| `startUrls` | array | (empty) | Fetch specific JobStreet job page URLs directly instead of searching. |
| `maxItems` | integer | `100` | Maximum listings to return across all queries. Set to `0` for unlimited. |

***

### Output schema

Every field below is present on every record. Fields the source does not publish for a given job are returned as `null` rather than omitted. Description fields are populated when `fetchDetails` is on.

| Field | Type | Description |
|---|---|---|
| `id` | string | JobStreet job ID. |
| `url` | string | Canonical job page URL. |
| `title` | string | Job title. |
| `company` | string|null | Employer name, cleaned of stray formatting. |
| `companyUrl` | string|null | Employer profile URL where published. |
| `companyLogo` | string|null | Employer logo URL where published. |
| `location` | string|null | Leaf location, e.g. city or district. |
| `locationSuburb` | string|null | District or suburb, when the source splits it out. |
| `locationState` | string|null | Region or state, when the source splits it out. |
| `country` | string | Market code: `SG`, `MY`, `PH` or `ID`. |
| `workTypes` | array | Employment types, e.g. `["Full time"]`. |
| `workArrangement` | string|null | On-site, hybrid or remote, when published. |
| `roleId` | string|null | Normalised role slug, e.g. `full-stack-developer`. |
| `salaryLabel` | string|null | Raw salary text as shown on the listing. |
| `salaryMin` | number|null | Minimum salary (null if not published). |
| `salaryMax` | number|null | Maximum salary (null if not published). |
| `salaryCurrency` | string|null | Currency code for the market. |
| `salaryPeriod` | string|null | `HOUR`, `MONTH` or `YEAR`. |
| `classifications` | array | Category and subcategory, e.g. `["Hospitality & Tourism › Waiting Staff"]`. |
| `bulletPoints` | array | Listing highlight bullets where published (empty array when none). |
| `teaser` | string|null | Short listing teaser. |
| `isFeatured` | boolean | Whether the listing is a featured placement. |
| `isExpired` | boolean | Whether the listing is expired. |
| `publishDateISO` | string|null | Publish date in ISO 8601. |
| `descriptionHtml` | string|null | Full description as HTML (with `fetchDetails`). |
| `descriptionText` | string|null | Full description as plain text (with `fetchDetails`). |
| `descriptionMarkdown` | string|null | Full description as Markdown (with `fetchDetails`). |
| `source` | string | Source host, e.g. `sg.jobstreet.com`. |
| `scrapedAt` | string | Timestamp the record was scraped, ISO 8601. |
| `contentHash` | string | Fingerprint of the tracked content, used for change detection. |
| `changeStatus` | string | `NEW`, `UPDATED`, `UNCHANGED` or `REAPPEARED`. |
| `isRepost` | boolean | Whether this listing was seen in a previous run. |
| `originalPublishDate` | string|null | Publish date first seen for a repost. |
| `originalUrl` | string|null | URL first seen for a repost. |

#### Example record

```json
{
  "id": "93154010",
  "url": "https://sg.jobstreet.com/job/93154010",
  "title": "F&B Service Crew",
  "company": "Song Gye Ok",
  "companyUrl": null,
  "location": "Orchard",
  "locationSuburb": "Orchard",
  "locationState": "Central Region",
  "country": "SG",
  "workTypes": ["Part time"],
  "workArrangement": "On-site",
  "salaryLabel": "$13 – $15 per hour",
  "salaryMin": 13,
  "salaryMax": 15,
  "salaryCurrency": "SGD",
  "salaryPeriod": "HOUR",
  "classifications": ["Hospitality & Tourism › Waiting Staff"],
  "teaser": "If you're excited about our company's vision for this role, we'd like to hear from you.",
  "isFeatured": false,
  "isExpired": false,
  "publishDateISO": "2026-07-06T15:50:25Z",
  "descriptionHtml": "<div><p>Job Title: Front of House Staff (Part-time)...</p></div>",
  "descriptionText": "Job Title: Front of House Staff (Part-time). Department: F&B Operations...",
  "descriptionMarkdown": "**Job Title: Front of House Staff (Part-time)**...",
  "source": "sg.jobstreet.com",
  "scrapedAt": "2026-07-06T17:14:59Z",
  "contentHash": "6576fdbc4d9ee925",
  "changeStatus": "NEW",
  "isRepost": false,
  "originalPublishDate": null,
  "originalUrl": null
}
```

***

### Examples

**Search for developer roles in Singapore:**

```json
{
  "country": "SG",
  "searchQuery": "developer",
  "location": "Singapore",
  "maxItems": 100
}
```

**Full-time nursing roles in Malaysia, full details:**

```json
{
  "country": "MY",
  "searchQuery": "nurse",
  "workType": "Full time",
  "fetchDetails": true,
  "maxItems": 200
}
```

**Batch several searches in one run, excluding senior roles:**

```json
{
  "country": "PH",
  "searchQueries": ["accountant", "auditor", "bookkeeper"],
  "excludeKeywords": ["senior", "manager"],
  "maxItems": 300
}
```

**Daily monitoring feed — only what changed:**

```json
{
  "country": "SG",
  "searchQuery": "data analyst",
  "daysOld": 1,
  "incrementalMode": true,
  "maxItems": 200
}
```

***

### 💰 Pricing

**$0.89 per 1,000 results** — you only pay for successfully retrieved listings. Failed retries and unchanged reposts are never charged.

| Results | Cost |
|---|---|
| 100 | ~$0.10 |
| 1,000 | ~$0.89 |
| 10,000 | ~$8.90 |
| 100,000 | ~$89.00 |

> Flat-rate alternatives typically charge $29–$49/month regardless of usage.

Subscription tiers lower the rate further, down to $0.59 per 1,000 at the highest tier. In incremental mode you pay only for new and changed listings, which cuts the cost of recurring monitoring sharply. Use the **Max results** cap to control your spend exactly.

***

### Performance

| Run size | Typical time |
|---|---|
| 100 results | under 1 minute |
| 1,000 results | 2–4 minutes |
| 10,000 results | 20–35 minutes |

***

### Known limitations

- Salary is often absent. Many employers don't publish a figure, so salary fields are frequently `null`.
- Region and suburb are split from the location label only when the source provides them; some listings carry a single location token.
- Full descriptions require `fetchDetails`. With it off, description fields are `null` and the run is faster and cheaper.
- Coverage is limited to the four JobStreet markets: Singapore, Malaysia, the Philippines and Indonesia.

***

### Technical details

- **Source:** JobStreet — Singapore, Malaysia, the Philippines and Indonesia
- **Coverage:** nationwide per market, filterable by location, category and work type
- **Retry:** automatic retry on network errors with exponential backoff

***

### Need a custom scraper?

**[Unfenced Group](https://www.unfencedgroup.nl)** builds Apify actors for any website — for free.

If the site you need isn't in our portfolio yet, just ask. We scope, build, and publish it at no cost to you. You only pay for results — we absorb the compute and proxy costs ourselves. Same pay-per-result pricing, same quality, same standards as every actor in this portfolio.

**Get in touch:** [www.unfencedgroup.nl](https://www.unfencedgroup.nl)

# Actor input Schema

## `country` (type: `string`):

Target market. Supported: SG (Singapore), MY (Malaysia), PH (Philippines), ID (Indonesia).

## `searchQuery` (type: `string`):

Job title, skill, or keyword to search for. Leave empty to fetch all recent listings. For several searches in one run, use Search queries below instead.

## `searchQueries` (type: `array`):

Run several keyword searches in a single run. Results are merged and de-duplicated. Overrides the single search query above when set. Leave empty to use the single field.

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

City or region name, e.g. 'Singapore', 'Kuala Lumpur', 'Manila'. Leave empty for all locations.

## `workType` (type: `string`):

Filter by employment type.

## `classificationId` (type: `integer`):

Optional SEEK classification ID to filter by job category (e.g. 1209 for ICT). Leave blank for all categories.

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

Only return listings with a salary at or above this amount, filtered server-side by JobStreet. Interpreted in the units set by Salary period below. Leave blank for no lower bound.

## `salaryMax` (type: `integer`):

Only return listings with a salary at or below this amount, filtered server-side by JobStreet. Interpreted in the units set by Salary period below. Leave blank for no upper bound.

## `salaryType` (type: `string`):

The period the salary min/max are expressed in. Applies only when a salary filter is set.

## `daysOld` (type: `integer`):

Only return listings posted within this many days. Leave blank for no date filter.

## `includeKeywords` (type: `array`):

Keep only listings whose title, company, or description contains at least one of these words. Applied after the search. Leave empty to keep everything.

## `excludeKeywords` (type: `array`):

Drop any listing whose title, company, or description contains one of these words, e.g. 'senior', 'internship'. Applied after the search.

## `maxAgeMinutes` (type: `integer`):

Drop listings older than this many minutes. Useful for near-real-time monitoring on a schedule. Leave blank for no limit.

## `fetchDetails` (type: `boolean`):

ON: fetches full job description, salary, company info, and all details. OFF: returns basic listing info only (faster, lower cost).

## `incrementalMode` (type: `boolean`):

Only return listings that are new or changed since the previous run. Each result carries a changeStatus of NEW, UPDATED, or REAPPEARED. Unchanged reposts are skipped and never charged. Ideal for scheduled monitoring.

## `skipReposts` (type: `boolean`):

Skip listings already seen in previous runs (based on 90-day deduplication cache). Superseded by incremental mode, which also reports what changed.

## `compactOutput` (type: `boolean`):

Return a reduced set of core fields (id, title, company, location, salary, work type, date, changeStatus). Smaller payload for AI agents, MCP clients, and automated pipelines.

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

Optional: provide specific JobStreet job page URLs to fetch directly instead of searching.

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

Maximum number of listings to return across all queries. Default 5.

## Actor input object example

```json
{
  "country": "SG",
  "searchQuery": "developer",
  "searchQueries": [
    "data analyst",
    "devops"
  ],
  "location": "",
  "workType": "",
  "salaryType": "monthly",
  "includeKeywords": [
    "remote"
  ],
  "excludeKeywords": [
    "internship"
  ],
  "fetchDetails": false,
  "incrementalMode": false,
  "skipReposts": false,
  "compactOutput": false,
  "maxItems": 100
}
```

# Actor output Schema

## `results` (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 = {
    "searchQuery": "developer",
    "searchQueries": [
        "data analyst",
        "devops"
    ],
    "includeKeywords": [
        "remote"
    ],
    "excludeKeywords": [
        "internship"
    ],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("unfenced-group/jobstreet-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 = {
    "searchQuery": "developer",
    "searchQueries": [
        "data analyst",
        "devops",
    ],
    "includeKeywords": ["remote"],
    "excludeKeywords": ["internship"],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("unfenced-group/jobstreet-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQuery": "developer",
  "searchQueries": [
    "data analyst",
    "devops"
  ],
  "includeKeywords": [
    "remote"
  ],
  "excludeKeywords": [
    "internship"
  ],
  "maxItems": 100
}' |
apify call unfenced-group/jobstreet-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=unfenced-group/jobstreet-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/8NCISZEZgViHWfO4u/builds/55cmllGSmvM8rwe5n/openapi.json
