# Upwork Job Scraper – Job Alerts with Client Intel (`crawl_craft/upwork-job-scraper-job-alerts-with-client-intel`) Actor

Scrolling Upwork for good jobs means missing them and wasting connects. Get jobs by keyword or link with pay, skills, exact proposal count and client history, plus a client score, opportunity score and red flags. Set alerts for new jobs only.

- **URL**: https://apify.com/crawl_craft/upwork-job-scraper-job-alerts-with-client-intel.md
- **Developed by:** [Crawl Craft](https://apify.com/crawl_craft) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.80 / 1,000 results

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 Job Scraper — job pay, proposals, client history and scores

Upwork Job Scraper collects public Upwork jobs by keyword, search link or job link and returns each job's pay, skills, exact proposal count and the client's country, spend, hire rate and rate paid. It adds a client score, an opportunity score and red flags to spot good leads before spending connects. Every response is structured JSON — ready to pass directly to an LLM, index into a vector store, or feed a lead-alert pipeline. **No Upwork account or cookies needed.** Schedule it with "only new jobs" for fresh leads.

#### What is the Upwork Job Scraper?

It reads the job pages any visitor can see on Upwork and turns every job into one clean row. Use it to scrape Upwork jobs for lead generation, to build Upwork job alerts, to compare clients before you apply, or to export Upwork jobs to CSV or Excel.

#### What Upwork job data can the Upwork Job Scraper extract?

- **Job:** title, full description, job type, budget or hourly range, experience level, project length, workload, category, skills, tools and publish time.
- **Competition:** exact Upwork proposals count, freelancers being interviewed, invites sent and unanswered, hires made, and a low / medium / high competition level.
- **Upwork client history:** country, city, time zone, member since, payment verification, total spent, rating, reviews, total hires, hire rate, jobs posted, open jobs, average hourly rate paid, industry and company size. Some jobs hide the client details from visitors; those rows still carry the job data and `detailsStatus` says `login_required`.
- **Scores:** a client quality score (0-100) with the reasons behind it, an opportunity score (0-100), and red flags such as an unverified payment method or 50+ proposals.
- **Requirements:** minimum job success score, English level, Rising Talent allowed, portfolio required, preferred locations.
- **Run info:** the keyword or link that found the job, a re-post marker and the time scraped.

#### How to Use (Apify Console)

1. Log in at https://console.apify.com → **Actors** and open **Upwork Job Scraper – Job Alerts with Client Intel**.
2. Add keywords, Upwork search links or job links to **Keywords, search links or job links**.
3. Set **Max jobs per keyword or link** (each input gets its own limit), then the filters you want: job type, experience level, budget, client country, minimum client score and more.
4. For job alerts, pick a short **Posted within** window, tick **Only new jobs since the last run** and add an alert webhook.
5. Click **Start** and watch the log — the first 25 jobs are listed in the log, then a running count, and every job appears in the Output tab while the run is going.
6. Open the **Output** tab, switch between the job views and the per-section tables, then export to JSON, CSV or Excel.

#### Upwork Job Scraper input

```json
{
  "urls": ["react developer", "https://www.upwork.com/nx/search/jobs/?q=python&t=0&contractor_tier=3"],
  "maxResults": 50,
  "jobType": "hourly",
  "experienceLevel": ["expert"],
  "verifiedPaymentOnly": true,
  "minClientScore": 50,
  "postedWithin": "24h",
  "incrementalMode": false,
  "proxyConfiguration": { "useApifyProxy": false }
}
```

| Field | What it does |
|---|---|
| `urls` | One per line: a keyword, an Upwork job search link (its filters are used) or a job link. Required. |
| `maxResults` | Total jobs, shared fairly between your keywords and links. `0` returns everything Upwork serves (about 5,050 per search). |
| `jobType`, `experienceLevel`, `workload`, `duration` | Pay model, level, weekly hours and project length. `any` means no filter. |
| `budget`, `hourlyRate` | USD ranges such as `500-5000`, `50-` (at least 50) or `-200` (up to 200). |
| `proposals`, `clientHires` | Ranges such as `0-4,5-9`, `50+` or `10+`. Proposals are checked against the exact count of every job. |
| `location`, `excludeLocations` | Client countries or regions to keep or drop. |
| `verifiedPaymentOnly`, `contractToHire` | Keep only payment-verified clients or contract-to-hire jobs. |
| `minClientScore`, `minClientTotalSpent`, `minClientRating`, `minClientReviewCount` | Skip weak clients. `0` keeps every client. |
| `includeKeywords`, `excludeKeywords` | Keep or drop jobs by words in the title, description or skills. |
| `postedWithin`, `fromDate`, `toDate` | How recent the jobs must be. |
| `incrementalMode` | Return only jobs not returned by an earlier run in your account. |
| `alertWebhookUrl` | Optional Slack or Discord webhook (top 10 jobs by opportunity score) or any URL (jobs as JSON). |
| `maxConcurrency`, `maxRequestRetries` | Speed and retry settings. |
| `proxyConfiguration` | Runs start without a proxy. If Upwork refuses the request, the run switches to a datacenter proxy, then to a residential proxy, and stays on it. |

Competitor-style names such as `query`, `searchUrl`, `startUrls`, `maxAgeMinutes`, `slackWebhookUrl` and `discordWebhookUrl` are accepted too.

#### Output

Each job is one row in the **Jobs** table, with seven views: Jobs, Pay and terms, Competition, Client intel, Scores and red flags, Freelancer requirements and Description. Separate tables hold the same data split by section: skills, red flags, client open jobs, clients, searches and failed inputs.

```json
{
  "jobId": "2115163845731295069",
  "title": "Full-Time AI Developer for Internal Tools",
  "description": "We're a US real estate investment company, and we build almost all of our internal software ourselves using Claude Code and Codex. We're hiring 2 full-time AI d...",
  "jobType": "HOURLY",
  "experienceLevel": "ExpertLevel",
  "budgetAmount": null,
  "hourlyBudgetMin": 4,
  "hourlyBudgetMax": 7,
  "engagementDuration": "More than 6 months",
  "skills": [
    "API Integration",
    "JavaScript",
    "Python",
    "TypeScript"
  ],
  "publishTime": "2026-10-27T22:28:56.070Z",
  "totalApplicants": 62,
  "interviewing": 44,
  "clientCountry": "United States",
  "clientCity": "West Hollywood",
  "clientTotalSpent": 92414.16,
  "clientPaymentVerified": true,
  "clientRating": 4.63,
  "clientReviewCount": 18,
  "clientTotalHires": 32,
  "clientHireRate": 48.8,
  "clientAvgHourlyRatePaid": 6.13,
  "clientMemberSince": "2022-12-12",
  "url": "https://www.upwork.com/jobs/~022115163845731295069",
  "competitionLevel": "high",
  "clientQualityScore": 79,
  "clientQualityReasons": [
    "payment verified",
    "$92,414 spent",
    "49% hire rate",
    "4.63★ from 18 reviews",
    "member 3.9 yrs",
    "pays ~$6/hr"
  ],
  "opportunityScore": 50,
  "redFlags": [
    "Pays low rates (avg $6.13/hr)",
    "50+ proposals"
  ],
  "detailsStatus": "ok",
  "searchQuery": "react developer"
}

```

Some fields are empty on purpose. A job whose client hides their details has `detailsStatus: "login_required"`; its client history, scores and red flags stay empty. `budgetAmount` is empty on hourly jobs and the hourly range is empty on fixed-price jobs. Jobs are saved in the order they finish, not in search order.

#### Use via API / MCP

Use the Actor as an Upwork jobs API from your own code with the Apify API:

```bash
curl -X POST "https://api.apify.com/v2/actors/<ACTOR_ID>/runs" \
     -H "Authorization: Bearer $APIFY_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"urls":["python developer"],"maxResults":20}'
```

For AI agents, add the Apify MCP server and let the agent find and call this Actor:

```bash
claude mcp add --transport http apify "https://mcp.apify.com"
```

The agent finds the Actor through the server's search, details and call tools.

#### Best Use Cases

- **Freelancers:** find fresh, low-competition jobs from clients who pay, before the proposal count climbs.
- **Agencies:** use the Upwork client scraper to build an Upwork lead generation feed filtered by budget, country and client score.
- **Market research:** track which skills, rates and budgets Upwork clients ask for.
- **Job alerts:** schedule the Actor with **Only new jobs** and a webhook to get new leads in Slack or Discord.

#### Frequently Asked Questions

**Do I need an Upwork account or cookies?** No. The Actor reads public data that any visitor can see.

**Why are some client fields empty?** Upwork hides client details on some jobs from visitors. Those rows keep the job data and are marked `login_required`.

**How many jobs can I get from one search?** Upwork serves about 5,050 results per search. Split bigger searches with filters such as job type, budget or country.

**How do I get only new jobs?** Turn on **Only new jobs since the last run** and schedule the Actor. It remembers the jobs it already returned in your account.

**Can I filter by client quality?** Yes. Use `minClientScore`, `minClientTotalSpent`, `minClientRating` and `minClientReviewCount`. The filters are checked on each job's full details.

**What does the opportunity score mean?** It is a 0-100 score from freshness, competition, budget and client quality. The client score and its reasons are in the same row.

**Can I export to CSV or Excel?** Yes. Open the Output tab and export any view or table.

**What if a run is slow or refused?** The Actor starts without a proxy and switches to a datacenter and then a residential proxy when Upwork refuses a request. Lower **Parallel requests** if you see rate-limit pauses.

**What if Upwork changes its pages?** The Actor adjusts to many small changes by itself. If a run returns nothing, report it through the Actor's Issues tab.

#### Support and Feedback

Found a bug or need a field? Open an issue from the Actor's **Issues** tab in Apify Console.

# Actor input Schema

## `urls` (type: `array`):

One per line: a keyword (python developer), an Upwork job search link (https://www.upwork.com/nx/search/jobs/?q=react\&t=0 - every filter in the link is used), or a link to a single job. Duplicates are removed. Add as many as you like.

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

Jobs to return for EACH keyword or search link (3 keywords x 50 = up to 150 jobs). 0 returns everything (Upwork serves about 5,050 per search). Free plan: up to 10 jobs per run in total.

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

Pay model of the jobs.

## `experienceLevel` (type: `array`):

Experience levels to include. Leave empty for all levels.

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

Hours per week. Applies to hourly jobs only, so choosing one limits the search to hourly jobs.

## `duration` (type: `string`):

Expected project length.

## `budget` (type: `string`):

Fixed-price budget range in USD, for example 500-5000, 1000- (at least 1000) or -200 (up to 200). Several ranges with commas.

## `hourlyRate` (type: `string`):

Hourly rate range in USD, for example 30-60 or 50- (at least 50).

## `proposals` (type: `string`):

Keep only jobs with this many proposals, for example 0-4 or 0-4,5-9 or 50+. Checked against the exact count of every job.

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

How many freelancers the client has hired before, for example 0, 1-9 or 10+.

## `location` (type: `array`):

Only jobs from clients in these countries or regions, for example United States, Canada, Europe. One per line.

## `verifiedPaymentOnly` (type: `boolean`):

Keep only clients with a verified payment method.

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

Keep only jobs that may turn into a full-time hire.

## `minClientScore` (type: `integer`):

Keep only clients scoring at least this (0-100, from payment verification, spend, hire rate, rating, reviews, account age and rate paid). 0 keeps every client.

## `minClientTotalSpent` (type: `integer`):

Keep only clients who spent at least this much on Upwork. 0 keeps every client.

## `minClientRating` (type: `number`):

Keep only clients with at least this feedback score (0-5). 0 keeps every client.

## `minClientReviewCount` (type: `integer`):

Keep only clients with at least this many reviews. 0 keeps every client.

## `excludeLocations` (type: `array`):

Drop jobs from clients in these countries. One per line.

## `includeKeywords` (type: `array`):

Keep only jobs whose title, description or skills contain at least one of these words. One per line.

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

Drop jobs whose title, description or skills contain any of these words. One per line.

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

Only jobs posted in this window. Pick a short window and schedule the run to get fresh leads.

## `fromDate` (type: `string`):

Earliest publish date, YYYY-MM-DD.

## `toDate` (type: `string`):

Latest publish date, YYYY-MM-DD.

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

Return only jobs not returned by an earlier run of this Actor in your account. Schedule the Actor to get only fresh leads.

## `alertWebhookUrl` (type: `string`):

Optional. A Slack or Discord webhook gets a message with the top 10 jobs by opportunity score; any other URL receives the jobs as JSON. Treat it like a password.

## `maxConcurrency` (type: `integer`):

How many job pages are opened at the same time. Without a proxy the run uses at most 8 and slows down by itself when Upwork pushes back.

## `maxRequestRetries` (type: `integer`):

How often a failed request is tried again.

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

The run starts without a proxy. If Upwork refuses a request it switches to a datacenter proxy, then to a residential proxy, and stays on it for the rest of the run.

## Actor input object example

```json
{
  "urls": [
    "python developer"
  ],
  "maxResults": 10,
  "jobType": "any",
  "workload": "any",
  "duration": "any",
  "verifiedPaymentOnly": false,
  "contractToHire": false,
  "minClientScore": 0,
  "minClientTotalSpent": 0,
  "minClientRating": 0,
  "minClientReviewCount": 0,
  "postedWithin": "any",
  "incrementalMode": false,
  "maxConcurrency": 10,
  "maxRequestRetries": 3,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Every job with pay, proposals, client country and the two scores.

## `payAndTerms` (type: `string`):

Budget, duration, workload and category per job.

## `clientIntel` (type: `string`):

Who the client is: location, history, spend, hire rate and rate paid.

## `scoresAndRedFlags` (type: `string`):

Client score, opportunity score, competition level and warning signs.

## `competition` (type: `string`):

Proposals, interviews, invites and hires per job.

## `requirements` (type: `string`):

What the client asks of freelancers.

## `description` (type: `string`):

Full text, skills and category per job.

## `skills` (type: `string`):

One row per skill a job asks for.

## `redFlags` (type: `string`):

One row per warning sign found on a job or its client.

## `clientOpenJobs` (type: `string`):

One row per other open job posted by the same client.

## `clients` (type: `string`):

One row per client found in the run, taken from the first of their jobs.

## `searches` (type: `string`):

One row per keyword or link you gave, with how many jobs it returned.

## `failedInputs` (type: `string`):

Links or jobs that could not be read.

## `summary` (type: `string`):

Counts and timing of the 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 = {
    "urls": [
        "python developer"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawl_craft/upwork-job-scraper-job-alerts-with-client-intel").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 = {
    "urls": ["python developer"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("crawl_craft/upwork-job-scraper-job-alerts-with-client-intel").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 '{
  "urls": [
    "python developer"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call crawl_craft/upwork-job-scraper-job-alerts-with-client-intel --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawl_craft/upwork-job-scraper-job-alerts-with-client-intel"
        }
    }
}
```

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/1Qmt3e3KfSq4VayWS/builds/MCd2Djw7tFZ39FKN3/openapi.json
