Upwork Scraper - Jobs, Freelancers & Project Catalog avatar

Upwork Scraper - Jobs, Freelancers & Project Catalog

Pricing

from $2.00 / 1,000 results

Go to Apify Store
Upwork Scraper - Jobs, Freelancers & Project Catalog

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

kane liu

Maintained by Community

Actor stats

1

Bookmarked

8

Total users

3

Monthly active users

6 hours ago

Last modified

Security

Limited permissions

Share

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)

FieldDescriptionExample
jobIdUpwork job identifier1234567890
titleJob titlePython data pipeline
descriptionPublic descriptionBuild a reporting pipeline
jobUrlLink to the jobhttps://www.upwork.com/jobs/_~021234567890
searchKeywordSearch phrase; all for the unfiltered feedpython developer
jobTypeHourly or fixed-price workhourly
hourlyMinMinimum hourly budget25
hourlyMaxMaximum hourly budget50
fixedPriceFixed-price budget, when present500
skillsPublic skill tags["Python", "SQL"]
experienceLevelRequested experienceIntermediate
proposalsRawProposal count or range as text5 to 10
proposalsMidNumeric interpretation of that count or range8
clientCountryPublic client countryCanada
clientTotalSpentPublic client spending as text10000.0
paymentVerifiedPublic payment-verification flagtrue
postedAtSource posting timestamp2026-10-20T12:00:00Z
scrapedAtCollection timestamp2026-10-24T08:00:00+00:00

Freelancer rows (freelancer)

FieldDescriptionExample
idProfile identifier~01example
profileUrlPublic profile linkhttps://www.upwork.com/freelancers/~01example
firstNamePublic first nameJane
titleProfessional headlineData engineer
hourlyRateDisplayed hourly rate45
countryPublic countryPortugal
totalEarningsPublic earnings, when availablenull
ratingPublic rating, when available4.9

Service rows (service)

FieldDescriptionExample
idService identifier1234567890
urlProject Catalog linkhttps://www.upwork.com/services/product/example-1234567890
titleService titleA Python reporting dashboard
priceFromStarting price50
priceToUpper price, when shown200
deliveryDaysDelivery time in days3
freelancerNamePublic seller nameJane Doe
ratingService rating4.9
reviewsCountReview count12

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.

FieldDescriptionExample
idCategory identifier123
categoryUrlCategory linkhttps://www.upwork.com/services/development-it
labelCategory nameDevelopment & IT
levelCategory depth, when supplied1

Sitemap rows (sitemap_url)

FieldDescriptionExample
urlPublished Upwork URLhttps://www.upwork.com/freelancers/~01example
sitemapKindURL categoryfreelancer
sitemapShardSitemap containing the URLhttps://www.upwork.com/sitemaps/freelancers-1.xml
scrapedAtCollection timestamp2026-10-24T08:00:00+00:00

How to scrape Upwork

  1. Open Upwork Scraper in Apify Console.
  2. Choose collectionSurface and fill the relevant keyword or URL field.
  3. Set maxItems to 3 for a small first run.
  4. Click Start, then inspect the dataset when the run finishes.
  5. 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

FieldWhat to enterDefault
collectionSurfacejobs, freelancers, services or sitemapjobs
keywordsUp to 10 phrases, each at most 200 characters; [] for unfiltered searchForm prefill: python developer
maxItemsRows per input, from 1 to 50070; form prefill: 3
profileUrlsPublic Upwork freelancer linksNone
categoryUrlsFreelancer category links, such as https://www.upwork.com/hire/python-developers/None
sortUse recency or relevance; the other displayed options are not currently supportedrecency
scrapeDetailsEnrich job rows with public detailstrue
maxDetailItemsMaximum jobs to enrich, from 0 to 505
maxPagesPerKeywordLegacy page setting; use maxItems to control current collection1
maxShardsLegacy sitemap setting; not enforced by current collection0
maxUrlsPerShardLegacy sitemap setting; not enforced by current collection0
timeoutSecsIndividual request timeout, 30–180 seconds; not the run timeout90

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 planStart eventPrice per rowPrice 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.

  • maxItems applies 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 (maxTotalChargeUsd in 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 after SUCCEEDED, 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 ApifyClient
client = 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

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+desc and client_rating+desc remain in the form for compatibility but are rejected during collection. Use recency or relevance.
  • Sitemap URLs are discovery records, not enriched jobs or profiles.
  • maxPagesPerKeyword, maxShards and maxUrlsPerShard should not be used as hard limits for current collection. Use maxItems and the run cost cap.

More job board and freelance marketplace scrapers from the same publisher:

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.