# Upwork Jobs Scraper (`shahidirfan/upwork-jobs-scraper`) Actor

Extract freelance jobs from Upwork with job titles, descriptions, budgets, hourly rates, skills, categories, client details, posting dates, and job URLs. Ideal for freelance job scraping, lead generation, market research, competitor analysis, and remote job monitoring.

- **URL**: https://apify.com/shahidirfan/upwork-jobs-scraper.md
- **Developed by:** [Shahid Irfan](https://apify.com/shahidirfan) (community)
- **Security:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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 does Upwork Jobs Scraper do?

Upwork Jobs Scraper collects job postings from Upwork search results and returns them as a structured dataset. Give it a keyword or any Upwork jobs search URL, and it saves each posting with its budget, contract type, experience level, duration, skills, client signals, and publication time. Every run writes to an Apify dataset that you can download as JSON, CSV, or Excel.

Use it to find new freelance work, watch a niche for new postings, compare budgets across skills, or feed job data into a spreadsheet, database, or AI workflow.

### Why use Upwork Jobs Scraper?

Upwork search pages are built for browsing, not for collecting. Sorting through results by hand does not scale, and copying postings into a sheet loses the numbers you need for comparison.

This Actor handles the repeated work:

- **One row per posting** with consistent field names, so a spreadsheet stays usable across runs.
- **Filters that carry over** from the URL you build on Upwork, including experience level, contract type, budget range, and hourly rate.
- **Client signals** such as country, payment verification, total spend, and review count when Upwork publishes them.
- **Duplicate protection** within a run, so paging through results does not repeat the same posting.
- **Scheduling** in Apify Console for daily or hourly monitoring.
- **Exports and integrations** through Apify datasets, webhooks, Make, Zapier, and Google Sheets.

### What data can you extract from Upwork?

| Field             | Description                                                                  |
| ----------------- | ---------------------------------------------------------------------------- |
| `title`           | Job posting title                                                            |
| `description`     | Posting text as published by the client                                      |
| `budget`          | Fixed budget or hourly range, for example `$500.00` or `$30.00 - $155.00/hr` |
| `contractType`    | `Fixed-price`, `Hourly`, or `Not specified`                                  |
| `experienceLevel` | `Entry level`, `Intermediate`, `Expert`, or `Not specified`                  |
| `projectLength`   | Expected duration and work load when Upwork lists them                       |
| `skills`          | Skill tags attached to the posting                                           |
| `postedDate`      | Relative age such as `3 hours ago`                                           |
| `publishedAt`     | Publication time as an ISO timestamp                                         |
| `clientInfo`      | Combined client signals in one readable string                               |
| `url`             | Direct link to the posting                                                   |
| `jobId`           | Upwork job identifier, useful for deduplication across runs                  |

`fixedBudget`, `hourlyRateMin`, `hourlyRateMax`, `clientCountry`, `clientPaymentVerified`, `clientTotalSpent`, and `clientReviewCount` keep the same values in numeric or boolean form, so you can sort and filter without parsing text.

Upwork shows fewer client signals to signed-out visitors. When a value is unavailable, the field is empty or `null` and `clientInfo` contains only the signals that were published.

### How to use Upwork Jobs Scraper

1. Open the Actor on Apify Store.
2. Paste an Upwork jobs search URL, or type a keyword.
3. Set the maximum number of jobs and pages.
4. Start the run.
5. Download the dataset or connect it to your workflow.

The simplest input is a keyword. The most precise input is a search URL built on Upwork itself, because every filter you set on the site travels with the URL.

### Input Parameters

| Parameter            | Type    | Required | Default                                                                | Description                                                                              |
| -------------------- | ------- | -------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `searchUrl`          | String  | No       | `https://www.upwork.com/nx/search/jobs/?q=web%20scraping&sort=recency` | Upwork jobs search URL. Filters in the URL are preserved                                 |
| `keyword`            | String  | No       | -                                                                      | Searches by keyword and takes precedence over `searchUrl`. Results are sorted by recency |
| `maxJobs`            | Integer | No       | `20`                                                                   | Maximum jobs to save. `0` means no limit, bounded by `maxPages`                          |
| `maxPages`           | Integer | No       | `20`                                                                   | Maximum result pages to request. Each page holds up to 50 jobs                           |
| `proxyConfiguration` | Object  | No       | Apify Proxy                                                            | Proxy settings. Apify Proxy is on by default to give each run a stable cloud connection  |

### Usage Examples

#### Keyword search

Collect the twenty newest postings for a skill:

```json
{
    "keyword": "rust developer",
    "maxJobs": 20
}
```

#### Filtered search by URL

Build the search on Upwork, copy the URL, and keep the filters: expert level, fixed price, and a budget between $500 and $1,000.

```json
{
    "searchUrl": "https://www.upwork.com/nx/search/jobs/?q=python&contractor_tier=3&t=1&amount=500-1000&sort=recency",
    "maxJobs": 100,
    "maxPages": 4
}
```

#### Large monitoring run

Collect up to 500 postings for a category, then schedule the run to repeat daily:

```json
{
    "searchUrl": "https://www.upwork.com/nx/search/jobs/?q=data%20entry&sort=recency",
    "maxJobs": 500,
    "maxPages": 20
}
```

#### Category page URL

A category page such as `https://www.upwork.com/freelance-jobs/web-scraping/` is accepted. Upwork lists only ten static postings there, so the Actor converts the category to its equivalent keyword search to return the full result set.

```json
{
    "searchUrl": "https://www.upwork.com/freelance-jobs/web-scraping/",
    "maxJobs": 100
}
```

### Sample Output

```json
{
    "title": "Web scraping for contact details",
    "description": "Hi. I'm looking for a list of contact email addresses for Scout groups, guide groups and woodcraft groups in the UK.",
    "budget": "$50.00",
    "experienceLevel": "Intermediate",
    "contractType": "Fixed-price",
    "projectLength": "Less than 1 month",
    "skills": ["Data Scraping", "Data Entry", "Prospect List"],
    "postedDate": "3 hours ago",
    "clientInfo": "",
    "url": "https://www.upwork.com/jobs/~022116455622075806435",
    "scrapedAt": "2026-10-31T12:16:04.124Z",
    "jobId": "2116455622075806435",
    "publishedAt": "2026-10-31T09:05:08.494Z",
    "fixedBudget": 50,
    "hourlyRateMin": null,
    "hourlyRateMax": null,
    "clientCountry": "",
    "clientPaymentVerified": false,
    "clientTotalSpent": null,
    "clientReviewCount": null
}
```

### Tips for Best Results

- Start with a small `maxJobs` value to confirm the search returns what you expect before running a large collection.
- Build filters on Upwork first, then copy the URL. The Actor does not need to reinterpret your filters because they travel in the URL.
- Use `sort=recency` for monitoring. The default relevance order is better for research on a fixed topic.
- Set `maxPages` to match your result budget. One page returns up to 50 jobs in a single request.
- Schedule daily or hourly runs and use Apify's dataset comparison or webhooks to act on new postings.
- If a field is empty, check another posting before assuming a problem. Signed-out visitors see fewer client details, and not every posting lists a budget or duration.
- Result counts can differ from what you see in your browser. Upwork varies search results by visitor location, so a run through a different proxy exit may return fewer or more postings for the same query.

### Output and Exports

Every run writes to the default dataset. Open the **Output** tab to preview the table, or download the data as JSON, CSV, Excel, XML, or HTML. The `statistics` key in the run's key-value store records the saved count, pages requested, extraction path, stop reason, and duration.

### Integrations

- **Google Sheets** - Send each run to a spreadsheet for review.
- **Webhooks** - Trigger your own service when a run finishes.
- **Make or Zapier** - Route new postings into alerts, databases, or messaging.
- **API** - Read the dataset from your own application using the Apify API.
- **Schedules** - Run the Actor hourly, daily, or weekly without manual work.

### Frequently Asked Questions

#### How many jobs can I collect in one run?

`maxJobs` accepts up to 10,000 and `maxPages` accepts up to 200. A page returns up to 50 jobs, so 200 pages can cover roughly 10,000 postings when the search has that many results. Set `maxJobs` to `0` to remove the job cap and rely on `maxPages`.

#### What happens if I supply both `searchUrl` and `keyword`?

The keyword wins. The Actor ignores the URL entirely so a keyword run is never mixed with a prefilled URL. Clear the unused field if you switch modes in Apify Console.

#### Why is `clientInfo` empty on some rows?

Upwork hides several client signals from signed-out visitors. Payment verification, country, spend, and review count appear when Upwork publishes them for the visitor; otherwise those fields stay empty. This is source behaviour, not a scraping failure.

#### Why do some postings show "Not specified" for budget?

Not every client publishes a budget or an hourly range. When Upwork has no value, the Actor returns `Not specified` and leaves `fixedBudget`, `hourlyRateMin`, and `hourlyRateMax` as `null`.

#### Can I export the data to CSV or Excel?

Yes. Apify datasets can be downloaded as CSV, Excel, JSON, XML, and HTML from the Output tab or through the API.

#### Can I run this on a schedule?

Yes. Create a schedule in Apify Console and reuse the same input. For monitoring, keep `sort=recency` and compare each dataset with the previous run.

#### Does the Actor need my Upwork account?

No. It reads public search results. You do not need to sign in, and the Actor does not ask for Upwork credentials.

#### What should I do if a run returns no jobs?

Check the run log and the `statistics` key. The stop reason tells you whether the search had no matches, the page limit was reached, or Upwork rejected the request. For request-level problems, see the troubleshooting notes in the repository's `API_DISCOVERY.md`.

#### Is it legal to collect Upwork job data?

The Actor reads publicly visible job postings. You are responsible for complying with Upwork's terms, applicable privacy law, and the rules that apply to your use of the data. Do not use it to collect personal data or to contact people who did not publish contact details.

### Related Actors

- [Fast LinkedIn Job Scraper](https://apify.com/shahidirfan/fast-linkedin-job-scraper) for LinkedIn job postings.
- [Wellfound Jobs Scraper](https://apify.com/shahidirfan/wellfound-jobs-scraper) for startup roles.
- [ZipRecruiter Job Scraper](https://apify.com/shahidirfan/ziprecuriter-job-scraper) for US job listings.

### Support

For issues, feature requests, or a custom Actor, use the Issues tab on the Actor page or contact the developer through Apify.

### Legal Notice

This Actor is designed for legitimate collection of publicly available Upwork job postings. Users are responsible for complying with applicable laws, Upwork's terms of service, and any privacy obligations that apply to their use of the data.

# Actor input Schema

## `searchUrl` (type: `string`):

An Upwork jobs search URL. Build the search on Upwork, then paste the URL so its filters (budget, experience level, category, sort) carry over. Example: https://www.upwork.com/nx/search/jobs/?q=web%20scraping\&sort=recency

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

Search by keyword instead of a URL. When supplied, this takes precedence over Search URL and results are sorted by recency.

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

Maximum number of jobs to save. Set to 0 for no limit; Max pages still applies.

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

Maximum result pages to request. Each page holds up to 50 jobs and one request.

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

Apify Proxy is enabled by default to give each run a stable cloud connection. Switch to your own proxy URLs if you already run proxies.

## Actor input object example

```json
{
  "searchUrl": "https://www.upwork.com/nx/search/jobs/?q=web%20scraping&sort=recency",
  "maxJobs": 20,
  "maxPages": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Complete Upwork job listings with all details

## `jobsCSV` (type: `string`):

Export jobs in CSV format

## `jobsJSON` (type: `string`):

Export jobs in JSON format

## `statistics` (type: `string`):

Scraping statistics and summary

# 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 = {
    "searchUrl": "https://www.upwork.com/nx/search/jobs/?q=web%20scraping&sort=recency"
};

// Run the Actor and wait for it to finish
const run = await client.actor("shahidirfan/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 = { "searchUrl": "https://www.upwork.com/nx/search/jobs/?q=web%20scraping&sort=recency" }

# Run the Actor and wait for it to finish
run = client.actor("shahidirfan/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 '{
  "searchUrl": "https://www.upwork.com/nx/search/jobs/?q=web%20scraping&sort=recency"
}' |
apify call shahidirfan/upwork-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,shahidirfan/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/ESxcyeepvrLSYcJAS/builds/6LxunNf3w8W0a5I2B/openapi.json
