SEEK, JobStreet & JobsDB Job Scraper — 8 Markets avatar

SEEK, JobStreet & JobsDB Job Scraper — 8 Markets

Pricing

from $0.50 / 1,000 results

Go to Apify Store
SEEK, JobStreet & JobsDB Job Scraper — 8 Markets

SEEK, JobStreet & JobsDB Job Scraper — 8 Markets

Scrape public job listings from SEEK, JobStreet and JobsDB across 8 APAC markets, one market per run. Export titles, companies, locations and salary fields to JSON, CSV or Excel. Add optional company data and inspect search coverage in RUN_SUMMARY.

Pricing

from $0.50 / 1,000 results

Rating

5.0

(1)

Developer

BlueData

BlueData

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

a day ago

Last modified

Share

SEEK, JobStreet & JobsDB Job Scraper — 8 Markets

Search public job listings across eight SEEK, JobStreet, and JobsDB markets with one consistent dataset. Filter by keywords, location, salary, and category; optionally add company context, descriptions, and application details. Results include source links and market/currency labels to support hiring research and cross-market comparison.

Start with a small search in one market. Optional data groups are off unless selected, and requested groups report observed availability. Field coverage varies by listing and market; results are not guaranteed to include every job or every selected field.

Choose a workflow

  • Regional hiring research: use the same fields across SEEK, JobStreet, and JobsDB, while keeping currency and market labels explicit.
  • Company context: select company profiles, aggregate reviews, work context, and application information without enabling every optional group.
  • Advanced/experimental change tracking: incremental runs may flag new and changed jobs. They are not a guaranteed monitoring or delivery service.
  • AI and automation: choose compact output and inspect run status before treating results as complete.

Salary bands remain in source currencies; the Actor does not convert currencies or infer missing pay. Applicant counts and login-only insights are not supported.

What this Actor does

This actor extracts structured job data from all eight SEEK markets — Australia and New Zealand (SEEK), Singapore, Malaysia, Indonesia, and the Philippines (JobStreet), Hong Kong and Thailand (JobsDB) — including structured salary (min/max/currency/period), real posting datetimes, work types and arrangements, categories, and source links. It supports keyword search, category filters, location and salary post-filters, and controllable result limits, so you can run the same query consistently. Opt-in detail enrichment adds full descriptions, apply links, company pages, classification IDs, and other supported fields; incremental mode can flag changes between runs. These are public-source observations, not proof that a job is still accepting applications or that an employer has officially closed it.

Key features

  • 🌏 Eight-market coverage — one input shape across AU, NZ, SG, MY, HK, TH, ID, and PH, with market-aware currency and locale handling.
  • 📦 Normalized output — consistent fields, source links, and run status make results easy to use in applications and Agent workflows.
  • 💰 Structured salary — salaryMin / salaryMax / salaryCurrency / salaryPeriod when provided by the source (THB, SGD, AUD, NZD, MYR, HKD, IDR, PHP), plus a placeholder status for implausibly low ranges such as monthly "1 - 2 THB". These are parsing heuristics, not evidence of employer intent.
  • 📅 Real posting dates — postedDate/postedDaysAgo from the true UTC timestamp, not a parsed "2 days ago" label.
  • 🎯 Category filter — categoryIds narrows results using source classification data. IDs can be read from enriched records.
  • 📋 Detail enrichment — toggle enrichDetails: true for the full description (plain text), apply link, company page, and classification IDs.
  • ♻️ Incremental mode — recurring runs flag isNew / isUpdated per record from cross-run state. First run builds the baseline; later runs highlight the diff.
  • 🛡️ Output validation — contractCheck: true checks expected fields for scheduled monitoring.
  • 📦 Compact mode — smaller payloads for AI-agent and MCP workflows.

Public contact fields are sensitive and remain off by default; enable them only when needed, and separately allow them in notifications. Data that requires a logged-in session, including applicant insights, is not accessed and is rejected if requested. Activity counts describe listing activity and are not applicant counts. Descriptions and company text use best-effort PII redaction; this is not a guarantee of anonymization.

What data can you extract?

Each result includes core listing fields (jobId, title, company, canonicalUrl, location, market, source, sourceCountry), structured salary (salaryText, salaryMin, salaryMax, salaryCurrency, salaryPeriod, salaryParseStatus), work context (workType, workArrangement, categories, teaser), posting data (postedAt, postedDaysAgo, postedDate, fetchedAt), relevance (queryTermsMatched, relevanceScore), provenance (searchQuery, searchUrl), and — when detail enrichment is on — description, applyUrl, companySearchUrl, classificationIds, subClassificationIds. Optional data groups can add listing metadata, work and location context, classifications, company profile and review summaries, public media, application details, public contacts, listing activity, and source references. Groups are off by default; fields are omitted when the source does not provide them, with availability reported in optionalDataStatus. Incremental runs add isNew / isUpdated.

Important interpretation limits

  • Visibility is not availability. Current Actor metadata (isPublic) controls deployment visibility; the README cannot override it. Public accessibility of a source listing is a separate concept and does not confirm application access or an open vacancy.
  • A posting date is not an open/closed decision. postedDate and postedAt describe when the source reported the listing. The Actor does not guarantee that the employer is still accepting applications, and a reliable closing date is not generally available.
  • Verify links before acting. Treat canonicalUrl as the source citation and applyUrl as a candidate application/share link. Re-check both at use time; a link can redirect, expire, require a login, or no longer represent an active vacancy.
  • Read salary status, not numbers alone. salaryParseStatus: suspicious nulls questionable numeric bounds while preserving readable salaryText. Check salaryWarnings; these are heuristics, not proof of fraud or pay accuracy. structured means parseable, not verified by an employer.
  • isExpired is a search observation. It means a previously seen listing was absent from a later eligible/full search under the incremental scope. It does not mean the employer officially closed the vacancy. It can reflect source changes, filters, or temporary absence; inspect RUN_SUMMARY before acting on it.

Input

The main inputs are search keywords, a market code, and a result limit.

Key parameters:

  • 🔍 Search: query, market (default TH, dropdown, 8 markets), location, startUrls, preset (Basic / Company context / Full optional data), sortMode, and maxResults (accepted output limit; 0 removes the requested output cap subject to safety limits unless exportExcel is enabled. Check run status for partial coverage).
  • 🧩 Flexible API input: query and location accept comma-separated text, JSON-string arrays, or native string arrays. Direct API callers may also use {"terms":["software engineer","data analyst"]} for query. startUrls accepts either URL strings or { "url": "..." } objects. If both query and startUrls are empty, the legacy default query is AI Engineer; an empty query with startUrls uses the URL targets without adding that default.
  • 📊 Custom XLSX: set exportExcel: true for a stateless run with maxResults from 1 through 1000. The requested workbook is available at the enabled-only JOBS.xlsx output link after a successful collection.
  • 🎯 Filters: salaryMin / salaryMax (market home currency, overlap), classification text, categoryIds.
  • 🎯 Post filters: includeKeywords / excludeKeywords, keywordMatch* field toggles, fromDate / toDate / maxAgeMinutes, customFilters (field/op/value engine).
  • 📄 Enrichment: choose preset first. Basic is fastest; Company context enables selected detail groups; Full optional data enables supported public groups except contacts/login-only data. Advanced users may still provide enrichDetails, descriptionMaxLength, or additionalData explicitly.
  • 🧩 Optional data (additionalData.<group>): listingMetadata, workContext, locationDetails, classifications, companyProfile, companyReviews, jobMedia, applicationInfo, publicContacts, sourceActivity, sourceReferences, searchInsights, and applicantInsights. Applicant insights are not available for public searches. Contact fields are sensitive and are sent in notifications only when explicitly allowlisted with notificationDataGroups.
  • 🤖 AI output: compact, excludeEmptyFields, contractCheck.
  • ♻️ Incremental: incremental, stateStoreName, stateKey, emitUnchanged, emitExpired, skipReposts (adds isNew / isUpdated / isExpired / isRepost flags).
  • 🔔 Notifications: webhookUrl / webhookHeaders, discordWebhookUrl, notifyOnlyChanges, notificationLimit, includeRunSummary.

Stateless quick start

Start with this small Basic search, which requests up to five software-engineer listings in Singapore. Standard market input selects one market per run; it does not automatically fan out across all eight markets. Advanced startUrls can select supported hosts and add URL-based searches.

{
"query": "software engineer",
"market": "SG",
"preset": "basic",
"maxResults": 5
}

Optional data groups are off unless selected; public contacts and applicant insights remain off in both examples.

Guidance for AI agents

When choosing inputs programmatically, select exactly one preset unless the user explicitly asks for custom fields:

  • basic: default choice for fast search; no detail enrichment.
  • company: use when the user asks for company, work-context, or application context; it enables the selected public detail groups.
  • full: use only when the user asks for the deepest available public data; it enables supported public groups but never login-only applicant insights or public contacts by default.

Keep market to the user's requested market, set maxResults to the requested bounded count, and use compact: true for agent-oriented output unless the user needs full teaser text. Do not send detailConcurrency; it is managed by the Actor. Do not claim completeness from sourceStatus: partial or coverage_limited, or treat not_provided, restricted, failed, and skipped_budget as equivalent. Read RUN_SUMMARY and optionalDataStatus before summarizing results. The standard input searches one market per run; do not silently fan out across all eight.

Preset reference

Presets add enforced defaults; additionalData may add more groups but a preset's groups cannot be turned off with false. Choose preset: "basic" when no enrichment is requested. It leaves enrichDetails off and enables no optional groups. preset: "company" enables exactly workContext, companyProfile, and applicationInfo; it is intended for company/work/application context and does not enable every optional group. Choose Basic instead of Company when those groups must stay off. preset: "full" enables enrichDetails by default plus these public groups: listingMetadata, workContext, locationDetails, classifications, companyProfile, companyReviews, jobMedia, applicationInfo, sourceActivity, sourceReferences, and searchInsights. Neither preset turns on publicContacts or applicantInsights; applicant insights are not available for public searches. Use explicit additionalData or enrichDetails only when the preset choices do not match the user's request. If a caller explicitly sends enrichDetails: false with Full, the optional preset groups still run but legacy description/apply-link enrichment is disabled. Preset values are lowercase basic, company, or full.

Input formats and location handling

Market selection does not automatically translate Malay or Indonesian query phrases into English. Submit the exact search terms you want sent to the source. Location names are not translated arbitrarily. The Actor retains the original location and reports the effective source location used; location filtering is not a guarantee of complete geographic coverage.

The saved INPUT preserves the original input, RUN_SUMMARY.locations reports canonical values, and RUN_SUMMARY.inputNotes records normalization. Each searchPlan entry and processed searches entry exposes originalLocation (the value you sent) and effectiveLocation (the source label actually used); for example originalLocation: "香港" and effectiveLocation: "Hong Kong". A location filter is a post-filter, not a guarantee of complete geographic coverage. If the source stops before full coverage, read sourceStatus, searches[].stopReason, searches[].filteredOut, and searches[].coverageComplete before concluding that no jobs exist. Hit the output cap and hitting a fetch-window cap are reported separately through resultLimitReached and coverageLimitReached.

Accepted API examples:

{
"query": ["software engineer", "data analyst"],
"market": "TH",
"location": "Bangkok",
"startUrls": ["https://th.jobsdb.com/th/software-engineer-jobs"],
"maxResults": 10,
"preset": "basic"
}
{
"query": {"terms": ["software engineer", "data analyst"]},
"market": "TH",
"startUrls": [{"url": "https://th.jobsdb.com/th/software-engineer-jobs"}]
}

If RUN_SUMMARY.sourceStatus is coverage_limited, inspect coverageLimitedSearches, searches[].coverageLimit, and filteredBy. The result is not a full-market result and is not proof that the source has no other matching jobs. coverageLimitReached: true means the fetch window ended before the source was exhausted; resultLimitReached answers the different question of whether the requested maxResults output cap was met. A run can show resultCount below maxResults with resultLimitReached: false because the coverage window was capped or because filters rejected the remaining candidates; filteredBy and coverageLimit distinguish those causes.

Query matching and language handling

Eight markets does not mean every query language is translated or understood. Queries are not automatically translated; submit the exact phrase you want searched. Supported query matching is literal evidence, not semantic search or a language translation service.

Choose queryMatchMode:

  • source (default): preserves source ranking when choosing candidates, but only records with complete target-specific literal evidence are emitted. Zero-evidence suggestions are filtered before output and never appear with relevanceScore: 0.
  • Non-Latin queries require literal evidence in the selected fields. Thai in TH and Chinese in HK are supported as exact text: the original query is sent unchanged, with no translation or added synonyms. Complete coverage with no literal match returns no_results and languageSupport: supported; incomplete coverage returns coverage_limited and languageSupport: unknown.
  • For the SG source, an Arabic- or Korean-script query whose first result page contains candidates but no literal query match is terminally marked unsupported_language instead of paging through generic fallback results. Literal matches on that page remain eligible. This terminal outcome counts as completed, but not fully covered; it does not set truncated.
  • all_terms: requires every normalized query token across the selected fields. ASCII tokens use word boundaries (engineer does not match engineering); other scripts use literal matching without a linguistic word segmenter.
  • phrase: requires the contiguous query phrase in a selected field.

The default strict-query fields are title, teaser and actual description. keywordMatch* selects a narrower field set. With no explicit include/exclude keywords, selecting fields also enables all-token query validation, so keywordMatchTitle: true with junior software engineer rejects a title that only says Software Developer. With explicit include/exclude keywords the old keyword-only scope is preserved; set queryMatchMode: all_terms to enforce both. Description-based query validation waits for requested enrichDetails and uses real description text, not teaser text mislabeled as description.

matchedTerms and matchedFields are literal evidence, not a semantic score. Every emitted relevanceScore is 1 for this search target, meaning all required token/phrase evidence was found in the original or normalized query. queryTermsMatched names the validated target query, not an incidental match to a different query in the batch. This is never a confidence probability. Strict checks can reject relevant synonyms and do not guarantee semantic accuracy. An empty complete Thai/Chinese exact query is no_results; a known unsupported language/source combination is unsupported_language. Configured page/scan or candidate caps return coverage_limited, not partial, and keep truncated: true plus coverageLimit so consumers know the source was not fully covered.

{
"query": "junior software engineer",
"market": "TH",
"location": "Bangkok",
"queryMatchMode": "all_terms",
"keywordMatchTitle": true,
"maxResults": 10,
"preset": "basic"
}

Search URLs

startUrls is optional and additive to explicitly supplied keyword searches. It accepts an array of HTTPS search/category URL strings or objects such as {"url":"https://th.jobsdb.com/th/developer-jobs"}. URLs must use one of the 8 supported SEEK/JobStreet/JobsDB hosts, must be search/category pages (not a homepage or detail page), and support at most 20 entries. A URL's host selects the market for that target; when a URL is supplied without a query, the search term is derived from its URL slug. If both query and startUrls are supplied, both sets of searches run and duplicate normalized URLs are merged. A URL-only input does not receive a hidden schema-default keyword. RUN_SUMMARY.searchPlan exposes each target URL and inputSource (query, startUrl, or a merged value) so default-query and URL-duplication cases are distinguishable. market still controls generated keyword searches and remains a useful explicit default. Example:

{
"query": "developer",
"market": "TH",
"startUrls": [{"url":"https://th.jobsdb.com/th/developer-jobs"}],
"preset": "basic",
"maxResults": 25
}

Filters and field semantics

  • classification matches text in source category labels; categoryIds matches source classification IDs and is not a salary or internal database ID. Read classification fields from an enriched sample before reusing an ID.
  • includeKeywords requires every term to match; excludeKeywords rejects a record when any term matches. With no keywordMatch* toggle, matching uses the legacy set of title, company, location, work type, teaser, category, and description. Selecting one or more keywordMatchTitle, keywordMatchCompany, keywordMatchLocation, keywordMatchCategory, or keywordMatchDescription restricts matching to those fields.
  • customFilters is a JSON array of {field, op, value} objects. Supported operators are includes, notIncludes, equals, notEquals, gt, gte, lt, and lte. Example: [ {"field":"salaryMin","op":"gte","value":50000} ]. A filter on an optional group requires that group in additionalData; a description/apply/classification detail filter requires enrichDetails or the corresponding optional group.

Filter order and missing values

The Actor maps and validates source listings first, then applies location, keyword, salary, date, classification, and any non-detail customFilters before accepting a candidate. Accepted candidates are deduplicated by job ID and fetched for requested detail groups. Filters that need detail fields (for example description or an optional company field) run after enrichment. When incremental/repost logic is enabled, change flags are computed after the final record is assembled; formatting such as compact happens last. maxResults counts final accepted output records, so deferred filters may inspect more source candidates or detail records than the output count.

Include/exclude keyword matching is case-insensitive substring matching; unlike strict query checks, it has no token boundaries, stemming, or language-aware segmentation. Missing keyword fields behave as empty text. If a salary threshold is set, records with no structured salary are excluded; records in a different currency are kept rather than incorrectly converted. Records with no parseable posting date are kept by date filters. A missing custom-filter field fails positive operators (includes, equals, numeric comparisons) and passes negative operators (notIncludes, notEquals). Multiple custom filters are ANDed together; multiple include terms are ANDed and exclude terms are ORed.

Salary plausibility

salaryParseStatus is structured, placeholder, suspicious, or not_provided. Suspicious heuristics cover implausible lower bounds crossing a known currency/period floor, ranges of at least 100:1, and multiple numeric bands in one display label. Example: AUD 1–500,000/year is not treated as a trustworthy structured range: numeric bounds become null and readable text remains. Salary thresholds exclude such null-bound records. salaryWarnings explains flags; foreign_currency alone keeps a structured range because legitimate foreign-currency offers exist. No automatic currency conversion is performed.

Multi-query execution and the global result cap

maxResults is a run-wide output cap, not a per-query output quota and not a guarantee that the source scan reaches every listing. The Actor fetches a candidate window sized for scanning (at least 100 listings per source page for bounded runs), because market filters can reject most candidates after fetching. For a multi-query or multi-URL plan, a best-effort run divides the source candidate allowance across targets so every planned target gets an execution opportunity before the final output cap is applied. A fill_target run may intentionally skip later targets once the run-wide output target is met. The Actor never emits more than maxResults; source request/time budgets, challenges, interruptions, and source scan/page caps can leave searches incomplete. Detail request limits are reported separately.

coverageMode selects how hard the run tries to reach maxResults:

  • best_effort (default): the bounded scan that stops at the standard safety limits. Use RUN_SUMMARY coverage facts to see whether the target was met.
  • fill_target: keeps scanning and enriching candidates until maxResults matched jobs are emitted, the source is exhausted, or a hard run limit (global candidate cap, page cap, request budget, or time budget) is reached. The page and detail-candidate safety windows are widened for that run; the hard caps and the run time budget still apply. Once the run-wide target is met, remaining planned targets are skipped with stopReason: max_results. Pricing is unchanged: only emitted dataset items and the start event are charged, and rejected candidates are not charged.

RUN_SUMMARY exposes:

  • plannedSearches: distinct targets after URL/alias deduplication.
  • attemptedSearches: targets whose source attempt started, including prefetches whose results were subsequently discarded. Retries do not inflate this count.
  • processedSearches: targets executed in this run, excluding skipped, discarded, and checkpoint-resumed targets.
  • completedSearches: target outcomes that reached a terminal result in this run, including coverage_limited and intentional maxResults caps. It excludes source request/time/challenge failures and failed attempts.
  • fullyCoveredSearches: processed targets for which the source reported complete pagination. A cap-limited search can be processed and completed while remaining not fully covered.
  • coverageLimitedSearches: processed targets stopped by an explicit source page/candidate cap; inspect their coverageLimit for the configured window.
  • Run-level resultLimitReached: final accepted output reached the requested maxResults cap. coverageLimitReached: a source page/candidate window cap cut collection short. They are independent. Legacy limitReached remains a broader cap signal; use the explicit flags to distinguish output limits from incomplete source coverage. Per-search flags report whether that target itself stopped at either limit. Final run-wide trimming can set the run-level output flag while a fully scanned search has stopReason: null and per-search resultLimitReached: false.
  • sourceStatus and searches[].status describe source search only. detailStatus describes enrichment separately; a detail-request budget (maxDetailRequests) or detail failure does not make a completed source search partial or truncated. The separate detail-candidate coverage cap is a source coverage limit and can make the run coverage-limited.
  • detailsEnriched counts valid detail responses per candidate listing, not final dataset rows. It can exceed resultCount when candidates are filtered after enrichment. detailsFailed counts unsuccessful detail responses.
  • coverageMode, requestedResults, targetReached, and shortfall state how close the run came to the maxResults target; candidatesScanned and candidatesRejected show the work done; run-level stopReason explains why the run stopped (target_reached, candidate_limit, time_limit, request_limit, interrupted, challenge, source_exhausted, early_stop). resultNotice is a short human-readable note when the target was not met; a TARGET_NOT_REACHED warning is added only for fill_target runs with a shortfall.
  • incrementalStateStatus is ready, run_scoped, unavailable, or not_requested. State-load failure suppresses isNew/isUpdated rather than claiming every result is new; check the warning INCREMENTAL_STATE_UNAVAILABLE.
  • baselineComplete and expiredRecordsReliable are reported for incremental runs. An incomplete coverage window makes the baseline partial: expiration markers are suppressed, expiredRecords stays 0, and the BASELINE_INCOMPLETE warning explains why.
  • searches[].coverageLimit identifies the page/candidate safety cap and configured limit when source coverage was cut short.
  • skippedSearches / discardedSearches: never attempted versus attempted but not processed; resumedTargets identifies work already completed in a checkpoint.
  • allSearchesProcessed: every planned target was processed, checkpoint-resumed, or intentionally skipped because a fill_target run had already met its output target.
  • searchPlan: each target's URL, original/normalized query, market, originalLocation/effectiveLocation, inputSource (query, startUrl, or a merged/duplicate value), attempted, executionStatus (processed, skipped, discarded, resumed) and reason such as max_results, request_limit, time_limit, circuit_breaker, unsupported_language, interrupted, or already_completed.

Reaching the output cap does not by itself skip later planned targets. A target that reaches its fair source quota can have stopReason: max_results while the batch remains sourceStatus: ok; fullyCoveredSearches stays lower than the completed count because the source was intentionally bounded. Explicit page, global-candidate, and detail-dependent candidate caps use sourceStatus: coverage_limited, COVERAGE_LIMIT_REACHED, and truncated: true; this describes incomplete source coverage, not an Actor failure. Request/time/challenge or mapping failures remain partial or a stronger failure status. Pure enrichment failures are reported in detailStatus without downgrading an otherwise completed source search.

Run summary reference

RUN_SUMMARY is available at /records/RUN_SUMMARY. Treat it as part of the result, not as an internal log. A representative successful or partial summary contains fields such as:

{
"resultCount": 20,
"detailsEnriched": 20,
"detailsFailed": 0,
"sourceStatus": "ok",
"limitReached": true,
"resultLimitReached": true,
"coverageLimitReached": false,
"truncated": false,
"optionalDataStatus": {
"companyProfile": {"status": "available", "available": 20}
},
"requestBudget": {
"source": {"limit": 50, "consumed": 21, "remaining": 29},
"detail": {"limit": 20, "consumed": 20, "remaining": 0}
}
}

The example is illustrative: counts and optional groups vary by input. Check sourceStatus, relevanceStatus, matched/unscored counts, searchPlan, resultCount, detailsFailed, truncated, stop/coverage fields, and each requested group's status before reporting success. Optional data can be missing for a legitimate listing, restricted, skipped by budget, or failed to load; these cases are not interchangeable.

For a retry, first distinguish partial/timeout/request_limit/challenge from a completed ok run. coverage_limited means the Actor ran but the source window was capped; inspect coverageLimit before deciding whether to increase work. Increase the run timeout or relevant process budget only when the user permits more work; do not treat a second run as a guaranteed continuation or as a deduplicated global result set. contractCheck: true checks required record shape, not source completeness. An empty completed search does not itself violate the record contract.

warning/warnings flag coverage caps, incomplete searches, unconfirmed literal relevance, and salary review needs. searches[].filteredBy counts location, relevance, keywords, salary, date, classification and custom rejections so a salary rejection is not misdiagnosed as an unresolved location. failureStage/failureCode expose bounded codes such as rate_limited, challenge_detected, coverage_limit_reached and contract_failure, never raw source errors or credentials.

A platform SUCCEEDED run can contain sourceStatus: coverage_limited with zero records: the Actor ran successfully, but the configured source window ended before full coverage. The run log emits status/stage/code and warnings. Genuine source or validation errors still fail with safe diagnostics. Changing query-validation mode resets the incremental baseline; the updated scope fingerprint can also reset baselines from older builds. Concurrent monitoring safety is still not guaranteed.

apify call bluedata/seek-jobstreet-jobsdb-scraper --input-file examples/basic-search.json --json
apify call bluedata/seek-jobstreet-jobsdb-scraper --input-file examples/company-context.json --json

maxResults is an accepted-output limit. maxSourceRequests and maxDetailRequests are process-only request controls (failed attempts and retries consume them); none of these inputs is a money or price cap. Detail fetch concurrency is managed automatically by the Actor and is not a user-facing input. Review RUN_SUMMARY and sourceStatus before treating a run as complete. Requested optional groups can be available, not_provided, restricted, failed, or skipped_budget; these statuses describe observed availability, not a completeness guarantee.

Input examples

Choose your company enrichment — a small sample without enabling public contacts or login-only data. Switch market to reuse this workflow elsewhere.

{
"query": "software engineer",
"market": "SG",
"preset": "company",
"maxResults": 3,
"additionalData": {
"companyProfile": true,
"workContext": true,
"applicationInfo": true
}
}

Selected detail groups fetch listing details even when enrichDetails is false; that switch separately controls legacy description/apply-link enrichment. Inspect optionalDataStatus and RUN_SUMMARY before using the results.

Recommended Agent-first search — Singapore Senior Software Engineer jobs with compact output.

{
"query": "Senior Software Engineer",
"market": "SG",
"preset": "basic",
"maxResults": 25,
"compact": true,
"contractCheck": true
}

ADVANCED / EXPERIMENTAL recurring monitor — reuse the same state store and key on every run. This is not the recommended quick start: concurrent monitor state, persistence failures, and resume request budgets are not hardened, and there is no production safety guarantee. The named store is opened in Apify cloud storage and created on first use. If state cannot be loaded or saved, check incrementalStateStatus and the INCREMENTAL_STATE_UNAVAILABLE warning; records are not falsely marked isNew when no baseline was loaded. Expiration markers are emitted only from a complete baseline: a coverage-limited run reports baselineComplete: false and expiredRecordsReliable: false, suppresses emitExpired output, and adds the BASELINE_INCOMPLETE warning.

{
"query": "Backend Engineer",
"market": "SG",
"maxResults": 50,
"compact": true,
"incremental": true,
"stateStoreName": "my-private-hiring-state",
"stateKey": "sg-backend-daily",
"notifyOnlyChanges": true
}

Basic keyword search — 25 Thailand developer jobs, no frills.

{
"query": "developer",
"market": "TH",
"maxResults": 25
}

Singapore with details — full descriptions and apply links included.

{
"query": "software engineer",
"market": "SG",
"maxResults": 25,
"enrichDetails": true,
"additionalData": {
"companyProfile": true,
"companyReviews": true
}
}

Australia, software-only + incremental — category filter and change flags for a scheduled monitor.

{
"query": "developer",
"market": "AU",
"maxResults": 100,
"categoryIds": "6290",
"incremental": true,
"stateStoreName": "my-private-hiring-state",
"stateKey": "au-developer-monitor"
}

Output

Each run produces a dataset of structured job records, downloadable as JSON, CSV, or the native Excel export from the Dataset tab in Apify Console, plus a RUN_SUMMARY record with counts, per-search status, optional-data availability, and source status. Canonical JSON is the representation for nested optional groups; it preserves the null/empty-value shape. Native Dataset export behavior is unchanged: its JSON, CSV, and native Excel renderings may flatten nested objects and arrays into separate columns and may omit or reshape null and empty values; it is not the custom workbook.

Optional custom XLSX

Set exportExcel to true on a stateless run (incremental must be false) and set maxResults to 1–1000. After collection returns, the enabled-only output link downloads the fixed JOBS.xlsx record from the default key-value store. The run summary is at /records/RUN_SUMMARY; inspect excelExport.status, rowCount, recordKey, and the source coverage fields before using the file. A failed requested export is reported as failed and is not advertised as available. A partial or coverage-limited source run may still provide a workbook of its acknowledged rows, but its status does not claim complete source coverage.

The custom workbook keeps a readable Jobs sheet with field/part rows and a Canonical sheet whose JSON chunks reconstruct the projected Dataset records exactly, including nested values, nulls, empty values, and large integers. Long text is split into UTF-16-safe cell parts. XML-invalid controls and lone surrogates use reversible \u{HEX} display encodings, while canonical JSON is ASCII encoded with underscore escaping to protect literal Excel escape sequences. These encodings are workbook-specific; native Dataset exports remain unchanged.

Use optionalDataStatus to distinguish available, not_provided, restricted, failed, and skipped_budget; these values do not assert complete source coverage. For operational decisions, also inspect salaryParseStatus, re-check canonicalUrl/applyUrl, and do not interpret isExpired as an official employer closure.

Example job record (illustrative)

{
"source": "jobsdb",
"sourceCountry": "TH",
"market": "TH",
"jobId": "12345678",
"title": "Full Stack Developer",
"company": "Example Employer",
"canonicalUrl": "https://th.jobsdb.com/job/12345678",
"location": "Chiang Mai",
"salaryText": "40,000 – 50,000 THB per month",
"salaryMin": 40000,
"salaryMax": 50000,
"salaryCurrency": "THB",
"salaryPeriod": "month",
"salaryParseStatus": "structured",
"workType": "Full time",
"workArrangement": "Office",
"categories": ["Software"],
"teaser": "Build and maintain web applications.",
"postedDaysAgo": 2,
"postedDate": "2026-10-14",
"postedDateTime": "2026-10-14T00:00:00+00:00",
"queryTermsMatched": ["developer"],
"relevanceScore": 1,
"sourceStatus": "ok",
"searchQuery": "developer",
"searchUrl": "https://th.jobsdb.com/th/developer-jobs",
"fetchedAt": "2026-10-16T00:00:00+00:00"
}

postedAt is intentionally omitted from this illustrative record: it is the source's relative display label (for example, "2 days ago"), while postedDate/postedDateTime are normalized UTC values. Do not parse postedAt as an ISO timestamp. Neither field confirms that the vacancy is still open; there is usually no reliable employer closing date.

Limits and short glossary

  • maxResults: accepted final output cap, default 20, hard ceiling 10,000, range 0–10,000. 0 removes the requested output cap; the Actor still applies run, page, and candidate safety limits, and a large scan may be required. It is not a spending limit. Custom JOBS.xlsx requires 1–1,000.
  • coverageMode: best_effort (default) or fill_target. fill_target widens the page and detail-candidate windows and scans until maxResults is met or a hard run limit is reached, then reports targetReached, shortfall, and a run-level stopReason. It is still bounded by the global candidate cap, request budgets, and the run time budget; it is not a spending limit. With maxResults: 0, fill_target is echoed but remains bounded like best_effort; set a positive maxResults to enable target filling.
  • maxSourceRequests: optional process-run budget for search/detail requests, range 0–20,000; failed attempts and retries consume it. Omitted preserves the legacy behavior.
  • maxDetailRequests: optional process-run detail-request budget, range 0–1,000; explicit 0 disables detail requests. It is separate from output count.
  • maxRetries: retries after failed source requests, default 2, range 0–10.
  • Detail concurrency: the number of detail requests the Actor may process in parallel. It is automatically managed (currently default 8) and is not a user-facing input; it is different from maxDetailRequests, which is a total request budget.
  • Enrichment: fetching detail records after search results, such as full descriptions, application links, or optional company groups.
  • Canonical JSON: the lossless nested record representation. Native Dataset CSV/Excel may flatten or reshape it; custom JOBS.xlsx includes a Canonical reconstruction sheet.
  • Partial coverage: the run stopped before establishing complete source coverage. It is not a successful-complete status.

The Actor also enforces safety limits on source scanning and detail candidates; those limits can produce a truthful partial run and are not price guarantees.

Pricing and resource use

The current default configuration uses 256 MB memory. Current pay-per-event pricing is $0.0005 per default-dataset item ($0.50 per 1,000 items) plus $0.00005 per run-start event. A run that produces no dataset items still incurs the start event. For example, 20 default-dataset items and one start event cost $0.01005 USD before any other adjustments or tax. Expiration markers in the default dataset are items too; pricing is not limited to newly found jobs, and there is no separate enrichment event charge in the current configuration. coverageMode: fill_target may scan and enrich more candidates, but the billed events are unchanged: emitted dataset items and the run start; rejected candidates are not charged. Memory above 1 GB increases the number of start events. Consult the Actor's current pricing tab as the authority for configured prices and usage. This example is not a spend cap; result and request limits do not cap charges.

How to run it

  1. Open this actor in Apify Console.
  2. Enter search keywords, pick a market, and set maxResults.
  3. Turn on enrichDetails if you need descriptions and apply links.
  4. Click Start and wait. The actor reports coverage_limited when a configured page/candidate cap stops paging, and partial for request/time or challenge failures.
  5. Export the dataset as JSON, CSV, or Excel.

AI Agent / MCP quick start

The Actor exposes a structured input/output contract that can be called by an authorized Agent or MCP client when enabled in the account.

CLI

Inspect the input contract before calling:

$apify actors info bluedata/seek-jobstreet-jobsdb-scraper --input

Call with structured JSON and machine-readable output:

$apify call bluedata/seek-jobstreet-jobsdb-scraper --input '{"query":"Senior Software Engineer","market":"SG","maxResults":25,"compact":true}' --json

Install Apify MCP into an authorized local AI client:

apify mcp install codex
# or: apify mcp install claude-code
# or: apify mcp install cursor

For safer Agent selection, expose only the tools/Actors needed for the workflow with --tools. Do not embed a development token in a shared config.

Agent calling rules

  • Use this Actor for supported public job-search intents; abstain when a request needs login-only data or unsupported markets/fields. Public contact fields are off by default and require explicit user intent.
  • Prefer compact: true for tool calls and use enrichDetails: true only when descriptions/apply links are required.
  • Treat sourceStatus, coverage/stop reasons, RUN_SUMMARY, and partial status as part of the answer; do not claim completeness when a run is partial.
  • Treat recurring monitoring as advanced/experimental: concurrent state, persistence failures, and resume budgets are not hardened, so there is no production safety guarantee. If used, keep the same stateStoreName/stateKey and review every run rather than assuming durable or concurrent safety.

This section documents the CLI/MCP interface only. Availability and service levels depend on the account configuration and current listing.

Use cases

  • Salary comparisons across APAC markets with structured, currency-tagged pay bands.
  • Recruitment sourcing and talent mapping with company, category, and date filters.
  • Advanced/experimental new-job monitoring with incremental change flags; no production safety guarantee is implied.
  • Cross-country hiring-trend analysis on one schema.
  • AI-agent and MCP pipelines via compact mode and the auto-generated OpenAPI spec.
  • Enrichment of hiring-company context; public contact fields are a separate sensitive opt-in.

Availability

Pricing is described above; the Actor's current pricing tab is authoritative. Actual run duration and available data vary with market, query, requested fields, source availability, and platform conditions. No fixed runtime, completeness, or recurring delivery is guaranteed.

FAQ

Which markets are supported?

All eight SEEK markets: AU, NZ (SEEK), SG, MY, ID, PH (JobStreet), HK, TH (JobsDB). One market code per run.

How many results can I get?

maxResults limits accepted output records after final filters. It is not a network-request or spending cap: enrichment and filters can require processing more candidates than the final output count. Set a run timeout and review usage. Runs that cannot complete the requested search report their status in the run summary; inspect coverage before treating results as complete.

Does it support recurring monitoring?

The existing incremental API remains available as an advanced/experimental feature. Enable incremental, reuse the same stateStoreName and stateKey, and keep the search scope stable. Concurrent state, persistence failures, and resume request budgets are not hardened; there is no production safety guarantee. The first run establishes a baseline and later runs may flag new or changed jobs, but each run's status and coverage must be reviewed. contractCheck checks required output fields, not the availability of every optional field.

Can I use it with the Apify API / MCP?

Yes, when the Actor is enabled for your account. Use the documented input and output contract and inspect run status before claiming complete coverage.

This actor extracts publicly available data without login-only access or credential use. Public contact fields are disabled by default and included only when explicitly requested; review the source terms and applicable privacy laws before using them. Descriptions and company text receive best-effort contact redaction; this does not guarantee anonymization.

Disclaimer

This actor accesses only publicly available data. You are responsible for how you use the extracted data and for complying with each site's terms of use and applicable data-protection law. This actor is an independent tool and is not affiliated with, endorsed by, or sponsored by SEEK, JobStreet, or JobsDB. All trademarks are the property of their respective owners.