# Upwork Jobs Scraper: Public Jobs & New Job Alerts (`zaiq/upwork-jobs-scraper`) Actor

Collect signed-out Upwork job listings, budgets, skills and available client details. Filter public jobs and track newly seen job IDs between runs. Access checks and unavailable pages produce free notes.

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

## Pricing

from $1.00 / 1,000 jobs

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: public jobs, client details and new-job alerts

Get public job titles, descriptions, budgets, hourly ranges, skills and dates from keyword searches, category pages or individual job links. Load the public job page for available client country, payment verification, hires, rating and proposal activity. Every row starts with a plain result verdict.

**$0.001 per basic job and $0.003 per job with client details**, the same price on every Apify plan and on every connection route. A row uses one of these prices, never both. Failed pages and access notes are free. The default input requests ten detailed jobs.

Use a snapshot key with `newOnly` for scheduled collection of newly seen job IDs. The Actor remembers jobs it actually delivered, so a spending limit does not hide jobs that were never delivered. It does not submit proposals or send alerts itself.

### What you get

| Field | What it holds |
|---|---|
| Job | `verdict`, `jobId`, `jobUid`, `title`, `url`, `posted`, `createdOn`, `renewedOn`, `description`, `skills` |
| Budget and scope | `jobType`, `budget`, `hourlyMin`, `hourlyMax`, `currency`, `duration`, `experienceLevel`; detailed rows also include category and category group |
| Client details | `clientCountry`, `paymentVerified`, `clientHires`, `clientActiveHires`, `clientRating`, `clientReviewCount`, `clientMemberSince`, `clientJobsPosted`, `clientLastActive`, `clientTotalSpent` when exposed |
| Proposal activity | `proposalsBand`, `proposalsCount`, `jobHires`, `interviewing`, `invitationsSent`, `unansweredInvites` when exposed on a public job page |
| Delivery and pricing | `change`, `scrapedAt`, `sourceUrl`, `inputSource`, `detailsStatus`, `pricingEvent`, `usedResidential`, `notes`, and the restart-safe `resultId` |
| Run summary | Source totals, pages read, filtering, skipped malformed records, request bytes, route escalations, spending-limit status and snapshot coverage |

### What people use it for

- **New-job feeds:** Run the same saved search on a schedule and consume new job rows through a webhook or dataset export.
- **Freelance prospecting:** Find public jobs within a stated budget, hourly range and experience level.
- **Client screening:** Use public verification, hiring history, ratings and proposal activity to prioritize jobs. Missing fields stay unknown.
- **Market research:** Compare job requirements and budgets across public searches and categories, within the reported collection limits.

### How to use it

1. Enter a phrase in **Search keywords**. The prefill uses `data analysis` and requests ten jobs.
2. Keep **Load client details** on for public client information, or turn it off for basic job rows.
3. Choose job, budget, experience or client filters and set your **Maximum cost per run**.
4. For repeat collection, set a **Snapshot key**, enable **Only newly seen jobs**, and reuse the same sources and filters. Start the run and open the Jobs table and SUMMARY.

### Input

| Field | Example | Notes |
|---|---|---|
| queries | \["data analysis"] | Up to 40 sources combined. Empty source arrays use the default search. |
| categories | \["data-entry"] | Public category slugs or links. The first page has the category’s own ordering; continuation follows its public search mapping. |
| jobUrls | \[] | Public job links. Always charged at the detail rate when successful. |
| includeDetails | true | Read client and proposal fields. A failed detail request gives a free note, without charging a partial job. |
| maxJobs / maxPagesPerSource | 10 / 3 | Unique delivered jobs across all inputs, and pages per source. Maximums: 1,000 jobs and 20 pages per source. |
| pageSize | 50 | 10, 20 or 50. Small maxJobs values use a consistent 10- or 20-item page to avoid fetching excess data. |
| sort / keywordMatch | newest / all-words | Every query word must appear in the title, description or skills by default. Phrase requires the whole phrase; source accepts the site’s own matching and suggestions. |
| jobType / experienceLevel | fixed / intermediate | Hourly or fixed; entry, intermediate or expert. Use all for either filter to be unrestricted. |
| minBudget / maxBudget | 100 / 1000 | Stated fixed-price USD budget. Missing budgets and hourly jobs fail an active fixed-budget filter. |
| minHourlyRate / maxHourlyRate | 20 / 75 | Require the advertised lower rate to meet the minimum and the upper rate to fit the maximum. Unknown rates are excluded. |
| paymentVerified / clientHistory | verified / 10-plus-hires | Applied to public detail-page data. These require includeDetails=true. Unknown values are excluded. |
| minClientSpent | Omitted | Only jobs with exposed total spending can pass. This field was hidden on the tested public job pages. Leave this filter unset unless your sources expose it. |
| snapshotKey / newOnly | daily-data-jobs / true | Keep a stable key for the same sources and filters. First run emits the baseline; later runs emit previously undelivered IDs. |

### Output

Each result is one row in the run's dataset (download it as JSON, CSV, Excel, XML or HTML, or read it through the API). An example row (values shortened):

```json
{
  "verdict": "Hourly $38–$48; intermediate; payment unverified; 5 to 10 proposals.",
  "kind": "job",
  "jobId": "~022114394737268013176",
  "title": "Customer Satisfaction Feedback Analysis",
  "posted": "2026-10-25T16:42:14.702Z",
  "jobType": "hourly",
  "budget": null,
  "hourlyMin": 38,
  "hourlyMax": 48,
  "currency": "USD",
  "duration": "1 to 3 months",
  "experienceLevel": "intermediate",
  "proposalsBand": "5 to 10",
  "proposalsCount": 6,
  "clientCountry": "India",
  "paymentVerified": false,
  "clientTotalSpent": null,
  "clientHires": null,
  "clientRating": null,
  "detailsStatus": "available",
  "url": "https://example.com/jobs/customer-feedback-analysis",
  "notes": [
    "clientTotalSpent was not exposed by the public job page.",
    "clientHires was not exposed by the public job page.",
    "clientRating was not exposed by the public job page."
  ],
  "pricingEvent": "job-details",
  "usedResidential": false
}
```

The example is an excerpt of a real run on 25 October 2026. Site addresses are replaced with example addresses; names and handles, where present, are removed or replaced. Numeric values and missing values are retained. The job description and named software skills are omitted from the example. The SUMMARY record lists coverage, failures, filtering, route usage and charges.

### Pricing

- **Basic job: $0.001** ($1 per 1,000).
- **Job with client details: $0.003** (detail page included).
- The same price on every Apify plan. Each delivered job has exactly one pricing event. There is no separate charge for loading the detail page.
- Apify’s **$0.00005 start charge counts towards the buyer’s Maximum cost per run**. It is separate from the job events and may apply even when no jobs are returned.
- Failures, login walls, access checks, filtered jobs, already-seen snapshot IDs and free notes have no job-event charge.
- No AI model is used. Snapshot state is stored in a named key-value store; long-term storage follows Apify’s storage pricing.

What typical jobs cost at the list price:

| Job | Cost |
|---|---|
| 10 basic jobs | $0.01005 including the start charge |
| 10 jobs with client details | $0.03005 including the start charge |
| 1,000 basic jobs | $1.00005 including the start charge |
| A search with no matching jobs | $0.00005 (the start charge) |

On Apify's Scale and Business plans the per-event prices are lower (Store discounts). Set **Maximum cost per run** and the Actor stops cleanly when it is reached: you never pay more than that.

### Tips

- Keep the default all-words keyword check when unrelated suggestions are unwanted. Use source matching when you deliberately want the site’s broader results.
- Use basic rows for broad monitoring, and submit selected job links for client details.
- Client filters can require reading several job pages before one matches. Set page limits and inspect filtered counts in SUMMARY.
- Use a separate snapshot key for each schedule, and avoid overlapping runs that write the same key. Downstream systems can deduplicate on jobId or resultId.
- An unchanged snapshot run still checks the requested public pages. It does not charge for job IDs it has already delivered.

### Use it through the API

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("zaiq/upwork-jobs-scraper").call(run_input={"queries": ["data analysis"], "maxJobs": 10, "includeDetails": true})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row)
```

**JavaScript**

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('zaiq/upwork-jobs-scraper').call({"queries": ["data analysis"], "maxJobs": 10, "includeDetails": true});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

**HTTP (one call, results back)**

```bash
curl -X POST 'https://api.apify.com/v2/acts/zaiq~upwork-jobs-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"queries": ["data analysis"], "maxJobs": 10, "includeDetails": true}'
```

### Integrations and schedules

- **Schedules:** run it every hour, day or week from the Apify Console (Schedules) and keep a history of results.
- **Make, Zapier, n8n, Google Sheets, Slack, Airtable:** connect it through Apify's integrations, or send each finished run to a **webhook**.
- **Datasets:** results stay in the run's dataset; export them or read them by API whenever you need them.

### Use it with AI agents (MCP)

Add Apify's MCP server (`https://mcp.apify.com`) to Claude, ChatGPT, Cursor, VS Code or any MCP client. The agent can find this Actor and run it for you: ask it for "find new data analysis jobs with a stated budget and public client history". AI agents can also pay for runs directly through Apify's agentic payments.

### Limits

- Only public, signed-out pages are read. Login and verification redirects are not followed. No CAPTCHA or access check is solved. A refused route can be retried through datacenter and fresh residential connections.
- Search pages hide much of the client record and can set verification=false as a placeholder. Basic rows report such values as null. Job pages may also omit total spending, hires, ratings or other client fields. A missing value is never inferred to be zero or false.
- Client country is the source’s value and can be a country name or a three-letter country code. Job and client activity can change after collection.
- Category ?page=2 links can repeat the first page. The Actor uses the category’s public search mapping when present and reports when continuation is unavailable.
- Public searches can return broader suggestions. Local keyword and numeric filters remove nonmatching rows. Unknown fields fail active filters.
- At most 150 HTTP requests and a 14-minute work budget checked between requests, with sequential requests and rate-limit backoff. Limits, short responses, repeated pages and malformed records make coverage incomplete in SUMMARY.
- New-only mode tracks delivered IDs within the fetched pages. It is not a guarantee of seeing every new job. Missing jobs are never labelled removed. Snapshot history is capped at 100,000 IDs; use a new key when it fills.
- Jobs and failure notes share the dataset. Filter kind=job for paid job rows. A successful Actor run can contain partial results or only free notes; read SUMMARY before treating coverage as complete.

### FAQ

**Do I need an account on the source site?**

No. The Actor reads only signed-out public pages.

**What happens when a job disappears?**

An unavailable page produces a free note. The Actor does not guess whether it was removed, expired, made private or restricted.

**Why is client spending missing?**

The public pages tested did not expose it. The field stays null. Requiring a minimum spending amount excludes jobs where that value is hidden.

**Why does the price change on some rows?**

Some source addresses are refused; the Actor retries through datacenter and fresh residential connections at no extra charge. usedResidential shows which rows needed it.

**Will I pay twice after a restart?**

Rows with an acknowledged stable resultId are skipped on restart and are not charged again. Snapshot delivery state is reconciled from those rows.

**Can I change filters and reuse a snapshot key?**

Use a new key for different sources, matching rules, sorting or filters. The Actor stops with a free note if a key belongs to a different scope. You can change delivery and page limits.

**Does this send alerts or apply to jobs?**

It produces datasets suitable for scheduled workflows. Connect a webhook or your own automation for alerts. It never applies to jobs or contacts anyone.

**Can I use it without code?**

Yes. Fill in the input form and export the Jobs table as JSON, CSV or Excel.

### Related Actors from Zaiq

- [ats-jobs-scraper](https://apify.com/zaiq/ats-jobs-scraper): public vacancies from company hiring systems
- [workday-jobs-scraper](https://apify.com/zaiq/workday-jobs-scraper): public company jobs from one hiring platform

### Support

Questions, a site that changed, or a field you need? Open an issue on this Actor's **Issues** tab: we answer there, usually within a day.

***

Also found as: upwork jobs scraper, upwork job search, public freelance jobs, freelance job alerts, upwork client details, job budget data.

# Actor input Schema

## `queries` (type: `array`):

Public job search phrases. Each is a separate search. With no sources, data analysis is used.

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

Category slugs or public category links. Example: data-entry. Category order comes from the public category page; continuation uses its own search mapping.

## `jobUrls` (type: `array`):

Individual HTTPS /freelance-jobs/apply/... or /job/... or /jobs/~... URLs. These always load the job detail page and use the detail event.

## `includeDetails` (type: `boolean`):

Open each matching job page for available client history, verification and proposal activity. Failed detail pages are free notes, not paid partial jobs.

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

Maximum unique jobs across all sources, including rows already delivered before a restart.

## `maxPagesPerSource` (type: `integer`):

Public pages to read per query or category. A category landing page counts as one. SUMMARY marks limited coverage.

## `pageSize` (type: `integer`):

10, 20 or 50 jobs per public search page. Larger pages reduce bytes per job. Other values produce a free input note. Small maxJobs values use a 10- or 20-item page consistently through the source.

## `sort` (type: `string`):

Search order. The first category page uses the category’s own ordering.

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

Keep hourly, fixed-price, or both types.

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

Keep the source’s stated experience level. Unknown values fail an active filter.

## `clientHistory` (type: `string`):

Filter by confirmed client hires. Requires Load client details; unknown values are excluded.

## `paymentVerified` (type: `string`):

Filter by verification on the public job page. Requires Load client details; hidden search placeholders are not treated as unverified.

## `minBudget` (type: `number`):

Minimum stated fixed-price USD budget. Jobs with missing budgets or hourly jobs are excluded.

## `maxBudget` (type: `number`):

Maximum stated fixed-price USD budget. Jobs with missing budgets or hourly jobs are excluded.

## `minHourlyRate` (type: `number`):

Require the advertised lower hourly USD rate to meet this value.

## `maxHourlyRate` (type: `number`):

Require the advertised upper hourly USD rate to be within this value.

## `minClientSpent` (type: `number`):

Minimum total USD spending exposed by the client record. Requires Load client details; unknown spending is excluded.

## `snapshotKey` (type: `string`):

Optional name for persistent delivered-job IDs (letters, digits, underscores, hyphens; up to 64 characters). Reuse with identical sources and filters. Use different keys for concurrent schedules.

## `newOnly` (type: `boolean`):

Requires Snapshot key. First run delivers the baseline; later runs emit only previously undelivered job IDs within fetched pages. No removed-job inference.

## `keywordMatch` (type: `string`):

By default every query word must appear in the title, description or skills. Phrase requires the full phrase. Source uses Upwork’s own matching, which can return unrelated suggestions.

## Actor input object example

```json
{
  "queries": [
    "data analysis"
  ],
  "categories": [],
  "jobUrls": [],
  "includeDetails": true,
  "maxJobs": 20,
  "maxPagesPerSource": 3,
  "pageSize": 50,
  "sort": "newest",
  "jobType": "all",
  "experienceLevel": "all",
  "clientHistory": "all",
  "paymentVerified": "any",
  "snapshotKey": "",
  "newOnly": false,
  "keywordMatch": "all-words"
}
```

# Actor output Schema

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

Dataset: the results.

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

What each input returned, and notes.

# 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 = {
    "queries": [
        "data analysis"
    ],
    "includeDetails": false,
    "maxJobs": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("zaiq/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 = {
    "queries": ["data analysis"],
    "includeDetails": False,
    "maxJobs": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("zaiq/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 '{
  "queries": [
    "data analysis"
  ],
  "includeDetails": false,
  "maxJobs": 20
}' |
apify call zaiq/upwork-jobs-scraper --silent --output-dataset

```

## MCP server setup

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