Upwork Scraper - Jobs, Freelancers & Project Catalog
Pricing
from $2.00 / 1,000 results
Upwork Scraper - Jobs, Freelancers & Project Catalog
Extract public Upwork jobs, freelancer profiles, Project Catalog services and sitemap URLs. Export budgets, skills, rates and public client details; pay per delivered row plus a start fee.
Pricing
from $2.00 / 1,000 results
Rating
0.0
(0)
Developer
kane liu
Maintained by CommunityActor stats
1
Bookmarked
8
Total users
3
Monthly active users
6 hours ago
Last modified
Categories
Security
Limited permissionsShare
Upwork Scraper
Export public Upwork jobs, freelancer profiles, Project Catalog services and sitemap links as structured rows.
- Job titles, budgets, skills, proposal counts and public client details.
- Freelancer profile links, rates, locations and public ratings.
- Project Catalog prices, delivery times and seller information.
- Search by keyword or collect an unfiltered feed.
- Pay $2.00 per 1,000 delivered rows on the Free plan, plus a small start fee.
What does Upwork Scraper do?
Upwork Scraper collects public data from upwork.com for job research, talent discovery and service comparisons. Each dataset row represents one entity, identified by rowType. It does not apply for jobs, send messages or access private account data.
- Jobs (
jobs): keyword search or the current public feed. - Freelancers (
freelancers): profiles, category listings or keyword search. - Project Catalog (
services): service listings, with optional keywords. - Sitemap (
sitemap): published URLs, not full page details. - Job detail enrichment: enabled by default, limited by
maxDetailItems. It fills the same job row rather than creating another billed row.
What data can you extract from Upwork?
Availability varies by page. Missing values remain empty or null; examples below illustrate formats, not guaranteed values.
Job rows (job)
| Field | Description | Example |
|---|---|---|
jobId | Upwork job identifier | 1234567890 |
title | Job title | Python data pipeline |
description | Public description | Build a reporting pipeline |
jobUrl | Link to the job | https://www.upwork.com/jobs/_~021234567890 |
searchKeyword | Search phrase; all for the unfiltered feed | python developer |
jobType | Hourly or fixed-price work | hourly |
hourlyMin | Minimum hourly budget | 25 |
hourlyMax | Maximum hourly budget | 50 |
fixedPrice | Fixed-price budget, when present | 500 |
skills | Public skill tags | ["Python", "SQL"] |
experienceLevel | Requested experience | Intermediate |
proposalsRaw | Proposal count or range as text | 5 to 10 |
proposalsMid | Numeric interpretation of that count or range | 8 |
clientCountry | Public client country | Canada |
clientTotalSpent | Public client spending as text | 10000.0 |
paymentVerified | Public payment-verification flag | true |
postedAt | Source posting timestamp | 2026-10-20T12:00:00Z |
scrapedAt | Collection timestamp | 2026-10-24T08:00:00+00:00 |
Freelancer rows (freelancer)
| Field | Description | Example |
|---|---|---|
id | Profile identifier | ~01example |
profileUrl | Public profile link | https://www.upwork.com/freelancers/~01example |
firstName | Public first name | Jane |
title | Professional headline | Data engineer |
hourlyRate | Displayed hourly rate | 45 |
country | Public country | Portugal |
totalEarnings | Public earnings, when available | null |
rating | Public rating, when available | 4.9 |
Service rows (service)
| Field | Description | Example |
|---|---|---|
id | Service identifier | 1234567890 |
url | Project Catalog link | https://www.upwork.com/services/product/example-1234567890 |
title | Service title | A Python reporting dashboard |
priceFrom | Starting price | 50 |
priceTo | Upper price, when shown | 200 |
deliveryDays | Delivery time in days | 3 |
freelancerName | Public seller name | Jane Doe |
rating | Service rating | 4.9 |
reviewsCount | Review count | 12 |
Category rows (service_category)
Directory results can include categories. These are distinct from service offers; an empty service search currently returns offers, not a category-tree export.
| Field | Description | Example |
|---|---|---|
id | Category identifier | 123 |
categoryUrl | Category link | https://www.upwork.com/services/development-it |
label | Category name | Development & IT |
level | Category depth, when supplied | 1 |
Sitemap rows (sitemap_url)
| Field | Description | Example |
|---|---|---|
url | Published Upwork URL | https://www.upwork.com/freelancers/~01example |
sitemapKind | URL category | freelancer |
sitemapShard | Sitemap containing the URL | https://www.upwork.com/sitemaps/freelancers-1.xml |
scrapedAt | Collection timestamp | 2026-10-24T08:00:00+00:00 |
How to scrape Upwork
- Open Upwork Scraper in Apify Console.
- Choose
collectionSurfaceand fill the relevant keyword or URL field. - Set
maxItemsto3for a small first run. - Click Start, then inspect the dataset when the run finishes.
- Download rows or read them through the API.
An empty JSON input {} searches for the form's prefilled python developer phrase, with a limit of 3 rows. This is an example run, not a free run: normal pricing applies. Sending keywords: [] deliberately selects the unfiltered feed instead. Non-job selections do not substitute the example.
Jobs
Leave collectionSurface as jobs. Enter up to 10 English phrases or clear keywords for the public feed. scrapeDetails enriches up to maxDetailItems jobs with available client and proposal information.
Freelancers
Set collectionSurface to freelancers. Supply profileUrls for specific profiles, categoryUrls for category listings, or keywords for talent search. Profile URLs take precedence over category URLs, which take precedence over keywords. A profile URL alone does not switch the surface automatically.
Project Catalog
Set collectionSurface to services. Enter keywords for specific services, or send an empty keyword list for unfiltered service listings. Clear the form's example keyword if you do not want it used.
Sitemap
Set collectionSurface to sitemap. No keyword is needed. Use maxItems to limit URL rows; sitemap membership does not guarantee that a page is still accessible.
What to fill in
| Field | What to enter | Default |
|---|---|---|
collectionSurface | jobs, freelancers, services or sitemap | jobs |
keywords | Up to 10 phrases, each at most 200 characters; [] for unfiltered search | Form prefill: python developer |
maxItems | Rows per input, from 1 to 500 | 70; form prefill: 3 |
profileUrls | Public Upwork freelancer links | None |
categoryUrls | Freelancer category links, such as https://www.upwork.com/hire/python-developers/ | None |
sort | Use recency or relevance; the other displayed options are not currently supported | recency |
scrapeDetails | Enrich job rows with public details | true |
maxDetailItems | Maximum jobs to enrich, from 0 to 50 | 5 |
maxPagesPerKeyword | Legacy page setting; use maxItems to control current collection | 1 |
maxShards | Legacy sitemap setting; not enforced by current collection | 0 |
maxUrlsPerShard | Legacy sitemap setting; not enforced by current collection | 0 |
timeoutSecs | Individual request timeout, 30–180 seconds; not the run timeout | 90 |
Jobs:
{"keywords": ["python developer"],"maxItems": 3}
Freelancers:
{"collectionSurface": "freelancers","keywords": ["python developer"],"maxItems": 3}
Project Catalog:
{"collectionSurface": "services","keywords": ["dashboard"],"maxItems": 3}
Sitemap:
{"collectionSurface": "sitemap","maxItems": 3}
maxItems is the canonical row-limit field; maxResults, limit and max_results are accepted aliases. keyword and query are accepted for keywords. Unknown fields do not fail the run: close misspellings are corrected and others ignored with a note. Invalid sort or surface values fall back to defaults.
The INPUT_REPORT record in the default key-value store lists fields filled, corrected, inferred or ignored. Resolver errors use INVALID_INPUT: <field> — <how to fix>. Later URL validation can instead name the field and expected URL format directly. Invalid inputs stop before rows are charged; a start fee can still apply. No automatic keyword translation or moving date window is configured.
Output example
Illustrative job row:
{"rowType": "job","id": "~021234567890","jobId": "1234567890","title": "Python data pipeline","description": "Build a reporting pipeline with Python and SQL.","jobUrl": "https://www.upwork.com/jobs/_~021234567890","searchKeyword": "python developer","jobType": "hourly","hourlyMin": 25,"hourlyMax": 50,"fixedPrice": null,"skills": ["Python", "SQL"],"clientCountry": "Canada","clientTotalSpent": "10000.0","paymentVerified": true,"proposalsRaw": "8","proposalsMid": 8,"scrapedAt": "2026-10-24T08:00:00+00:00"}
Export the dataset as JSON, CSV, Excel, XML, RSS or an HTML table. RUN_SUMMARY records the outcome and row count; ERROR_SUMMARY explains failures. PARTIAL_FAILURES, when present, lists inputs that did not finish successfully.
Why use this Upwork scraper?
- Pay per delivered row. Only rows written to the dataset incur a row charge; the start fee is separate.
- Keep delivered results. Rows already written remain available after a stop or timeout. Partial collection results can also be delivered; an abrupt stop does not guarantee delivery of unwritten rows.
- Inspect failures. Input reports and error records distinguish invalid input from unavailable data.
- Compare public data. Budgets, skills and client details support research without inventing missing values.
- Use several inputs. Collect multiple keywords or freelancer URLs in one run.
- No proxy setup. You do not need to set up proxies or supply an Upwork login.
How much does it cost to scrape Upwork?
The Actor uses pay-per-event pricing. Every row type has the same price. Platform usage is included in these prices.
| Apify plan | Start event | Price per row | Price per 1,000 rows |
|---|---|---|---|
| Free | $0.005 | $0.002 | $2.00 |
| Starter | $0.005 | $0.002 | $2.00 |
| Scale | $0.005 | $0.002 | $2.00 |
| Business | $0.005 | $0.002 | $2.00 |
The start event is charged per GB of allocated memory, with a minimum of one event. At the default 512 MB, a normal run start costs $0.005.
maxItemsapplies to each keyword or URL input, defaults to 70, and accepts up to 500. Larger requests are collected in parts of at most 70 rows when more results are available.- All row types share the per-input limit. An unfiltered search counts as one input.
- Free Apify plan users have a limit of 200 rows per run and 10 runs of this Actor.
- Set Maximum cost per run to cap the whole run (
maxTotalChargeUsdin the API).
Example: three keywords returning 50 rows each produce 150 billed rows. At 512 MB, the normal start plus rows cost 150 × $0.002 + $0.005 = $0.305 on the Free plan. A row limit is a ceiling, not a promise that enough matches exist.
What happens when the run timeout is too short
If a search needs longer than the selected run timeout, the Actor can pause for automatic continuation rather than treating the wait as a collection failure. Its status message reads: "Queued on the collection backend; this run will resume automatically when results are ready (about N s)." The run temporarily appears as ABORTED.
When results are ready, the same run resumes, reports "Resumed: delivering N rows.", writes to the same dataset and finishes as SUCCEEDED if collection succeeds. Previously delivered rows are skipped, so they are not delivered or charged twice. No manual restart is needed for this pause.
- Automatic pauses are limited to 3 per run. Keep the default run timeout of 3600 seconds to reduce interruptions.
- Synchronous API endpoints (
run-sync,run-sync-get-dataset-items) wait at most 300 seconds and may return an empty dataset while the run is paused. For longer work, start asynchronously and read the dataset afterSUCCEEDED, or use a succeeded-event webhook.
Use with AI agents and the API
Apify MCP server
Add the Actor to a remote MCP client such as Claude, ChatGPT, Cursor or VS Code:
https://mcp.apify.com?tools=lentic_clockss/upwork-jobs-scraper
Sign in with Apify through OAuth, or send Authorization: Bearer <YOUR_APIFY_TOKEN>. The tool starts a run and returns run and dataset IDs; the agent reads rows with get-dataset-items. Clients without remote support can use npx @apify/actors-mcp-server --tools lentic_clockss/upwork-jobs-scraper with APIFY_TOKEN set.
Example prompt: "Find three Upwork jobs for Python developers and return their titles, budgets and job links."
Apify API
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("lentic_clockss/upwork-jobs-scraper").call(run_input={"keywords": ["python developer"], "maxItems": 3})for row in client.dataset(run["defaultDatasetId"]).iterate_items():print(row)
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('lentic_clockss/upwork-jobs-scraper').call({keywords: ['python developer'], maxItems: 3});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items);
Over HTTP, send your JSON input to POST https://api.apify.com/v2/acts/lentic_clockss~upwork-jobs-scraper/runs with an Authorization: Bearer <YOUR_APIFY_TOKEN> header. Read the resulting dataset with GET https://api.apify.com/v2/datasets/<datasetId>/items?format=json.
Integrations
Use Apify integrations for Google Sheets, Make, Zapier, n8n, Slack or Airtable. Configure webhooks for downstream processing and Apify Schedules for recurring runs.
FAQ
Is it legal to scrape Upwork?
This Actor collects public data. Public availability is not permission for every use: you are responsible for complying with Upwork's terms and applicable data-protection law. Processing personal data requires a lawful basis.
Why did my run end as ABORTED?
An automatic continuation pause can temporarily show ABORTED; check for the queued status message described above. A manually stopped run is not the same as that pause and should not be assumed to restart.
How many results can I get?
Request 1–500 rows per input with maxItems. Actual delivery depends on available matches, source limits, your cost cap and Free-plan limits. Sitemap rows count just like job or profile rows.
Why are some fields empty?
Upwork does not expose every field publicly. Detail enrichment is limited to selected jobs, and some values remain unavailable even on detail pages. Empty values are not inferred.
Do I need an Upwork account?
No Upwork login is required. Private proposals, messages and account-only data are outside the scope.
How do I collect jobs without keywords?
Send keywords: [] or clear the Console keyword list. Omitting all target fields starts the small example instead.
Can I get client phone numbers or email addresses?
Do not expect them. Public job pages do not provide these contact details; empty contact fields are not filled with Upwork's own contact information.
What happens when a page cannot be collected?
The Actor reports the problem rather than inventing rows. It can keep valid partial results, but an unsuccessful collection with no trustworthy rows is not presented as a successful empty search.
Limitations
- Public Upwork data only; no applications, private messages or private proposals.
- No country filter, historical archive or automatic translation is exposed in the input form.
- Page changes, removed listings and unavailable fields can reduce results.
relevance+desc,client_total_charge+descandclient_rating+descremain in the form for compatibility but are rejected during collection. Userecencyorrelevance.- Sitemap URLs are discovery records, not enriched jobs or profiles.
maxPagesPerKeyword,maxShardsandmaxUrlsPerShardshould not be used as hard limits for current collection. UsemaxItemsand the run cost cap.
Related scrapers
More job board and freelance marketplace scrapers from the same publisher:
- LinkedIn Scraper
- Indeed Scraper
- Freelancer.com Scraper
- Glassdoor Scraper
- SEEK Scraper
- Bayt Scraper
- Fiverr Scraper
Browse all of our scrapers at apify.com/lentic_clockss.
Support
Report a problem or request a field through the Issues tab of this Actor's page. Include the run ID and the input you used.