# JobsDB Hong Kong & Thailand Job Listings Extractor (`vexaloom-labs/jobsdb-hong-kong-and-thailand-job-listings`) Actor

Pull job listings from JobsDB's Hong Kong and Thailand markets by keyword, category, location, or company, with salary, work-type, work-arrangement, and date-posted filters.

- **URL**: https://apify.com/vexaloom-labs/jobsdb-hong-kong-and-thailand-job-listings.md
- **Developed by:** [vexaloom-labs](https://apify.com/vexaloom-labs) (community)
- **Categories:** Jobs, Automation, Lead generation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

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

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

## JobsDB Hong Kong & Thailand Job Listings Extractor

JobsDB is the leading job board in Hong Kong and Thailand, part of the SEEK employment network. This actor queries its live search across seven modes — keyword search, category browse, location browse, company browse, latest postings, direct job-ID lookup, or pasted job URLs — and returns one structured record per listing, optionally enriched with the full ad body, applicant-demand signals and the employer's company profile. No login, cookies, or API key required.

### What this actor does

- **Seven modes:** `searchJobs`, `browseByCategory`, `browseByLocation`, `browseByCompany`, `latestJobs`, `byJobId`, `byUrls`
- **Two markets:** Hong Kong (`hk.jobsdb.com`) and Thailand (`th.jobsdb.com`); `byUrls` reads the market from each link, so HK and TH links can be mixed in one run
- **Search filters:** category (29 industries, multi-select), sub-category IDs, work type (multi-select), work arrangement (on-site/hybrid/remote, multi-select), company/advertiser ID (multiple), salary range + period, date posted, sort order
- **Detail enrichment:** full description as plain text, HTML, or both; abstract; expiry/status; applicant count, volume and resume/cover-letter percentages; screening questions; badges; company industry, size, website, description, rating/review count and perks
- **Empty fields are omitted** — every record only contains data that was actually found

### Output per job

- `jobId` — unique JobsDB listing ID
- `title`, `teaser`, `abstract`, `bulletPoints[]` — headline and job highlights
- `descriptionText`, `descriptionHtml` — full ad body in the chosen `descriptionFormat`
- `companyName`, `companyUrl`, `logoUrl`, `coverImageUrl`
- `advertiserId`, `advertiserName`, `advertiserVerified`, `employerId`, `companyId` — internal JobsDB identifiers for the employer/advertiser account (feed `advertiserId` back into `mode=browseByCompany` to pull every current listing from that employer; `companyId` is the ID embedded in `companyUrl`)
- `category`, `categoryId`, `subCategory`, `subCategoryId`
- `location`, `locations[]`, `countryCode`; `locationCountry`, `locationCountryCode`, `locationRegion`, `locationState`, `locationSuburb`, `locationPostcode` when the ad carries a structured address
- `workTypes[]` — e.g. `Full time`, `Part time`, `Contract/Temp`, `Casual/Vacation`
- `workArrangements[]` — e.g. `On-site`, `Hybrid`, `Remote`
- `salaryLabel` — as displayed on the listing (when the advertiser disclosed one)
- `salaryMinValue`, `salaryMaxValue`, `salaryPeriod` (`hourly`/`monthly`/`annual`), `salaryCurrency` (`HKD`/`THB`) — structured numbers parsed from `salaryLabel` when it's a clear numeric range (omitted for non-numeric labels like `Negotiable` or `Competitive`)
- `listingDate`, `listingDateDisplay`, `postedAt`, `postedDate`, `updatedAt`, `expiresAt`, `expiredAt`
- `isFeatured`, `isExpired`, `isLinkOut`, `isProfileApply`, `isVerified`, `jobStatus`
- `tags[]` — urgency/status badges shown on the listing, e.g. `Urgently hiring`, `Expiring soon`, `Be an early applicant`
- `badges[]`, `displayTags[]` — work-arrangement and campaign badges from the ad
- `screeningQuestions[]` — questions the employer asks in the application form
- `applicantCount`, `applicantCountLabel`, `applicantVolumeLabel`, `applicantMessage`, `resumePercentage`, `coverLetterPercentage` — applicant-demand signals when JobsDB exposes them
- `companyIndustry`, `companySize`, `companyWebsite`, `companyDescription`, `companyRating`, `companyReviewCount`, `companyPerks[]`, `companyProfileId` — structured employer profile when one exists
- `normalizedRoleTitle`, `normalizedLocation`, `broaderLocationName`, `normalizedCompanyName` — SEEK's normalized taxonomy values
- `roleId`, `country`, `applyUrl`, `jobUrl`
- `recordType: "job"`, `scrapedAt`

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `searchJobs` | `searchJobs` / `browseByCategory` / `browseByLocation` / `browseByCompany` / `latestJobs` / `byJobId` / `byUrls` |
| `country` | string | `HK` | `HK` (Hong Kong) or `TH` (Thailand); `byUrls` uses each link's host instead |
| `keywords` | string | – | Job title / skill / company keyword; leave empty to browse every job in the selected mode |
| `location` | string | – | Area/city, e.g. `Central`, `Kowloon`, `Bangkok` (required for `browseByLocation`) |
| `classification` | array | – | One or more job category IDs (required for `browseByCategory`) — 29 categories available in the UI multi-select; picking several matches ANY of them |
| `subclassification` | string | – | One or more numeric sub-category IDs, comma-separated, to narrow `classification` further, e.g. `6038` = Project Engineering under Engineering — find one via the `subCategoryId` output field on any scraped job |
| `workType` | array | – | Multi-select: `242` Full time / `243` Part time / `244` Contract/Temp / `245` Casual/Vacation |
| `workArrangement` | array | – | Multi-select: `1` On-site / `2` Hybrid / `3` Remote |
| `advertiserId` | string | – | One or more numeric JobsDB company/advertiser IDs, comma-separated (required for `browseByCompany`) — find one via the `advertiserId` output field on any scraped job |
| `jobId` | string | – | One or more numeric JobsDB job IDs, comma-separated or as an array (required for `byJobId`) — find one via the `jobId` output field on any previously scraped job |
| `urls` | array | – | JobsDB job links or numeric IDs (required for `byUrls`); HK and TH links can be mixed |
| `includeDetails` | boolean | `true` | Fetch full description, expiry, applicant signals, screening questions, badges and company profile (one extra request per job) |
| `descriptionFormat` | string | `text` | `text` / `html` / `both` |
| `salaryMin` | int | – | Minimum salary |
| `salaryMax` | int | – | Maximum salary |
| `salaryType` | string | `annual` | `annual` / `monthly` / `hourly` — period `salaryMin`/`salaryMax` are expressed in |
| `dateRange` | string | – | Only jobs posted in the last `1`/`3`/`7`/`14`/`31` days |
| `sortMode` | string | `KeywordRelevance` | `KeywordRelevance` or `ListedDate` (newest first) |
| `maxItems` | int | `50` | Hard cap on emitted records (1–1000) |

#### Example: search with filters and full details

```json
{
  "mode": "searchJobs",
  "country": "HK",
  "keywords": "data analyst",
  "workType": ["242"],
  "salaryMin": 20000,
  "salaryMax": 40000,
  "salaryType": "monthly",
  "dateRange": "7",
  "sortMode": "ListedDate",
  "includeDetails": true,
  "descriptionFormat": "both",
  "maxItems": 50
}
```

#### Example: browse a category

```json
{
  "mode": "browseByCategory",
  "country": "TH",
  "classification": ["6281"],
  "maxItems": 100
}
```

#### Example: newest jobs in a location

```json
{
  "mode": "browseByLocation",
  "country": "HK",
  "location": "Kowloon",
  "maxItems": 50
}
```

#### Example: all current listings from one company

```json
{
  "mode": "browseByCompany",
  "country": "HK",
  "advertiserId": "61282529",
  "maxItems": 50
}
```

#### Example: remote jobs only

```json
{
  "mode": "searchJobs",
  "country": "HK",
  "keywords": "developer",
  "workArrangement": ["3"],
  "maxItems": 50
}
```

#### Example: look up specific jobs by ID

```json
{
  "mode": "byJobId",
  "country": "HK",
  "jobId": "93889825,93902058"
}
```

#### Example: paste job links from both markets

```json
{
  "mode": "byUrls",
  "urls": [
    "https://hk.jobsdb.com/job/95849989",
    "https://th.jobsdb.com/job/95856128"
  ],
  "includeDetails": true,
  "descriptionFormat": "text"
}
```

### Use cases

- **Job seekers & career sites** — aggregate fresh listings by role, category, location, or remote/hybrid arrangement, with the full ad body
- **Recruiters & staffing agencies** — monitor competitor job postings, screening questions, and applicant demand
- **Labor market research** — track hiring demand and posted salary ranges across categories and regions in HK/TH
- **HR tech / salary benchmarking tools** — collect structured salary ranges plus company industry, size and perks
- **Lead generation** — find companies actively hiring in a given industry
- **Competitor/company tracking** — pull every current listing from a specific employer with `mode=browseByCompany`
- **Shortlist refresh** — re-check saved job URLs for expiry, updated content and current applicant volume

### FAQ

**What's JobsDB?** A leading job board across Hong Kong and Thailand, part of the SEEK employment network. See [jobsdb.com](https://hk.jobsdb.com).

**Do I need to log in or provide cookies?** No. This actor uses JobsDB's public job-search and job-detail endpoints — no account, API key, or proxy required.

**What does `includeDetails` add?** One extra request per job fills the full description, abstract, expiry/status, applicant-demand signals, screening questions, badges, structured location and company profile. Turn it off to scrape more listings faster at a lower cost.

**Why is `descriptionText` missing on some jobs?** A small number of advertisers publish only an external apply link. When the ad has no body, the field is omitted rather than filled with a placeholder.

**Why is `salaryLabel` missing on some jobs?** Not every advertiser discloses a salary. The field is only included when JobsDB displays one.

**Why is `salaryMinValue`/`salaryMaxValue` missing even though `salaryLabel` is present?** A handful of advertisers enter free text instead of a number (e.g. `Negotiable`, `Competitive`, `Attractive Salary Package`). The structured fields are only added when `salaryLabel` is confidently a numeric amount or range — never a guessed value.

**Why are applicant signals missing on some jobs?** JobsDB only exposes them for jobs with enough application activity, and external-apply jobs omit them entirely. The field is absent rather than zeroed when the source has no signal.

**What's the difference between `category` and `subCategory`?** JobsDB classifies each job into a broad category (e.g. `Information & Communication Technology`) and a more specific sub-category (e.g. `Developers/Programmers`).

**How fresh is the data?** Real-time — every run queries JobsDB's live APIs directly.

**Why does `jobUrl` sometimes fail a `curl`/HEAD check?** JobsDB's listing pages sit behind bot-detection that blocks non-browser HTTP clients (no cookies, no JS) while allowing real browsers through normally. The URLs themselves are valid, live links — opening them in a browser works fine. `logoUrl` and `coverImageUrl` point at JobsDB's image CDN and are directly fetchable without a browser.

**Can I use Thai-script place names for `location` in Thailand?** Yes. `location: "กรุงเทพ"` and `location: "Bangkok"` both work for `country: "TH"` — the actor automatically detects Thai-script input and queries JobsDB's Thai-language endpoint so the place name resolves correctly. `category`, `workTypes`, and `workArrangements` are always normalized back to English in the output either way, so filtering/grouping by those fields stays consistent regardless of which locale a record was fetched under. `teaser`, `descriptionText`, `bulletPoints`, `screeningQuestions`, and `subCategory` remain in whatever language the advertiser/JobsDB used (Thai for Thai-script location lookups), since those are free text with no fixed English translation to fall back on.

**Which markets are supported?** Hong Kong and Thailand, JobsDB's two active markets. Other former JobsDB markets (Singapore, Indonesia, Philippines) have migrated to the sister JobStreet/Jora brands and are out of scope for this actor.

**How do I get an `advertiserId` to use with `mode=browseByCompany`?** Run any other mode first and copy the `advertiserId` field from a job posted by the company you're interested in, then feed it back in as input.

**How do I use `subclassification`?** It narrows a `classification` down to a specific sub-category (e.g. only `Developers/Programmers` inside `Information & Communication Technology`). Copy a value from the `subCategoryId` output field on a job you've already scraped in the category you want, then pass it as input on a later run.

**Can I filter by more than one category, work type, or work arrangement at once?** Yes. `classification` and `workType` and `workArrangement` are multi-select — pick as many as you want and a job matching ANY of your selections is returned. `subclassification` and `advertiserId` accept multiple comma-separated IDs the same way, e.g. `advertiserId: "61282529,61359613"` pulls current listings from both companies in one run.

**How do I re-fetch a specific job I already scraped?** Use `mode: "byJobId"` with the `jobId` values, or `mode: "byUrls"` with the saved `jobUrl` links — comma-separated arrays are fine. Useful for refreshing a shortlist or checking whether a listing is still live.

# Actor input Schema

## `mode` (type: `string`):

What to fetch.

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

Which JobsDB market to scrape.

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

Job title, skill, or company to search for. Leave empty to browse every job in the selected mode; an explicit value narrows the results.

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

Free-text location/area to browse or filter by, e.g. `Central`, `Kowloon`, `Bangkok`. Required for mode=browseByLocation.

## `classification` (type: `array`):

Job categor(y/ies). Pick one or more (OR match) — required for mode=browseByCategory; optional filter for other modes. Leave empty for any category.

## `subclassification` (type: `string`):

One or more numeric JobsDB sub-category IDs (comma-separated for OR match) to narrow a `classification` further (e.g. `6038` = Project Engineering under Engineering). Get valid IDs from the `subCategoryId` field on a previously scraped job. Optional filter for any mode.

## `workType` (type: `array`):

Filter by employment type. Pick one or more (OR match). Leave empty for any work type.

## `workArrangement` (type: `array`):

Filter by on-site/hybrid/remote work arrangement. Pick one or more (OR match). Leave empty for any arrangement.

## `advertiserId` (type: `string`):

One or more numeric JobsDB advertiser IDs (comma-separated for OR match) to browse all current listings from one or more employers/agencies. Required for mode=browseByCompany; get an ID from the `advertiserId` field on any previously scraped job. Also works as an optional filter in other modes.

## `jobId` (type: `string`):

One or more numeric JobsDB job IDs (comma-separated, or pass an array) to look up specific listings directly. Required for mode=byJobId; get an ID from the `jobId` field on any previously scraped job. Ignored in other modes.

## `urls` (type: `array`):

One or more JobsDB job links (hk.jobsdb.com/job/… or th.jobsdb.com/job/…) or numeric job IDs to look up directly. Required for mode=byUrls; the market is taken from each link.

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

Fetch each listing's full description, expiry status, applicant-demand signals, screening questions, badges and company profile. Adds one request per job; turn off for the fastest, cheapest run.

## `descriptionFormat` (type: `string`):

How the full job description is returned when details are included.

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

Minimum salary (in the local currency, per `salaryType` period). Leave unset for no lower bound.

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

Maximum salary (in the local currency, per `salaryType` period). Leave unset for no upper bound.

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

Period the salary min/max values are expressed in.

## `dateRange` (type: `string`):

Only include jobs posted within this window.

## `sortMode` (type: `string`):

Result ordering.

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

Hard cap on emitted records.

## Actor input object example

```json
{
  "mode": "searchJobs",
  "country": "HK",
  "keywords": "engineer",
  "location": "",
  "classification": [],
  "subclassification": "",
  "workType": [],
  "workArrangement": [],
  "advertiserId": "",
  "jobId": "",
  "urls": [],
  "includeDetails": true,
  "descriptionFormat": "text",
  "salaryType": "annual",
  "dateRange": "",
  "sortMode": "KeywordRelevance",
  "maxItems": 3
}
```

# Actor output Schema

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

Dataset containing all scraped JobsDB job listings.

# 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 = {
    "mode": "searchJobs",
    "country": "HK",
    "keywords": "engineer",
    "location": "",
    "classification": [],
    "subclassification": "",
    "workType": [],
    "workArrangement": [],
    "advertiserId": "",
    "jobId": "",
    "urls": [],
    "includeDetails": true,
    "descriptionFormat": "text",
    "salaryType": "annual",
    "dateRange": "",
    "sortMode": "KeywordRelevance",
    "maxItems": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("vexaloom-labs/jobsdb-hong-kong-and-thailand-job-listings").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 = {
    "mode": "searchJobs",
    "country": "HK",
    "keywords": "engineer",
    "location": "",
    "classification": [],
    "subclassification": "",
    "workType": [],
    "workArrangement": [],
    "advertiserId": "",
    "jobId": "",
    "urls": [],
    "includeDetails": True,
    "descriptionFormat": "text",
    "salaryType": "annual",
    "dateRange": "",
    "sortMode": "KeywordRelevance",
    "maxItems": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("vexaloom-labs/jobsdb-hong-kong-and-thailand-job-listings").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 '{
  "mode": "searchJobs",
  "country": "HK",
  "keywords": "engineer",
  "location": "",
  "classification": [],
  "subclassification": "",
  "workType": [],
  "workArrangement": [],
  "advertiserId": "",
  "jobId": "",
  "urls": [],
  "includeDetails": true,
  "descriptionFormat": "text",
  "salaryType": "annual",
  "dateRange": "",
  "sortMode": "KeywordRelevance",
  "maxItems": 3
}' |
apify call vexaloom-labs/jobsdb-hong-kong-and-thailand-job-listings --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vexaloom-labs/jobsdb-hong-kong-and-thailand-job-listings"
        }
    }
}
```

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/d1RKTdJH42al1BenR/builds/rIESg6oUkusHumoXj/openapi.json
