Upwork Jobs Scraper - Budget, Skills, Proposals, Client Country
Pricing
$0.99 / 1,000 jobs
Upwork Jobs Scraper - Budget, Skills, Proposals, Client Country
Search Upwork's public job board by keyword: full description, fixed budget or hourly range, experience level, skills, posted time, proposals, client country and payment verification. No login or cookies. Deduplicated across queries; incomplete rows are marked, never shown as empty.
Pricing
$0.99 / 1,000 jobs
Rating
0.0
(0)
Developer
Lantern Lab
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
Upwork Jobs Scraper
Search Upwork's public job board by keyword and get each job back with its full description, fixed budget or hourly range, experience level, skills, posted time, proposals count, and the client's country, payment verification, total spend and rating — public statistics only, never a client or company name. No Upwork login, no cookies of yours.
Price: $0.99 per 1,000 jobs. Partial job rows, search query status rows and the run summary are free, and so is a query that finds nothing.
Quick example
Input:
{"searchQueries": ["react native", "shopify"],"maxJobs": 50,"sortBy": "recency"}
One job row (recordType: "job", shortened):
{"recordType": "job","jobId": "2107267731025843411","title": "Next.js Website Review & Bug Audit","description": "Summary\n\nWe're looking for an experienced Next.js developer to review an existing website ...","jobType": "fixed","budget": 100,"hourlyRateMin": null,"hourlyRateMax": null,"currency": "USD","experienceLevel": "Expert","skills": ["React", "JavaScript", "Web Development"],"category": "Web Development","projectLength": "Less than 1 month","proposalsText": "20 to 50","proposalsCount": 30,"clientCountry": "United States","clientPaymentVerified": true,"clientTotalSpent": 1017.5,"clientRating": 5,"clientReviewCount": 6,"url": "https://www.upwork.com/jobs/~022107267731025843411","searchQuery": "react native","publishedAt": "2026-10-06T00:32:35.522Z","partial": false,"collectedAt": "2026-10-23T21:50:20.034Z"}
Limits
- How many jobs a query reaches is up to Upwork's public search. It lists open jobs only, and the count it shows a visitor who is not signed in varies: on 2026-10-24 "python" listed 113 jobs in one run and 1,828 in another. This Actor does not sign in, so a query ends when Upwork's list ends, even below
maxJobs. Thequery_statusrow gives the count Upwork listed (jobsAvailable). - Private listings: search fields only. Some listed jobs (about 1 in 10 in our tests) are private listings whose page opens only for signed-in users. They are still written, free, with what the search results show (title, description, type, budget or rate where shown, experience level, skills, posted time) and
partial: true,partialReason: "private_listing". Their client country, payment verification and proposals arenull. - About 2 seconds per job: every job's own page is read for the client and proposals fields, one at a time.
- Public data only. Upwork does not show a visitor the client's name or company, and this Actor never collects them; the client's city is also left out.
- Runs a real browser on Apify Proxy (datacenter), because Upwork refuses plain HTTP clients and requests without a proxy. When Upwork refuses a request, the Actor waits, then retries from a fresh IP.
- No scheduling or monitoring built in; use Apify schedules.
What it does
- Searches each keyword the way Upwork's own job search does, newest first or by best match, 50 jobs per results page.
- Reads every new job's page for the fields the results page leaves out: client country, payment verification, total spend, rating, proposals, interviewing and invites, category and workload.
- Deduplicates across the whole run: a job found by two queries is written and charged once, for the first query that found it.
- Keeps Upwork's job ID (
jobId), its publish time (publishedAt) and our collection time (collectedAt) as separate fields. - Says what happened to every query. A query with no job, a query that was cut short, and a job whose page could not be read are different records and are never confused: anything incomplete is marked
partial: truewith the reason. - Caps itself:
maxUpstreamRequestsis a hard ceiling on page requests to Upwork for the whole run, retries included, derived from your input when you leave it empty, so a run is never unbounded.
Input
| Field | Default | Meaning |
|---|---|---|
searchQueries | — | Keywords, one per line, in any language. At least one, at most 100. Repeats (ignoring case) run once. |
maxJobs | 50 | Upper bound on jobs per query, from the top of the results. Max 5,000. |
sortBy | recency | recency (newest first) or relevance (Upwork's best match). |
maxUpstreamRequests | derived | Hard ceiling on page requests to Upwork for the whole run, retries included. |
proxyConfiguration | Apify Proxy (datacenter) | The proxy the browser goes through. |
Output
One row per job, recordType: "job". Charged when partial is false:
| Field | Meaning |
|---|---|
jobId, ciphertext | Upwork's job ID (the dedupe key) and the ID in the job's URL. |
title, description | As posted; the description is the full text. |
jobType | fixed or hourly. |
budget | Fixed-price budget; null on hourly jobs and when the client hid it. |
hourlyRateMin, hourlyRateMax | Hourly range; null on fixed-price jobs and when the client set none. |
currency | USD. |
experienceLevel | Entry level, Intermediate or Expert. |
skills | The skills the job lists. |
category, categoryGroup, projectLength, workload | As the job's page shows them. |
proposalsText | The proposals range the job's page shows (Less than 5, 5 to 10, 10 to 15, 15 to 20, 20 to 50, 50+). |
proposalsCount, interviewing, invitesSent | The counts behind the job's "Activity on this job" panel. |
clientCountry, clientPaymentVerified | The client's country and whether their payment method is verified. |
clientTotalSpent, clientRating, clientReviewCount, clientMemberSince | The client's public history; clientRating is null until the client has a review. |
url | The job's Upwork page. |
searchQuery | The query that found the job first in this run. |
publishedAt | When Upwork published the job. |
partial, partialReason | true with a reason when only the search-result fields could be read: private_listing, blocked, upstream_error, upstream_request_cap. Partial rows are free. |
collectedAt | Our own collection time. |
One row per search query, recordType: "query_status", free: outcome is found, empty (no public job matches) or partial, with jobsAvailable (the count Upwork listed), jobsReturned, partialJobs, privateJobs, duplicatesSkipped, jobsGone (jobs closed between the search and their page), partialReason (blocked, upstream_error, upstream_request_cap, charge_limit_reached, not_collected) and statusText.
The last row is recordType: "RUN_SUMMARY" — what the queries came to, jobs collected and charged, partial jobs by reason, repeated jobs written once, requests sent, refusals and IP changes, and why collection stopped. The same object is also in the run's key-value store under RUN_SUMMARY.