# Upwork Jobs Scraper - No Login, Client Intel, $0.0015/Job (`gio21/upwork-jobs-scraper`) Actor

Scrape Upwork job listings by keyword: title, full description, budget, contract type, experience level, skills, proposal count and client intel (country, spend, verified). No login needed. $0.0015/job.

- **URL**: https://apify.com/gio21/upwork-jobs-scraper.md
- **Developed by:** [Gio](https://apify.com/gio21) (community)
- **Categories:** Jobs, Lead generation, AI
- **Features:** Limited permissions
- **Stats:** 274 total users, 15 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.50 / 1,000 job scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 - No Login, Client Intel, $0.0015/Job

Scrape freelance job postings from [Upwork](https://www.upwork.com) by search query: title, full description, budget, contract type, experience level, skills, proposal count and client intelligence (country, lifetime spend, payment-verified status). No login, no cookies, no browser extension to install.

### Why use this scraper

Upwork does not expose a public API for job listings, so anyone doing lead generation, market research or rate benchmarking on Upwork is stuck copy-pasting from search results by hand, or paying for a tool that only returns the title and budget and drops the client signals that actually matter (spend history, verification status, proposal count).

This actor searches Upwork exactly the way a freelancer would, then structures every visible field on the result, job page and client card into one dataset row. You get the data needed to qualify a lead or price a job before you ever click into Upwork itself.

### Overview

The actor takes a search query, the same kind you would type into Upwork's own search bar, and returns one row per job posting it finds, up to the cap you set. A typical batch of 50 jobs completes in a couple of minutes. Every record combines the job listing itself with the client-side signals Upwork shows next to it: country, total historic spend and payment verification, plus the proposal-count bucket that tells you how much competition a posting already has.

### Supported inputs

- **Keyword search** - `query` accepts any keyword or phrase Upwork's own search bar accepts, for example `"python developer"` or `"google ads specialist"`.
- **Upwork search operators inside the query** - Upwork's own operators work unmodified, for example `location:` or `budget:`, so `"react developer location:United States"` filters at the source.
- **Result cap** - `max_jobs` caps how many jobs are returned and billed in a single run (1 to 200); pagination through Upwork's result pages is handled automatically up to that cap.

For more than 200 jobs on one topic, run the actor multiple times with different query variants rather than raising the cap, since Upwork itself paginates search results in batches.

### Use cases

- **Freelancers and agencies doing lead generation** - filter results on `clientCountry` and `clientTotalSpent` to prioritize outreach to clients who have a track record of paying well.
- **Rate and pricing research** - pull hundreds of postings for a skill (e.g. "shopify developer") and compare `budget`, `contractType` and `experienceLevel` to set a competitive rate.
- **Market and demand analysis** - run the same query on a schedule to track how posting volume and required skills shift for a given niche over weeks or months.
- **AI-assisted proposal pipelines** - feed each job's `description` and client fields into an LLM that scores fit and drafts a first-pass proposal before the queue fills up with competing bids.

### How it works

The actor issues the same search Upwork's own site handles, walks the result pages up to the requested cap, and opens each job's page and client card to pull the fields that are not shown in the results list itself: full description, skills, proposal-count bucket and client spend/verification. No login or session cookie is used at any point, since job listings and the client summary card are public information on Upwork.

### Input configuration

Search by keyword, default cap:

```json
{
  "query": "python developer",
  "max_jobs": 50
}
```

Filtered by location using Upwork's own operator:

```json
{
  "query": "react developer location:United States",
  "max_jobs": 100
}
```

Niche query with a small cap for a quick check:

```json
{
  "query": "shopify developer budget:$1000+",
  "max_jobs": 20
}
```

### Output sample

Real output from a production run against Upwork (description trimmed for readability; the actor does not truncate it):

```json
[
  {
    "jobId": "022102064185225514117",
    "title": "Senior AI Engineer / AI Developer",
    "description": "We are specifically looking for someone who has worked inside large, mature enterprise development environments. This is not an entry-level AI role...",
    "contractType": "Hourly: $70.00 - $100.00",
    "budget": "$70.00 - $100.00",
    "experienceLevel": "Expert",
    "duration": "1 to 3 months, Less than 30 hrs/week",
    "postedOn": "Posted 16 minutes ago",
    "skills": ["AI App Development", "Artificial Intelligence", "Jira", "Enterprise Architecture"],
    "extraSkills": 0,
    "clientVerified": false,
    "clientCountry": "",
    "proposals": "",
    "clientTotalSpent": "",
    "url": "https://www.upwork.com/jobs/~022102064185225514117"
  }
]
```

`clientCountry`, `proposals` and `clientTotalSpent` come back empty on some postings, that is Upwork's own client card not showing that data for the client in question, not a scraping gap.

### Key output fields

| Field | Type | Description |
|-------|------|--------------|
| `jobId` | string | Canonical Upwork job ID, use it to deduplicate across runs |
| `title` | string | Job title |
| `description` | string | Full job description text |
| `contractType` | string | Hourly with rate range, or Fixed-price |
| `budget` | string | Budget for fixed-price jobs, empty for hourly |
| `experienceLevel` | string | Entry, Intermediate or Expert |
| `duration` | string | Estimated project duration and weekly hours |
| `postedOn` | string | Relative time since posting, as shown by Upwork |
| `skills` | array of string | Skills listed on the posting |
| `extraSkills` | integer | Count of additional skills beyond those listed |
| `proposals` | string | Proposal-count bucket shown by Upwork, when available |
| `clientCountry` | string | Client's country, when shown |
| `clientTotalSpent` | string | Client's lifetime spend on Upwork, when shown |
| `clientVerified` | boolean | True when the client's payment method is verified |
| `url` | string | Direct link to the job posting |

### Pricing

Pay-per-result pricing, no subscription:

| Event | Price |
|-------|-------|
| Job scraped | $0.0015 per job |
| Actor start | $0.00005 per GB of memory (one-time per run) |

1,000 jobs costs $1.50 plus a negligible per-run start fee.

### What makes this richer than the competition

Compared to two of the larger Upwork job scrapers on the Store:

- **neatrat/upwork-job-scraper** returns the core job fields but does not surface client spend history (`clientTotalSpent`) as a structured field, which is the single strongest signal for qualifying a lead before you pitch.
- **blackfalcondata/upwork-scraper** advertises client intelligence too, but does not expose Upwork's own search operators (like `location:` or `budget:`) as documented input behavior, so filtering happens after the fact instead of at the source.

This actor accepts Upwork's own search operators directly in `query`, so you only pay for jobs that already match your filter, and every client-side signal Upwork shows (country, spend, verification, proposal bucket) comes through as its own field instead of being buried in a description blob.

### Notes & limitations

- `clientCountry`, `clientTotalSpent` and `proposals` are only as complete as what Upwork itself displays on the client card; brand-new clients or postings with no proposals yet come back empty on those fields.
- `budget` is populated for fixed-price jobs only; for hourly contracts the rate range lives in `contractType` instead.
- Coverage matches what Upwork's own search returns for the query given; very broad queries may need to be split into narrower variants to get past 200 results.
- This actor does not submit proposals, message clients, or interact with Upwork beyond reading public listings.

### FAQ

**Do I need an Upwork account to use this?**
No. The actor reads public job listings and public client summary cards; no login or cookies are required.

**Can I use Upwork's own search operators?**
Yes. Anything you can type into Upwork's search bar works in the `query` field, including operators like `location:` and `budget:`.

**Why are some client fields empty?**
Upwork itself does not always show spend history, country or proposal count for every client, most often for brand-new accounts. The actor returns exactly what Upwork displays.

**How do I get more than 200 jobs for one topic?**
Run the actor again with a narrower or different query variant rather than raising `max_jobs` past the per-run cap.

**Can I deduplicate results across scheduled runs?**
Yes, store `jobId` and skip rows you have already seen on later runs.

**Is it legal to scrape Upwork?**
The actor collects only publicly available job-posting and client-summary information. You are responsible for how you use the data and for complying with Upwork's terms and applicable law.

### For AI Agents & LLM Apps

This actor is available over the [Apify MCP server](https://mcp.apify.com) for direct use inside Claude, ChatGPT and other MCP-compatible agents, or via the Apify API for a synchronous call that returns dataset items in one request:

```bash
curl "https://api.apify.com/v2/acts/gio21~upwork-jobs-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "python developer", "max_jobs": 20}'
```

The response is a JSON array of job objects, ready to feed into an agent's context or a proposal-drafting pipeline without any extra parsing step.

### Disclaimer

Upwork is a trademark of its respective owner. This actor is an independent tool that reads publicly available Upwork job listings; it is not affiliated with, endorsed by, or sponsored by Upwork.

### SEO Keywords

Upwork scraper, Upwork API, Upwork jobs scraper, Upwork job listings, Upwork lead generation, freelance job scraper, Upwork client data, Upwork data extraction, Upwork search scraper, Upwork jobs by keyword.

# Actor input Schema

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

Keywords to search Upwork for, exactly as you'd type in its search bar. Supports Upwork operators like 'location:' or 'budget:'. Example: 'python developer', 'google ads specialist location:United States'.

## `max_jobs` (type: `integer`):

Maximum number of jobs to return (1-200). Pagination is handled automatically. For more than 200, schedule multiple runs with different keywords. Example: 50.

## Actor input object example

```json
{
  "query": "python developer",
  "max_jobs": 10
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

# 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 developer",
    "max_jobs": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("gio21/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 = {
    "query": "python developer",
    "max_jobs": 10,
}

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

```

## MCP server setup

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