# Upwork Scraper - Freelance Jobs & Client Intel (`scrapesage/upwork-scraper`) Actor

Scrape Upwork freelance jobs: title, description, fixed or hourly budget, experience level, workload, duration and skills. Client intelligence adds payment verified, total spend, hire rate, rating, country and the proposal count. Paste any Upwork search, category or job URL.

- **URL**: https://apify.com/scrapesage/upwork-scraper.md
- **Developed by:** [Scrape Sage](https://apify.com/scrapesage) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.30 / 1,000 job listings

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 Scraper - Freelance Jobs & Client Intel

> **Disclaimer:** This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Upwork Inc. or any of its subsidiaries. All trademarks mentioned are the property of their respective owners. "Upwork" is referenced only to describe the publicly available website this Actor collects data from.

Scrape **[Upwork](https://www.upwork.com)** freelance jobs into a clean dataset: title, full description, fixed-price budget or hourly range, experience level, workload, duration, required skills and the exact posting timestamp.

Then turn on **client intelligence** and every row also tells you *who is hiring and whether they are worth a proposal*: **payment method verified**, **total spend**, **hire rate**, **star rating**, jobs posted, open jobs, hours billed, country and city — plus that job's **proposal count** and interview count.

### Why this Upwork scraper?

- **Client intelligence is the product, not a footnote.** `clientPaymentVerified`, `clientTotalSpend`, `clientHireRate`, `clientRating`, `clientReviews`, `clientTotalJobsPosted`, `clientCountry`. Filter to *only* verified clients who have spent over $10,000 and you have a proposal shortlist instead of a job list.
- **`proposals` on every enriched row.** How many freelancers have already applied is the single best signal of whether a job is worth your time, and it is on the record as a number.
- **Budgets as numbers, routed correctly.** `budgetAmount` for fixed-price work and `hourlyMin` / `hourlyMax` for hourly — never mixed, because a $75/hour rate reported as a "$75 budget" is worse than no number at all.
- **Three surfaces, one record shape.** Search URLs (followed page by page), Upwork's own category pages (one block of ~30 richer jobs) and single job URLs all produce the same columns.
- **Monitor mode** returns only jobs posted since your last run — pair it with a Schedule and be early to the ones that matter.

### Use cases

- **Freelancers & agencies** — build a filtered pipeline of jobs from verified, high-spend clients in your skills, and see the competition before you spend a Connect.
- **Lead generation** — a client posting repeatedly is a buyer. Country, spend and hire rate on every row.
- **Market & rate research** — what a skill actually pays, by level, type and category.
- **Recruitment & talent intelligence** — which skills are in demand and what the market pays for them.
- **AI agents & pipelines** — a structured, machine-readable feed of the largest freelance marketplace.

### How to use

1. Enter **search terms**, **category slugs**, or paste Upwork **URLs** straight from your browser.
2. Narrow with job type, experience level, budget band, skills or title text.
3. Turn on **client intelligence** (or set any client filter, which turns it on for you).
4. Set **Maximum jobs** and run.

### Input

```json
{
    "searchQueries": ["python"],
    "jobTypes": ["Hourly"],
    "experienceLevels": ["Expert"],
    "minBudget": 50,
    "includeClientDetails": true,
    "paymentVerifiedOnly": true,
    "minClientSpend": 10000,
    "maxResults": 100
}
```

- **searchQueries** — one term per line; each is searched separately and results merged.
- **startUrls / urlsFromFile** — any Upwork search, category or single-job URL. Every filter already in a pasted URL is kept. Type them, upload a file, or link a remote `.txt` / `.csv` / Google Sheet.
- **categories** — Upwork's own `/freelance-jobs/<slug>/` pages such as `data-science` or `website-development`. Each returns **one block of about 30 jobs with richer fields** than search — the exact posting timestamp, workload and duration come already filled in, which makes it the cheapest way to sweep a discipline. Upwork does not page these landing pages, so reach for a search term when you want more than ~30 jobs from one discipline.
- **jobTypes** (Fixed price · Hourly), **experienceLevels** (Entry · Intermediate · Expert) — pick-lists, so a filter cannot silently fail.
- **minBudget / maxBudget**, **titleQuery**, **skillsAny**, **excludeKeywords**.
- **includeClientDetails** + **maxDetailRecords** — see below.
- **paymentVerifiedOnly**, **minClientSpend**, **minClientRating**, **clientCountries** — client-quality filters. Each one switches client details on automatically, because the filter cannot be applied without them.
- **maxPagesPerUrl**, **maxResults**, **monitorMode / monitorKey**.

> **Tip:** run with nothing set and you get a 10-job sample rather than a full sweep, so an exploratory or agent-issued call is never an expensive surprise.

### Output

Four dataset views: **Jobs** · **Client intelligence** · **Newest first** · **By skill**.

```json
{
    "recordType": "job",
    "ciphertext": "~022047808109244543596",
    "title": "Desarrollador python para actualizar programa de python 2 a python 3.",
    "url": "https://www.upwork.com/jobs/~022047808109244543596",
    "jobType": "Fixed price",
    "experienceLevel": "Intermediate",
    "budgetAmount": 400,
    "hourlyMin": null,
    "hourlyMax": null,
    "workload": "Less than 30 hrs/week",
    "duration": "More than 6 months",
    "category": "Web Development",
    "occupation": "Back-End Development",
    "skills": ["Python", "Python Script"],
    "description": "…the full job post…",
    "postedAt": "2026-04-24T22:41:14.665Z",

    "proposals": 22,
    "interviewing": 0,
    "clientPaymentVerified": true,
    "clientTotalSpend": 400,
    "clientRating": 0,
    "clientReviews": 0,
    "clientTotalJobsPosted": 1,
    "clientJobsWithHires": 1,
    "clientHireRate": 100,
    "clientOpenJobs": 1,
    "clientCountry": "ESP",
    "clientCity": "Usurbil",
    "clientLastActive": "2026-08-10T21:21:55.538Z"
}
```

#### Client intelligence — `includeClientDetails`

Off by default because it costs one extra request per job. Turn it on and each job's own page is read to add the buyer's record and the job's competition. `maxDetailRecords` caps how many are enriched, so a broad search cannot become a large bill by accident — jobs past the cap are still returned, just without the client record.

`clientHireRate` is derived for you (`jobsWithHires ÷ jobsPosted`), because "posts a lot, hires rarely" is exactly the client you want to filter out.

#### What to expect (field coverage)

| Field group | Coverage |
|---|---|
| Title, URL, job type, experience level, description, skills | ~100% |
| Budget (fixed) **or** hourly range | present according to the contract type — a fixed-price job has no hourly range and vice versa |
| Workload, duration | on category-page and enriched records |
| `postedAt` (exact timestamp) | category-page and enriched records; search-page rows carry Upwork's relative `postedText` instead |
| Client fields, `proposals` | only with client intelligence on |
| `clientRating`, `clientReviews` | `0` for genuinely new clients who have not been reviewed yet — that is a real signal, not a gap |

### Monitor mode

Turn on **`monitorMode`** and the actor remembers every job already returned under that key and emits only new ones, tagged `monitorEvent: "new"`. It is **orthogonal to [Apify Schedules](https://docs.apify.com/platform/schedules)**: the Schedule sets the cadence, monitor mode decides what is new.

### A note on reliability

Upwork is heavily protected — it refuses ordinary datacenter and residential proxies outright. This actor reaches it through a commercial unlocking layer, which is why it returns data where simpler scrapers return empty pages. That layer occasionally serves a partial render; the actor detects short pages and refetches them, and any run that still comes up empty says so plainly on the run card rather than blaming your input.

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

This Actor uses Apify's **pay-per-event** pricing: you are charged only for the results it delivers, with no monthly rental and no start fee. The events it can charge are:

- **Job listing** - One Upwork job: title, full description, fixed-price budget or hourly range, experience level, workload, duration, required skills, category and the posting time.
- **Client intelligence** - One job's own page fetched through the unlocker to add the buyer's record: payment-method verified, total spend, jobs posted, jobs with hires, derived hire rate, star rating and review count, open jobs, hours billed, country, city and timezone - plus that job's proposal, interview and invite counts and its exact posting timestamp.

The current price of each event is shown on the **Pricing** tab of this page. Set a maximum total charge on the run if you want a hard cap on spend, and use the input limits to control how much the Actor fetches.

### Automate & schedule

[Schedules](https://docs.apify.com/platform/schedules), [webhooks](https://docs.apify.com/platform/integrations/webhooks), and one-click [integrations](https://docs.apify.com/platform/integrations) for Google Sheets, Slack, Airtable, Make and Zapier.

### Integrate with any app

**JSON, CSV, Excel, XML and RSS** through the [Dataset API](https://docs.apify.com/api/v2#/reference/datasets/item-collection); start it from the [API](https://docs.apify.com/api/v2#/reference/actors/run-collection/run-actor) or any [client library](https://docs.apify.com/api/client/js/).

### Use with AI assistants (MCP)

Exposed through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp), so Claude, ChatGPT, Cursor and any MCP client can call it as a tool — *"find expert-level Python jobs from payment-verified clients who have spent over $10k, posted this week"* becomes one tool call.

### Agent-ready: autonomous payments (x402 & Skyfire)

This actor is **agent-ready** — AI agents can discover it, run it, and **pay for it autonomously**, with no Apify account and no human in the loop. It uses [pay-per-event](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event) pricing and [limited permissions](https://docs.apify.com/platform/actors/development/permissions), so it qualifies for Apify's agentic-payment standards:

- **[x402](https://docs.apify.com/platform/integrations/x402)** — an open, HTTP-native payment protocol. Agents pay per run in USDC on the Base network directly through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp) — no account, no API key.
- **[Skyfire](https://docs.apify.com/platform/integrations/skyfire)** — agent-to-service payments for fully autonomous AI-agent workflows.

Building an AI agent, MCP tool, or autonomous data pipeline? This scraper is ready to plug in and pay as it goes.

### More job & hiring scrapers from scrapesage

- [InfoJobs Scraper](https://apify.com/scrapesage) — Spain's largest job board, with structured salary ranges
- [Arbeitsagentur Scraper](https://apify.com/scrapesage) — Germany's federal job board, 700,000+ positions
- [StepStone Scraper](https://apify.com/scrapesage) — German jobs with salary and company
- Browse everything at [apify.com/scrapesage](https://apify.com/scrapesage)

### More scrapers from scrapesage

Related Actors in the same category:

- **[MyCareersFuture Scraper](https://apify.com/scrapesage/mycareersfuture-scraper)** - Singapore Jobs, Salaries & Leads
- **[Foundit Scraper](https://apify.com/scrapesage/foundit-scraper)** - Jobs, Salaries, Skills & Recruiter Leads
- **[Glassdoor Scraper](https://apify.com/scrapesage/glassdoor-scraper)** - Company Reviews, Ratings & Interviews
- **[Multi-ATS Job Scraper](https://apify.com/scrapesage/multi-ats-job-scraper)** - Greenhouse, Lever, Ashby, Workday
- **[Y Combinator Scraper](https://apify.com/scrapesage/ycombinator-scraper)** - Companies, Founders & Jobs

### FAQ

**Do I need an Upwork account or API key?**
No. This reads only public job pages.

**Why is `proposals` missing on some rows?**
Proposal counts live on a job's own page, so they appear only when client intelligence is on and the job was within `maxDetailRecords`.

**Search page or category page — which should I use?**
Category pages return one block of about 30 jobs with the exact posting timestamp, workload and duration already included, so they are cheaper per job - but Upwork does not page them, so ~30 is all a category can give. Use search terms when you need depth, a specific query, or want to reuse a filtered URL from your browser; search pages carry about 10 jobs each and are followed for as many pages as you ask for.

**What does `clientHireRate` mean?**
The share of that client's posted jobs that ended in a hire, as a percentage. A client with 40 jobs posted and 4 hires has a 10% hire rate — usually a sign to skip.

**Can I get freelancer profiles too?**
Not in this actor — it covers jobs and the clients posting them.

**Are budgets in USD?**
Upwork publishes in USD and the numeric fields are plain USD amounts. The currency code is kept where Upwork provides one.

### Data & lawful use

This Actor reads only what Upwork publishes to logged-out visitors: it does not log in, use cookies or session tokens, create accounts, or reach anything behind a sign-in. Names, handles, bios and engagement figures are public, but they relate to identifiable people, so treat the output as personal data. If you are in the EU or UK you are the data controller for what you do with it: have a lawful basis (usually legitimate interest for research, marketing analytics or B2B prospecting), honour access and deletion requests, and do not use the output for spam or unsolicited messaging.

Under [Apify's Standard Actor Contract](https://docs.apify.com/legal/standard-actor-contract), which governs your use of this Actor, you are the controller of any personal data in your input and output and scrapesage acts only as your processor: that data is processed solely to run your job, written only to your own Apify storage, never used for any other purpose and never shared onward. If you need help with a data-subject request that involves this Actor's output, open an issue on the Issues tab.

### Disclaimer

**This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Upwork Inc. or any of its subsidiaries. All trademarks mentioned are the property of their respective owners.**

"Upwork" and any related marks are the property of their respective owners and are used here only in a descriptive, nominative sense - to identify the publicly accessible website from which this Actor collects data. This Actor is not an official Upwork product, is not authorised or certified by Upwork Inc., and does not distribute Upwork software. It collects only publicly available information; you are responsible for ensuring your use of that data complies with applicable laws, regulations and the terms of the source website.

### Need help?

Open an issue on the actor's **Issues** tab with your input and the run ID and it will be looked at.

***

*This actor collects publicly available job postings for research and market-analysis purposes. It is not affiliated with, endorsed by, or sponsored by Upwork. Upwork is a trademark of its respective owner.*

# Actor input Schema

## `searchQueries` (type: `array`):

What to search Upwork for, one term per line - a skill, a role or a technology (for example <code>python</code>, <code>shopify developer</code>, <code>copywriter</code>). Each term is searched separately and the results are merged and de-duplicated.

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

Paste Upwork URLs straight from your browser and every filter already in them is kept. A search URL (<code>/nx/search/jobs/?q=...</code>) is followed page by page - paste one that already has <code>\&page=3</code> and it starts there. A category URL (<code>/freelance-jobs/...</code>) returns a single block of ~30 richer records, because Upwork does not page those landing pages. A single job URL (<code>/jobs/~0...</code>) is scraped with full client details. Type them, upload a file, or link a remote text file.

## `urlsFromFile` (type: `string`):

A block of Upwork URLs, one per line - or a single link to a .txt / .csv file, a Google Sheet or a Drive file containing them. Blank lines, # comments, quotes and duplicates are all handled.

## `categories` (type: `array`):

Upwork's own <code>/freelance-jobs/\<slug>/</code> discipline pages. Each returns about 30 jobs with richer structured fields than the search page - the exact posting time, workload and duration are all included - so this is the cheapest way to sweep a whole discipline. All 83 slugs were read from Upwork's own navigation, so a mistyped one is rejected before the run instead of quietly costing a request and returning nothing.

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

Keep only fixed-price or hourly contracts.

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

Upwork's own three levels.

## `minBudget` (type: `integer`):

Keep only jobs at or above this figure. Compared against the fixed-price budget, or the top of the hourly range for hourly contracts. Jobs that publish no figure are dropped when this is set.

## `maxBudget` (type: `integer`):

Keep only jobs at or below this figure.

## `titleQuery` (type: `string`):

Keep only jobs whose title contains this text (case-insensitive) - the quickest way to tighten a broad search.

## `skillsAny` (type: `array`):

Keep a job if it lists at least one of these skills, matched loosely (for example <code>react</code> matches <code>React Native</code>).

## `excludeKeywords` (type: `array`):

Drop any job whose title or description contains one of these terms.

## `includeClientDetails` (type: `boolean`):

<b>The reason to use this actor.</b> Off by default because it costs one extra request per job. Turn it on and every row gains the buyer's record: payment-method verified, total spend, jobs posted, hire rate, star rating and review count, open jobs, hours billed, country, city and timezone - plus the job's own proposal count, interview count and exact posting timestamp. Any of the client filters below switches this on automatically.

## `maxDetailRecords` (type: `integer`):

A ceiling on how many job pages are fetched for client details, so a broad search cannot become a large bill by accident. Jobs past the cap are still returned, just without the client record.

## `paymentVerifiedOnly` (type: `boolean`):

The single most useful quality filter on Upwork - unverified clients are the ones who waste a freelancer's proposals. Switches client details on automatically.

## `minClientSpend` (type: `integer`):

Keep only jobs from clients who have already spent at least this much on Upwork. Switches client details on automatically.

## `minClientRating` (type: `integer`):

Keep only jobs from clients rated at or above this (out of 5). Clients with no rating yet are dropped. Switches client details on automatically.

## `clientCountries` (type: `array`):

Keep only jobs whose client is in one of these countries, matched on the code Upwork publishes (for example <code>USA</code>, <code>GBR</code>, <code>AUS</code>, <code>ESP</code>). Switches client details on automatically.

## `maxPagesPerUrl` (type: `integer`):

How many result pages to follow for each search term or search URL - about 10 jobs per page. Category listings ignore this: Upwork serves them as one block of ~30 richer jobs, so use a search term when you want depth.

## `maxResults` (type: `integer`):

Stops the run once this many jobs have been collected across all searches.

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

Remembers every job already returned under this monitor key and emits only new ones, tagged monitorEvent: "new". Pair it with an Apify Schedule to watch a niche and be first to propose.

## `monitorKey` (type: `string`):

Names the memory used by monitor mode. Use a different key per saved watch so separate monitors never share state.

## Actor input object example

```json
{
  "searchQueries": [
    "python"
  ],
  "includeClientDetails": false,
  "maxDetailRecords": 50,
  "paymentVerifiedOnly": false,
  "maxPagesPerUrl": 5,
  "maxResults": 50,
  "monitorMode": false,
  "monitorKey": "default"
}
```

# Actor output Schema

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

All scraped Upwork jobs 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 = {
    "searchQueries": [
        "python"
    ]
};

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

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

```

## MCP server setup

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