# Upwork Jobs Scraper (`fetch_cat/upwork-jobs-scraper`) Actor

Export public Upwork jobs with descriptions, budgets, skills and visible client details. Filter and download matching freelance opportunities.

- **URL**: https://apify.com/fetch\_cat/upwork-jobs-scraper.md
- **Developed by:** [Hanna Nosova](https://apify.com/fetch_cat) (community)
- **Categories:** Jobs, Developer tools
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.23 / 1,000 job saveds

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 Job Scraper

Use this Upwork job scraper to export public Upwork jobs with descriptions, budgets, skills, publication dates and visible client details to CSV, JSON or your own application.

- **Find opportunities:** search for freelance projects by keyword and source search operators.
- **Evaluate public client facts:** review country, payment verification, spending, hires and feedback where Upwork exposes them.
- **Keep access limits visible:** a public search preview can have account-restricted details. Such jobs have `detailStatus: "restricted"`, a warning, and no invented client object.
- **Stay within public data:** this Actor does not sign in, accept account cookies, apply for jobs or collect private account information.

### How to scrape Upwork jobs

Start with two jobs to check your search:

```json
{"query":"web scraping","maxItems":2,"includeDetails":true}
```

For a broader feed, use `{"query":"python","maxItems":50}`. For a specific public client market, add `"clientCountry":"Canada"`. Filters run against actual source fields and may require scanning more results than are saved.

### Output example

This shortened example comes from an actual public job. Values change as Upwork updates the listing.

```json
{
  "jobId": "2094154303070879807",
  "url": "https://www.upwork.com/jobs/~022094154303070879807",
  "title": "Build a Car Listing Scraper with Instant TG Notifications",
  "jobType": "FIXED",
  "fixedBudget": 10.0,
  "currency": "USD",
  "client": {
    "country": "United Kingdom",
    "totalSpent": 24305.91,
    "paymentVerified": true
  },
  "detailStatus": "complete"
}
```

Full records include the description, skill identifiers, public client summary, activity and source metadata. Download results from the run's Dataset tab. The `OUTPUT` key-value record reports status, saved/scanned counts, source restrictions and any reason the run stopped early.

### Who is it for?

- **Freelancers:** find projects matching their skills and budget.
- **Agencies:** monitor public demand and qualify opportunities using visible client information.
- **Analysts:** compare published skills, budgets and client markets over time.
- **Developers:** feed public job records into dashboards, spreadsheets and alert workflows.

### Input settings

| Setting | JSON key | Meaning |
|---|---|---|
| Search query | `query` | Words or Upwork search operators. Defaults to web scraping when neither query nor searchUrl is supplied. |
| Public Upwork search URL | `searchUrl` | Optional https://www.upwork.com/nx/search/jobs/?q=... URL. Supports q and sort; use dedicated inputs for filters. Leave query blank when using this field. |
| Maximum saved jobs | `maxItems` | Stop after this many unique matching jobs. Public previews with restricted details count as jobs. |
| Jobs requested per page | `pageSize` | 1–50. Smaller pages can reduce unused search results in small runs. |
| Maximum search pages | `maxPages` | Bounds scanning when filters are narrow. Hitting this limit is reported as partial coverage. |
| Search order | `sort` | Newest publication first or Upwork relevance. |
| Published on or after | `postedSince` | ISO date or timezone-qualified timestamp; compared with the source publication time. |
| Job type | `jobType` | Keep fixed-price or hourly jobs. |
| Experience level | `experienceLevel` | Keep the requested source experience level. |
| Category name or slug | `category` | Match an exact public category or category-group name/slug, case-insensitively. Requires details. |
| Client country | `clientCountry` | Exact source country name, case-insensitive, such as Canada. This is the hiring client location, not the proxy or applicant location. |
| Explicitly remote jobs only | `remoteOnly` | Require the source to explicitly label REMOTE. Unspecified arrangements are excluded; Upwork often leaves this field unspecified. Requires details. |
| Verified-payment clients only | `paymentVerifiedOnly` | Require a public true payment-verification value. Missing/restricted values do not match. |
| Include public job and client details | `includeDetails` | Default true. Retrieve full public description, category, client facts and activity. Account-restricted jobs retain their public preview with detailStatus=restricted. |
| Fail on incomplete extraction | `failOnBlocked` | Fail on source blocks, unexpected errors or scan limits, while preserving saved jobs. False permits an explicitly partial run. Zero-row blocks always fail. |
| Request timeout in seconds | `requestTimeoutSecs` | Maximum time for one public request, capped by the shared runtime deadline. |
| Total runtime budget | `maxRunSeconds` | Includes a shutdown reserve of at least 30 seconds and 10 percent. Increase for many jobs or restrictive filters. |
| Maximum session attempts per operation | `maxSessionAttempts` | Bounded retry count for guest bootstrap, network failures and HTTP blocks. Account restrictions are not retried. |
| Apify proxy configuration | `proxyConfiguration` | Automatic US datacenter selection is tried first, with residential fallback after a session failure. Explicit proxy groups are respected. No external proxy credentials are accepted. |
| Minimum hourly rate | `hourlyRateMin` | Local filter on the corresponding public source value; unavailable values do not match. |
| Maximum hourly rate | `hourlyRateMax` | Local filter on the corresponding public source value; unavailable values do not match. |
| Minimum fixed budget | `fixedBudgetMin` | Local filter on the corresponding public source value; unavailable values do not match. |
| Maximum fixed budget | `fixedBudgetMax` | Local filter on the corresponding public source value; unavailable values do not match. |
| Minimum public client spend | `minClientSpend` | Local filter on the corresponding public source value; unavailable values do not match. |
| Minimum client rating | `minClientRating` | Local filter on the corresponding public source value; unavailable values do not match. |
| Maximum proposal count | `maxProposals` | Local filter on the corresponding public source value; unavailable values do not match. |
| Description/title must include all | `includeKeywords` | Case-insensitive text filters applied to the full public detail when available, otherwise to the public preview. |
| Description/title must exclude all | `excludeKeywords` | Case-insensitive text filters applied to the full public detail when available, otherwise to the public preview. |

`searchUrl` accepts public job-search URLs containing `q` and `sort`. Other URL filters produce a clear validation error; use the dedicated Actor inputs instead. If both URL and query are supplied, they must agree.

`remoteOnly` requires an explicit source REMOTE value. Many jobs leave their work arrangement unspecified; these do not match that strict filter. A client's country is different from applicant-location restrictions. Company size is the source's classification, not an inferred employee count.

### Output fields

| Field | Meaning |
|---|---|
| `jobId` | Stable source job ID |
| `ciphertext` | Source public job ciphertext |
| `url` | Canonical Upwork job URL |
| `title` | Source title |
| `description` | Full public description when details are complete; source search preview otherwise |
| `jobType` | FIXED or HOURLY |
| `query` | Requested search expression |
| `sourceUrl` | Public search URL |
| `scrapedAt` | UTC collection timestamp |
| `detailStatus` | complete, restricted or not\_requested |
| `currency` | Source budget currency; never inferred from viewer |
| `experienceLevel` | Normalized source experience level |
| `workload` | Source workload |
| `createdAt` | Source creation timestamp |
| `postedAt` | Source publication timestamp |
| `status` | Source job status |
| `detailSource` | Public detail source |
| `workArrangement` | remote, on\_site or unspecified, from onSiteType |
| `fixedBudget` | Source fixedBudget; null when not applicable or unavailable |
| `hourlyRateMin` | Source hourlyRateMin; null when not applicable or unavailable |
| `hourlyRateMax` | Source hourlyRateMax; null when not applicable or unavailable |
| `weeklyRetainerBudget` | Source weeklyRetainerBudget; null when not applicable or unavailable |
| `premium` | Source premium flag |
| `skills` | Source skill identifiers and labels |
| `warnings` | warnings |
| `duration` | duration |
| `category` | category |
| `categoryGroup` | categoryGroup |
| `client` | Public client summary; omitted for restricted previews |
| `activity` | activity |
| `qualifications` | Public applicant/location requirements, distinct from client country |

The `client` object includes country, city, timezone, totalSpent, totalHires, activeHires, hoursBilled, reviewCount, rating, jobsWithHires, openJobs, memberSince, paymentVerified, companyIndustry and companySize. Individual values can be null when Upwork does not disclose them; zero is kept as zero.

`activity` includes totalApplicants, totalHired, totalInvitedToInterview, unansweredInvites, invitationsSent, numberOfPositionsToHire and lastBuyerActivity. `qualifications` preserves public countries, regions, states, timezones, localMarket, onSiteType, prefEnglishSkill, risingTalent and locationCheckRequired. Empty location requirements must not be interpreted as the client's location.

### Pricing

The `start` event is charged once when the first matching job is ready to save. The `item` event is charged for each unique saved job. A useful public preview with restricted details counts as a saved job and is clearly marked. Error/status messages are written to the run summary and are not dataset jobs.

See the [current pricing and discounts](https://apify.com/fetch_cat/upwork-jobs-scraper/pricing). No additional Upwork account or external data subscription is required.

### Tips and limits

- Keep `includeDetails` enabled for public client fields and complete descriptions. Turning it off intentionally exports only search previews.
- A genuine empty search can succeed with zero rows. A challenge, malformed response or failed bootstrap cannot.
- Results reflect a changing marketplace. Closed, removed, private or restricted jobs may become unavailable between search and detail retrieval.
- Tight filters can scan many jobs. `maxPages` and the runtime budget bound this work; inspect the run summary for partial coverage.
- With `failOnBlocked=true`, unexpected blocks or incomplete extraction fail the run while preserving jobs already saved.
- Source IDs control duplicates. Reuse job IDs in your own database when combining separate runs.
- Residential proxy access is required for the verified route. The default configuration selects Apify residential US proxies.
- Upwork's source fields can omit spend, location, rating or rates. This Actor does not fill them with guesses.
- This Actor is independent of Upwork and does not automate applications or contact job posters.

### Upwork jobs API integration

Use your own Apify token as an environment variable. A run returns a default dataset ID.

#### Node.js

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('fetch_cat/upwork-jobs-scraper').call({
  query: 'python', maxItems: 20, includeDetails: true
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("fetch_cat/upwork-jobs-scraper").call(
    run_input={"query": "python", "maxItems": 20, "includeDetails": True}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

#### cURL

```bash
curl --request POST   --header "Authorization: Bearer $APIFY_TOKEN"   --header "Content-Type: application/json"   --data '{"query":"python","maxItems":20,"includeDetails":true}'   "https://api.apify.com/v2/acts/fetch_cat~upwork-jobs-scraper/run-sync-get-dataset-items"
```

For longer searches, start an asynchronous run and read its dataset after completion.

### MCP and AI agents

Connect through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp). One local configuration is:

```json
{
  "mcpServers": {
    "apify": {
      "command": "npx",
      "args": ["-y", "@apify/actors-mcp-server", "--actors", "fetch_cat/upwork-jobs-scraper"],
      "env": { "APIFY_TOKEN": "YOUR_APIFY_TOKEN" }
    }
  }
}
```

CLI: `npx -y @apify/actors-mcp-server --actors fetch_cat/upwork-jobs-scraper`.

Example prompt: “Find 20 public Python freelance jobs. Keep public client details, identify restricted previews, and compare budgets without inventing missing rates.”

### FAQ

**What data can I export with upwork job scraper?** Export titles, job IDs, URLs, descriptions, skills, publication dates and available budgets. Full public details also include visible client facts and activity; restricted details are flagged explicitly.

**Can I run Upwork Jobs Scraper through an API, schedule, or MCP client?** Yes. Use the API examples or Apify MCP configuration above. Save an input as a task to schedule it, and deduplicate scheduled results using jobId.

**How much does it cost to use Upwork Jobs Scraper?** A one-time search event applies when the first matching job is ready, plus one event per saved job. The Pricing tab shows the current rate for your plan. Account-restricted public previews count as saved jobs.

**Does this need an Upwork login?** No. It uses public job data and will not ask for your Upwork cookies.

**Why does a job have no client object?** Upwork may expose the search preview while requiring registration for details. Check `detailStatus` and warnings.

**Are null spend and zero spend equivalent?** No. Null means the source did not disclose the value. Zero is a source value.

**Why did a restrictive filter return fewer jobs?** Only observed matching values qualify. Missing or restricted values do not count as matches, and the scan has page/time limits.

**Can I schedule it?** Use an Apify task and schedule after confirming the input. Use job IDs to avoid duplicates across scheduled runs.

### Related Actors

Explore [Indeed Jobs Scraper](https://apify.com/fetch_cat/indeed-jobs-scraper) and [LinkedIn Jobs Scraper](https://apify.com/fetch_cat/linkedin-jobs-scraper) for other job markets. Also compare [Glassdoor Jobs Scraper](https://apify.com/fetch_cat/glassdoor-jobs-scraper), [Google Jobs Scraper](https://apify.com/fetch_cat/google-jobs-scraper) and [RemoteOK Jobs Scraper](https://apify.com/fetch_cat/remoteok-jobs-scraper). Their source coverage differs from Upwork.

### Support

Report bugs or wrong output through this Actor's Issues tab. Include the Apify run ID or run URL, input JSON, expected and actual output, and one reproducible public Upwork URL. Never share cookies, tokens or private account information.

# Actor input Schema

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

Words or Upwork search operators. Defaults to web scraping when neither query nor searchUrl is supplied.

## `searchUrl` (type: `string`):

Optional https://www.upwork.com/nx/search/jobs/?q=... URL. Supports q and sort; use dedicated inputs for filters. Leave query blank when using this field.

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

Stop after this many unique matching jobs. Public previews with restricted details count as jobs.

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

1–50. Smaller pages can reduce unused search results in small runs.

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

Bounds scanning when filters are narrow. Hitting this limit is reported as partial coverage.

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

Newest publication first or Upwork relevance.

## `postedSince` (type: `string`):

ISO date or timezone-qualified timestamp; compared with the source publication time.

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

Keep fixed-price or hourly jobs.

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

Keep the requested source experience level.

## `category` (type: `string`):

Match an exact public category or category-group name/slug, case-insensitively. Requires details.

## `clientCountry` (type: `string`):

Exact source country name, case-insensitive, such as Canada. This is the hiring client location, not the proxy or applicant location.

## `remoteOnly` (type: `boolean`):

Require the source to explicitly label REMOTE. Unspecified arrangements are excluded; Upwork often leaves this field unspecified. Requires details.

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

Require a public true payment-verification value. Missing/restricted values do not match.

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

Default true. Retrieve full public description, category, client facts and activity. Account-restricted jobs retain their public preview with detailStatus=restricted.

## `failOnBlocked` (type: `boolean`):

Fail on source blocks, unexpected errors or scan limits, while preserving saved jobs. False permits an explicitly partial run. Zero-row blocks always fail.

## `requestTimeoutSecs` (type: `integer`):

Maximum time for one public request, capped by the shared runtime deadline.

## `maxRunSeconds` (type: `integer`):

Includes a shutdown reserve of at least30seconds and10percent. Increase for many jobs or restrictive filters.

## `maxSessionAttempts` (type: `integer`):

Bounded retry count for guest bootstrap, network failures and HTTP blocks. Account restrictions are not retried.

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

Automatic US datacenter selection is tried first. If a session fails, the remaining bounded attempts use Apify residential proxies. Explicit proxy groups are respected. No external proxy credentials are accepted.

## `hourlyRateMin` (type: `number`):

Local filter on the corresponding public source value; unavailable values do not match.

## `hourlyRateMax` (type: `number`):

Local filter on the corresponding public source value; unavailable values do not match.

## `fixedBudgetMin` (type: `number`):

Local filter on the corresponding public source value; unavailable values do not match.

## `fixedBudgetMax` (type: `number`):

Local filter on the corresponding public source value; unavailable values do not match.

## `minClientSpend` (type: `number`):

Local filter on the corresponding public source value; unavailable values do not match.

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

Local filter on the corresponding public source value; unavailable values do not match.

## `maxProposals` (type: `number`):

Local filter on the corresponding public source value; unavailable values do not match.

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

Case-insensitive text filters applied to the full public detail when available, otherwise to the public preview.

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

Case-insensitive text filters applied to the full public detail when available, otherwise to the public preview.

## Actor input object example

```json
{
  "query": "web scraping",
  "maxItems": 20,
  "pageSize": 50,
  "maxPages": 20,
  "sort": "newest",
  "remoteOnly": false,
  "paymentVerifiedOnly": false,
  "includeDetails": true,
  "failOnBlocked": true,
  "requestTimeoutSecs": 20,
  "maxRunSeconds": 600,
  "maxSessionAttempts": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [],
    "apifyProxyCountry": "US"
  },
  "includeKeywords": [],
  "excludeKeywords": []
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

## `summary` (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": "web scraping",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("fetch_cat/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": "web scraping",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("fetch_cat/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": "web scraping",
  "maxItems": 20
}' |
apify call fetch_cat/upwork-jobs-scraper --silent --output-dataset

```

## MCP server setup

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