# Dice Jobs Scraper | Tech Hiring Change Feed (`danthedataman/dice-tech-jobs-ledger`) Actor

Search Dice tech jobs or supply job URLs. Export available descriptions, salary and skills, then track changes across runs. Fields and coverage depend on Dice.

- **URL**: https://apify.com/danthedataman/dice-tech-jobs-ledger.md
- **Developed by:** [Eli J](https://apify.com/danthedataman) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.06 / 1,000 saved dice job or change rows

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/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

### What you can export

Use this **Dice jobs scraper** for technology recruiting and hiring research. Search by keywords and location, or supply job URLs, to export job identities, available descriptions, salary information, skills and employer context.

Optional incremental mode reports changes across runs. Source fields vary by listing, and observed disappearance is subject to the completion safeguards described below.

### Try the example input

```json
{
  "queries": [
    "python developer"
  ],
  "maxResults": 30
}
```

The form prefills that example. API input `{}`, omitted queries, null queries or empty input lists produce no rows and exit successfully with guidance. A `startUrls` array replaces generated queries; an explicit empty array means no work. Invalid inputs and unreadable jobs are stored in the run's `ERRORS` record and never sold as dataset rows.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `queries` | array | Form prefill \["python developer"] | One or more keywords. Omitted, null or \[] produces no generated searches. |
| `location` | string | `""` | Single city, state or country. Used when locations is omitted. |
| `locations` | array | — | Multiple locations combined with every query. \[] generates no searches. |
| `startUrls` | array | — | Dice search, keyword SEO or job-detail HTTPS URLs. When supplied, replaces queries; \[] means no work. |
| `maxResults` | integer | Form prefill 30; API 100 | Total emitted row limit, 0–10000. 0 is unlimited, including change rows. |
| `maxResultsPerSource` | integer | `0` | Emitted jobs per input source. 0 is unlimited. |
| `maxPages` | integer | `0` | Pages per search source. 0 continues until the source ends or repeats. |
| `pageSize` | integer | `30` | Requested page size, 1–100. Dice may use its own page size. |
| `radius` | integer | `30` | Location radius in the selected distance unit; 0 is passed through. |
| `unit` | string | `"mi"` | Distance unit: mi or km. |
| `postedDate` | string | `"ANY"` | ANY, ONE, THREE, SEVEN, FOURTEEN or THIRTY. 14/30 days are filtered locally by source posting time. |
| `employmentType` | array | `[]` | Employment filters: FULLTIME, PARTTIME, CONTRACTS, THIRD_PARTY. |
| `employerType` | array | `[]` | Employer filters: Direct Hire, Recruiter, Other. |
| `workplaceTypes` | array | `[]` | Workplace filters: Remote, Hybrid, On-Site. |
| `includeRemote` | boolean | `true` | Include remote jobs; false excludes them in generated searches unless workplaceTypes is selected, which takes precedence. |
| `easyApply` | boolean | `false` | Only Easy Apply jobs in generated searches when true. |
| `willingToSponsor` | boolean | `false` | Only sponsorship jobs in generated searches when true. |
| `includeDetails` | boolean | `true` | Read full detail pages. false uses search summaries; direct job URLs still read the detail. |
| `includeCompanyProfile` | boolean | `false` | Read public employer overview and company metadata when an ID is available; missing values stay null. |
| `compact` | boolean | `false` | Emit core identity, title, employer, location, salary, dates and URLs only. |
| `descriptionMaxLength` | integer | `0` | Maximum characters per description representation; 0 keeps full descriptions. |
| `descriptionFormat` | string | `"all"` | all, text, html or markdown. Markdown uses plain text, which is valid Markdown. |
| `excludeEmptyFields` | boolean | `false` | Remove top-level nulls, empty strings and empty arrays. |
| `incrementalMode` | boolean | `false` | Persist a scoped baseline and emit NEW, UPDATED and REAPPEARED jobs. |
| `stateKey` | string | `""` | Stable tracker key; omitted derives one from sources, detail/profile mode and local date filter. Serialize runs using a shared key. |
| `emitUnchanged` | boolean | `false` | Also emit UNCHANGED jobs in incremental mode. |
| `emitExpired` | boolean | `false` | Emit EXPIRED jobs only after a fully traversed successful scan; caps, failures and clamps suppress expiration. |
| `skipReposts` | boolean | `false` | Suppress a new identity with content matching a tracked inactive job. |
| `maxConcurrency` | integer | `30` | Concurrent detail reads, 1–40. Higher values need measured target and memory headroom. |
| `maxRetriesPerRequest` | integer | `3` | Retries after the first request, 0–8. Classified failures rotate to a fresh session. |
| `rotateAfterRequests` | integer | `48` | Proactive session retirement request count; 0 disables proactive retirement. |
| `maxRunSeconds` | integer | `240` | HTTP work budget in seconds, 1–86400. Partial traversal is recorded in RUN_STATS. |
| `proxyConfiguration` | object | — | Omitted starts direct and uses Apify StaticUS3 datacenter fallback after HTTP 403/429 or a recognized challenge. Explicit settings are passed to the SDK and disable automatic fallback. |

Filters apply to generated searches. Pasted search URLs retain their filters and page number. When workplaceTypes is selected, Dice gives that selection precedence over includeRemote: its effective includeRemote setting is true for selections containing Remote and false for Hybrid or On-Site alone. A job can list several workplace types. The Actor verifies the echoed workplace filter and the effective source setting. A pasted job URL reads that job even when `includeDetails` is false. Radius and units are sent to Dice's public search page; location resolution is performed by Dice. Unknown or missing sponsorship and location values remain null.

### Examples

Search batches across the United States and Canada:

```json
{"queries":["python developer","data engineer"],"locations":["Austin, TX, USA","Toronto, ON, Canada"],"maxResults":100,"maxPages":3}
```

Remote jobs posted recently:

```json
{"queries":["java developer"],"postedDate":"SEVEN","workplaceTypes":["Remote"],"employmentType":["FULLTIME"],"maxResults":50}
```

Paste a search URL or a current job URL:

```json
{"startUrls":["https://www.dice.com/jobs?q=data+engineer&location=Toronto%2C+ON%2C+Canada"],"maxResults":20}
```

Track a query on repeat runs:

```json
{"queries":["python developer"],"maxResults":100,"incrementalMode":true,"stateKey":"python-hiring"}
```

Search summaries and compact output:

```json
{"queries":["python developer"],"includeDetails":false,"compact":true,"maxResults":20}
```

### Output

Every normal row describes one identified job, with `recordType` and `type` equal to `job`. The GUID in `id` is the stable deduplication key; `jobId` is Dice's separate legacy identifier. All listed normal fields are emitted, including nulls for unavailable data. Change-feed fields appear only in incremental mode. Compact mode and description selection deliberately reduce the fields. Results are in the default dataset and can be exported through Apify as JSON, CSV or Excel.

| Group | Fields |
| --- | --- |
| Stable identity | `recordType`, `type`, `id`, `jobKey`, `guid`, `dice_id`, `jobId`, `positionId`, `position_id`, `identity` |
| Job and company | `title`, `company`, `companyName`, `companyProfileId`, `clientBrandId`, `companyPageUrl`, `companyUrl`, `companyLogoUrl`, `companyProfile`, `entities`, `media` |
| Location and workplace | `location`, `jobLocation`, `postalCode`, `applicantLocationRequirements`, `sourceCountry`, `employmentType`, `employmentTypeStructured`, `employerType`, `workplaceTypes`, `workSetting`, `isRemote`, `workFromHomeAvailability` |
| Salary | `salary`, `salaryText`, `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryUnit`, `pricing` |
| Application and contacts | `easyApply`, `willingToSponsor`, `applyType`, `applyUrl`, `contactName`, `skills`, `extractedEmails`, `extractedPhones`, `extractedUrls`, `socialProfiles`, `attributes`, `availability` |
| Descriptions | `summary`, `description`, `descriptionHtml`, `description_text`, `description_html`, `descriptionMarkdown`, `descriptionLength`, `content` |
| Source times | `postedDate`, `posted`, `postDate`, `modifiedDate`, `updated`, `validThrough`, `timestamps` |
| Source and search context | `url`, `canonicalUrl`, `detailsPageUrl`, `portalUrl`, `source`, `sourceDomain`, `sourceUrl`, `searchUrl`, `searchQuery`, `searchId`, `searchPage`, `searchPageSize`, `searchSortBy`, `searchIncludeRemote`, `score`, `isSponsored`, `isFeatured`, `detailFetched`, `contentQuality`, `fetchedAt`, `scrapedAt`, `source_context` |
| Change feed | `changeType`, `isRepost`, `repostOfId`, `repostDetectedAt` |

`salaryMin` and `salaryMax` preserve source values, including nonnumeric placeholders such as "Depends on Experience". Only numeric values are usable as amounts; check `salaryCurrency` and `salaryUnit` (such as HOUR or YEAR) separately. A null unit means Dice did not supply one; the Actor does not guess it from an unlabelled amount. `descriptionLength` is the full plain-text character count before optional truncation. `score` is Dice's source relevance score, with no normalization. Search page numbers and page sizes count source pages and source records. Times preserve source ISO values; `scrapedAt` and `fetchedAt` are UTC extraction timestamps. Unknown booleans remain null.

The grouped `identity`, `source_context`, `content`, `entities`, `media`, `pricing`, `timestamps`, `attributes` and `availability` objects carry the corresponding identity, discovery, job, company, salary, time and application data. Structured location is in `jobLocation`; `location` stays a readable string. Contacts and URL arrays contain only matches found in the public description. Company profile links are passed through. With includeCompanyProfile, companyProfile contains the public employer ID, legacy_company_id, company_info (name, website, founded, employees, categories, office policy, account type, logo, locations and social media), general_information (overview HTML and header), technologies, company_technologies and overviewText. These fields can be null. An unavailable profile adds an ERRORS record while retaining the independently identified job.

#### Legacy field mapping

Snake-case names map to canonical fields: `company_page_url` → `companyPageUrl`, `company_name` → `companyName`, `company_logo_url` → `companyLogoUrl`, `job_id` → `jobId`, `posted_date` → `postedDate`, `modified_date` → `modifiedDate`, `salary_min`/`salary_max` → `salaryMin`/`salaryMax`, and `description_text`/`description_html` retain their spellings. Grouped `location.formatted_location` → `location`; `location.locality`, region, country and postal code → `jobLocation.addressLocality`, `addressRegion`, `addressCountry` and `postalCode` when the detail source supplies them. Summary location parts use Dice’s original keys in `jobLocation`. The grouped identity, content, entities, media, pricing, timestamps, attributes and availability fields are retained as documented above.

### Change tracking

The first run emits NEW jobs and stores a baseline in a named key-value store. Repeat runs compare stable job identities and observable content, ignoring retrieval times, search IDs and optional employer-profile enrichment. Stable source job fields drive updates, including remote status, employer type, applicant location and contact name. Employer-profile-only changes do not create job change events. Default output includes NEW, UPDATED and REAPPEARED records. `emitUnchanged` includes UNCHANGED records. A new identity matching an inactive job’s role content can identify a repost; a previously tracked identity returning is REAPPEARED. `skipReposts` suppresses its output.

EXPIRED means an earlier job was absent from a fully traversed, successful scan of the same searches after local date filtering. It is a search-scope observation, not proof that a vacancy closed. Failures, page clamps, row/page limits and time limits suppress expiration. State changes for emitted rows follow successful delivery; a row rejected at the customer's charge limit is not acknowledged. Run one tracker at a time per state key. Changing its source universe, detail/profile mode or local date filter requires another key.

### Coverage and limits

Dice can widen the search radius when no jobs match. A response reporting zero jobs, zero total results and zero pages is accepted as an empty answer with guidance. A populated response whose reported radius differs from the requested radius is rejected; widened jobs are not presented as matching that radius.

The Actor reads public Dice search and job-detail pages without a Dice account. US and Canada locations are supported by the page route; exact results depend on Dice. Some search windows clamp or repeat deep pages. The Actor detects this, records the incomplete scan in ERRORS/RUN_STATS, and stops that source. Partition large collections by keyword, location or posted date; it makes no claim to a complete global Dice census or a guaranteed row count.

When proxyConfiguration is omitted, requests start direct and use concurrent requests with fresh-session retries for source errors, blocks and rate limits. HTTP 403/429, or a 200 response whose parsing fails with the recognized Just a moment challenge title, activates datacenter fallback within the remaining HTTP work budget; explicit proxy settings, including useApifyProxy false, disable automatic fallback. The log records fallback activation once per run. Concurrent failures on the same retired session share a fresh retry session. A 404/410 job is an unresolved input rather than a paid result. Completed jobs are saved in batches; only the delivered prefix advances feed state when a charge limit truncates a batch. Work already in flight is cancelled when the charge limit binds. The log reports aggregate concurrent HTTP seconds by warm-up, search, detail, company, session wait and retry wait, plus classified retry counts. `RUN_STATS` records delivered rows, request count, decoded response-body bytes and whether source traversal completed; these bytes are not a proxy billing meter. Zero-result searches finish successfully. Only a run where all attempted work failed is marked failed.

Dice's robots file disallows query searches. Its terms restrict automated retrieval. Review the current source terms for your use. This Actor is independent of Dice. Salary, description, sponsorship, company and contact completeness varies by listing; missing data is never fabricated.

The transport reuses HTTP/1.1 sessions and reads only requested pages, with an eight-second request wait before classified retry within the overall work budget. The warm-up timing category is zero because no extra homepage request is made.

Runs support 256–4096 MB in Apify's run settings. Memory also determines CPU allocation: 1024 MB supplies one quarter of a CPU core and 4096 MB supplies one full core.

### Billing and support

Apify's synthetic `apify-actor-start` event is charged once up to 1024 MB and once per additional gigabyte (2048 MB: two units; 4096 MB: four). The custom processing event remains once per run at every memory allocation.

The listing's pay-per-event prices apply to the platform's start event, saved result rows and one `dice-run-processing` event per run, including empty or invalid-input runs. This processing event covers run initialization; its price is set by the listing. The Actor stops before fetching if the processing fee cannot be charged or no result row remains affordable, and also stops when result delivery reaches the limit. If processing initialization raises an error, the run fails with a reason in ERRORS before collecting jobs. Error records are kept out of the dataset. No notification delivery or account access is included. Use Apify's Issues tab for support and include your input, run URL and an example of the expected record.

# Changelog

This Actor's version history is a separate document: https://apify.com/danthedataman/dice-tech-jobs-ledger/changelog.md

# Actor input Schema

## `queries` (type: `array`):

One or more keywords. Omitted, null or \[] produces no generated searches.

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

Single city, state or country. Used when locations is omitted.

## `locations` (type: `array`):

Multiple locations combined with every query. \[] generates no searches.

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

Dice search, keyword SEO or job-detail HTTPS URLs. When supplied, replaces queries; \[] means no work.

## `maxResults` (type: `integer`):

Total emitted row limit. 0 is unlimited, including change rows.

## `maxResultsPerSource` (type: `integer`):

Emitted jobs per input source. 0 is unlimited.

## `maxPages` (type: `integer`):

Pages per search source. 0 continues until the source ends or repeats.

## `pageSize` (type: `integer`):

Requested page size, 1–100. Dice may use its own page size.

## `radius` (type: `integer`):

Location radius in the selected distance unit; 0 is passed through.

## `unit` (type: `string`):

Distance unit: mi or km.

## `postedDate` (type: `string`):

ANY, ONE, THREE, SEVEN, FOURTEEN or THIRTY. 14/30 days are filtered locally by source posting time.

## `employmentType` (type: `array`):

Employment filters: FULLTIME, PARTTIME, CONTRACTS, THIRD_PARTY.

## `employerType` (type: `array`):

Employer filters: Direct Hire, Recruiter, Other.

## `workplaceTypes` (type: `array`):

Workplace filters: Remote, Hybrid, On-Site.

## `includeRemote` (type: `boolean`):

Include remote jobs; false excludes them in generated searches unless workplaceTypes is selected, which takes precedence.

## `easyApply` (type: `boolean`):

Only Easy Apply jobs in generated searches when true.

## `willingToSponsor` (type: `boolean`):

Only sponsorship jobs in generated searches when true.

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

Read full detail pages. false uses search summaries; direct job URLs still read the detail.

## `includeCompanyProfile` (type: `boolean`):

Read public employer overview and company metadata when an ID is available; missing values stay null.

## `compact` (type: `boolean`):

Emit core identity, title, employer, location, salary, dates and URLs only.

## `descriptionMaxLength` (type: `integer`):

Maximum characters per description representation; 0 keeps full descriptions.

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

all, text, html or markdown. Markdown uses plain text, which is valid Markdown.

## `excludeEmptyFields` (type: `boolean`):

Remove top-level nulls, empty strings and empty arrays.

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

Persist a scoped baseline and emit NEW, UPDATED and REAPPEARED jobs.

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

Stable tracker key; omitted derives one from sources, detail/profile mode and the local date filter. Serialize runs using a shared key.

## `emitUnchanged` (type: `boolean`):

Also emit UNCHANGED jobs in incremental mode.

## `emitExpired` (type: `boolean`):

Emit EXPIRED jobs only after a fully traversed successful scan; caps, failures and clamps suppress expiration.

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

Suppress a new identity with content matching a tracked inactive job.

## `maxConcurrency` (type: `integer`):

Concurrent detail reads, 1–40. Higher values need measured target and memory headroom.

## `maxRetriesPerRequest` (type: `integer`):

Retries after the first request, 0–8. Classified failures rotate to a fresh session.

## `rotateAfterRequests` (type: `integer`):

Proactive session retirement request count; 0 disables proactive retirement.

## `maxRunSeconds` (type: `integer`):

HTTP work budget in seconds, 1–86400. Partial traversal is recorded in RUN_STATS.

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

Omitted starts direct and uses Apify StaticUS3 datacenter fallback after HTTP 403/429 or a recognized challenge. Explicit settings are passed to the SDK and disable automatic fallback.

## Actor input object example

```json
{
  "queries": [
    "python developer"
  ],
  "location": "",
  "maxResults": 30,
  "maxResultsPerSource": 0,
  "maxPages": 0,
  "pageSize": 30,
  "radius": 30,
  "unit": "mi",
  "postedDate": "ANY",
  "employmentType": [],
  "employerType": [],
  "workplaceTypes": [],
  "includeRemote": true,
  "easyApply": false,
  "willingToSponsor": false,
  "includeDetails": true,
  "includeCompanyProfile": false,
  "compact": false,
  "descriptionMaxLength": 0,
  "descriptionFormat": "all",
  "excludeEmptyFields": false,
  "incrementalMode": false,
  "stateKey": "",
  "emitUnchanged": false,
  "emitExpired": false,
  "skipReposts": false,
  "maxConcurrency": 30,
  "maxRetriesPerRequest": 3,
  "rotateAfterRequests": 48,
  "maxRunSeconds": 240
}
```

# Actor output Schema

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

No description

## `errors` (type: `string`):

No description

## `coverage` (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 = {
    "queries": [
        "python developer"
    ],
    "maxResults": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("danthedataman/dice-tech-jobs-ledger").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 = {
    "queries": ["python developer"],
    "maxResults": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("danthedataman/dice-tech-jobs-ledger").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 '{
  "queries": [
    "python developer"
  ],
  "maxResults": 30
}' |
apify call danthedataman/dice-tech-jobs-ledger --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,danthedataman/dice-tech-jobs-ledger"
        }
    }
}
```

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/qEeoMC98pP5IxftZP/builds/U4lk8OLkVKSY7HHgb/openapi.json
