# Upwork Jobs Universal Scraper (`scrapifier/upwork-jobs-universal-scraper`) Actor

Stay on top of new Upwork jobs that match your search. Keyword, type, rate, skills and country filters, full description, budget and a direct job URL. No login. Pay per job.

- **URL**: https://apify.com/scrapifier/upwork-jobs-universal-scraper.md
- **Developed by:** [Scrapifier](https://apify.com/scrapifier) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 1 total users, 0 monthly users, 77.8% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.02 / 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.
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

Stay on top of new Upwork jobs that match what you already search for. Type the same keywords you use on Upwork, add filters, or paste a search URL, and export every matching posting as JSON, CSV or Excel — or pull them from the API on a schedule.

⚡ For every job you get the **title**, the **full description**, hourly range or fixed budget, experience level, engagement length, required **skills**, posting time and a **direct job URL**. No Upwork login. You **pay per job**.

[![Apify Actor](https://apify.com/actor-badge?actor=scrapifier/upwork-jobs-universal-scraper)](https://apify.com/scrapifier/upwork-jobs-universal-scraper)

### 🎯 Why scrape Upwork jobs?

- **Job alerts**: sort by most recent, turn on “Only new jobs since last run”, schedule the Actor every 5–15 minutes, and feed Slack, email or your own board.
- **Freelance market research**: measure demand, rates and required skills for a query across countries.
- **Lead lists**: every new posting that matches your niche is an opportunity, with budget and description attached.
- **AI / matching datasets**: full plain-text descriptions at scale for classification or ranking models.

### 📊 What data does Upwork Jobs Scraper extract?

| Field | Description |
|---|---|
| `id`, `ciphertext` | Upwork job id and the id used in the job URL |
| `url` | Direct job page (`https://www.upwork.com/jobs/~02…`) |
| `title` | Job title |
| `description` | Full plain-text description (`fullDescription` on by default) |
| `jobType` | `HOURLY` or `FIXED` |
| `experienceLevel` | `EntryLevel`, `IntermediateLevel` or `ExpertLevel` |
| `hourlyRateMin`, `hourlyRateMax` | Hourly budget bounds |
| `fixedBudget` | Fixed-price amount |
| `durationWeeks` | Engagement length in weeks |
| `hourlyEngagementType` | Hourly engagement type when Upwork sets one |
| `skills`, `skillUids` | Skill labels and Upwork skill ids |
| `publishedAt` | Publication time (ISO 8601) |
| `searchInput` | The search or pasted URL that produced this job |

This Actor reads the **public job card** (the same data you see before opening a job as a visitor). Client spend, payment-verified flags and proposal counts are not on that card and are not returned.

With `flatten` enabled, `skills` and `skillUids` become comma-separated strings so every CSV column stays flat.

### 🚀 How to scrape Upwork jobs

1. Type your **Search keywords** (`python`, `react developer`, `logo design`) and set **Sort by** to **Most recent first** for alerts.
2. Optionally filter: job type, experience level, client country, hourly/fixed budget, client hires, duration, workload, timezone, contract-to-hire, posted-within.
3. Or paste full **Upwork search URLs**. Each URL is scraped with its own filters, on top of the keyword search.
4. Set **Maximum jobs** (`0` = every unique job this search returns) and click **Start**.
5. For a feed of *new* work only, enable **Only new jobs since last run** and put the Actor on a schedule.
6. Open **Storage** to preview jobs and export JSON, CSV, Excel or XML, or read them from the dataset API.

### 📥 Input parameters

| Field | Key | Type | Default | Description |
|---|---|---|---|---|
| Search keywords | `query` | string | `python` (prefill) | What you would type on Upwork. Empty = the whole feed with the other filters. |
| Sort by | `sort` | select | `recency` | `recency` (best for alerts) or `relevance`. |
| Job type | `jobType` | select | any | `hourly` or `fixed`. |
| Experience level | `experienceLevel` | select | any | `entry`, `intermediate`, `expert`. |
| Client country | `location` | string | none | Full country name (`United States`, `France`). Two-letter codes return nothing. |
| Min/max hourly rate | `hourlyRateMin` / `hourlyRateMax` | integer | none | USD bounds for hourly jobs. |
| Min/max fixed budget | `fixedBudgetMin` / `fixedBudgetMax` | integer | none | USD bounds for fixed-price jobs. |
| Client hires | `clientHires` | select | any | No hires / 1–9 / 10+. |
| Contract-to-hire only | `contractToHire` | boolean | `false` | Only contract-to-hire postings. |
| Project duration | `projectDuration` | select | any | `week`, `month`, `semester`, `ongoing`. |
| Workload | `workload` | select | any | `as_needed`, `part_time`, `full_time`. |
| Client timezone | `timezone` | string | none | IANA name (`America/New_York`). |
| Skill / category ids | `skillUid`, `categoryUid`, `subcategoryUid` | string | none | Optional ids from a previous result or from a search URL. |
| Posted within | `postedWithin` | select | any time | Last 24 hours to 30 days. Applied on `publishedAt`. |
| Upwork search URLs | `startUrls` | array of URLs | none | Full search-result URLs, scraped with their own filters. |
| Maximum jobs | `maxJobs` | integer | `0` | Total cap (`0` = every unique job this search returns). |
| Only new jobs since last run | `incrementalMode` | boolean | `false` | Skip job ids already returned for this search. |
| Fetch full descriptions | `fullDescription` | boolean | `true` | Include the full plain-text `description`. |
| Flatten output | `flatten` | boolean | `false` | Lists become comma-separated strings. |
| Report input status | `emitInputStatus` | boolean | `true` | Write an `input_status` report to OUTPUT. |
| Proxy configuration | `proxyConfiguration` | object | Apify Proxy, residential | Keep Apify Proxy on for cloud runs. |

Example input (alert feed):

```json
{
  "query": "python",
  "sort": "recency",
  "jobType": "hourly",
  "hourlyRateMin": 30,
  "postedWithin": "1",
  "maxJobs": 50,
  "incrementalMode": true
}
```

To reproduce an exact Upwork search, build it on the site and paste the results URL into `startUrls`.

### 📤 Output example

```json
{
  "id": "2095999094643674588",
  "ciphertext": "~022095999094643674588",
  "url": "https://www.upwork.com/jobs/~022095999094643674588",
  "title": "Python developer for data pipeline",
  "description": "We need a Python developer to build and maintain ETL jobs…",
  "jobType": "HOURLY",
  "experienceLevel": "IntermediateLevel",
  "hourlyRateMin": 30,
  "hourlyRateMax": 50,
  "fixedBudget": null,
  "durationWeeks": 12,
  "hourlyEngagementType": "PART_TIME",
  "skills": ["Python", "ETL", "PostgreSQL"],
  "skillUids": ["1031626745410101248"],
  "publishedAt": "2026-09-05T00:16:16.967Z",
  "searchInput": "python | hourly"
}
```

The run's OUTPUT record also holds stats and, per search, an `input_status` entry with how many jobs Upwork announced, how many were collected, and why the search stopped.

### 💡 Ready-to-run examples

**Hourly Python jobs, most recent first (alerts)**

```json
{ "query": "python", "sort": "recency", "jobType": "hourly", "maxJobs": 0, "incrementalMode": true }
```

**Fixed-price design work in the United States**

```json
{ "query": "logo design", "jobType": "fixed", "location": "United States", "fixedBudgetMin": 100, "maxJobs": 100 }
```

**From a pasted Upwork search URL**

```json
{ "startUrls": [{ "url": "https://www.upwork.com/nx/search/jobs/?q=react&sort=recency&t=0" }], "maxJobs": 0 }
```

**Expert-level jobs posted today, flat CSV**

```json
{ "query": "machine learning", "experienceLevel": "expert", "postedWithin": "1", "sort": "recency", "maxJobs": 80, "flatten": true }
```

### 💰 Pricing: how much does it cost to scrape Upwork?

You pay only for the jobs you receive, with no subscription and no start fee. New Apify accounts get free monthly platform credits to try it. Cap volume with `maxJobs`, and narrow the set with filters before you pay for it.

### ⚙️ Performance, proxies and limits

- **Speed**: results come **50 jobs per page** until the search is exhausted or you hit `maxJobs`.
- **Completeness**: `maxJobs: 0` (the default) keeps going until every unique job for your filters is collected. Upwork serves about **5,050** jobs per raw search; when that is not enough the Actor continues with narrower slices (hourly vs fixed, client hires, experience, rate) and unions unique ids. Pages overlap slightly so a live feed that moved between requests does not drop listings.
- **Dedup**: listings are unique on `ciphertext`. Incremental mode remembers that id across runs of the same search.
- **Full descriptions** come with each listing. Turn `fullDescription` off only if you want smaller records.
- **Proxy**: keep Apify Proxy on for cloud runs; a **residential** proxy is the reliable default.
- **Client panel** (spend, verified payment, proposal count) is not part of the public job card this Actor returns.

### 🔌 Integrations and API

Run Upwork Jobs Scraper from the [Apify API](https://docs.apify.com/api/v2), the [Python](https://docs.apify.com/api/client/python) or [JavaScript](https://docs.apify.com/api/client/js) client, or on a [schedule](https://docs.apify.com/platform/schedules). Connect it to Make, Zapier, n8n, Google Sheets, Slack or any webhook through [Apify integrations](https://apify.com/integrations).

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapifier~upwork-jobs-universal-scraper/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{ "query": "python", "sort": "recency", "maxJobs": 25 }'
```

### ❓ FAQ

**Is it legal to scrape Upwork?**

The Actor only reads job listings Upwork publishes to anyone without logging in. Descriptions can contain personal data, so process them in line with GDPR and other privacy laws, respect Upwork's terms of use, and stay responsible for how you use the data. This is not legal advice.

**Do I need an Upwork account, login or cookies?**

No. Nothing to log in to, and no cookies to paste.

**How do I get only jobs posted since my last run?**

Enable `incrementalMode`, keep `sort` on `recency`, and schedule the Actor. The dataset of each run then contains only new `ciphertext` values for that search.

**How many jobs can one search return?**

Set `maxJobs` to `0` (default) to collect every unique job for your filters, 50 per page. A single Upwork search stops around 5,050 results; the Actor then continues automatically with narrower slices and de-duplicates on `ciphertext`. Cap with `maxJobs` if you only want a sample.

**Why is the client country empty when I did not set the location filter?**

The public job card does not include the client's country. The `location` input **filters** the search to that country; it is not copied onto each row. Use `searchInput` to see which search produced the job.

**Can I export to Excel or Google Sheets?**

Yes. Every run's dataset downloads as CSV, XLSX, JSON or XML from the Storage tab. Turn on `flatten` for flat columns.

**Does it get the full job description?**

Yes. `fullDescription` is on by default.

### 🔗 Related scrapers

- [Indeed Jobs Scraper](https://apify.com/scrapifier/indeed-jobs-universal-scraper): job listings on any Indeed country site, with salary and company profile.
- [Glassdoor Jobs Scraper](https://apify.com/scrapifier/glassdoor-jobs-universal-scraper): job listings with Glassdoor company ratings and salary estimates.

### 💬 Support

Questions, bugs or a feature request? Open an issue in the **Issues** tab of this Actor. If Upwork Jobs Scraper saves you time, a rating on the Store helps others find it.

# Actor input Schema

## `query` (type: `string`):

What to search for, as you would type it on Upwork ("python", "react developer", "logo design"). Leave empty to browse the whole feed with the filters below.

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

"Most recent first" is what you want for job alerts and scheduled runs.

## `jobType` (type: `string`):

Hourly or fixed-price. Leave on Any type for both.

## `experienceLevel` (type: `string`):

Required freelancer level on the posting.

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

Full country name as on Upwork ("United States", "France", "United Kingdom"). Two-letter codes like "US" return nothing.

## `hourlyRateMin` (type: `integer`):

Only hourly jobs whose budget starts at or above this rate.

## `hourlyRateMax` (type: `integer`):

Only hourly jobs whose budget is at or below this rate.

## `fixedBudgetMin` (type: `integer`):

Only fixed-price jobs at or above this amount.

## `fixedBudgetMax` (type: `integer`):

Only fixed-price jobs at or below this amount.

## `clientHires` (type: `string`):

How many freelancers the client has already hired on Upwork.

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

Only jobs marked contract-to-hire.

## `projectDuration` (type: `string`):

Expected project length.

## `workload` (type: `string`):

Weekly commitment on hourly jobs.

## `timezone` (type: `string`):

IANA timezone ("America/New\_York", "Europe/Paris"). Empty = any.

## `skillUid` (type: `string`):

Optional Upwork skill uid (the long numeric id from a previous result's skillUids). Filters to jobs tagged with that skill.

## `categoryUid` (type: `string`):

Optional Upwork category uid from a search URL (category2\_uid=…).

## `subcategoryUid` (type: `string`):

Optional Upwork subcategory uid from a search URL (subcategory2\_uid=…).

## `postedWithin` (type: `string`):

Keep jobs published inside this window. Applied after fetch; with "Most recent first" the run stops once jobs are older than the window.

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

Optional: paste full Upwork job-search URLs (https://www.upwork.com/nx/search/jobs/?q=python\&sort=recency). Each URL is scraped with its own filters, in addition to the keyword search above.

## `maxJobs` (type: `integer`):

Stop after this many new jobs in total. 0 = every unique job this search returns. Broad searches are continued automatically past one result window (~5,050) by splitting on type, client hires, experience and rate.

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

Remember job ids already returned for this search and only push listings that are new. Turn this on for scheduled alerts.

## `fullDescription` (type: `boolean`):

Keep each job's complete description. Off = omit the description; listing metadata is unchanged.

## `flatten` (type: `boolean`):

Join the skills list into a comma-separated string so every column stays flat.

## `emitInputStatus` (type: `boolean`):

Write an input\_status report to the run's OUTPUT record (how many jobs each search collected and why it stopped).

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

Keep Apify Proxy on. A residential proxy gives the most reliable cloud runs.

## Actor input object example

```json
{
  "query": "python",
  "sort": "recency",
  "jobType": "",
  "experienceLevel": "",
  "clientHires": "",
  "contractToHire": false,
  "projectDuration": "",
  "workload": "",
  "postedWithin": "0",
  "maxJobs": 0,
  "incrementalMode": false,
  "fullDescription": true,
  "flatten": false,
  "emitInputStatus": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

All Upwork job postings extracted by this run.

# 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 = {
    "query": "python",
    "maxJobs": 0,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapifier/upwork-jobs-universal-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 = {
    "query": "python",
    "maxJobs": 0,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapifier/upwork-jobs-universal-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 '{
  "query": "python",
  "maxJobs": 0,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call scrapifier/upwork-jobs-universal-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapifier/upwork-jobs-universal-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/8AQaycN5Z6gRaaVOz/builds/qZqJ2zlAy4dIAKWbc/openapi.json
