# Upwork Freelancer Search — Rates, Earnings, JSS, Work History (`moxlade/upwork-freelancers`) Actor

Search Upwork freelancers by keyword, skills, rate, JSS, badge, country, hours and tenure. Per profile: exact earnings, every contract with feedback both ways, education, employment, languages, certificates. Seconds from a maintained census; live search on request. Pay per row, no start fee.

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

## Pricing

from $4.00 / 1,000 stored profile rows

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?

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 Freelancer Search — Rates, Earnings, JSS, Work History

**Search Upwork freelancers the way a client would — by rate, Job Success Score, badge, country, hours billed, tenure, hire-again rate and skills — and get what a profile page hides: exact lifetime earnings, hours, every contract with the feedback in both directions, education, employment history, languages, certificates.** Answered in seconds from a maintained census of 28,000+ full profiles (69,000+ indexed, refreshed daily), with a live search on request. One event per delivered row, no start fee, a zero-row run costs nothing.

### What you get that a page scrape cannot give you

- **Filters a page scrape cannot do.** Job Success Score, hours billed, jobs worked, lifetime earnings, member-since, last-worked-within-N-days, hire-again rate, availability, identity verification and skills are filter inputs here, not just output columns. A numeric rate band (`rateMin` / `rateMax`) works as stated — every acceptance run checks 25 of 25 rows.
- **The stats a profile page hides.** Exact lifetime earnings where Upwork exposes the figure (with `earnings_precision` saying `exact` or `band`), total hours, hourly vs fixed jobs, completed contracts, hire-again percentage, average rating, member-since and last-worked dates, availability, contract-to-hire and verification flags — 53 typed fields per row.
- **History, not a snapshot.** Every row carries `fetched_at`, `age_days`, `first_seen_at`, `last_seen_at` and a `versions` count. `changedSince` returns only freelancers whose rate, title, JSS, badge, hours, jobs, skills, availability or hire-again rate moved since a date — with the changes as `{field, from, to, at}`.
- **Contract history with feedback in both directions.** `include: ["contracts"]` adds each freelancer's completed contracts — title, type, dates, hours, charges, client name, the client's feedback with its six detail scores, and what the freelancer wrote about the client. `fullProfiles: true` re-reads a profile now and stores every contract it has, charges included on private ones.
- **The sections a profile page does not show a visitor.** Education, employment history, languages with proficiency, certificates and other experiences — on every full profile read, and on stored rows as they are refreshed (`sections_fetched_at` says when). `include` adds them to any row.
- **Private profiles are an answer.** Look up a list of ids and the ones that went private come back as `status: private` with `private_since` and their last public state (5,000 such profiles tracked, re-checked weekly), instead of silently missing.
- **Seconds, not minutes.** A stored search answers 25 rows in well under 5 s and 500 rows in one call; it cannot be blocked or rate-limited because it reads a maintained table. `liveSearch: true` asks Upwork for the current result in its own order when you need that.

### Who it's for

- **Recruiters and talent teams** — shortlist by the numbers a client cares about — JSS, hours, hire-again rate, rate band, country, recent activity — and export 500 profiles in one run.
- **Agencies and freelancers benchmarking** — see what people with your skills and badge actually charge and earn, by country and tenure, from real listed rates and exact earnings.
- **Market and labor researchers** — a census with time on it: member-since cohorts, rate and badge changes over time, who went private — questions a live scrape cannot answer.
- **Product and lead-gen builders** — one API-shaped dataset with a stable id (`ciphertext`) to join on, typed fields, and `changedSince` for incremental pulls.

### Quick start

**Top Rated Python developers in the US, $50–100/h**

```json
{
  "query": "python developer",
  "topRated": true,
  "countries": [
    "United States"
  ],
  "rateMin": 50,
  "rateMax": 100,
  "maxItems": 25
}
```

**Active, proven: JSS 95+, 1,000+ hours, worked in the last 60 days**

```json
{
  "jssMin": 95,
  "hoursMin": 1000,
  "lastWorkedWithinDays": 60,
  "sort": "jss",
  "maxItems": 50
}
```

**Look up profiles by URL**

```json
{
  "profileUrls": [
    "https://www.upwork.com/freelancers/~01d699cdd471617458"
  ]
}
```

**Profiles with their contract history**

```json
{
  "query": "django",
  "topRated": true,
  "include": [
    "contracts",
    "languages",
    "education"
  ],
  "maxItems": 25
}
```

**Fresh full profiles, every contract**

```json
{
  "profileUrls": [
    "~01d699cdd471617458",
    "~01b237e28ffe15f101"
  ],
  "fullProfiles": true
}
```

**What changed since a date**

```json
{
  "query": "python",
  "changedSince": "7d",
  "includeStale": true,
  "maxItems": 100
}
```

### Output

One record per freelancer:

| field | meaning |
|---|---|
| `ciphertext` | Upwork's stable profile id (`~01…`). Deduplicate and join on this. Filled on every row. |
| `url` | The public profile URL. Filled on every row. |
| `person_id` | Upwork's numeric person id. Filled on every row. |
| `name` | Display name as Upwork shows it (first name + initial). Filled on every row. |
| `title` | The profile headline. Filled on every row. |
| `overview` | The profile overview, full text. Filled on every row. |
| `country` | Country as shown on the profile. Filled on every row. |
| `country_code` | ISO 3166-1 alpha-2. Filled on every row. |
| `region` | Upwork's region: Americas, Asia, Europe, Africa, Oceania. Filled on 9% of rows. |
| `city` | City. Filled on every row. |
| `state` | State or province, where Upwork shows one. Filled on 99% of rows. |
| `timezone` | Timezone as Upwork labels it (e.g. `UTC+08:00 …`). Filled on 9% of rows. |
| `rate` | Listed hourly rate, USD. Filled on every row. |
| `jss` | Job Success Score, 0–100. Null when Upwork shows none (new, or hidden). Filled on 90% of rows. |
| `jss_hidden` | The freelancer hides their JSS. Filled on 9% of rows. |
| `total_earnings` | Lifetime earnings, USD. Exact when `earnings_precision` is `exact`; a lower bound (`$10K+` → 10000) when `band`. Filled on 48% of rows. |
| `earnings_precision` | `exact` (read from Upwork's search payload), `band` (the card's rounded figure), or null. Filled on 48% of rows. |
| `earnings_hidden` | The freelancer hides their earnings. Filled on every row. |
| `badge` | `top_rated_plus`, `top_rated`, `rising_talent`, or null. Filled on 74% of rows. |
| `top_rated` | Top Rated (includes Top Rated Plus). Filled on every row. |
| `top_rated_plus` | Top Rated Plus. Filled on every row. |
| `hours` | Total hours billed on Upwork. Filled on every row. |
| `jobs_total` | Jobs worked, as the profile reports it. Filled on every row. |
| `jobs_hourly` | Hourly contracts. Filled on every row. |
| `jobs_fixed` | Fixed-price contracts. Filled on every row. |
| `contracts_total` | Completed contracts in the work history. Filled on every row. |
| `member_since` | Account creation date. Filled on every row. |
| `last_worked_on` | Date of the most recent work on Upwork. Filled on every row. |
| `hire_again_pct` | Share of clients who would hire again, 0–100. Null when Upwork does not show the stat (it needs enough client feedback first). Filled on 74% of rows. |
| `rating` | Average client rating, 0–5. Filled on every row. |
| `feedback_count` | Contracts with client feedback. Filled on every row. |
| `recent_hours` | Hours billed in the recent window Upwork reports. Filled on every row. |
| `recent_jobs` | Jobs in the recent window Upwork reports. Filled on every row. |
| `availability` | `fullTime`, `partTime`, `notSure` (as needed). Filled on every row. |
| `contract_to_hire` | Open to contract-to-hire. Filled on every row. |
| `id_verified` | Identity verified by Upwork. Filled on every row. |
| `phone_verified` | Phone verified by Upwork. Filled on every row. |
| `skills` | Skills in the profile's own order. Filled on every row. |
| `portfolio_count` | Portfolio items. Filled on every row. |
| `agency_name` | Agency the freelancer belongs to, when shown on the card. Filled on 3% of rows. |
| `agency_ciphertext` | That agency's Upwork id. Filled on 3% of rows. |
| `status` | `public`, or `private` when the profile is no longer public — then `private_since` says since when and the other fields are the last public state. Filled on every row. |
| `private_since` | When we first saw the profile private. Filled on 0% of rows. |
| `source` | `stored` (answered from the census) or `live` (fetched for this run). Filled on every row. |
| `completeness` | `profile` (full profile read) or `card` (search card only — the stats fields are null). Filled on every row. |
| `fetched_at` | When this row's data was read from Upwork. Filled on every row. |
| `age_days` | Days since `fetched_at`. Filled on every row. |
| `first_seen_at` | When the census first saw this person. Filled on every row. |
| `last_seen_at` | When the census last saw this person (search or profile). Filled on every row. |
| `card_seen_at` | When this person last appeared in a search result we read. Filled on 9% of rows. |
| `versions` | How many distinct states of this profile the census has recorded. Filled on every row. |
| `changes` | With `changed_since`: the fields that moved in the window, as `{field, from, to, at}`. |
| `contracts` | With `include=contracts`: completed contracts, newest first — title, type, dates, hours, rate, charges, client name, the client's feedback (score, comment, six detail scores) and the freelancer's feedback about the client. A full profile fetch stores every contract; a stored row carries the page the profile showed (`contracts_total` says how many exist). Filled on 0% of rows. |
| `contracts_in_progress` | With `include=contracts`: contracts open at the last read. Filled on 0% of rows. |
| `contracts_complete` | True when every contract is stored (a full profile fetch happened); false when only the profile page's newest ones are. Filled on every row. |
| `portfolios` | With `include=portfolios`: portfolio items — title, role, description, URL, skills. Filled on 0% of rows. |
| `testimonials` | With `include=testimonials`: client testimonials on the profile. Filled on 0% of rows. |
| `specialized_profiles` | With `include=specialized_profiles`: the freelancer's specialized profiles and their occupations. Filled on 0% of rows. |
| `education` | With `include=education`: schools — institution, degree, area of study, from/to. Filled on 0% of rows. |
| `employment_history` | With `include=employment_history`: employment outside Upwork — company, title, city, country, from/to, current, description. Filled on 0% of rows. |
| `languages` | With `include=languages`: languages with proficiency (basic, conversational, fluent, native or bilingual) and whether Upwork verified it. Filled on 0% of rows. |
| `certificates` | With `include=certificates`: certificates — name, provider, earned on, expires on, verified. Filled on 0% of rows. |
| `other_experiences` | With `include=other_experiences`: the profile's other experience entries — subject and description. Filled on 0% of rows. |
| `sections_fetched_at` | When education, employment, languages, certificates and other experiences were last read; null when never (they are read on every profile fetch since 2026-09-12). Filled on 99% of rows. |
| `rank` | Position in the result set (1-based). |

Example record:

```json
{
  "ciphertext": "~01d699cdd471617458",
  "url": "https://www.upwork.com/freelancers/~01d699cdd471617458",
  "person_id": "899818158596096000",
  "name": "Ronald T.",
  "title": "Python Developer",
  "overview": "I specialize in API development and web scraping using Python, with hands-on experience in popular frameworks such as Django, Django REST Framework, and Flask. I'm also proficient  …(truncated for display)",
  "country": "Philippines",
  "country_code": "PH",
  "region": "Asia",
  "city": "San Jose Del Monte Bulacan",
  "state": null,
  "timezone": "UTC+08:00 Hong Kong SAR, Perth, Singapore, Taipei",
  "rate": 30.0,
  "jss": 100,
  "jss_hidden": false,
  "total_earnings": 182073.96,
  "earnings_precision": "exact",
  "earnings_hidden": false,
  "badge": "top_rated",
  "top_rated": true,
  "top_rated_plus": false,
  "hours": 8937.500343,
  "jobs_total": 10,
  "jobs_hourly": 13,
  "jobs_fixed": 1,
  "contracts_total": 9,
  "member_since": "2017-08-22",
  "last_worked_on": "2026-07-26",
  "hire_again_pct": 40.0,
  "rating": 4.635285549,
  "feedback_count": 7,
  "recent_hours": 0.0,
  "recent_jobs": 2,
  "availability": "notSure",
  "contract_to_hire": false,
  "id_verified": false,
  "phone_verified": false,
  "skills": [
    "Data Extraction",
    "Python",
    "Data Scraping",
    "App Development",
    "TypeScript",
    "API Development",
    "Django",
    "RESTful API",
    "Front-End Development",
    "React",
    "Docker",
    "Docker Compose",
    "PostgreSQL",
    "MySQL"
  ],
  "portfolio_count": 2,
  "agency_name": null,
  "agency_ciphertext": null,
  "status": "public",
  "private_since": null,
  "source": "stored",
  "completeness": "profile",
  "fetched_at": "2026-09-11T23:26:39.206998Z",
  "age_days": 0,
  "first_seen_at": "2026-08-18T10:06:09.617698Z",
  "last_seen_at": "2026-09-11T23:53:22.629601Z",
  "card_seen_at": "2026-09-11T23:53:22.629601Z",
  "versions": 1,
  "changes": null,
  "contracts": null,
  "contracts_in_progress": null,
  "contracts_complete": null,
  "portfolios": null,
  "testimonials": null,
  "specialized_profiles": null,
  "education": null,
  "employment_history": null,
  "languages": null,
  "certificates": null,
  "other_experiences": null,
  "sections_fetched_at": null,
  "rank": 1
}
```

### Pricing

**Pay per delivered row, nothing else.** `profile_stored` — a row answered from the census — **$4.00 per 1,000 rows**. `card_live` — a row of a live search — **$5.00 per 1,000**. `profile_live` — a freshly read full profile — **$20.00 per 1,000**. No start fee, no minimum charge, and a run that returns nothing costs nothing: on the acceptance runs the charged event count equals the dataset row count on every run, and the zero-row run charged $0.00. A row is charged only after it is in your dataset, so `maxItems` is also your spending cap.

### Usage patterns

- **Stored search with filters** — Set `query` and any filters; rows come from the census in seconds, sorted by relevance, rate, hours, JSS, earnings or recency. Use `maxItems` to cap rows and spend. Ready-made: [Top Rated US Python developers at $50–100/h](https://apify.com/moxlade/upwork-freelancers/examples/top-rated-us-python-developers-50-100) and [Active and proven: JSS 95+, 1,000+ hours, worked recently](https://apify.com/moxlade/upwork-freelancers/examples/active-proven-freelancers).
- **Look up specific profiles** — Pass `profileUrls` (URLs or `~01…` ids). Private profiles answer as `status: private`; ids the census has not seen yet are listed in the run's status message.
- **What changed since last week** — Set `changedSince` to a date or a window (`7d`, `2w`, `1m`): only profiles that moved come back, each with a `changes` list. Pair it with `includeStale: true` to cover the whole census. Ready-made, schedule it weekly: [What changed this week](https://apify.com/moxlade/upwork-freelancers/examples/what-changed-this-week).
- **Full profiles with every contract** — Pass `profileUrls` with `fullProfiles: true`: each profile is read from Upwork now, its complete contract history fetched and stored, and the row comes back `source: live` with `contracts`, `education`, `employment_history`, `languages`, `certificates`, `other_experiences`, `portfolios`, `testimonials` and `specialized_profiles`. Billed as `profile_live` per profile. Ready-made: [Full profiles: every contract, feedback both ways, education](https://apify.com/moxlade/upwork-freelancers/examples/full-profiles-with-every-contract).
- **Upwork's own order, right now** — Set `liveSearch: true` with a `query` (and any of the badge, country, region, rate, JSS or earnings filters): the run fetches the current Upwork result for that query, returns it in Upwork's order as `source: live`, and those rows join the census. Ready-made: [Live search in Upwork's own order](https://apify.com/moxlade/upwork-freelancers/examples/live-search-upwork-order).

### Input configuration

| field | type | default | what it does |
|---|---|---|---|
| `query` | `string` |  | Matched against the profile title, overview and skills. Leave empty to filter the whole census by the fields below. |
| `profileUrls` | `array` |  | Look up specific freelancers instead of searching: profile URLs (https://www.upwork.com/freelancers/~01…) or bare ids (~01…). Up to 5,000. A private profile is answered as status: private with the date it went private. |
| `fullProfiles` | `boolean` | `false` | With profileUrls: re-read each profile from Upwork now and fetch everything it has — the complete contract history (hours, charges also on private contracts, the client's feedback and the freelancer's feedback about the client), education, employment history, languages, certificates, other experiences, portfolios, testimonials and specialized profiles. Billed as profile\_live per profile; about 10 s per profile. Without it, the stored profile is returned (profile\_stored). |
| `topRated` | `boolean` |  | Only Top Rated freelancers (includes Top Rated Plus). |
| `topRatedPlus` | `boolean` |  | Only Top Rated Plus. |
| `risingTalent` | `boolean` |  | Only freelancers with the Rising Talent badge. |
| `countries` | `array` |  | Country names as Upwork shows them, e.g. United States, United Kingdom, Germany. Any of these. |
| `regions` | `array` |  | Upwork's regions. Any of these. |
| `rateMin` | `integer` |  | Listed hourly rate at least this. |
| `rateMax` | `integer` |  | Listed hourly rate at most this. |
| `jssMin` | `integer` |  | 0–100. Profiles without a shown JSS are excluded when this is set. |
| `hoursMin` | `integer` |  | Total hours billed on Upwork. |
| `jobsMin` | `integer` |  | Jobs worked, as the profile reports it. |
| `earnedMin` | `integer` |  | Uses the exact figure where known, the card's lower bound otherwise. |
| `memberSinceAfter` | `string` |  | Joined Upwork on or after this date (YYYY-MM-DD). |
| `memberSinceBefore` | `string` |  | Joined Upwork on or before this date (YYYY-MM-DD). |
| `lastWorkedWithinDays` | `integer` |  | Last work on Upwork within this many days — active freelancers only. |
| `hireAgainMin` | `integer` |  | Share of clients who would hire again. |
| `skills` | `array` |  | Skill names as Upwork lists them, e.g. Python, Django, Web Scraping. A profile matches if it lists any of them. |
| `availability` | `array` |  | As stated on the profile: more than 30 hrs/week (fullTime), less than 30 (partTime), as needed (notSure). |
| `hasContracts` | `boolean` |  | Only freelancers with at least one completed contract. |
| `idVerified` | `boolean` |  | Only freelancers whose identity Upwork has verified. |
| `excludeAgencies` | `boolean` |  | Drop freelancers listed under an agency. |
| `changedSince` | `string` |  | ISO date or timestamp, or a window like 7d, 2w, 1m (so a scheduled task never needs editing). Only profiles whose rate, title, JSS, badge, hours, jobs, skills, availability or hire-again rate moved since then; each row carries the changes as {field, from, to, at}. |
| `include` | `array` |  | Sections to add to every row from the stored profile at no extra charge: contracts (completed contracts with feedback both ways; the full list after a fullProfiles read), portfolios, testimonials, specialized\_profiles, education, employment\_history, languages, certificates, other\_experiences. |
| `sort` | `relevance` / `rate` / `rate_asc` / `hours` / `jss` / `earnings` / `last_worked_on` / `first_seen` / `fetched` | `"relevance"` | Order of the stored results. Upwork's own relevance order comes only with liveSearch. |
| `maxItems` | `integer` | `50` | Upper bound on rows returned — and on what the run can cost. |
| `maxAgeDays` | `integer` | `30` | Stored rows read from Upwork longer ago than this are left out of a search. Every row carries fetched\_at and age\_days regardless. |
| `includeStale` | `boolean` | `false` | Ignore maxAgeDays and return every matching stored profile. |
| `includePrivate` | `boolean` | `false` | Search results also return profiles that are no longer public (status: private, with private\_since and the last public state). Lookups by id always answer them. |
| `liveSearch` | `boolean` | `false` | Ask Upwork for the current result for the query instead of the stored census: Upwork's own relevance order, and freelancers the census has not seen yet. Slower (under a minute for 25 rows), billed as card\_live per row. Needs a query. |
| `maxPages` | `integer` | `10` | With liveSearch, how many result pages to walk at most (10 rows per page) before returning what passed the filters. |

### FAQ

**Where does the data come from, and how fresh is it?**

From a census of public Upwork freelancer profiles that is refreshed every day and re-reads the oldest profiles on a rolling basis. Every row says when it was read (`fetched_at`, `age_days`); a stored search leaves out rows older than `maxAgeDays` (default 30) unless you set `includeStale`. `liveSearch` reads Upwork at run time.

**Are the earnings exact?**

Where Upwork exposes the figure, yes — `earnings_precision: exact`. Where only a rounded band is shown, `total_earnings` is that band's lower bound and `earnings_precision` is `band`. When the freelancer hides earnings, `earnings_hidden` is true and the figure is null. The share of rows with an exact figure grows as the census re-reads profiles; it is stated on the Output tab from the latest acceptance run.

**Why is a profile missing?**

A stored search only returns profiles the census holds. If you need someone the census has not reached, `liveSearch` finds them for a keyword, and a lookup by id reports the missing ids in the status message so you can see exactly what was not found.

**Where is this documented in more depth?**

On moxlade.com: [what the actor returns, field by field](https://moxlade.com/upwork-freelancer-scraper/), [how to export freelancers to CSV or JSON](https://moxlade.com/guides/export-upwork-freelancers-to-csv/), [how to filter by rate, Job Success Score and country](https://moxlade.com/guides/find-upwork-freelancers-by-rate-jss-country/), [the official Upwork API against this census](https://moxlade.com/guides/upwork-api-freelancer-data/), and [every Upwork freelancer scraper on Apify measured on one day](https://moxlade.com/guides/best-upwork-freelancer-scrapers/). The MCP corpus behind the buyer side of Upwork is at [buyer.moxlade.com](https://buyer.moxlade.com).

**Is there example code?**

Yes — ready-to-run scripts for every job on this page (a filtered shortlist to CSV, a one-call export, enriching a list of profile URLs, stored versus live, full profiles, weekly changes) in Python, JavaScript and curl: [github.com/getmoxlade/upwork-freelancers-examples](https://github.com/getmoxlade/upwork-freelancers-examples). The comparison with the other Upwork scrapers on the store is reproducible from [github.com/getmoxlade/upwork-scraper-comparison](https://github.com/getmoxlade/upwork-scraper-comparison).

**What does a run cost?**

One event per row that lands in your dataset — $4 per 1,000 stored rows, $5 per 1,000 live search rows, $20 per 1,000 live profiles — nothing for the run itself and nothing for a zero-row run. `maxItems` bounds both. Measured on our acceptance runs: 25 stored rows answered in 0.4–1.9 s of run time; 25 live rows in Upwork's order in under 30 s including the search.

### Integration

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('moxlade/upwork-freelancers').call({"query": "python developer", "topRated": true, "countries": ["United States"], "rateMin": 50, "rateMax": 100, "maxItems": 25});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('moxlade/upwork-freelancers').call(run_input={'query': 'python developer', 'topRated': True, 'countries': ['United States'], 'rateMin': 50, 'rateMax': 100, 'maxItems': 25})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

#### CLI

```bash
apify call moxlade/upwork-freelancers --input '{"query": "python developer", "topRated": true, "countries": ["United States"], "rateMin": 50, "rateMax": 100, "maxItems": 25}'
```

#### REST

```bash
curl -X POST "https://api.apify.com/v2/acts/moxlade~upwork-freelancers/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' -d '{"query": "python developer", "topRated": true, "countries": ["United States"], "rateMin": 50, "rateMax": 100, "maxItems": 25}'
```

### Support

support@moxlade.com

*This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.*

# Actor input Schema

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

Matched against the profile title, overview and skills. Leave empty to filter the whole census by the fields below.

## `profileUrls` (type: `array`):

Look up specific freelancers instead of searching: profile URLs (https://www.upwork.com/freelancers/~01…) or bare ids (~01…). Up to 5,000. A private profile is answered as status: private with the date it went private.

## `fullProfiles` (type: `boolean`):

With profileUrls: re-read each profile from Upwork now and fetch everything it has — the complete contract history (hours, charges also on private contracts, the client's feedback and the freelancer's feedback about the client), education, employment history, languages, certificates, other experiences, portfolios, testimonials and specialized profiles. Billed as profile\_live per profile; about 10 s per profile. Without it, the stored profile is returned (profile\_stored).

## `topRated` (type: `boolean`):

Only Top Rated freelancers (includes Top Rated Plus).

## `topRatedPlus` (type: `boolean`):

Only Top Rated Plus.

## `risingTalent` (type: `boolean`):

Only freelancers with the Rising Talent badge.

## `countries` (type: `array`):

Country names as Upwork shows them, e.g. United States, United Kingdom, Germany. Any of these.

## `regions` (type: `array`):

Upwork's regions. Any of these.

## `rateMin` (type: `integer`):

Listed hourly rate at least this.

## `rateMax` (type: `integer`):

Listed hourly rate at most this.

## `jssMin` (type: `integer`):

0–100. Profiles without a shown JSS are excluded when this is set.

## `hoursMin` (type: `integer`):

Total hours billed on Upwork.

## `jobsMin` (type: `integer`):

Jobs worked, as the profile reports it.

## `earnedMin` (type: `integer`):

Uses the exact figure where known, the card's lower bound otherwise.

## `memberSinceAfter` (type: `string`):

Joined Upwork on or after this date (YYYY-MM-DD).

## `memberSinceBefore` (type: `string`):

Joined Upwork on or before this date (YYYY-MM-DD).

## `lastWorkedWithinDays` (type: `integer`):

Last work on Upwork within this many days — active freelancers only.

## `hireAgainMin` (type: `integer`):

Share of clients who would hire again.

## `skills` (type: `array`):

Skill names as Upwork lists them, e.g. Python, Django, Web Scraping. A profile matches if it lists any of them.

## `availability` (type: `array`):

As stated on the profile: more than 30 hrs/week (fullTime), less than 30 (partTime), as needed (notSure).

## `hasContracts` (type: `boolean`):

Only freelancers with at least one completed contract.

## `idVerified` (type: `boolean`):

Only freelancers whose identity Upwork has verified.

## `excludeAgencies` (type: `boolean`):

Drop freelancers listed under an agency.

## `changedSince` (type: `string`):

ISO date or timestamp, or a window like 7d, 2w, 1m (so a scheduled task never needs editing). Only profiles whose rate, title, JSS, badge, hours, jobs, skills, availability or hire-again rate moved since then; each row carries the changes as {field, from, to, at}.

## `include` (type: `array`):

Sections to add to every row from the stored profile at no extra charge: contracts (completed contracts with feedback both ways; the full list after a fullProfiles read), portfolios, testimonials, specialized\_profiles, education, employment\_history, languages, certificates, other\_experiences.

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

Order of the stored results. Upwork's own relevance order comes only with liveSearch.

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

Upper bound on rows returned — and on what the run can cost.

## `maxAgeDays` (type: `integer`):

Stored rows read from Upwork longer ago than this are left out of a search. Every row carries fetched\_at and age\_days regardless.

## `includeStale` (type: `boolean`):

Ignore maxAgeDays and return every matching stored profile.

## `includePrivate` (type: `boolean`):

Search results also return profiles that are no longer public (status: private, with private\_since and the last public state). Lookups by id always answer them.

## `liveSearch` (type: `boolean`):

Ask Upwork for the current result for the query instead of the stored census: Upwork's own relevance order, and freelancers the census has not seen yet. Slower (under a minute for 25 rows), billed as card\_live per row. Needs a query.

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

With liveSearch, how many result pages to walk at most (10 rows per page) before returning what passed the filters.

## Actor input object example

```json
{
  "query": "python developer",
  "fullProfiles": false,
  "sort": "relevance",
  "maxItems": 50,
  "maxAgeDays": 30,
  "includeStale": false,
  "includePrivate": false,
  "liveSearch": false,
  "maxPages": 10
}
```

# Actor output Schema

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

All scraped records in the default dataset. One record per freelancer:

# 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"
};

// Run the Actor and wait for it to finish
const run = await client.actor("moxlade/upwork-freelancers").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" }

# Run the Actor and wait for it to finish
run = client.actor("moxlade/upwork-freelancers").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"
}' |
apify call moxlade/upwork-freelancers --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,moxlade/upwork-freelancers"
        }
    }
}
```

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/2faiWqJkfFFFpxevO/builds/nFWBOrJOKxh33iAtb/openapi.json
