Upwork Job Alerts: New Jobs Monitor & Filters avatar

Upwork Job Alerts: New Jobs Monitor & Filters

Pricing

from $0.42 / 1,000 job posts

Go to Apify Store
Upwork Job Alerts: New Jobs Monitor & Filters

Upwork Job Alerts: New Jobs Monitor & Filters

Get alerts for new Upwork jobs: schedule it every few minutes or hours and each run returns only the job posts published since the last run, filtered by keyword, budget, hourly rate, experience level, skills and category. Send them to email, Slack or Google Sheets with Apify integrations.

Pricing

from $0.42 / 1,000 job posts

Rating

0.0

(0)

Developer

Nice Dev

Nice Dev

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

9 hours ago

Last modified

Share

🔔 What is Upwork Job Alerts?

Upwork Job Alerts finds the new Upwork job posts that match your search, and only those. Give it a keyword — or paste a search URL straight from Upwork — set the filters you care about (budget, hourly rate, experience level, skills, category…) and put it on a schedule: every run returns the job posts that no earlier run delivered, each with its full description, skills, budget or hourly range, experience level, project duration and exact publication time. Send them to your email, Slack or Google Sheets with Apify's integrations, or read them from the API, Make, Zapier or n8n.

It reads the same job posts an anonymous visitor sees on Upwork's search pages. No Upwork account needed. It is set for monitoring: Only new job posts is ON by default, newest first.

Try it: type python in Search keyword, hit Start, and look at the Output tab. Run it again a few minutes later: only the job posts published in between come back.

📋 What data can you extract from Upwork?

One row per job post, 48 fields — 23 always, 25 more with the Client details option:

CategoryWhat you get
🏷️ Job posttitle, link to the job page, Upwork's own id — Fix Broken Python Scraper
📝 Description and skillsthe full text as the client wrote it, line breaks kept; every skill asked for — Python, Scrapy
💰 Budgetfixed price, or hourly range, or weekly retainer, in USD — $20 / $40-60 an hour
🎓 Experience levelentry level, intermediate or expert
⏳ Duration and hourshow long the project lasts, how many hours a week — 1 to 3 months
🕒 Dateswhen it was posted, to the millisecond — what the date filters read
🔎 Where it came fromwhich of your keywords found it, the search page, when it was read
👤 Client details (option)money spent, hires, hours, reviews and rating, payment verified, country and city, member since, company; the job's proposals, interviews and invites — $26,694 spent · 53 hires · 5.0 · Saudi Arabia

Every field, with an example, is listed in the Output section below.

✅ Why use Upwork Job Alerts?

  • 🔔 Only the new job posts. A run returns what no earlier run of the same search delivered, so a schedule gives you a feed, not the same list again and again. A run that finds nothing new saves 0 rows and is charged only the run-start fee (plus the pages your filters kept nothing of, if you set any).
  • ⏱️ As often as you want. Schedule it every few minutes or every few hours with Apify Schedules.
  • 🎯 Every filter Upwork itself offers, in plain words: job type, experience level, project duration, hours per week, contract-to-hire, budget, hourly rate, category, skills, client hires, client country, client time zone.
  • 🧹 Filters Upwork does not have, run on our side: publication date, words a job post must contain, words it must not.
  • 🗂️ Several keywords in one alert — one search each, results deduplicated.
  • 👤 The client behind each job post, as an option: how much they have spent, how many freelancers they hired, their rating, whether their payment is verified, and how many proposals the job already has.
  • 🚨 It tells you when it is wrong. If Upwork changes its pages, the run stops and says which field vanished, instead of quietly charging you for empty columns.

🔔 How to get Upwork job alerts

  1. Click Try for free, type your keyword in Search keyword (python, web scraping, logo design) and set the filters you want (budget, experience level, skills, category…).
  2. Run it once. The first run of a search has nothing to compare with: it returns the newest job posts, up to Max job posts, and remembers them.
  3. Save it as a Task (one Task per alert) and open Schedules in the Apify Console: create a schedule (every few minutes, every hour, every morning…) and add your Task to it.
  4. Open the Integrations tab of the Task and pick where the new job posts go: email (Gmail), Slack, Airtable, or Zapier to reach Google Sheets and many other apps. A webhook can also call your own system when a run succeeds, and the dataset is always available from the API.

From then on every run returns only the job posts published since the previous one. The Actor itself sends nothing: the alert is the run, and Apify's integrations carry its result where you want it.

One alert per Task. Each search keeps its own memory of what it delivered (see Monitoring below), so a Task for python and a Task for react never hide each other's job posts. Changing the keywords of a Task starts a new alert: its first run returns the newest job posts again.

💰 How much does it cost to monitor Upwork jobs?

This Actor uses pay per event pricing: $0.45 per 1,000 job posts saved ($0.44 on the Bronze plan, $0.43 on Silver, $0.42 on Gold and above), plus $0.01 per run start (1 dollar per 100 runs). Platform usage (compute, proxy) is included in the price. Examples: 10,000 job posts ≈ $4.50; an alert scheduled 100 times a month that finds nothing new costs $1 in run starts; an hourly alert (720 runs a month) that finds 20 new job posts per run ≈ $7.20 + $6.48 = $13.68 a month, without a plan discount.

A filtered-out job post is never charged, and neither is one a previous run already delivered. A results page on which your filters kept nothing is charged $0.00023 ($0.23 per 1,000 such pages; $0.20 on Gold and above): a filter that matches almost nothing stops being free to run, never expensive. Pages of job posts already delivered, or already dropped by the same filters in an earlier run, are not charged: a monitoring run without filters that finds nothing new costs only the run-start fee. To cap any run, set Maximum cost per run in the run options: the run stops before it would go over.

With Include client details ticked, each job post delivered WITH its client's details is also charged the Client details event: $0.89 per 1,000 job posts with their client ($0.88 on the Bronze plan, $0.87 on Silver, $0.86 on Gold and above), on top of the job post. A job post whose page gives no client (a private or closed job) goes out without them and is not charged for it. The option is slower — about 3 seconds per job post — and the run uses more memory, set automatically when the box is ticked. It is unticked by default.

Upwork itself never returns more than 5 050 job posts for one search, whatever you set.

⚙️ Input

You can set everything in the Console form, or pass this JSON to the API. An alert on one keyword:

{
"query": "web scraping",
"onlyNew": true,
"maxItems": 100
}

An alert for experienced freelancers, hourly work paid at least $40, without the words you do not want:

{
"query": "python",
"jobType": "hourly",
"experienceLevel": ["Intermediate", "Expert"],
"minHourlyRate": 40,
"excludeKeywords": ["wordpress", "unpaid"],
"stateKey": "python-hourly"
}

Watching three searches in one Task, only the new ones:

{
"searchQueries": ["web scraping", "data extraction", "crawler"],
"stateKey": "scraping-watch",
"maxItems": 500,
"maxItemsPerQuery": 200
}

A one-off search that returns every match, not only the new ones:

{
"query": "logo design",
"onlyNew": false,
"maxItems": 250
}
FieldNotes
startUrlsUpwork search URLs copied from the site. Max 100 URLs. The keyword, sort and page are read from the URL; the filters below still apply
query, searchQueriesKeyword, and more keywords (one search each), up to 100 per run
maxItems, maxItemsPerQueryMax job posts (100 by default, 0 = no limit), and the cap of each search. Keep it above the number of new job posts between two runs
sortByrecency (newest first, the default), relevance or client_total_charge
onlyNew, stateKey, resetStateMonitoring: on by default; the memory key (empty = one memory per search) and its reset
jobTypehourly or fixed; empty = both
experienceLevelEntry level, Intermediate, Expert
projectDurationLess than one month, 1 to 3 months, 3 to 6 months, More than 6 months — checked on every job post, fixed-price ones included
workloadLess than 30 hrs/week, 30+ hrs/week, As needed — hourly job posts only, unless a fixed budget is set too
contractToHireOnly job posts flagged as leading to a long-term contract
minFixedBudget, maxFixedBudgetFixed price in USD — fixed-price job posts only, unless an hourly filter is set too: then each kind matches its own filter
minHourlyRate, maxHourlyRateHourly rate in USD — hourly job posts only, unless a fixed budget is set too
categories, subcategoriesUpwork category names, e.g. Web, Mobile & Software Dev; a name Upwork does not know stops the run at once and lists the valid ones
skillsSkills the job post asks for, e.g. Python, Web Scraping; several = the job post must ask for all of them
clientHiresNo hires, 1 to 9 hires, 10+ hires
clientLocationsCountry names as Upwork writes them, e.g. United States
clientTimezonesThe client's time zone as Upwork names it, e.g. Europe/Paris, America/New_York; several = any of them
postedAfter, postedBeforeA date (2026-09-01, a UTC day, the whole day included) or a period before now (7 days, 1 month; via the API also 2 hours)
includeKeywords, excludeKeywordsWords a job post must, or must not, contain
maxDescriptionLengthCut each description; 0 = the whole text
includeClientDetailsClient details option: each job post with its client (25 more columns), charged per job post that has them
proxyConfiguration, minRequestIntervalMs, maxRequestRetries, debugLogAdvanced

📦 Output

One item per job post:

{
"id": "2101592032775799065",
"ciphertext": "~022101592032775799065",
"url": "https://www.upwork.com/jobs/_~022101592032775799065/",
"title": "Fix Broken Python Scraper",
"description": "Need a quick fix for a broken Python scraper used on an e-commerce site.",
"skills": ["Python", "Scrapy", "Data Scraping"],
"skillsDetailed": [{ "uid": "996364628025274386", "name": "Python", "freeText": false }],
"jobType": "FIXED",
"budgetType": "FIXED",
"fixedBudget": 20,
"hourlyBudgetMin": null,
"hourlyBudgetMax": null,
"weeklyRetainerBudget": null,
"experienceLevel": "Intermediate",
"contractorTier": "IntermediateLevel",
"durationLabel": "1 to 3 months",
"durationWeeks": 9,
"hourlyEngagementType": null,
"publishedAt": "2026-09-20T08:40:24.219Z",
"createdAt": "2026-09-20T08:39:21.353Z",
"clientDetailsRead": true,
"clientPaymentVerified": true,
"clientCountry": "Saudi Arabia",
"clientCity": "Saihat",
"clientTimezone": "Asia/Baghdad (UTC+03:00)",
"clientTotalSpent": 26694.47,
"clientHires": 53,
"clientActiveHires": 6,
"clientJobsWithHires": 51,
"clientTotalHours": 100.17,
"clientReviews": 40,
"clientRating": 5,
"clientMemberSince": "2022-09-02T00:00:00.000Z",
"clientIndustry": "Finance & Accounting",
"clientCompanySize": 1,
"clientJobsPosted": null,
"clientOpenJobs": null,
"clientAvgHourlyRate": null,
"clientIsEnterprise": false,
"applicantsCount": 9,
"interviewingCount": 6,
"invitesSent": 28,
"unansweredInvites": 22,
"hiredCount": 0,
"clientLastActivityAt": "2026-09-28T13:09:08.031Z",
"searchQuery": "python",
"searchUrl": "https://www.upwork.com/nx/search/jobs/?q=python&sort=recency&page=1",
"scrapedAt": "2026-09-20T09:49:31.185Z"
}

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

All 48 fields

FieldsWhat you get
title, descriptionJob post: the text as the client wrote it, line breaks kept
skills, skillsDetailedSkill names picked from Upwork's list, and the same with Upwork's own ids so you can re-filter. A skill the client typed in by hand is not in Upwork's search, so not here
jobType, budgetTypeFIXED, HOURLY or WEEKLY_RETAINER
fixedBudget, hourlyBudgetMin, hourlyBudgetMax, weeklyRetainerBudgetBudget: what the client is offering, in USD
experienceLevel, contractorTierEntry level, Intermediate or Expert, in words and in Upwork's code
durationLabel, durationWeeks, hourlyEngagementTypeDuration and hours: how long, and how many hours a week
publishedAt, createdAtISO 8601 UTC, to the millisecond — you can sort an alert feed by the second. publishedAt = when the post went live (what the date filters read); createdAt = what the job page counts "Posted … ago" from, hours earlier for about 1 post in 4
url, id, ciphertextThe public job page and Upwork's own references
searchQuery, searchUrl, scrapedAtWhich of your searches found it, and when
clientDetailsReadClient details option: true when the job's page gave its client; false = the fields below are null
clientTotalSpent, clientHires, clientActiveHires, clientJobsWithHires, clientTotalHours, clientAvgHourlyRateClient's history: money spent (USD), freelancers hired, contracts still active, job posts that ended in a hire, hours billed, average hourly rate paid
clientReviews, clientRating, clientPaymentVerifiedReviews received, average rating out of 5, payment method verified
clientCountry, clientCity, clientTimezone, clientMemberSinceWhere the client is, and since when on Upwork
clientIndustry, clientCompanySize, clientIsEnterprise, clientJobsPosted, clientOpenJobsThe client's company and job posts
applicantsCount, interviewingCount, invitesSent, unansweredInvites, hiredCount, clientLastActivityAtThis job's activity: proposals, interviews, invites, hires, and when the client last looked at it

💡 Tips

Monitoring: only the new job posts

Only new job posts is on by default. It remembers the ids it has delivered in a store that never expires (upwork-job-alerts-seen, up to 150,000 listings per key) and returns only the others. The memory belongs to the Monitoring key: left empty, each search (its keywords, or its Start URLs) has its own memory, so two Tasks never share one. Type a key of your own to keep the memory when you change the keywords or the filters of an alert, or to let several runs share it. Reset monitoring memory forgets everything under that key and returns every match again.

A job post is remembered once it is delivered. One your filters dropped is remembered too, for the same filters only: change a filter and it can come back. Set Max job posts above the number of new job posts that appear between two runs: the newest come first, and the run stops at the cap.

How to reduce costs

The price is per job post, so the levers are maxItems, maxItemsPerQuery, the filters (a filtered-out job post is free; a whole page of them costs $0.00023) and onlyNew, which is on by default: you never pay twice for the same job post. With Sort by: Newest first and postedAfter, a search stops after 5 hours of older posts.

How to get more results

For a one-off list, untick Only new job posts, set maxItems to 0 and use a broad keyword, or several keywords in More search keywords. Upwork stops every search at 5 050 job posts: to go further, split it (see below).

Several searches in one run

Put your keywords in More search keywords: each one becomes its own search. A job post found by two of them is saved once. Max job posts per search stops the first keyword from eating the whole budget. You can also paste several search URLs into startUrls: each one is a search of its own, with the same cap. For separate alerts that go to separate places, make one Task per keyword.

Filter by publication date

postedAfter and postedBefore take a date (2026-09-01 is a UTC day, and the whole day is included) or a period before now (7 days, 2 weeks, 1 month; via the API also 2 hours or a full ISO date-time). The filter reads publishedAt. Filtered-out job posts are never charged and do not count in maxItems; the run summary tells how many were filtered. With Sort by: Newest first, a search stops after 5 hours of older posts (Upwork ranks a few posts up to 3 hours before it publishes them), so a short window costs very little; a results page that holds only posts older than the window ends the search and is not charged. Upwork shows only the 5 050 newest posts of a search, about the last 31 hours on all of Upwork: postedBefore reaches older posts in a narrower search (a keyword, a category).

Getting more than 5 050 job posts

That is Upwork's own ceiling for one search, not ours. Split the run: the same keyword with jobType hourly then fixed, or one run per entry of categories, or by budget band.

🔌 Integrations and API

Schedule it with Apify Schedules, and carry each run's new job posts where you need them with Apify integrations: email (Gmail), Slack, Airtable, and Google Sheets or any other app through Zapier, Make or n8n. A webhook can start your own code when a run succeeds. Call the Actor via the Apify API or the JavaScript and Python clients, and fetch the dataset as JSON or CSV from any tool.

🤖 Use with AI agents (MCP)

AI agents (Claude, ChatGPT, Cursor…) can find and run this Actor through the Apify MCP server, billed to their Apify account like any run. It returns one item per Upwork job post. Actor id: nice_dev/upwork-job-alerts; MCP server with this Actor only: https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/upwork-job-alerts.

Smallest input, for a cheap first call (a one-off search, so onlyNew is off):

{
"query": "python",
"onlyNew": false,
"maxItems": 10
}

Key output fields: url, title, description, skills, jobType, fixedBudget, hourlyBudgetMin, publishedAt. With onlyNew left on, an agent that calls it again with the same search gets only the job posts published since its last call.

Cost: $0.45 per 1,000 job posts plus $0.01 per run start ($0.42 per 1,000 on the Gold plan); a results page on which the filters kept nothing costs $0.00023; with includeClientDetails, each job post delivered with its client costs $0.89 per 1,000 more ($0.86 on Gold), see the pricing section above. Cap each call with maxItems and, through the API, with the run option maxTotalChargeUsd.

❓ FAQ

How do I get Upwork job alerts?

Make a Task with your keyword and filters, put it on an Apify Schedule, and add an integration (email, Slack, Google Sheets…) to the Task. Every run returns only the job posts no earlier run delivered. See How to get Upwork job alerts above.

Does the first run return anything?

Yes. A search with an empty memory has nothing to compare with: its first run returns the newest job posts, up to Max job posts, and remembers them. The runs after it return only what was published since.

The Actor only reads what Upwork shows publicly to any anonymous visitor. It logs in to nothing. Results describe job posts written by clients and can contain personal data, which is protected by GDPR: do not store it without a legitimate reason. You are responsible for using the data in compliance with Upwork's Terms of Use and applicable law. This Actor is not affiliated with Upwork.

Does it need a login or a proxy?

No login. The proxy is included in the price: leave the default setting (the residential proxy is not available). A request the site turns away is retried at once on a new proxy session, up to 10 times on top of the retries (without a proxy, after a pause of 5 seconds, doubled at each retry up to 150 seconds).

Apify free plan: the included proxy is for paying Apify plans only. On the free plan, add your own proxy URLs in Proxy configuration; without them the run stops at once with a message saying so.

Does it return the client's spending, rating or number of proposals?

Yes, with the Client details option. Tick Include client details: each job post then comes with its client's money spent, hires, hours, reviews and rating, payment verification, country, city and company, and with the job's proposals, interviews and invites. Each job post delivered with them is charged the Client details event on top of the job post : $0.89 per 1,000 ($0.86 on the Gold plan). A private or closed job gives no client: it goes out without, uncharged for it.

Is the data safe to open in Excel or to show on a web page?

Titles and descriptions are the clients' own words, copied as they are. A text can begin with -, +, = or @ (-20% rate for a long contract, =SUM of my needs): Excel and Google Sheets may read such a cell of a CSV file as a formula or as a number. The Actor leaves the text as it is, so that the JSON and the API give the real value: when you open a CSV, import these columns as text. Every URL field holds an https://www.upwork.com/… address built from Upwork's own reference, never a link copied from a payload. On a web page, escape every field like any text written by a stranger.

Known limitations

  • 5 050 job posts per search, Upwork's own ceiling (a search URL pasted past page 101 returns nothing). Split the run to go further.
  • Only the keyword, the sort and the page are read from a pasted search URL: set the filters in the form, they apply to pasted URLs too.
  • Client details come with the option only, about 3 seconds per job post; a private or closed job has none.
  • There is no filter for the number of proposals or for payment verification, because Upwork's search offers none.
  • onlyNew remembers job ids, not their content: a job post the client edited is not returned again.
  • If more new job posts appear between two runs than Max job posts, the newest are returned and the oldest of them are not: raise Max job posts or run more often.
  • Two runs sharing the same stateKey at the same time may both return the same new job post.

A run that reaches its timeout stops itself about 45 seconds before it: no new page is asked, what it read is saved and, with onlyNew, remembered, and the run ends Succeeded with "Stopped before the run's timeout". Resurrect it to go on from there, or give the next run a longer timeout (Run options).

A run the platform stops without warning (out of memory)

  • Resurrect it: it goes on from where it stood at most a minute before the stop. What it had read since is read again, and the job posts already saved are skipped: none is delivered or charged twice, and maxItems still counts them.
  • With onlyNew, the memory is saved a few seconds after the job posts are written: resurrect the stopped run and the job posts it had saved meanwhile join the memory; leave it stopped for good, and the next run may return the last few seconds of them once more.

Something doesn't work?

The last line of the log counts the job posts saved, filtered out and already delivered, and the requests that failed after every retry. Those requests are listed, with the reason, in the FAILED_REQUESTS record of the run's key-value store. A run that saved nothing and had failed requests fails, and its last message gives the cause (a search URL that does not exist says so, instead of "run it again"). A run that saved job posts can still fail: when at least as many requests failed for good as were read (page 1 read, the next pages blocked), it keeps what it saved and fails with "Most requests failed", so that a blocked run never looks like a complete one.

If Upwork changes its pages, you are told instead of paying for blank rows. A results page that counts job posts but gives none the Actor can read is an error (listed in FAILED_REQUESTS), never a quiet "No listings found". If the first 20 listings read all lack a column every job post fills (title, description, publishedAt, createdAt, jobType, experienceLevel, durationLabel, durationWeeks, skills, ciphertext, and fixedBudget or hourlyEngagementType on the posts of that kind), the run saves nothing more, stops and fails, and its last message names the missing field: at most those first listings are charged. A job post that postedAfter / postedBefore drops because it has no date at all counts among those 20.

🛟 Support

Open an issue in the Issues tab with a link to your run: the run log and the FAILED_REQUESTS record of the key-value store show exactly which URLs failed and why.