Upwork Job Scraper | Client Intel & Proposals
Pricing
from $2.00 / 1,000 results
Upwork Job Scraper | Client Intel & Proposals
Scrape Upwork jobs by keyword, budget, experience or client quality. Every job arrives fully enriched — client country, lifetime spend, rating, hires, average rate paid and exact proposal counts — with incremental monitoring and alerts. No add-ons, no Upwork account.
Pricing
from $2.00 / 1,000 results
Rating
0.0
(0)
Developer
Corvuslab
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Upwork Job Scraper 💼
Extract structured job data from Upwork — with the full client-quality panel (country, lifetime spend, payment verification, rating, review count), exact proposal counts, full descriptions, incremental change tracking and built-in notifications.
No Upwork account, cookie or session token required. The client panel and the exact number of proposals come back on every public job, at no extra per-job fee.
Great for freelancer lead generation, market and rate research, competitive analysis, and feeding live job data into dashboards or AI agents.
✨ Key features
- 🏅 Client intelligence on every job — country, city, timezone, lifetime spend, payment-verified flag, rating, review count, total hires, hours billed, industry, company size and member-since date.
- 💵 What the client actually pays —
clientAvgHourlyRatePaidis their average hourly rate across past contracts, so you can spot a low-paying client before spending Connects. Present for clients with hourly history. - 🧩 Project shape — every job is tagged Ongoing, One-time or Complex project.
- 📉 Exact proposal counts — the real number of applicants, not a "5 to 10" bucket, plus hires, interviews and invitations sent.
- 🎯 Low-competition filtering — combine proposal brackets with client-quality gates to surface jobs worth writing a proposal for.
- 🔍 Batch searches — many keyword queries in one run, deduplicated by job ID so overlapping searches never bill you twice.
- 🔗 Paste-a-URL mode — drop in any Upwork search URL and every filter in it is read and replayed automatically.
- 🧮 Native Upwork operators — all-words, any-words, exact-phrase, none-of-these and title-only search run server-side, so excluded jobs never reach your bill.
- 🔁 Incremental mode — recurring runs emit only what changed. Unchanged jobs are recognised before their detail page is fetched, so you skip the work and the cost.
- ♻️ Repost detection — flag or drop jobs re-listed under a new ID.
- 🔔 Notifications — push matching jobs to Telegram, Slack, Discord or any webhook.
- 🪶 AI-friendly output —
compactmode, description truncation andexcludeEmptyFieldsfor lean records in LLM and MCP pipelines. - ⚡ Fast and cheap — talks to Upwork's JSON API directly, so there is no browser to boot and no HTML to parse. Each job transfers a few KB instead of a rendered page, and detail pages are fetched in parallel across multiple proxy IPs.
📥 Input
| Field | Type | Description |
|---|---|---|
query | string | Keywords, e.g. "python". Leave blank to browse everything. |
queries | array | Several keyword searches in one run. |
searchUrls | array | Upwork search URLs pasted from your browser; their filters are parsed. |
maxResults | integer | Max jobs per search. Upwork caps any search at 5,000; 0 takes the max. Default 50. |
sort | enum | recency (default), relevance, client_total_charge, client_rating. |
jobType | enum | hourly or fixed. |
experienceLevel | array | entry, intermediate, expert. |
category | enum | Top-level Upwork category, or a raw category UID. |
budget | enum | Fixed-price bracket: 0-99, 100-499, 500-999, 1000-4999, 5000+. |
hourlyRate | string | Hourly range, e.g. "30-80" or "50-" for $50/hr and up. |
workload | enum | as_needed, part_time, full_time. |
duration | enum | week, month, semester, ongoing. |
contractToHire | boolean | Only jobs that may convert to a full-time role. |
location | string | Client country or region in full, e.g. "United States", "Europe". |
skills | string | Upwork skill tags, comma-separated. |
verifiedPaymentOnly | boolean | Only clients with a verified payment method. |
minClientTotalSpent | integer | Minimum client lifetime spend (USD). |
minClientRating | integer | Minimum client rating, 0–5. |
minClientReviewCount | integer | Minimum number of client reviews. |
clientHires | enum | 0, 1-9, 10+ previous hires. |
proposals | array | Competition brackets: 0-4, 5-9, 10-14, 15-19, 20-49, 50+. |
excludeLocations | array | Drop jobs from these client countries. |
allWords / anyWords / exactPhrase / noneWords / titleSearch | string | Upwork's native AND / OR / exact / NOT / title-only search. |
includeKeywords / excludeKeywords | array | Keep or drop jobs matching these terms. |
keywordMatchTitle / keywordMatchDescription / keywordMatchSkills | boolean | Limit keyword matching to certain fields. None on = all fields. |
fromDate / toDate | string | Keep jobs posted in this window (YYYY-MM-DD; toDate inclusive). |
maxAgeMinutes | integer | Keep only jobs posted within N minutes before the run. |
customFilters | array | Rule-based filters on any output field, e.g. [{"field":"clientTotalSpent","operator":"gte","value":10000}]. Operators: includes, notIncludes, equals, notEquals, gt, gte, lt, lte, exists, notExists. |
enrichDetails | boolean | Fetch each job's detail page for the client panel and proposal count. Default true. |
detailConcurrency | integer | Parallel detail requests, 1–30. Each uses its own proxy IP. Default 20. |
descriptionFormat | enum | all, text, html, markdown. |
descriptionMaxLength | integer | Truncate descriptions (0 = full text). |
compact | boolean | Core fields only. |
excludeEmptyFields | boolean | Drop null/empty fields. |
incrementalMode | boolean | Emit only NEW / UPDATED jobs on recurring runs. |
stateKey | string | Stable name for the tracking state (auto-derived if blank). |
emitUnchanged / emitExpired | boolean | Also emit UNCHANGED / EXPIRED jobs. |
skipReposts | boolean | Detect and drop reposts. |
telegramToken + telegramChatId | string | Notify Telegram (BotFather token + chat ID). |
slackWebhookUrl / discordWebhookUrl | string | Notify a Slack / Discord incoming webhook. |
webhookUrl + webhookHeaders | string / object | POST structured JSON anywhere, with optional headers. |
notificationLimit | integer | Max jobs listed per message (default 5). |
notifyOnlyChanges | boolean | Only alert on changed jobs (needs incremental mode). |
includeRunSummary | boolean | Prepend a one-line summary to notifications. Default true. |
sessionToken | string | Optional. Unlocks the ~10% of jobs whose details are account-gated. |
proxyConfiguration | object | Apify proxy settings. Datacenter is plenty. |
Example input
{"query": "react developer","maxResults": 100,"sort": "recency","jobType": "hourly","experienceLevel": ["intermediate", "expert"],"hourlyRate": "40-","verifiedPaymentOnly": true,"minClientTotalSpent": 5000,"proposals": ["0-4", "5-9"]}
📤 Output
Each job is pushed to the run's default dataset. Example record (abridged):
{"jobId": "2082001692817092210","title": "React/Supabase Developer Needed for SAAS Web Application","url": "https://www.upwork.com/jobs/~022082001692817092210","jobType": "HOURLY","experienceLevel": "ExpertLevel","hourlyBudgetMin": 40,"hourlyBudgetMax": 100,"budgetAmount": null,"budgetCurrency": "USD","engagementType": "FULL_TIME","engagementDuration": "3 to 6 months","engagementDurationWeeks": 18,"projectType": "Ongoing project","projectTypeCode": "EMPLOYMENT_POSITION","skills": ["React", "JavaScript", "Supabase", "API"],"category": "Web Development","categoryGroup": "Web, Mobile & Software Dev","publishTime": "2026-07-28T07:16:01.685Z","clientCountry": "Poland","clientCity": "Radwanice","clientTimezone": "Europe/Berlin (UTC+02:00)","clientTotalSpent": 1265.14,"clientRating": 5,"clientReviewCount": 5,"clientTotalHires": 8,"clientActiveHires": 1,"clientTotalHours": 214.5,"clientJobsWithHires": 7,"clientAvgHourlyRatePaid": 24.98,"clientPaymentVerified": true,"clientIndustry": "Automotive","clientCompanySize": 1,"clientMemberSince": "2023-05-19T00:00:00.000Z","totalApplicants": 22,"totalHired": 0,"totalInterviewing": 0,"personsToHire": 1,"lastClientActivity": "2026-07-28T08:14:48.977Z","minJobSuccessScore": 0,"preferredEnglishLevel": "ANY","preferredFreelancerType": "ANY","description": "…","descriptionMarkdown": "…","detailsFetched": true,"searchQuery": "react developer","scrapedAt": "2026-07-28T08:27:26.541Z"}
The Output tab also ships three ready-made views: Overview (title, budget, proposals, client quality), Client intelligence (the full client panel) and Changes (the incremental tracking fields).
In compact mode each record is trimmed to the 17 fields needed to identify, price
and triage a job: jobId, title, url, jobType, experienceLevel, budgetAmount,
hourlyBudgetMin/Max, skills, publishTime, totalApplicants, clientCountry,
clientTotalSpent, clientPaymentVerified, clientRating, searchQuery and
changeType.
Incremental fields
With incrementalMode on, every record also carries:
changeType—NEW,UPDATED,UNCHANGEDorEXPIREDfirstSeenAt/lastSeenAt— when the job was first and most recently seencontentHash— hash of the tracked fieldsisRepost/repostOfId/repostDetectedAt— set when repost detection matches an earlier listing
🔁 Incremental mode & monitoring savings
Running on a schedule? Turn on incrementalMode. The first run builds a baseline;
every run after that emits — and charges for — only the jobs that actually changed.
Unchanged jobs are recognised from the search response alone, before their detail page is fetched, so you skip both the work and the cost.
Two consecutive runs of the same search, where nothing has changed in between:
| First run (baseline) | Second run (nothing changed) | |
|---|---|---|
| Jobs found | all of them | all of them |
| Detail pages fetched | all | none |
| Records emitted & charged | all | none |
The second run still checks every job — it just recognises each one as unchanged and charges for none of them.
Because this actor is pay-per-result, your cost after the baseline scales with how much the market actually changes ("churn"), not with how many listings you monitor:
| Daily churn | Records emitted per run (after baseline) | Approx. saving vs a full re-scrape |
|---|---|---|
| 5% (stable niche) | ~5% | ~90% |
| 15% (moderate) | ~15% | ~80% |
| 30% (fast-moving) | ~30% | ~65% |
Set emitExpired to also receive listings that have dropped off, and emitUnchanged
if you want the full set every run. Keep maxResults consistent between scheduled
runs — it defines the set of jobs being tracked.
🔔 Notifications
Get alerted the moment matching jobs appear. Fill in only the channels you want; each fires independently when a run finishes.
- ✈️ Telegram —
telegramToken(from @BotFather) +telegramChatId. - 💬 Slack —
slackWebhookUrl(Incoming Webhook). - 🎮 Discord —
discordWebhookUrl(channel webhook). - 🪝 Generic webhook —
webhookUrlreceives structured JSON (run metadata plus ajobsarray) with optionalwebhookHeaders. Ideal for n8n / Make / Zapier.
Pair maxAgeMinutes with a schedule for near-real-time alerts, or incrementalMode +
notifyOnlyChanges to hear only about genuinely new work.
💡 Use cases
- Find work before it gets crowded — sort by newest, filter to 0–4 proposals and payment-verified clients with real spend history.
- Qualify clients before you spend Connects — lifetime spend, rating, review count, hire rate and last-activity time on every job.
- Rate and market research — track hourly ranges and budgets by skill or category over time.
- Skill-demand analysis — measure which technologies are actually being hired for.
- Agency lead generation — build outreach lists from client country, industry and spend.
- Feed AI agents — compact JSON straight into an LLM, MCP tool or vector store.
💰 Pricing
Pay-per-event — two events, no add-ons.
| Event | Price |
|---|---|
| Actor start | $0.001 per run |
| Job record | $0.002 per job |
| Run | Cost |
|---|---|
| 10 jobs | $0.021 |
| 100 jobs | $0.201 |
| 1,000 jobs | $2.001 |
$0.002 buys the whole record, not a starting point. Every job arrives with the full client panel — country, city, timezone, lifetime spend, rating, review count, total hires, hours billed, industry, company size, and the average hourly rate that client actually pays — plus the exact proposal count and the freelancer requirements.
There is no enrichment add-on, no per-field surcharge and no session cookie to set up. What you see in the output example above is what a single $0.002 record contains, on a run you can start in one click.
You only pay for jobs you actually receive. Server-side filters (allWords,
noneWords, exactPhrase, titleSearch) discard non-matching jobs before they are
billed, and with incrementalMode on, a run that finds nothing new costs only the
$0.001 start fee.
❓ FAQ
Do I need an Upwork account or login? No. The client panel, proposal counts and
descriptions all come from Upwork's public data. The optional sessionToken only adds
the minority of jobs whose detail pages are account-gated.
Why is detailsFetched false on some jobs? Around 10% of listings hide their
detail page from logged-out visitors. Those records still carry every listing-level
field (title, budget, skills, dates); only the client panel is missing. Supply a
sessionToken if you need 100% coverage.
Why do some jobs have no clientTotalSpent? Clients can enable financial privacy,
which hides lifetime spend from everyone. Similarly, clientIndustry and
clientCompanySize are only present when the client has filled in a company profile.
What client data is not included? The aggregate hiring history is complete — total hires, active hires, hours billed, jobs with hires, rating, review count and member-since. The one thing Upwork does not expose publicly is the client's itemised contract history (each past job with its individual feedback text); that sits behind an authenticated scope, so no logged-out scraper can reach it.
Can I get more than 5,000 results from one search? No — Upwork itself refuses paging past 5,000. Split the search (by category, budget bracket or date window) to cover more ground.
Does it handle large runs reliably? Yes. Upwork limits requests per IP, so the
actor spreads a run across one proxy IP per concurrent worker and lets faster IPs take
more of the load. Transient failures are retried with backoff rather than dropped, so
a large run finishes with its client panel intact instead of silently half-empty.
Raise detailConcurrency for more parallelism on very large runs.
Why are verifiedPaymentOnly and proposals applied after fetching? Upwork only
honours those filters for signed-in users, so this actor evaluates them against the
client panel instead. It switches detail enrichment on automatically when you use them.
Is it legal to scrape Upwork? This actor extracts publicly available data. Web scraping of public information is generally considered legal, but you should review the target site's terms of service and ensure your use case complies with applicable laws and regulations, including GDPR where relevant.
This is an unofficial actor and is not affiliated with, endorsed by, or connected to Upwork. "Upwork" is a trademark of its respective owner and is used here only to describe the site this actor works with.