# Upwork Jobs Scraper — Freelance Listings & Monitor (`crawloop/upwork-jobs-scraper`) Actor

Upwork Jobs Scraper for freelance listings and a new-job monitor. Search by keyword or a search URL and export title, budget, hourly range, skills, experience, and posted time as JSON.

- **URL**: https://apify.com/crawloop/upwork-jobs-scraper.md
- **Developed by:** [Andrej Kiva](https://apify.com/crawloop) (community)
- **Categories:** Jobs, Lead generation, AI
- **Stats:** 2 total users, 1 monthly users, 75.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.20 / 1,000 job listings

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?

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

## Upwork Jobs Scraper — Freelance Listings & Monitor

> **Disclaimer:** Unofficial tool — not affiliated with, sponsored by, or endorsed by Upwork Inc. or its affiliates. Data is read from publicly accessible job-search results only. No login. You are responsible for complying with applicable law (including GDPR where personal data appears) and the site’s terms. No warranty on accuracy or availability. Provided for informational and research use.

**Upwork scraper** for public freelance job listings (**Upwork jobs scraper**, **Upwork job monitor**). A practical **Upwork API alternative**: search by **keyword** or paste a **search URL**, then export **title**, **description**, **fixed budget**, **hourly range**, **skills**, **experience level**, **duration**, and **posted time** as **JSON**, **CSV**, or **Excel**. Fast **HTTP**, **no headless browser**, **no login**. Call it from **Python**, **Node.js**, **cURL**, or **Apify MCP**.

Built for freelancers and agencies who want a **new-job monitor**: schedule a run and save only listings that were not in the previous pull.

| Actor | Focus |
| :--- | :--- |
| Upwork Jobs Scraper ◄── you are here | Freelance listings and a new-job monitor |
| [LinkedIn Jobs Scraper](https://apify.com/crawloop/linkedin-jobs-scraper) | Public LinkedIn job listings |
| [Seek Jobs Scraper](https://apify.com/crawloop/seek-jobs-scraper) | Australia and New Zealand job cards |
| [InfoJobs Job Scraper](https://apify.com/crawloop/infojobs-jobs-scraper) | Spain jobs, contract, and EUR salary |
| [APEC Jobs Scraper](https://apify.com/crawloop/apec-jobs-scraper) | France cadre jobs |
| [Stepstone Jobs Scraper](https://apify.com/crawloop/stepstone-jobs-scraper) | DACH commercial job board |
| [Arbeitsagentur Jobs Scraper](https://apify.com/crawloop/arbeitsagentur-jobs-scraper) | Germany public Jobsuche |
| [Glassdoor Scraper](https://apify.com/crawloop/glassdoor-scraper) | Employer reviews, salaries, and jobs |
| [Naukri Jobs Scraper](https://apify.com/crawloop/naukri-jobs-scraper) | India Naukri.com jobs |
| [ZipRecruiter Jobs Scraper](https://apify.com/crawloop/ziprecruiter-jobs-scraper) | United States ZipRecruiter jobs |

### When to use this Actor

- You need an **Upwork jobs scraper** that returns structured JSON without a browser
- You want **keyword search**, newest first, with **budget**, **hourly range**, and **skills**
- You run a **daily or hourly monitor** and only want **new job ids** saved
- You scrape Upwork from **Python**, **Node.js**, or an **MCP** assistant

### When not to use this Actor

- **Single job pages** (`/jobs/~…`). Those URLs are skipped. Use a keyword or a search URL.
- **Freelancer profiles**, proposals, or messages. This Actor reads the public job search only.
- **Client spend, hire rate, and proposal count.** The anonymous search does not include those fields.

### Key features

- Keyword search and Upwork search URLs (`q`)
- Newest-first pages, up to 50 jobs per page
- Hourly vs fixed-price, experience level, and budget filters
- Monitor mode with a named store of seen job ids
- Residential proxy on by default, because datacenter IPs often get a challenge page

### Input

| Field | Required | Default | Description |
| :--- | :--- | :--- | :--- |
| `keyword` | No | — | Search text. Empty means the newest public jobs. |
| `searchKeywords` | No | — | Extra keywords, each searched on its own. |
| `startUrls` | No | — | Search URLs. The `q` parameter is the keyword. |
| `jobType` | No | Any | `HOURLY` or `FIXED`. |
| `experienceLevel` | No | Any | `Entry`, `Intermediate`, or `Expert`. |
| `hourlyMin` / `hourlyMax` | No | — | USD hourly window. |
| `fixedMin` / `fixedMax` | No | — | USD fixed-budget window. |
| `maxItems` | No | 50 | Row cap across all keywords. `0` means no row cap. |
| `maxPages` | No | 3 | Pages per keyword. |
| `monitorMode` | No | false | Save only job ids not seen in the named store. |
| `monitorStoreName` | No | `upwork-jobs-monitor` | One name per watch. |
| `resetMonitorState` | No | false | Clear seen ids before this run. |
| `proxyConfiguration` | No | US residential | Leave residential on unless a direct run already works. |

```json
{
  "keyword": "python",
  "jobType": "HOURLY",
  "experienceLevel": "Expert",
  "hourlyMin": 30,
  "maxItems": 50,
  "monitorMode": true,
  "monitorStoreName": "upwork-python-watch"
}
```

### Output

Each dataset row is one public job card.

| Field | Description |
| :--- | :--- |
| `jobId` | Upwork job id |
| `title` | Job title |
| `description` | Public description |
| `url` | Job URL |
| `jobType` | `HOURLY` or `FIXED` |
| `experienceLevel` | `Entry`, `Intermediate`, or `Expert` |
| `hourlyMin` / `hourlyMax` | Posted hourly range in USD, when the job is hourly |
| `fixedBudget` | Posted fixed budget in USD, when the job is fixed-price |
| `durationLabel` / `durationWeeks` | Engagement length |
| `skills` | Skill labels |
| `postedAt` | Publish time |
| `keyword` | Search text that returned the row |
| `scrapedAt` | Time this run saved the row |

```json
{
  "jobId": "2107753753929570153",
  "title": "Python automation for a weekly report",
  "description": "Build a small Python job that pulls a spreadsheet and emails a summary.",
  "url": "https://www.upwork.com/jobs/~022107753753929570153",
  "jobType": "FIXED",
  "experienceLevel": "Expert",
  "hourlyMin": null,
  "hourlyMax": null,
  "fixedBudget": 150,
  "durationLabel": "1 to 3 months",
  "durationWeeks": 9,
  "skills": ["Python", "Automation"],
  "postedAt": "2026-10-07T08:43:52.730Z",
  "keyword": "python",
  "scrapedAt": "2026-10-07T09:00:00Z"
}
```

### Use cases

- Watch a skill keyword and send new fixed-price or hourly jobs to a sheet
- Compare posted budgets for the same keyword over a week
- Feed titles, skills, and budgets into an assistant that drafts proposals
- Keep a baseline of seen ids, then schedule the Actor so later runs stay short

### Integration examples

#### Node.js

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('crawloop/upwork-jobs-scraper').call({
  keyword: 'python',
  maxItems: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.slice(0, 5));
```

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient(token)
run = client.actor("crawloop/upwork-jobs-scraper").call(
    run_input={
        "keyword": "python",
        "maxItems": 50,
    }
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item.get("title"), item.get("jobType"), item.get("fixedBudget"), item.get("url"))
```

#### cURL

```bash
curl "https://api.apify.com/v2/acts/crawloop~upwork-jobs-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keyword":"python","maxItems":50}'
```

### MCP and AI assistants

Use this Actor from AI tools via [Apify MCP](https://docs.apify.com/platform/integrations/mcp). Connect your Apify account, then call `crawloop/upwork-jobs-scraper`.

Example prompts:

- "Run Upwork Jobs Scraper for python, max 50, and return title, jobType, fixedBudget, hourlyMin, skills, and url as JSON"
- "Monitor new Upwork logo design jobs with monitorMode on and monitorStoreName upwork-logo-watch"
- "Pull Upwork expert hourly jobs for React, then run Glassdoor Scraper for a company named in the description"

### Suite next step

This Actor covers public Upwork job cards and a new-job monitor. For the same skill on public employment postings, run [LinkedIn Jobs Scraper](https://apify.com/crawloop/linkedin-jobs-scraper). When a description names an employer you want to research, run [Glassdoor Scraper](https://apify.com/crawloop/glassdoor-scraper). For country job boards, continue with [Seek Jobs Scraper](https://apify.com/crawloop/seek-jobs-scraper) or [InfoJobs Job Scraper](https://apify.com/crawloop/infojobs-jobs-scraper).

### FAQ

**Is this an official Upwork API?**
No. It reads the public visitor job search. You do not log in and you do not need Upwork developer credentials.

**Can I paste a search URL?**
Yes. `startUrls` reads the `q` parameter. Individual `/jobs/~` pages are skipped.

**What does monitor mode do?**
Seen job ids are stored in a named Key-Value store. The first run is the baseline. Later scheduled runs save only ids that were not seen before, and stop when a newest-first page is already known. Use one `monitorStoreName` per watch.

**Why is a residential proxy on?**
Datacenter addresses often receive a challenge page instead of the visitor token. Leave residential on. Turn it off only after a direct run succeeds.

**Are client spend and proposal counts included?**
No. The anonymous search returns the job card: title, description, budget or hourly range, skills, experience, duration, and posted time.

**Can I export JSON, CSV, or Excel?**
Yes. Every run writes a dataset you can download as JSON, CSV, or Excel, or pull with the Apify API.

### Related Actors

| Actor | Focus |
| :--- | :--- |
| Upwork Jobs Scraper ◄── you are here | Freelance listings and a new-job monitor |
| [LinkedIn Jobs Scraper](https://apify.com/crawloop/linkedin-jobs-scraper) | Public LinkedIn job listings |
| [Seek Jobs Scraper](https://apify.com/crawloop/seek-jobs-scraper) | Australia and New Zealand jobs |
| [InfoJobs Job Scraper](https://apify.com/crawloop/infojobs-jobs-scraper) | Spain jobs |
| [Glassdoor Scraper](https://apify.com/crawloop/glassdoor-scraper) | Employer reviews, salaries, and jobs |
| [ZipRecruiter Jobs Scraper](https://apify.com/crawloop/ziprecruiter-jobs-scraper) | United States jobs |

# Actor input Schema

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

Search text, for example python or logo design. Leave empty to take the newest public jobs.

## `searchKeywords` (type: `array`):

Optional extra keyword queries. Each keyword is searched separately and results are deduplicated by job id.

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

Optional Upwork job-search URLs. The q parameter is used as the keyword. Individual /jobs/~ pages are skipped.

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

Keep hourly jobs, fixed-price jobs, or both. Applied after search.

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

Experience the client asked for. Applied after search.

## `hourlyMin` (type: `integer`):

Drop hourly jobs with no posted maximum, or whose posted maximum is below this USD amount. Fixed-price jobs are dropped when this is set.

## `hourlyMax` (type: `integer`):

Drop hourly jobs whose posted minimum is above this USD amount.

## `fixedMin` (type: `integer`):

Drop fixed-price jobs below this USD budget. Hourly jobs are dropped when this is set.

## `fixedMax` (type: `integer`):

Drop fixed-price jobs above this USD budget.

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

Maximum job rows to save across all searches. 0 means no row cap (still limited by max pages).

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

Safety cap. Each page asks for up to 50 jobs, newest first.

## `monitorMode` (type: `boolean`):

Save and charge only job ids that were not seen in a previous run. The first run is the baseline. Use a stable monitor store name with an Apify Schedule.

## `monitorStoreName` (type: `string`):

Named Key-Value store for seen job ids. Use one name per watch.

## `resetMonitorState` (type: `boolean`):

Clear seen job ids before this run.

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

Residential proxy is on by default. Datacenter IPs often receive Upwork's challenge page instead of the visitor token. Turn the proxy off only if a direct run already succeeds.

## Actor input object example

```json
{
  "keyword": "python",
  "jobType": "",
  "experienceLevel": "",
  "maxItems": 50,
  "maxPages": 3,
  "monitorMode": false,
  "monitorStoreName": "upwork-jobs-monitor",
  "resetMonitorState": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

Default dataset items.

# 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 = {
    "keyword": "python"
};

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

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

```

## MCP server setup

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