# Upwork Jobs Scraper - Most Comprehensive (`kaix/upwork-jobs-scraper`) Actor

🔥 ~$0.1/1K jobs 🔥 Collect public Upwork job search results by keyword or filtered search URL. Fetch known jobs by URL, optionally add public job details, and export fixed structured records without an Upwork account.

- **URL**: https://apify.com/kaix/upwork-jobs-scraper.md
- **Developed by:** [Kai](https://apify.com/kaix) (community)
- **Categories:** Jobs, Developer tools, Lead generation
- **Stats:** 2 total users, 2 monthly users, 77.6% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.08 / 1,000 jobs

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

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Upwork Jobs Scraper

Collect public Upwork job listings as structured dataset records. No Upwork account, API key, or cookies are required.

Each job can include its public identity and posting time, description, location
rule, hourly or fixed-price budget, experience level, project duration, weekly
hours, skills, proposal activity, public client summary, and collection context.

For a first search, set a keyword and `maxItems`. Turn on `fetchDetails` when
you need full descriptions, job activity, and client fields. After the job
finishes, open the default Dataset to view or export the results.

### Search public jobs

#### Start with a keyword

Enter a keyword and set `fetchDetails` to `true` when you need full job details:

```json
{
  "keyword": "typescript developer",
  "maxItems": 20,
  "fetchDetails": true
}
```

You can leave all search fields empty to browse public jobs.

### Choose an input mode

| Mode | Select it with | Detail behaviour |
|---|---|---|
| Filter search | `keyword`, Advanced Search text, public filters, or no search fields. | Search-page data is included. `fetchDetails: true` adds the full job fields. |
| Saved public search | `searchUrls` | The saved URL controls the search. `maxItems` and `fetchDetails` still apply. |
| Direct lookup | `jobUrls` | Full details are always collected. Search fields cannot be combined with direct lookup. |

### What public search can and cannot filter

The Actor uses Upwork's public job search. It does not use an Upwork account,
private cookies, or signed-in search settings.

Public search does not apply signed-in filters such as proposal count, previous
clients, verified payment, or account-location matching. Upwork can leave these
parameters in a copied URL, but they do not change anonymous results.

### How filters combine

- Values in one multi-select field use OR. Most different fields use AND.
- Client locations and client time zones use OR.
- An hourly-rate limit selects hourly jobs. A fixed-price value selects fixed-price jobs.
- Fixed-price presets and one custom fixed-price range use OR.
- Project length and weekly hours apply only to hourly jobs.
- Either end of an hourly or fixed-price range can be omitted. When both ends are present, the maximum must be greater than the minimum.

### Input examples

Use the examples below for the three input modes and their filters. You can combine compatible filter examples. Do not combine `keyword` with the Advanced Search text fields. Keep `searchUrls` and `jobUrls` as separate modes.

#### Keyword search

```json
{
  "keyword": "typescript developer",
  "sort": "recency",
  "maxItems": 20,
  "fetchDetails": true
}
```

#### Advanced text search

```json
{
  "allWords": "typescript api",
  "anyWords": "node deno",
  "excludedWords": "wordpress php",
  "exactPhrase": "long term",
  "titleWords": "backend developer"
}
```

#### Skills and categories

```json
{
  "skillIds": ["1031626773660942336"],
  "categoryIds": ["531770282580668418"],
  "subcategoryIds": ["531770282584862733"]
}
```

Get these numeric IDs from the `ontology_skill_uid`, `category2_uid`, and `subcategory2_uid` parameters in a filtered public Upwork search URL.

#### Hourly jobs

```json
{
  "jobTypes": ["hourly"],
  "hourlyRateMin": 25,
  "hourlyRateMax": 75,
  "experienceLevels": ["intermediate", "expert"]
}
```

#### Fixed-price jobs

```json
{
  "jobTypes": ["fixed"],
  "fixedPriceRanges": ["500-999", "1000-4999"],
  "fixedPriceMin": 250,
  "fixedPriceMax": 900
}
```

#### Client filters

```json
{
  "clientHistory": ["1-9", "10-"],
  "clientLocations": ["United States"],
  "clientTimezones": ["America/New_York"],
  "contractToHire": true
}
```

#### Hourly project filters

```json
{
  "jobTypes": ["hourly"],
  "projectLengths": ["month", "semester"],
  "hoursPerWeek": ["as_needed", "full_time"],
  "pageSize": "20"
}
```

#### Saved public search

```json
{
  "searchUrls": [
    "https://www.upwork.com/nx/search/jobs/?q=typescript&t=0&contractor_tier=2&sort=recency"
  ]
}
```

The Actor keeps the query parameters in each saved search URL. During pagination, it changes only the `page` parameter.

#### Direct job URL template

The URL below is a format template. It is not a saved public job. Replace it
with a current Upwork job URL.

```json
{
  "jobUrls": [
    "https://www.upwork.com/jobs/Example-job_~022087000000000000001/"
  ]
}
```

Direct lookup gets job details automatically.

You can also paste the short public form, such as
`https://www.upwork.com/jobs/~02...`. The Actor accepts the optional `frkscc`
tracking value and saves the canonical job URL.

### Input reference

| Field | Type | Default | Description |
|---|---|---:|---|
| `keyword` | string | empty | Job title, skill, or phrase. Maximum 200 characters. |
| `sort` | string | `relevance` | `relevance` or `recency`. Ignored when `searchUrls` is set. |
| `allWords` | string | empty | Require all words. Maximum 200 characters. |
| `anyWords` | string | empty | Require one or more words. Maximum 200 characters. |
| `excludedWords` | string | empty | Exclude these words. Maximum 200 characters. |
| `exactPhrase` | string | empty | Require this exact phrase. Maximum 200 characters. |
| `titleWords` | string | empty | Require these words in the title. Maximum 200 characters. |
| `skillIds` | string\[] | none | Up to 50 numeric skill IDs, each 6 to 30 digits. |
| `categoryIds` | string\[] | none | Up to 12 numeric category IDs, each 6 to 30 digits. |
| `subcategoryIds` | string\[] | none | Up to 50 numeric subcategory IDs, each 6 to 30 digits. |
| `jobTypes` | string\[] | none | `hourly`, `fixed`, or both. |
| `hourlyRateMin` | number | none | Minimum hourly rate, from 0 through 99,999 USD. |
| `hourlyRateMax` | number | none | Maximum hourly rate, from 0 through 100,000 USD. It must be greater than the minimum. |
| `fixedPriceRanges` | string\[] | none | One or more of `0-99`, `100-499`, `500-999`, `1000-4999`, and `5000-`. |
| `fixedPriceMin` | number | none | Custom minimum fixed price, from 0 through 99,999 USD. |
| `fixedPriceMax` | number | none | Custom maximum fixed price, from 0 through 100,000 USD. It must be greater than the minimum. |
| `experienceLevels` | string\[] | none | `entry`, `intermediate`, `expert`, or a combination. |
| `clientHistory` | string\[] | none | `0`, `1-9`, `10-`, or a combination. |
| `clientLocations` | string\[] | none | Up to 50 public region, subregion, or country names. Each value can contain at most 100 characters. |
| `clientTimezones` | string\[] | none | Up to 50 public time-zone values. Each value can contain at most 100 characters. |
| `projectLengths` | string\[] | none | `week`, `month`, `semester`, `ongoing`, or a combination. Hourly jobs only. |
| `hoursPerWeek` | string\[] | none | `as_needed`, `full_time`, or both. Hourly jobs only. |
| `contractToHire` | boolean | `false` | Keep only contract-to-hire jobs. |
| `pageSize` | string | `10` | Jobs per Upwork page: `10`, `20`, or `50`. |
| `searchUrls` | string\[] | none | Up to 20 public `https://www.upwork.com/nx/search/jobs/` URLs. Each URL can contain at most 2,048 characters. |
| `maxItems` | integer | `20` | Maximum unique jobs for the entire run. Range: 1 through 5,000. |
| `jobUrls` | string\[] | none | Up to 5,000 public Upwork job URLs with a `~02` job ID. Each URL can contain at most 2,048 characters. |
| `fetchDetails` | boolean | `false` | Add the full description, location rule, activity, and public client summary. |

### Output example

Each dataset item is one job. No saved public job record is available here. The
syntax-only object below shows field placement. Its values do not belong to one
real job; use the typed reference for the output contract.

<details>
<summary>View the job shape template</summary>

```json
{
  "id": "~022087000000000000001",
  "title": "Build a TypeScript API",
  "url": "https://www.upwork.com/jobs/Build-Typescript-API_~022087000000000000001/",
  "postedText": "Posted 4 minutes ago",
  "description": {
    "snippet": "Build a reliable public API in TypeScript.",
    "text": "Build a reliable API with tests and clear documentation.",
    "html": "<h2>Summary</h2><p>Build a reliable API with tests and clear documentation.</p>"
  },
  "locationEligibility": "Worldwide",
  "contract": {
    "type": "hourly",
    "hourlyRate": { "min": 30, "max": 55, "currency": "USD" },
    "fixedPrice": null,
    "experienceLevel": "Expert",
    "duration": "1 to 3 months",
    "weeklyHours": "Less than 30 hrs/week"
  },
  "skills": ["TypeScript", "Node.js"],
  "activity": {
    "proposals": "10 to 15",
    "interviewing": 2,
    "invitesSent": 1,
    "unansweredInvites": 0
  },
  "client": {
    "paymentVerified": true,
    "location": "Canada",
    "localTime": "3:20 PM",
    "memberSince": "Jan 2, 2020",
    "totalSpentText": "$20K total spent",
    "hiresText": "12 hires, 3 active",
    "hoursText": "1,200 hours",
    "rating": null,
    "reviewCount": null
  },
  "meta": {
    "sourceUrl": "https://www.upwork.com/nx/search/jobs/?q=typescript",
    "page": 1,
    "position": 1,
    "scrapedAt": "2026-08-12T00:00:00.000Z",
    "detailStatus": "fetched"
  }
}
```

</details>

### Output reference

| Field | Type | Description |
|---|---|---|
| `id` | string | Upwork job ID. |
| `title` | string | Job title. |
| `url` | string | Canonical public job URL. |
| `postedText` | string | null | Posting time or source date text. |
| `description` | object | `snippet`, `text`, and `html` are strings or `null`. Full values need `fetchDetails`. |
| `locationEligibility` | string | null | Public location rule, such as `Worldwide`. |
| `contract` | object | `type` is `hourly`, `fixed`, or `unknown`. `experienceLevel`, `duration`, and `weeklyHours` are strings or `null`. |
| `contract.hourlyRate` | object | null | `min` and `max` are numbers; `currency` is a string. Null for fixed-price jobs or when no range is shown. |
| `contract.fixedPrice` | object | null | `amount` is a number; `currency` is a string. Null for hourly jobs or when no budget is shown. |
| `skills` | string\[] | Public skill labels. |
| `activity` | object | `proposals` is a string or `null`; `interviewing`, `invitesSent`, and `unansweredInvites` are numbers or `null`. |
| `client` | object | `paymentVerified` is boolean or `null`; location, time, membership, spend, hire, and hour fields are strings or `null`; `rating` and `reviewCount` are numbers or `null`. |
| `meta` | object | `sourceUrl` and `scrapedAt` are strings; `page` and `position` are numbers or `null`; `detailStatus` is listed below. Page and position are null for direct lookup. |

`meta.detailStatus` has these meanings:

- `not_requested`: The item contains search-page data only.
- `fetched`: Full job details were added.
- `unavailable`: The job closed or became unavailable before the detail request.
- `failed`: The search item was saved, but the optional detail request failed.

### Limits

- Search URL mode accepts at most 20 URLs. Direct lookup accepts at most 5,000 job URLs. `maxItems` limits the complete run, not each URL.
- Each search source stops after 500 public result pages, even when `maxItems` is higher.
- The Actor makes one scraper request for the full input. The request times out after 310 seconds (about five minutes). Large result sets, especially with `fetchDetails: true`, can make the run fail before it reaches `maxItems`.
- Advanced text cannot be used with `keyword`. Direct lookup cannot be used with search fields.
- Project length and weekly-hours filters apply only to hourly jobs.
- Upwork does not show every field for every job. Missing scalar values are `null`; missing skills are `[]`.
- Closed or unavailable jobs in direct lookup are skipped.

# Actor input Schema

## `keyword` (type: `string`):

Enter a job title, skill, or phrase. Do not use this field with the Advanced search text fields. A Search URL overrides this field.

## `sort` (type: `string`):

Use Upwork's relevance order or show the newest public jobs first.

## `allWords` (type: `string`):

Return jobs that contain all of these words. Do not use the Keyword field with Advanced search text.

## `anyWords` (type: `string`):

Return jobs that contain one or more of these words.

## `excludedWords` (type: `string`):

Do not return jobs that contain one or more of these words.

## `exactPhrase` (type: `string`):

Return jobs that contain this exact phrase.

## `titleWords` (type: `string`):

Return jobs that contain these words in the job title.

## `skillIds` (type: `array`):

Enter numeric Upwork skill IDs from the ontology\_skill\_uid value in a public Upwork search URL.

## `categoryIds` (type: `array`):

Enter numeric Upwork category IDs from the category2\_uid value in a public Upwork search URL.

## `subcategoryIds` (type: `array`):

Enter numeric Upwork subcategory IDs from the subcategory2\_uid value in a public Upwork search URL.

## `jobTypes` (type: `array`):

Return hourly jobs, fixed-price jobs, or both. An hourly rate adds the hourly type. A fixed-price value adds the fixed type.

## `hourlyRateMin` (type: `number`):

Minimum hourly rate in US dollars. You can set only one end of the range.

## `hourlyRateMax` (type: `number`):

Maximum hourly rate in US dollars. If you set both rates, this value must be greater than the minimum.

## `fixedPriceRanges` (type: `array`):

Select one or more public Upwork fixed-price ranges.

## `fixedPriceMin` (type: `number`):

Minimum fixed price in US dollars. You can use this with the preset fixed-price ranges.

## `fixedPriceMax` (type: `number`):

Maximum fixed price in US dollars. If you set both prices, this value must be greater than the minimum.

## `experienceLevels` (type: `array`):

Return jobs for one or more Upwork experience levels.

## `clientHistory` (type: `array`):

Filter by the number of people that the client hired on Upwork.

## `clientLocations` (type: `array`):

Enter public Upwork location names, such as Sweden, Europe, or United States. Location and time-zone selections use OR.

## `clientTimezones` (type: `array`):

Enter public Upwork time-zone values, such as Europe/Stockholm. Location and time-zone selections use OR.

## `projectLengths` (type: `array`):

Filter hourly jobs by the planned project length.

## `hoursPerWeek` (type: `array`):

Filter hourly jobs by the expected weekly hours.

## `contractToHire` (type: `boolean`):

Return only public jobs that can lead to a full-time role.

## `pageSize` (type: `string`):

Select the number of jobs that Upwork returns in each search page. A Search URL overrides this value.

## `searchUrls` (type: `array`):

Paste public Upwork URLs from /nx/search/jobs/. When present, these URLs override Keyword, Advanced search, Sort results, all filter fields, and Jobs per Upwork page.

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

Maximum number of unique jobs to save across the complete run.

## `jobUrls` (type: `array`):

Fetch known public Upwork jobs. Full job URLs and short /jobs/~02... URLs are supported. A short URL can include one frkscc tracking value, which the Actor removes. Clear Keyword, Search URLs, Advanced search text, and all search filters before you use direct lookup.

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

Add the full public description, eligibility, job activity, and client summary. This makes one extra request per job.

## Actor input object example

```json
{
  "keyword": "typescript developer",
  "sort": "relevance",
  "contractToHire": false,
  "pageSize": "10",
  "maxItems": 20,
  "fetchDetails": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

Public Upwork jobs from search or direct lookup, with optional public detail enrichment.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("kaix/upwork-jobs-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("kaix/upwork-jobs-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 '{}' |
apify call kaix/upwork-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kaix/upwork-jobs-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/0tN04Y9Lb5Q76d21M/builds/2dv5SDFr95YjCMHMy/openapi.json
