Upwork Freelancer Search — Rates, Earnings, JSS, Work History avatar

Upwork Freelancer Search — Rates, Earnings, JSS, Work History

Pricing

from $4.00 / 1,000 stored profile rows

Go to Apify Store
Upwork Freelancer Search — Rates, Earnings, JSS, Work History

Upwork Freelancer Search — Rates, Earnings, JSS, Work History

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.

Pricing

from $4.00 / 1,000 stored profile rows

Rating

0.0

(0)

Developer

Moxlade

Moxlade

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

2 days ago

Last modified

Share

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

{
"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

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

Look up profiles by URL

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

Profiles with their contract history

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

Fresh full profiles, every contract

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

What changed since a date

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

Output

One record per freelancer:

fieldmeaning
ciphertextUpwork's stable profile id (~01…). Deduplicate and join on this. Filled on every row.
urlThe public profile URL. Filled on every row.
person_idUpwork's numeric person id. Filled on every row.
nameDisplay name as Upwork shows it (first name + initial). Filled on every row.
titleThe profile headline. Filled on every row.
overviewThe profile overview, full text. Filled on every row.
countryCountry as shown on the profile. Filled on every row.
country_codeISO 3166-1 alpha-2. Filled on every row.
regionUpwork's region: Americas, Asia, Europe, Africa, Oceania. Filled on 9% of rows.
cityCity. Filled on every row.
stateState or province, where Upwork shows one. Filled on 99% of rows.
timezoneTimezone as Upwork labels it (e.g. UTC+08:00 …). Filled on 9% of rows.
rateListed hourly rate, USD. Filled on every row.
jssJob Success Score, 0–100. Null when Upwork shows none (new, or hidden). Filled on 90% of rows.
jss_hiddenThe freelancer hides their JSS. Filled on 9% of rows.
total_earningsLifetime earnings, USD. Exact when earnings_precision is exact; a lower bound ($10K+ → 10000) when band. Filled on 48% of rows.
earnings_precisionexact (read from Upwork's search payload), band (the card's rounded figure), or null. Filled on 48% of rows.
earnings_hiddenThe freelancer hides their earnings. Filled on every row.
badgetop_rated_plus, top_rated, rising_talent, or null. Filled on 74% of rows.
top_ratedTop Rated (includes Top Rated Plus). Filled on every row.
top_rated_plusTop Rated Plus. Filled on every row.
hoursTotal hours billed on Upwork. Filled on every row.
jobs_totalJobs worked, as the profile reports it. Filled on every row.
jobs_hourlyHourly contracts. Filled on every row.
jobs_fixedFixed-price contracts. Filled on every row.
contracts_totalCompleted contracts in the work history. Filled on every row.
member_sinceAccount creation date. Filled on every row.
last_worked_onDate of the most recent work on Upwork. Filled on every row.
hire_again_pctShare 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.
ratingAverage client rating, 0–5. Filled on every row.
feedback_countContracts with client feedback. Filled on every row.
recent_hoursHours billed in the recent window Upwork reports. Filled on every row.
recent_jobsJobs in the recent window Upwork reports. Filled on every row.
availabilityfullTime, partTime, notSure (as needed). Filled on every row.
contract_to_hireOpen to contract-to-hire. Filled on every row.
id_verifiedIdentity verified by Upwork. Filled on every row.
phone_verifiedPhone verified by Upwork. Filled on every row.
skillsSkills in the profile's own order. Filled on every row.
portfolio_countPortfolio items. Filled on every row.
agency_nameAgency the freelancer belongs to, when shown on the card. Filled on 3% of rows.
agency_ciphertextThat agency's Upwork id. Filled on 3% of rows.
statuspublic, 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_sinceWhen we first saw the profile private. Filled on 0% of rows.
sourcestored (answered from the census) or live (fetched for this run). Filled on every row.
completenessprofile (full profile read) or card (search card only — the stats fields are null). Filled on every row.
fetched_atWhen this row's data was read from Upwork. Filled on every row.
age_daysDays since fetched_at. Filled on every row.
first_seen_atWhen the census first saw this person. Filled on every row.
last_seen_atWhen the census last saw this person (search or profile). Filled on every row.
card_seen_atWhen this person last appeared in a search result we read. Filled on 9% of rows.
versionsHow many distinct states of this profile the census has recorded. Filled on every row.
changesWith changed_since: the fields that moved in the window, as {field, from, to, at}.
contractsWith 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_progressWith include=contracts: contracts open at the last read. Filled on 0% of rows.
contracts_completeTrue when every contract is stored (a full profile fetch happened); false when only the profile page's newest ones are. Filled on every row.
portfoliosWith include=portfolios: portfolio items — title, role, description, URL, skills. Filled on 0% of rows.
testimonialsWith include=testimonials: client testimonials on the profile. Filled on 0% of rows.
specialized_profilesWith include=specialized_profiles: the freelancer's specialized profiles and their occupations. Filled on 0% of rows.
educationWith include=education: schools — institution, degree, area of study, from/to. Filled on 0% of rows.
employment_historyWith include=employment_history: employment outside Upwork — company, title, city, country, from/to, current, description. Filled on 0% of rows.
languagesWith include=languages: languages with proficiency (basic, conversational, fluent, native or bilingual) and whether Upwork verified it. Filled on 0% of rows.
certificatesWith include=certificates: certificates — name, provider, earned on, expires on, verified. Filled on 0% of rows.
other_experiencesWith include=other_experiences: the profile's other experience entries — subject and description. Filled on 0% of rows.
sections_fetched_atWhen 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.
rankPosition in the result set (1-based).

Example record:

{
"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 and Active and proven: JSS 95+, 1,000+ hours, worked recently.
  • 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.
  • 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.
  • 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.

Input configuration

fieldtypedefaultwhat it does
querystringMatched against the profile title, overview and skills. Leave empty to filter the whole census by the fields below.
profileUrlsarrayLook 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.
fullProfilesbooleanfalseWith 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).
topRatedbooleanOnly Top Rated freelancers (includes Top Rated Plus).
topRatedPlusbooleanOnly Top Rated Plus.
risingTalentbooleanOnly freelancers with the Rising Talent badge.
countriesarrayCountry names as Upwork shows them, e.g. United States, United Kingdom, Germany. Any of these.
regionsarrayUpwork's regions. Any of these.
rateMinintegerListed hourly rate at least this.
rateMaxintegerListed hourly rate at most this.
jssMininteger0–100. Profiles without a shown JSS are excluded when this is set.
hoursMinintegerTotal hours billed on Upwork.
jobsMinintegerJobs worked, as the profile reports it.
earnedMinintegerUses the exact figure where known, the card's lower bound otherwise.
memberSinceAfterstringJoined Upwork on or after this date (YYYY-MM-DD).
memberSinceBeforestringJoined Upwork on or before this date (YYYY-MM-DD).
lastWorkedWithinDaysintegerLast work on Upwork within this many days — active freelancers only.
hireAgainMinintegerShare of clients who would hire again.
skillsarraySkill names as Upwork lists them, e.g. Python, Django, Web Scraping. A profile matches if it lists any of them.
availabilityarrayAs stated on the profile: more than 30 hrs/week (fullTime), less than 30 (partTime), as needed (notSure).
hasContractsbooleanOnly freelancers with at least one completed contract.
idVerifiedbooleanOnly freelancers whose identity Upwork has verified.
excludeAgenciesbooleanDrop freelancers listed under an agency.
changedSincestringISO 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}.
includearraySections 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.
sortrelevance / 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.
maxItemsinteger50Upper bound on rows returned — and on what the run can cost.
maxAgeDaysinteger30Stored rows read from Upwork longer ago than this are left out of a search. Every row carries fetched_at and age_days regardless.
includeStalebooleanfalseIgnore maxAgeDays and return every matching stored profile.
includePrivatebooleanfalseSearch 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.
liveSearchbooleanfalseAsk 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.
maxPagesinteger10With 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, how to export freelancers to CSV or JSON, how to filter by rate, Job Success Score and country, the official Upwork API against this census, and every Upwork freelancer scraper on Apify measured on one day. The MCP corpus behind the buyer side of Upwork is at 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. The comparison with the other Upwork scrapers on the store is reproducible from 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

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

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

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

REST

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.