Upwork Jobs Scraper: Pay, Clients & Job Alerts, No Login avatar

Upwork Jobs Scraper: Pay, Clients & Job Alerts, No Login

Pricing

from $0.63 / 1,000 saved jobs

Go to Apify Store
Upwork Jobs Scraper: Pay, Clients & Job Alerts, No Login

Upwork Jobs Scraper: Pay, Clients & Job Alerts, No Login

Scrape public Upwork job posts to find clients, track freelance demand and get new job alerts. Filter by skill, budget and client history. Get pay, skills, applicants and client spend. No login.

Pricing

from $0.63 / 1,000 saved jobs

Rating

0.0

(0)

Developer

Cliqto Media

Cliqto Media

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

6 days ago

Last modified

Features

Limited permissions

Share

Use this Upwork jobs scraper to get public Upwork job posts by search phrase, search link, job link or job ID. Find clients, track freelance demand or get new job alerts. Each row has pay, skills, applicants and public client data such as country, spend and rating. No Upwork account or cookies needed.

Start with queries: ["web scraping"] and maxItems: 10. The new Input form has this example filled in. Pricing is $0.001 per run start + $0.0001 per source request + $0.90 per 1,000 saved jobs on the Free plan (down to $0.63 on Gold). Check RUN_SUMMARY to see why a run saved fewer jobs than you asked for.

Upwork Jobs Scraper: a search phrase gives job rows with pay, client spend and applicants, ready to export as JSON or CSV

Table of contents

What it does

The Actor reads public job posts from Upwork. You can search by phrase, paste a search link from Upwork, or read known jobs by link or ID. You do not need an Upwork account, cookies or your own proxy.

Each job is one Dataset row. The row has the title, description, budget or hourly rate, payment type, experience level, skills, duration, workload and post time. With public job details on, it also has the client country, spend, rating, reviews, hires, applicants and freelancer requirements.

Use filters to keep only the jobs you want. Use Only new jobs on a schedule to get new jobs after each run.

Good fit

  • Find new clients and leads for a freelance team or agency.
  • Track demand, pay and skills in a job category.
  • Get an alert when a new job matches your search.
  • Feed job rows to a sheet, database, Slack, Notion or an AI agent.

This Actor does not log in, read private or invite-only jobs, send proposals or contact clients. It does not read freelancer profiles. It does not get client names, emails or phone numbers.

👉 Try it now: open the input form

How it works

  1. You give search phrases, Upwork links or job IDs. Bad input stops the run before any source request.
  2. The Actor reads public search pages. It then reads public details for the jobs it found, unless you turn includeDetails off. It slows down when Upwork asks it to.
  3. It applies your filters and saves one row per unique job. It stops at maxItems or at the end of the search.
  4. It writes a RUN_SUMMARY report with counts and the reason the run stopped. It can also post a short summary to Slack or Notion.

👉 Try it now: open the input form

Quick start

Input form: box 1 is the Search phrases field with web scraping, box 2 is the Maximum results field with 10

  1. Open the Input tab. Keep web scraping in Search phrases, or type your own phrase (box 1).

  2. Keep 10 in Maximum results for a first test (box 2). Filters are optional, see Input.

  3. Click Start.

  4. When the run ends, open the Output tab. Each row is one job (box 4). Check the job title, budget, client spend and applicants.

    Output table: one row per job with title, type, budget, hourly rate, client country, client spend, applicants, post time and job link

Expected result: up to 10 job rows from the newest public jobs for your phrase. Live jobs change all the time, so your rows will differ. An API call must give its own search phrase, link or job ID. An empty API input stops with EMPTY_INPUT.

👉 Try it now: open the input form

Use cases

Buyer jobInput pathOutput entityKey fieldsNext integration
Find clients who pay wellqueries, minClientTotalSpent, paymentVerified: trueOne job per rowtitle, clientCountry, clientTotalSpent, clientRating, urlCRM or a sheet
Get new job alertsqueries, onlyNew: true, Apify ScheduleOnly jobs not saved beforetitle, budget, hourlyRange, postedAt, urlSlack or Notion with deliveryConnector
Track market demand and payqueries or categories, maxItems: 1000One job per rowskills, hourlyBudgetMin, hourlyBudgetMax, budget, categorySheet or BI tool
Copy a search you made on UpworkstartUrls with an Upwork search linkOne job per rowtitle, jobType, experienceLevel, postedAt, urlDataset API
Check known jobsjobIds or job links in startUrlsOne row per public jobjobStatus, totalApplicants, hired, clientLastActivity, detailsStatusYour own job tracker

👉 Try it now: open the input form

Input

Give at least one of queries, query, startUrls, rawUrl or jobIds. An empty input stops with EMPTY_INPUT. All other fields are optional. Filled filters all apply together (AND). Duplicate jobs are saved once. The largest run we tested saved 6,000 jobs; maxItems accepts more, but larger runs are not proven.

Input fields

FieldPlain nameTypeRequiredDefault / prefillAllowed valuesEffectExampleRecommendation
queriesSearch phrasesarrayOne of the source fieldsPrefill: ["web scraping"]AnySearch each phrase. Results from all searches share the result limit. Leave blank when you use URLs or job IDs.["web scraping"]Start here.
startUrlsSearch or job URLsarrayOne of the source fieldsNonePublic upwork.com search and job linksPublic Upwork search or job links. Search filters in a link take the place of the same input filters.[{"url":"https://www.upwork.com/nx/search/jobs/?q=python"}]Use to copy a search from Upwork.
jobIdsJob IDsarrayOne of the source fieldsNoneNumeric ID or ID that starts with ~02Read public details for these job IDs. Use the numeric ID or the ID that starts with ~02.["2117261165716622937"]Use for known jobs.
maxItemsMaximum resultsintegerNoDefault and prefill: 101–1,000,000Stop after saving this many matching jobs across all inputs. The source may have fewer jobs. Pages read before this limit still count as source work.100Use 10 for a first test.
includeDetailsRead public job detailsbooleanNoDefault: truetrue, falseInclude the public client panel, job activity and requirements. A job that is visible only to members keeps its search fields.trueKeep on. Turn off for a cheaper list with search fields only.
modeRun modestringNoDefault: searchsearch, details, monitorSearch finds jobs. Details reads job links or IDs. Monitor keeps a history and returns new or changed jobs."monitor"Keep search unless you read known jobs or need a monitor.
jobTypePayment typesarrayNoNonehourly, fixed, weekly_retainerKeep hourly, fixed-price or weekly retainer jobs.["hourly"]Use it, as it narrows the search itself.
experienceLevelExperience levelsarrayNoNoneentry, intermediate, expertKeep the selected experience levels.["expert"]Use it, as it narrows the search itself.
categoriesCategoriesarrayNoNone12 Upwork categoriesUpwork category IDs. Choose a category by name. Leave blank for all categories.["531770282580668418"]Pick by name in the form.
minBudgetMinimum fixed budgetnumberNoNone0 or moreKeep jobs with a known fixed budget at or above this amount.500Use for lead lists.
maxBudgetMaximum fixed budgetnumberNoNone0 or moreKeep jobs with a known fixed budget at or below this amount.5000Optional.
minHourlyRateMinimum hourly ratenumberNoNone0 or moreKeep jobs whose known minimum hourly rate is at or above this amount.30Use for lead lists.
maxHourlyRateMaximum hourly ratenumberNoNone0 or moreKeep jobs whose known maximum hourly rate is at or below this amount.100Optional.
workloadWeekly workloadarrayNoNonepart_time, full_time, as_neededFilter by the weekly time needed.["full_time"]Optional.
durationProject durationarrayNoNoneweek, month, semester, ongoingFilter by the source project duration group.["month"]Optional.
contractToHireContract-to-hire onlybooleanNoOfftrue, falseKeep jobs marked as contract-to-hire. Needs public job details.trueOptional.
includeKeywordsRequired wordsarrayNoNoneAnyEach word or phrase must appear in the title, description or skills.["python"]Use for must-have words.
excludeKeywordsExcluded wordsarrayNoNoneAnySkip a job if any word or phrase appears in its title, description or skills.["wordpress"]Use to remove noise.
requiredSkillsRequired skillsarrayNoNoneAnyEach skill name must be in the job skills. Match ignores letter case.["Python"]Use exact skill names.
titleContainsWords in titlestringNoNoneAnyKeep jobs whose title contains this phrase. Match ignores letter case."scraper"Optional.
maxAgeMinutesMaximum job age in minutesnumberNoNone0 or moreKeep jobs published within this many minutes.1440Use for fresh jobs.
maxJobAgeJob ageobjectNoNoneUnits: minutes, hours, days, weeksAlternative age filter with value and unit. Units: minutes, hours, days or weeks.{"value":2,"unit":"days"}Same as maxAgeMinutes, but easier to read.
fromDatePublished fromstringNoNoneYYYY-MM-DD or ISO time ending in ZKeep jobs published on or after this UTC date or time. Use YYYY-MM-DD or an ISO time ending in Z."2026-11-01"Optional.
toDatePublished throughstringNoNoneYYYY-MM-DD or ISO time ending in ZKeep jobs published through this UTC date or time. A date without a time includes the full day."2026-11-04"Optional.
proposalsProposal rangesarrayNoNone0-4, 5-9, 10-14, 15-19, 20-49, 50-Keep jobs with an exact applicant count inside these ranges. Needs public job details.["0-4"]Use 0-4 for low competition.
maxApplicantsMaximum applicantsintegerNoNone0 or moreKeep jobs with a known applicant count at or below this value. Needs public job details.10Use for low competition.
paymentVerifiedPayment verified onlybooleanNoOfftrue, falseKeep clients with a verified payment method. Needs public job details.trueUse for lead lists.
minClientTotalSpentMinimum client spendnumberNoNone0 or moreKeep clients with a known public lifetime spend at or above this amount. Needs public job details.10000Use to find proven buyers.
minClientRatingMinimum client ratingnumberNoNone0–5Keep clients with a known public rating at or above this value. Needs public job details.4.5Removes clients with no reviews.
minClientReviewCountMinimum client reviewsintegerNoNone0 or moreKeep clients with at least this many public reviews. Needs public job details.5Optional.
clientHistoryClient hiring historyarrayNoNone0, 1-9, 10-Keep clients with no hires, 1–9 hires or 10+ hires.["10-"]Use 10- for repeat buyers.
clientCountriesClient countriesarrayNoNoneAnyClient country names, such as United States. Needs public job details.["United States"]Use for a market or language.
excludeLocationsExclude client countriesarrayNoNoneAnySkip jobs from these client countries. Needs public job details.["India"]Use to drop markets.
timezonesClient timezonesarrayNoNoneAnyUpwork timezone filter values from a search link.Value from a linkCopy from a link.
onlyNewOnly new jobsbooleanNoOfftrue, falseKeep a history across runs and return jobs not saved before. Use Return changed jobs to include edits.trueUse on a schedule.
emitChangedReturn changed jobsbooleanNoOfftrue, falseWith Only new jobs, also return jobs whose public fields changed.trueUse to track edits.
stateNameHistory namestringNoNone. History uses upwork-jobs-scraper-stateLetters, digits, hyphens; up to 51 charactersUse the same history name on later runs. Use a different name for a separate monitor. Only needed for Only new jobs."python-jobs"Use one name per monitor.
resetStateReset job historybooleanNoOfftrue, falseIgnore the previous history for this run. New history contains only jobs saved by this run.trueUse once, then turn off.
skipRepostsSkip repeated job textbooleanNoOfftrue, falseKeep a history and skip a new job ID when its cleaned title and description match a job saved before.trueUse for alerts.
deliveryConnectorSend results to (optional)stringNoNoneAnyYour Slack, Notion or other MCP connector. After the run the Actor posts a short summary there. Connect services once in Apify Console → MCP connectors.Your connectorOptional.
deliveryTargetNotion page or channel (optional)stringNoNoneAnyWhere to post the summary: a Notion page link (each run is added on top of that page) or a Slack channel ID. Leave empty to let the connector decide.Slack channel IDOptional.
descriptionFormatDescription formatstringNoDefault: texttext, markdown, htmlReturn plain text, basic Markdown, or escaped HTML. Line breaks are kept. The source formatting may be simplified."markdown"Keep text.
descriptionMaxLengthDescription length limitintegerNoNone0 or moreTrim the returned description to this many characters. Leave blank for the full public text.500Use for smaller exports.
compactCompact outputbooleanNoOfftrue, falseLeave out the nested requirements and skill IDs. Other fields stay the same.trueUse for smaller exports.
excludeEmptyFieldsHide empty fieldsbooleanNoOfftrue, falseLeave out fields whose value is null or an empty string.trueKeep off for fixed CSV columns.
querySingle search phrasestringOne of the source fieldsNoneAnyA single search phrase. When set, it takes the place of Search phrases."python"Use queries in the form.
rawUrlSingle search URLstringOne of the source fieldsNonePublic upwork.com search linkA public Upwork job search link. Its filters take the place of the same input filters."https://www.upwork.com/nx/search/jobs/?q=python"Use startUrls for many links.
sortSearch orderstringNoDefault: recencyrecency, relevance+descRead newest jobs first, or use the source relevance order. Large searches may be split by category."recency"Keep recency for alerts.
perPageJobs per search pageintegerNoDefault: 501–50Read this many jobs per page. A smaller page reads less data when you need only a few jobs.20Keep 50.
pageFirst search pageintegerNoDefault: 11–101Start from this page. Page 1 starts at the newest matching jobs.2Keep 1.
pagesToScrapeSearch pages per inputintegerNoNone1 or moreOptional page limit for each search. Leave blank to keep paging until the result limit or the end.3Use to cap source requests.
subcategoriesSubcategory IDsarrayNoNoneAnyUpwork subcategory IDs from a search link.ID from a linkCopy from a link.
skillIdsSkill IDsarrayNoNoneAnyUpwork skill IDs from a search link.ID from a linkCopy from a link. Use requiredSkills for names.
hourlyRateRangeHourly range from search linkstringNoNoneAnyUse an Upwork hourly range such as 20-50. For a simple minimum or maximum, use the hourly rate fields below."20-50"Use minHourlyRate unless you copy a link.
fixedPriceRangeFixed budget ranges from search linkstringNoNoneAnyUse Upwork budget ranges such as 100-499,500-999. For a simple minimum or maximum, use the budget fields below."100-499,500-999"Use minBudget unless you copy a link.
customFiltersCustom field filtersarrayNoNoneOperators: includes, notIncludes, equals, notEqualsFilter public fields with includes, notIncludes, equals or notEquals. Use objects with key, operator and value. Filters are combined with AND.[{"key":"jobType","operator":"equals","value":"hourly"}]Use for rare needs.
concurrencyJobs at a timeintegerNoDefault: 41–8Read up to this many jobs together. The Actor waits when the source asks it to slow down.2Lower to 1 or 2 if you see rate limits.

categories uses Upwork IDs. In the form you pick the name. In the API use the IDs from the form or from an Upwork search link. customFilters[].key accepts title, description, jobType, experienceLevel, budget, tags, skills, absoluteDate, paymentVerified, clientLocation, clientCountry, allowedApplicantCountries, clientRating, clientTotalSpent and proposals.

Interacting settings

WhenInput AInput BEffective behaviorExampleCost/result effect
Both search fields are filledqueryqueriesquery takes the place of queries.query: "python" and queries: ["java"] search only for python.Fewer searches, fewer source requests.
A link has a filter and the same input is filledstartUrls or rawUrlSame filter, such as jobTypeThe filter in the link wins for that link.A link with t=1 keeps fixed jobs even with jobType: ["hourly"].Rows follow the link.
A client filter is on and details are offminClientRating, minClientTotalSpent, clientCountries, paymentVerified and similarincludeDetails: falseThe run stops with DETAIL_FILTER_NEEDS_DETAILS.includeDetails: false with paymentVerified: true.One start event, no rows.
You need edits as well as new jobsonlyNew: trueemitChanged: trueNew jobs and changed jobs are saved. changeType tells which.onlyNew: true, emitChanged: true.Changed rows are charged as saved jobs.
You choose monitor modemode: "monitor"onlyNew, emitChangedBoth turn on. You can set emitChanged: false.mode: "monitor".The first run saves all matches. Later runs save only new or changed jobs.
You run two separate monitorsstateNameonlyNew or skipRepostsEach name keeps its own history. Without a name, all runs share upwork-jobs-scraper-state.stateName: "python-jobs".A new name starts over, so the first run saves all matches.
You restart a historyresetState: trueonlyNewThe run ignores the old history. The new one has only this run's jobs.resetState: true once, then remove it.Saves all matches again, and they are charged again.
Both age filters are filledmaxJobAgemaxAgeMinutesmaxJobAge wins.maxJobAge: {"value": 2, "unit": "days"}.Older jobs are filtered and not charged as saved jobs.

Input examples

{
"queries": ["web scraping"],
"maxItems": 10
}

Expected behavior: reads the newest jobs for the phrase, with public details. Expected output: up to 10 job rows. Limit or cost note: about $0.0106 on Free, $0.0079 on Gold.

Well-paying clients only

{
"queries": ["n8n automation"],
"maxItems": 100,
"jobType": ["hourly"],
"minHourlyRate": 30,
"paymentVerified": true,
"minClientTotalSpent": 10000
}

Expected behavior: searches, reads details, then keeps only matching jobs. Expected output: up to 100 rows. A narrow filter may give fewer. Limit or cost note: rows that do not match are free, but the source requests used to check them are paid.

New job alerts on a schedule

{
"queries": ["web scraping"],
"maxItems": 50,
"onlyNew": true,
"stateName": "web-scraping-alerts",
"deliveryConnector": "<your connector>",
"deliveryTarget": "<Slack channel ID or Notion page link>"
}

Expected behavior: the first run saves up to 50 jobs as a starting point. Later runs save only jobs not saved before. Expected output: new jobs, plus a short summary in Slack or Notion. Limit or cost note: a run with no new jobs still pays for the start and source requests.

{
"mode": "details",
"jobIds": ["2117261165716622937"],
"maxItems": 1
}

Expected behavior: reads public details for this job. Expected output: one row. A job that is hidden from visitors gives no row and adds to membersOnly in RUN_SUMMARY. Limit or cost note: no row means no job event.

👉 Try it now: open the input form

Output

The Dataset has one row per saved job. The default table shows the main columns. All fields are in the JSON, CSV and API exports. RUN_SUMMARY in the default key-value store has the counts and stop reason.

Output fields

FieldMeaningTypeCan be empty?Example / enumSource / notes
jobIdUpwork job ID.stringNo"2117926027372106681"Job post
idThe same ID as jobId, for imports.stringNo"2117926027372106681"Job post
ciphertextUpwork ID used in the job link.stringNo~022117261165716622937Job post
titleJob title, with contact details removed.stringNoSee example belowJob post
descriptionFull public job text, with email addresses and phone-like numbers removed.stringYesFull job textJob post
budgetFixed-price budget. Null for hourly jobs or an undisclosed budget.numberYes10000Job post
budgetCurrencyCurrency code of the fixed budget.stringYes"USD"Job post
hourlyRangeKnown minimum and maximum hourly rates. Null limits mean undisclosed.objectYes{"min": 6, "max": 25}Job post
hourlyBudgetMinKnown minimum hourly rate.numberYes6Job post
hourlyBudgetMaxKnown maximum hourly rate.numberYes25Job post
weeklyRetainerBudgetWeekly retainer budget, when public.numberYesNullJob post
jobTypehourly, fixed or weekly_retainer.stringYeshourly, fixed, weekly_retainerJob post
experienceLevelentry, intermediate or expert.stringYesentry, intermediate, expertJob post
skillsPublic skill names.arrayYes["Data Extraction Tools", "Software Debugging"]Job post
skillIdsPublic skill IDs.arrayYes["1253342873018576896", "1031626781403627520"]Job post
durationProject duration label.stringYes"Less than 1 month"Job post
durationWeeksProject duration in weeks.numberYes3Job post
workloadWeekly work requirement.stringYes"Less than 30 hrs/week"Job post
postedAtPublication time reported by Upwork.stringYes"2026-11-04T10:24:51.983Z"Job post
absoluteDateThe same publication time as postedAt.stringYes"2026-11-04T10:24:51.983Z"Job post
createdAtTime the job was created.stringYes"2026-11-04T10:24:49.148Z"Job post
renewedAtSource renewal or sourcing time, if given.stringYes"2026-11-04T09:55:21.845Z"Job post
clientCountryClient country name. Source country codes are converted to country names. Null when hidden.stringYes"United States"Public job details
clientCountryCodeTwo-letter ISO country code from the public client country name. Null when the name cannot be matched.stringYes"US"Actor
clientCityPublic client city, when visible.stringYesCity namePublic job details
clientTimezonePublic client timezone label.stringYes"America/Chicago (UTC-06:00)"Public job details
clientPaymentVerifiedWhether the public page shows a verified payment method.booleanYesfalsePublic job details
paymentVerifiedThe same value as clientPaymentVerified.booleanYesfalsePublic job details
clientTotalSpentPublic lifetime client spend. A hidden amount stays null.numberYes17584.47Public job details
clientSpentCurrencyCurrency of the public client spend.stringYes"USD"Public job details
clientRatingPublic client rating. Null when the client has no reviews.numberYes5Public job details
clientReviewCountPublic review count.numberYes0Public job details
clientTotalHiresPublic total client assignments.numberYes10Public job details
clientActiveHiresPublic active assignments.numberYes3Public job details
clientTotalHoursPublic hours billed.numberYes0Public job details
clientMemberSincePublic client account creation date.stringYes"2021-03-17T00:00:00.000Z"Public job details
clientIndustryPublic client industry. Null when empty.stringYes"Health & Fitness"Public job details
clientCompanySizeCompany size group reported by Upwork. The group code is not a staff count.numberYes10Public job details
clientHireRatePercentNull when this value is hidden from visitors.numberYesNullPublic job details
clientAvgHourlyRateNull when this value is hidden from visitors.numberYesNullPublic job details
categoryBroad job category.stringYes"Data Science & Analytics"Job post
subcategoryJob subcategory.stringYes"Data Extraction/ETL"Job post
contractToHireWhether the job is marked contract-to-hire.booleanYesfalseJob post
totalApplicantsExact public applicant count. Null means unknown.numberYes18Public job details
proposalsThe same applicant count as totalApplicants.numberYes18Public job details
interviewingPublic count of invited interviews.numberYes3Public job details
invitesSentPublic invitations sent.numberYes5Public job details
unansweredInvitesPublic unanswered invitations.numberYes2Public job details
hiredPublic hires on this job.numberYes0Public job details
positionsToHireNumber of positions on this job.numberYes0Public job details
clientLastActivityLast client activity time shown on the job page.stringYes"2026-11-04T10:27:50.616Z"Public job details
allowedApplicantCountriesApplicant country preferences from the public job requirements.arrayYes["United States"]Public job details
qualificationsPublic freelancer requirements. Missing values stay null; ANY or 0 means no requirement.objectYesSee note belowPublic job details
jobStatusopen or closed, from the public page.stringYesopen, closedPublic job details
detailsStatusavailable, not_requested, members_only, not_found or unavailable (the source gave no usable answer twice, so the row keeps search fields only).stringNoavailable, not_requested, members_only, not_found, unavailableActor
sourceSource website.stringNo"https://www.upwork.com"Actor
urlPublic Upwork job link.stringNo"https://www.upwork.com/jobs/~022117926027372106681"Job post
querySearch phrase that found the job. Null for a direct lookup.stringYes"web scraping"Actor
fetchedAtUTC time when the job was read.stringNo"2026-11-04T12:08:00.361Z"Actor
firstSeenAtFirst saved time in this monitor history, or this run.stringYes"2026-11-04T12:08:00.361Z"Actor
lastSeenAtLast time this row was saved.stringYes"2026-11-04T12:08:00.361Z"Actor
changeTypenew or changed, based on the monitor history.stringYesnewActor
tagsThe same public skill names as skills.arrayYes["Data Extraction Tools", "Software Debugging"]Job post
clientLocationThe same public country as clientCountry.stringYes"United States"Public job details
clientCountryRawCountry text returned by Upwork before conversion.stringYes"United States"Public job details

qualifications has countries, regions, englishLevel, freelancerType, minHours, minHoursPerWeek, minJobSuccess, portfolioRequired and risingTalent. ANY or 0 means no requirement.

Output entities and surfaces

SituationDataset/API/KVS/live viewResult shapeUser action
Normal runDataset rows, RUN_SUMMARY status: "complete"One row per saved jobUse the rows.
No jobs match, or no new jobsEmpty Dataset, RUN_SUMMARY status: "not_found" with filtered or unchanged counts; run SUCCEEDEDNo rowsChange the phrase or filters. For a monitor this is normal.
Job hidden from visitorsNo row for a direct lookup, status: "restricted", membersOnly count. Search rows keep search fields with detailsStatus: "members_only"Fewer fieldsOpen the url while logged in on Upwork.
Your cost limit is reachedSaved rows stay, partial: true, stopReason: "CHARGE_LIMIT"; run SUCCEEDEDPartial rowsRaise the limit and run again.
Upwork gives no usable details for one jobThe row keeps search fields with detailsStatus: "unavailable"; a direct lookup lists the job in unprocessedFewer fieldsRun that job again later.
Source block, too long a wait, timeout or stopSaved rows stay, partial: true, stopReason tells why; run FAILEDPartial rows or noneSee Troubleshooting.
Bad inputNo rows, status: "error", stopReason tells why; run FAILEDNo rowsFix the input.

Output example

Real row, some fields left out:

{
"jobId": "2117702916010329632",
"title": "Social Media automated data collection (API) and Website Data Collection",
"jobType": "hourly",
"hourlyRange": {
"min": 6,
"max": 25
},
"budget": null,
"experienceLevel": "intermediate",
"skills": [
"Data Scraping",
"Web Scraping",
"API Integration",
"Social Media Marketing"
],
"duration": "1 to 3 months",
"postedAt": "2026-11-03T19:38:23.701Z",
"clientCountry": "United States",
"clientPaymentVerified": true,
"clientTotalSpent": 599.1,
"clientSpentCurrency": "USD",
"clientRating": 5,
"clientReviewCount": 2,
"clientTotalHires": 5,
"totalApplicants": 37,
"jobStatus": "open",
"detailsStatus": "available",
"url": "https://www.upwork.com/jobs/~022117702916010329632",
"fetchedAt": "2026-11-04T12:08:00.362Z"
}

Send results to Slack or Notion

Pick your connector in the optional deliveryConnector field. After the run saves its data, the Actor posts a short summary (up to 25 rows and a link to the full dataset) there. Put a Notion page link or a Slack channel ID in deliveryTarget: in Notion every run is added on top of that one page, so your workspace does not fill up with new pages. A delivery error never fails the run and costs nothing extra. Connect a service once in Apify Console → MCP connectors.

Use case → fields

Use caseFields to read first
Lead listtitle, clientCountry, clientTotalSpent, clientRating, clientPaymentVerified, url
Job alerttitle, budget, hourlyRange, skills, postedAt, changeType, url
Market researchcategory, subcategory, skills, experienceLevel, hourlyBudgetMin, hourlyBudgetMax, budget
Competition checktotalApplicants, interviewing, invitesSent, hired, clientLastActivity

👉 Try it now: open the input form

Pricing

Pricing unit

You pay per event. Apify platform usage is included in these prices.

EventWhenFreeBronzeSilverGold and up
Run start (apify-actor-start)Once per GB of run memory, at least once. The default 256 MB is one event.$0.001$0.001$0.001$0.001
Source request (source-page)One search page or one batch of up to 6 job details$0.0001$0.0001$0.0001$0.0001
Saved job (job)One job row saved to the Dataset$0.00090$0.00081$0.00072$0.00063
  • No results: you pay for the start and source requests.
  • A saved row with fewer fields (detailsStatus is members_only or unavailable) is charged as a saved job.
  • Filtered jobs and unchanged monitor jobs are not charged as saved jobs. The source requests used to read them are paid.
  • A partial run pays only for the work it did. A failed request never costs more than a successful one.
  • Use Maximum cost per run (maxTotalChargeUsd in the API) to set a limit. The smallest limit is $0.0022. When the limit is reached, the run keeps its saved rows and ends as SUCCEEDED with stopReason: "CHARGE_LIMIT".

Cost examples

Measured source requests. Your count depends on the search and on how many jobs have details.

ScenarioInput scopeExpected item/event countBase chargeOptional chargeStop/cap behavior
First test1 phrase, maxItems: 101 start, 6 source requests, 10 jobs$0.0106 Free, $0.0079 GoldNoneStops at 10 jobs
Small list1 phrase, maxItems: 1001 start, about 29 source requests, 100 jobs$0.0939 Free, $0.0669 GoldNoneStops at 100 jobs
Market sample1 phrase, maxItems: 10001 start, about 284 source requests, 1,000 jobs$0.9294 Free, $0.6594 GoldNoneStops at 1,000 jobs or your cost limit
Large exportBroad phrase, maxItems: 60001 start, about 1,215 source requests, 6,000 jobs$5.5225 Free, $3.9025 GoldNoneNeeds a longer timeout
Monitor with no new jobsonlyNew: true, 1 job checked1 start, 1 source request, 0 jobs$0.0011NoneEnds with no rows

👉 Try it now: open the input form

Limits

  • Only public data. There is no login, so private and invite-only jobs are not available.
  • Some jobs show details only to members. In our 6,000-job test, 1,055 jobs had no public details. These rows keep the search fields and have detailsStatus: "members_only".
  • clientAvgHourlyRate and clientHireRatePercent are hidden from visitors, so they stay null.
  • The largest tested run saved 6,000 jobs in about 20 minutes. The default timeout is 300 seconds. Raise it for runs over about 1,000 jobs.
  • A big search is split by category, type and experience level to get past the source limit of about 5,000 results per search. So a big result has no single global order.
  • If the source still holds back part of a search, the summary says SOURCE_CAP and sourceCapPartitions counts the affected parts.
  • skipReposts skips only exact repeats of the cleaned title and description. Reworded reposts are saved.
  • A monitor history keeps up to 100,000 jobs. Closed and then the oldest jobs are dropped first.
  • title and description have email-like and phone-like text replaced with [contact removed].
  • You are responsible for how you use the data and for following Upwork's Terms and the laws that apply to you.

👉 Try it now: open the input form

API, export and integrations

  • API: start a run with POST https://api.apify.com/v2/acts/cliqtomedia~upwork-jobs-scraper/runs, using the same JSON as the form. Read rows from https://api.apify.com/v2/datasets/<defaultDatasetId>/items?view=jobs. Leave out view=jobs to get all fields. Read the report from RUN_SUMMARY in the default key-value store.
  • Export: download the Dataset as JSON, CSV, Excel or XML from the Output tab or the API.
  • Scheduling: create an Apify Schedule with onlyNew: true. The next system reads jobId, changeType and url.
  • Webhooks / no-code: use an Apify webhook on run success, then read the Dataset with Make, Zapier or n8n. Use jobId as the unique key.
  • Slack / Notion: set deliveryConnector, see Send results to Slack or Notion.

Use with AI agents (MCP)

  • MCP server URL with only this Actor: https://mcp.apify.com?tools=cliqtomedia/upwork-jobs-scraper.
  • Smallest input: {"queries": ["web scraping"], "maxItems": 10}.
  • Key fields: title (job name), hourlyRange or budget (pay), clientTotalSpent (public lifetime client spend), totalApplicants (exact public count), url (job link).
  • Typical run: 10 jobs in about 6 seconds, up to about a minute if Upwork refuses the first requests, about $0.0106 on the Free plan.
  • Limits: public data only, keep maxItems small in agent chats, and read stopReason in RUN_SUMMARY when the Dataset has fewer rows than asked.

👉 Try it now: open the input form

Troubleshooting

The run has no rows

Symptom: the run is SUCCEEDED and the Dataset is empty. Likely reason: no job matched, or a monitor found nothing new. Check: scanned, filtered and unchanged in RUN_SUMMARY. Action: use fewer filters or a wider phrase. For a monitor, wait for new jobs.

The search returned jobs that do not match my phrase

Symptom: a very unusual phrase returns a few jobs that look unrelated. Likely reason: Upwork's own search also returns loosely related posts. Check: title and description in the rows. Action: add includeKeywords or titleContains to keep only jobs with your exact words.

The run stops with DETAIL_FILTER_NEEDS_DETAILS

Symptom: the run is FAILED and has no rows. Likely reason: a client or applicant filter is on, but includeDetails is false. Check: stopReason in RUN_SUMMARY. Action: set includeDetails to true, or remove the filter.

The run saved fewer jobs than maxItems

Symptom: fewer rows than maxItems. Likely reason: the search ended, filters removed jobs, or the run stopped early. Check: saved, filtered, partial and stopReason in RUN_SUMMARY. Action: for CHARGE_LIMIT, raise the cost limit. For DEADLINE, raise the timeout. For SOURCE_CAP, narrow the search by category or date.

The run failed with RATE_LIMIT_WINDOW or SOURCE_BLOCKED

Symptom: the run is FAILED. Rows saved before the stop stay in the Dataset. Likely reason: Upwork asked for a long wait or refused the request with a browser check. The Actor waits, opens a new connection and tries up to twice more. It does not solve these checks. How often: in our short test runs Upwork refused the first requests in 25 of 68 runs. The Actor recovered in 23 of those 25, and these runs took about 15–50 seconds instead of about 6. The other 2 runs (about 3% of all) ended with SOURCE_BLOCKED and cost about $0.0013. Starting the run again usually works. In our latest runs of 1,000 and 6,000 jobs Upwork refused nothing. Check: stopReason, rateLimits, retries and sourceRefusals in RUN_SUMMARY. Action: run again later, and lower concurrency to 1 or 2. This does not mean that there are no jobs.

Fields are empty for some jobs

Symptom: client fields are null and detailsStatus is members_only, not_requested or unavailable. Likely reason: Upwork shows these details only to members, includeDetails is off, or Upwork gave no usable answer for this job twice. Check: detailsStatus in the row. Action: keep includeDetails on. For a members_only job, open url while logged in on Upwork. A monitor remembers a job with empty fields: it sends the job again with full details only when emitChanged is on. To get an unavailable job right away, run it by its link or ID.

The history name is not available

Symptom: warnings in RUN_SUMMARY has STATE_NAME_UNAVAILABLE. Likely reason: this name is used by a store that this run cannot open. Check: stateStoreId in RUN_SUMMARY. Action: nothing is needed, as the Actor uses a similar name. To control the name, set a new stateName.

👉 Try it now: open the input form

FAQ

Do I need an Upwork account or cookies?

No. The Actor reads only what Upwork shows to visitors. Do not give it your login.

Yes. Run your search on Upwork, copy the link and paste it in startUrls. Filters in the link are used.

How do I get only new jobs?

Set onlyNew: true and run the Actor on an Apify Schedule. The first run saves all matches up to maxItems as a starting point. Later runs save only jobs not saved before.

Why do I pay for source requests when no job is saved?

The Actor must read the source to find out if a job matches. So each source request is charged, even if your filters remove all jobs. Make filters part of the search when you can, for example jobType or experienceLevel.

Why is clientRating null?

The client has no public reviews yet. null means that the value is unknown or hidden, and it is not the same as 0.

How fresh is the data?

Each row is read during the run, and fetchedAt shows the time. Rows are not updated later. Run again or use a schedule for fresh data.

Can this Actor apply for jobs or contact clients?

No. It only reads public job posts.

Is this an official Upwork product?

No. This Actor is not affiliated with or endorsed by Upwork.

👉 Try it now: open the input form

👉 Try it now: open the input form

Support

Open the Issues tab of this Actor. Include the run ID, your input without private data, and the stopReason from RUN_SUMMARY.

This Actor is not affiliated with or endorsed by Upwork.

👉 Try it now: open the input form