# CutShort Jobs Scraper · Salary & skills · $1/1K (`mlg14/cutshort-scraper`) Actor

Collect CutShort job listings by skill, category URL or job URL for recruiting research, salary benchmarking and job-market tracking. Returns salary, experience, skills, locations, remote status, company details and posting dates, with change tracking. $1/1K jobs.

- **URL**: https://apify.com/mlg14/cutshort-scraper.md
- **Developed by:** [MLG Data](https://apify.com/mlg14) (community)
- **Categories:** Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## CutShort Scraper

Scrape CutShort public job listings by skill, category URL, or direct job URL. Export CutShort jobs to JSON, CSV, or a spreadsheet with salary ranges, experience, skills, locations, remote status, posting dates, and public company details. This actor is a practical CutShort API alternative for teams that need a repeatable job feed without maintaining their own collection process.

Each result represents one unique public job. The actor combines overlapping categories, keeps the source's displayed pay separate from internal numeric bounds, and can mark jobs as new, updated, or unchanged across scheduled runs. It reads public job data only; applying for a role may still require a site account.

### What data can you extract from CutShort?

The default output contains the core job fields below. Optional fields appear when the relevant input switch is enabled. Empty source values remain `null` or empty arrays; the actor does not invent a location, salary, employer, or posting date.

| Field | Description | Example |
| --- | --- | --- |
| `jobId` | Stable source identifier for the posting | `6ab22a15b2a99f741b0afe28` |
| `jobUrl` | Canonical public job page | Public `/job/` URL |
| `applyUrl` | Source application link, which may require an account | Public link or `null` |
| `title` | Posted role title | `Data Engineer` |
| `descriptionText` | Job description converted to readable plain text | Role duties and requirements |
| `descriptionHtml` | Source-sanitized markup, when enabled | Paragraph markup |
| `skills` | Skills attached to the posting | Source skill labels |
| `locations` | Structured source locations | `[]` for some remote roles |
| `locationsText` | Displayed location label | `Remote only` |
| `roleTypes` | Source role categories | `full_time` |
| `remoteType` | Exact source remote setting | `remote_only` |
| `remote` | Conservative remote-only normalization | `true`, `false`, or `null` |
| `hiringForClient` | Whether the poster is hiring for a client | `false` |
| `experienceMin` | Displayed lower experience bound, in years | `3` |
| `experienceMax` | Displayed upper experience bound, in years | `10` |
| `salaryMin` | Displayed lower pay bound in source currency | `1100000` |
| `salaryMax` | Displayed upper pay bound in source currency | `1500000` |
| `salaryInternalMin` | Separate numeric lower bound in the public payload | Number or `null` |
| `salaryInternalMax` | Separate numeric upper bound in the public payload | Number or `null` |
| `salaryCurrency` | Source currency code | `INR` |
| `salaryPeriod` | Period explicitly present in source pay text | `year` |
| `salaryText` | Original displayed pay label | `₹11L - ₹15L / yr` |
| `postedAt` | Source posting timestamp | ISO timestamp |
| `validThrough` | Expiration date from a direct public job page | ISO date or `null` |
| `companyName` | Source employer name | Source value or `null` |
| `companyId` | Source employer identifier | Identifier or `null` |
| `companyUrl` | Public employer profile URL | Public `/company/` URL |
| `companyWebsite` | Employer website, when company details are enabled | URL or `null` |
| `companyLinkedin` | Employer profile link, when details are enabled | URL or `null` |
| `companyLogoUrl` | Public employer logo, when details are enabled | Image URL or `null` |
| `companyType` | Source employer type, when details are enabled | Source label |
| `companySize` | Source employer size, when details are enabled | Source range |
| `companyStage` | Source employer stage, when details are enabled | Source label |
| `companyFounded` | Source founding year, when details are enabled | Year or `null` |
| `companyTechStack` | Source technology labels, when details are enabled | Array of labels |
| `sourceUrls` | Category or direct URLs that returned the job | Array of URLs |
| `matchedSearchTerms` | Requested terms whose pages contained the job | `python` |
| `dedupeKey` | Stable identity for combining overlapping results | Source job ID |
| `contentHash` | Digest of normalized job content | Hexadecimal digest |
| `changeStatus` | Recurring-run comparison, when monitoring is enabled | `NEW` |

`remote` is `true` only when the source says `remote_only`. It is `false` when the source says `remote_not_okay`. The source value `remote_okay` means remote work is permitted but does not establish a remote-only role, so the normalized field is `null`. Keep `remoteType` when that distinction matters to your analysis.

The visible salary fields use the source's displayed or vanity bounds. If the source labels pay as hidden or “Best in industry,” `salaryMin` and `salaryMax` stay empty. The separate `salaryInternalMin` and `salaryInternalMax` fields preserve numeric bounds present in the public payload; they are not presented as the displayed offer. No currency conversion is made. A yearly or monthly period appears only when the source pay label says so.

### How to scrape CutShort

1. Enter one or more skill or role terms, such as `python`, or paste public `/jobs/` category URLs into `startUrls`. A direct public `/job/` URL also works for a specific vacancy.
2. Choose the number of pages to read per category. Each page currently carries up to 50 jobs. Set `maxItems` to the number of unique rows you want in the final dataset.
3. Add location, role type, remote-only, or posting-age filters if needed. Enable public company details when your export needs website, profile, size, stage, or technology fields.
4. Run the actor. The run collects each requested source, combines records with the same job ID, applies filters, and writes results to the default dataset.
5. Download the dataset as JSON, CSV, or a spreadsheet file. For repeated collection, schedule the same input and enable monitoring to label changes.

If both `skills` and `startUrls` are empty, the actor uses the public Python jobs category. This default makes a first run useful without building a URL by hand. Category slugs must exist on the site; a term that does not map to a public category returns a source error rather than a misleading empty dataset.

### Input

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `skills` | String array | `[]` | Skill or role terms converted into public category paths; combine several terms to broaden coverage. |
| `startUrls` | URL list | `[]` | Public CutShort `/jobs/` categories or `/job/` pages. |
| `locations` | String array | `[]` | Keep jobs containing any requested location text, ignoring letter case. |
| `roleTypes` | String array | `[]` | Keep jobs with any exact source role type, such as `full_time`. |
| `remoteOnly` | Boolean | `false` | Keep only `remote_only` jobs. |
| `postedWithinHours` | Integer | Unset | Keep dated jobs within the requested age; retain undated jobs. |
| `maxItems` | Integer | `50` | Maximum unique results emitted after filters. |
| `maxPagesPerSource` | Integer | `2` | Category pages to fetch, from 1 through 9. |
| `includeCompanyDetails` | Boolean | `false` | Add the public employer detail fields in the table above. |
| `includeDescriptionHtml` | Boolean | `false` | Add sanitized HTML as well as plain description text. |
| `monitoringMode` | Boolean | `false` | Compare jobs against the previous successful run for the same scope. |
| `includeUnchanged` | Boolean | `false` | Also emit unchanged jobs while monitoring. |
| `stateName` | String | `default` | Separate independent recurring monitors. |
| `proxyConfiguration` | Object | Proxy enabled | Network configuration for source requests. |

A focused input for a single category looks like this:

```json
{
  "skills": ["python"],
  "maxPagesPerSource": 1,
  "maxItems": 40,
  "includeCompanyDetails": true
}
```

For multiple categories, list several terms or paste verified category URLs. Terms and URLs are combined. `startUrls` accepts URL strings and standard request-list entries containing a `url` key. Query parameters on an input category URL are not used as filters; use the actor's input filters and page limit instead. Direct job URLs are read once and can provide an expiry date that category records do not include.

### Output example

This excerpt is from a successful run of the input above. It shows actual source values; the long description, employer identity, links, and other output fields are omitted here for readability.

```json
{
  "jobId": "6ab22a15b2a99f741b0afe28",
  "title": "Data Engineer",
  "locations": [],
  "locationsText": "Remote only",
  "roleTypes": ["full_time"],
  "remoteType": "remote_only",
  "remote": true,
  "experienceMin": 3,
  "experienceMax": 10,
  "salaryText": "₹11L - ₹15L / yr",
  "salaryMin": 1100000,
  "salaryMax": 1500000,
  "salaryCurrency": "INR",
  "salaryPeriod": "year",
  "postedAt": "2026-09-22T07:11:17.478Z",
  "hiringForClient": false,
  "matchedSearchTerms": ["python"]
}
```

That run returned 40 jobs. All 40 had a job ID, public URL, title, listed skills, displayed location, remote type, posting timestamp, employer name, and content hash. A record can still have an empty structured `locations` array while the displayed `locationsText` says “Remote only”; both fields are retained rather than forcing one representation into the other.

### Use cases

- **Hiring market tracking:** Compare public role counts, skill labels, locations, and posted pay across repeated category runs. Use the source timestamp and stable ID to distinguish newly seen listings from older inventory.
- **Salary research:** Export displayed ranges along with currency and period. Keep internal bounds separate so calculations do not accidentally treat them as the advertised compensation.
- **Remote opportunity feeds:** Filter `remote_only` roles and retain the exact remote setting so downstream readers can distinguish remote-only from remote-permitted vacancies.
- **Talent demand analysis:** Combine several skill categories, deduplicate jobs appearing in more than one result set, and inspect the matched terms for each role.
- **Public employer research:** Enable company details to attach a public website, size, stage, and technology labels where the source supplies them.
- **Recurring alerts:** Schedule the same source and filters, then use `changeStatus` to process new or changed records without handling every unchanged row again.

### How much does it cost to scrape CutShort?

The configured result price is **$1.00 per 1,000 emitted jobs**, or $0.001 per dataset row. This is a per-result charge, so an empty result set does not add result charges. Platform usage for ordinary runs is covered by the configured result price. Check the current Store pricing panel before a large scheduled workload, since pricing can change.

At this rate, 40 emitted jobs cost **$0.04**, 500 cost **$0.50**, and 5,000 cost **$5.00**. These examples count delivered rows, not source pages requested. If monitoring is set to omit unchanged jobs, only the new and updated rows emitted in that run count toward the result charge. Setting a smaller `maxItems` also limits the number of chargeable results.

The actor stops writing when the platform reports a per-run charge limit. Category requests are still needed to find and compare results, so use a focused set of categories and a sensible page limit. Large descriptions and optional public company fields can increase dataset size, even though the per-result price stays the same.

### Tips for best results

Start with a known public category URL if you are unsure how the site spells a role slug. The actor's skill input creates a hyphenated `<term>-jobs` path; some categories use specialized spelling. A public category URL removes that ambiguity. The sitemap and visible category links on the site are useful places to find valid category paths.

Use several related categories when you need more than one category's visible ranking window. Each category currently allows nine pages of 50 jobs, and related categories can reveal different vacancies. A job that appears in two categories is emitted once by source job ID. `sourceUrls` records each category page that returned it, and `matchedSearchTerms` records the input terms associated with those pages.

Set `maxPagesPerSource` to 1 for a quick current snapshot or to 2 for a wider default sample. Raise it toward 9 when the category is broad enough to justify additional requests. The actor ends early if a page contains fewer than 50 jobs. `maxItems` caps the final unique rows; it does not change the number of jobs the site exposes per page.

For a location filter, enter a distinctive city or region string. The actor checks the source's structured locations and displayed location label, ignoring letter case. For role type filters, use the site's exact role type labels. `remoteOnly` is intentionally strict: it selects only `remote_only`, not `remote_okay`.

Enable `includeDescriptionHtml` only when markup matters to your workflow. Plain description text is present by default and is easier to search. Enable `includeCompanyDetails` when you need employer metadata. Both switches change the content hash because they change the normalized record; keep these choices stable across monitored runs.

### Limits

A category may report thousands of live jobs while exposing only a bounded ranking window to public requests. In observed public responses, pages 1 through 9 returned up to 50 jobs each and page 10 returned “jobs not found.” The actor stops at page 9 and does not claim to collect every historical vacancy in a large category. Category overlap helps extend coverage, but it does not guarantee a complete inventory.

Public pages can change ranking or disappear. A job missing from a later category run is not labeled closed, because it may simply have moved outside that ranking window. Monitoring labels only records that are present in the current collection. A row can reappear and be labeled new if it was outside the previous stored snapshot.

Fields vary by posting. Employers may omit a website, founding year, or technology stack. Some salary labels conceal the displayed range even when separate numbers are present in the public payload. An application link can require a site account. No private application data, candidate records, or account-only details are requested. Direct job pages may provide a `validThrough` date; category pages generally do not.

The actor accepts public `/jobs/` and `/job/` URLs on the target domain. Unsupported URL shapes fail early. A source response that fails or lacks the expected JSON structure fails the run, so a partial result set is not presented as a complete success. Successful monitoring snapshots are written after dataset output, and a budget stop does not advance that snapshot.

### Use with AI agents (MCP)

Add this actor to any MCP client (Claude, Cursor, VS Code, n8n and others) with one URL:

`https://mcp.apify.com?tools=mlg14/cutshort-scraper`

The actor returns one dataset item per CutShort job (title, salary, experience, skills, locations, remote status, posting date). Keep `maxItems` small so a run finishes quickly.

**Prompt 1:** "Find 20 recent Python jobs on CutShort and show salary and location."

```json
{
  "skills": ["python"],
  "maxItems": 20,
  "maxPagesPerSource": 1
}
```

**Prompt 2:** "Get remote-only jobs from the CutShort Python category with company details."

```json
{
  "startUrls": [{"url": "https://cutshort.io/jobs/python-jobs"}],
  "remoteOnly": true,
  "includeCompanyDetails": true,
  "maxItems": 25,
  "maxPagesPerSource": 1
}
```

**Prompt 3:** "Which Python jobs on CutShort were posted in the last 48 hours?"

```json
{
  "skills": ["python"],
  "postedWithinHours": 48,
  "maxItems": 30,
  "maxPagesPerSource": 1
}
```

### Use with connected workflows

A connected workflow can run this actor with an input object and read the resulting dataset. Two practical requests are:

- “Collect the first two pages of public Python job listings, keep remote-only roles, and return title, pay label, location, and posting date.”
- “Run the same public job categories each morning, include company details, and show only jobs marked new or updated.”

Keep the input scope stable for recurring comparisons. Changing categories, filters, page limit, or result limit produces a different snapshot scope. Use a distinct `stateName` for independent feeds so their histories stay separate.

### FAQ

#### Is it legal to collect these jobs?

The actor reads public job-listing data. You are responsible for using it in line with the site's terms, applicable law, and privacy rules. Do not use job data to profile candidates or attempt to access private accounts.

#### Do I need to configure a proxy?

The default configuration enables the platform proxy. Public JSON requests succeeded during development without a challenge. If the site later changes its access controls, the shared fetch layer can rotate sessions and escalate its network route. A custom proxy configuration is available when needed.

#### How fast is a run?

The observed one-page, 40-result golden run completed successfully in seconds after its build. Longer runs depend on the number of category pages, source response time, and the number of rows written. Company details are embedded in the public response, so enabling them does not add one request per job.

#### Can I schedule and monitor it?

Yes. Schedule a run with stable inputs and enable `monitoringMode`. The first successful monitored run marks current jobs `NEW`. Later runs compare content hashes and mark `NEW`, `UPDATED`, or `UNCHANGED`. By default unchanged rows are skipped; set `includeUnchanged` to keep them. Monitoring does not infer that a missing job has closed.

#### Can I export to a spreadsheet?

Yes. The default dataset can be downloaded as JSON, CSV, or a spreadsheet file. Arrays such as `skills`, `locations`, and `sourceUrls` remain arrays in JSON; a tabular export may represent them as serialized values.

#### Why is a field empty?

The site may omit it, or an optional field group may be disabled. `validThrough` generally needs a direct job URL. A hidden salary label leaves the displayed numeric fields empty. A remote-only posting can have no structured city even while the displayed location label is present.

#### Can this collect every job in a broad category?

No. The public category endpoint exposes a finite page window. Use several relevant categories and deduplication for broader coverage, and treat the result as the visible public slice at collection time.

### Integrations

Use the dataset endpoint in a data pipeline, trigger a webhook after a run, schedule recurring collection, or route the export into a spreadsheet or database. Stable job IDs and content hashes support joins and updates; `sourceUrls` keeps the origin of each record visible. When combining runs, key records by `dedupeKey` and compare `contentHash` rather than relying on row order, because category rankings can shift.

### Support

Open an issue on the Issues tab; we reply within 24h and add fields on request.

# Actor input Schema

## `skills` (type: `array`):

Skills or role terms used to build public CutShort job-category searches. Example: \["python", "data engineer"]. Use several terms to reach more distinct jobs; combine with startUrls. Leave empty to use only URLs.

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

Public cutshort.io category URLs (/jobs/...) or direct job URLs (/job/...), as request objects. Example: \[{"url": "https://cutshort.io/jobs/python-jobs"}]. Use it for known categories; combine with skills.

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

Keep only jobs whose source location contains any of these values, ignoring case. Example: \["Bangalore", "Remote"]. Use it to narrow by city; leave empty for all locations.

## `roleTypes` (type: `array`):

Keep only jobs matching a source role type such as full_time or internship. Example: \["full_time"]. Use it to exclude internships or contract roles; leave empty for all role types.

## `remoteOnly` (type: `boolean`):

If true, keep only jobs whose source remote type is remote_only. Example: true for remote roles. Leave false (default) to include on-site and hybrid jobs.

## `postedWithinHours` (type: `integer`):

Keep jobs posted within this many hours (integer 1 to 8760) when a posting date is available; undated jobs are kept. Example: 24 for the last day. Leave empty for no date filter; use it for daily monitoring.

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

Maximum unique jobs returned after collection and filters (integer 1 to 10000). Example: 50. Use a small value for quick tests and a large one for full exports.

## `maxPagesPerSource` (type: `integer`):

Pages of 50 jobs fetched per category (integer 1 to 9; the source stops after page 9). Example: 2 gives up to 100 jobs per category. Use a higher value for deeper coverage.

## `includeCompanyDetails` (type: `boolean`):

If true, adds company website, profile links, logo, type, size, stage, founding year and tech stack. Example: true for lead lists. Leave false (default) for job data only.

## `includeDescriptionHtml` (type: `boolean`):

If true, adds the sanitized job description HTML next to the plain text. Example: true to keep formatting. Leave false (default) when plain text is enough.

## `monitoringMode` (type: `boolean`):

If true, compares each job with the previous successful run of the same source and filters and sets changeStatus (new, updated, unchanged). Example: true for scheduled runs. Leave false for one-off exports.

## `includeUnchanged` (type: `boolean`):

When monitoringMode is true, also return jobs with the same content as the previous run. Example: true for full snapshots. Leave false (default) to return only new and updated jobs.

## `stateName` (type: `string`):

Name that keeps independent recurring monitors separate (1 to 80 characters). Example: python-daily. Use a different name per feed; leave the default for a single monitor.

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

Apify Proxy settings for public page requests. Leave the default (Apify Proxy enabled) unless you need your own proxies.

## Actor input object example

```json
{
  "skills": [
    "python"
  ],
  "startUrls": [
    {
      "url": "https://cutshort.io/jobs/python-jobs"
    }
  ],
  "locations": [],
  "roleTypes": [],
  "remoteOnly": false,
  "maxItems": 50,
  "maxPagesPerSource": 2,
  "includeCompanyDetails": false,
  "includeDescriptionHtml": false,
  "monitoringMode": false,
  "includeUnchanged": false,
  "stateName": "default",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Public job records in the default dataset.

# 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 = {
    "skills": [
        "python"
    ],
    "startUrls": [
        {
            "url": "https://cutshort.io/jobs/python-jobs"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("mlg14/cutshort-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "skills": ["python"],
    "startUrls": [{ "url": "https://cutshort.io/jobs/python-jobs" }],
    "proxyConfiguration": { "useApifyProxy": True },
}

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

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

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

```

## CLI example

```bash
echo '{
  "skills": [
    "python"
  ],
  "startUrls": [
    {
      "url": "https://cutshort.io/jobs/python-jobs"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call mlg14/cutshort-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mlg14/cutshort-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/j305Y9NPCPsncI9fn/builds/drsXcSnxJB9YKRCMx/openapi.json
