Upwork Job Scraper avatar

Upwork Job Scraper

Pricing

from $35.00 / 1,000 job returneds

Go to Apify Store
Upwork Job Scraper

Upwork Job Scraper

Scrape Upwork job search results: title, budget, skills, experience level and posting time, from keywords or search URLs. Public search first. MCP-ready.

Pricing

from $35.00 / 1,000 job returneds

Rating

0.0

(0)

Developer

Khadin Akbar

Khadin Akbar

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

2

Monthly active users

9 days ago

Last modified

Share

Upwork Job Scraper helps freelancers, agencies, and AI agents pull structured Upwork job-search rows — title, description, budget or hourly range, skills, experience level, duration, engagement, posting time, and permalinks — from keywords or Upwork nx/search/jobs URLs. It runs on Apify (API, schedules, integrations, MCP) so you can monitor niches without manual browsing.

Two routes, and what each row contains

  1. Public search reader (primary). The Actor reads Upwork's public job search page — the results a signed-out visitor sees, about 10 jobs per page — through Apify's unblocking proxy. Sort, contract type and experience level are applied by Upwork (for sortBy: relevance the public search uses Upwork's default best-match order); minHourlyRate is applied to the returned rows. These rows have source: "upwork-public-search".

  2. Signed-in fallback route. A managed, signed-in job source. A search uses it only when:

    • the public reader could not serve that search (blocked, or sent to a login page), or
    • you set paymentVerified: true or minClientSpend to 1k/10k, or
    • you supply cookies, or
    • a pasted startUrls URL carries a signed-in filter parameter: payment_verified, hourly_rate, client_hires or amount.

    On fallback rows, paymentVerified and minClientSpend are checked again before saving; rows that fail them, or whose client spend is unknown, are dropped and not charged.

Fields that are null on public-search rows. Upwork hides these from signed-out visitors, so the public reader leaves them null instead of guessing:

FieldPublic-search rowsFallback rows
clientCountry, clientCityalways nullfilled when Upwork shows them
clientSpendalways nullfilled (USD)
clientPaymentVerifiedalways nullfilled
clientRating, clientHires, clientReviewsCountalways nullfilled when shown
proposalsRangealways nullfilled when shown
connectsalways nullusually null
postedAtRawalways nullrelative time text when shown

budget, hourlyRange and fixedPrice are null on either route when the posting states no budget or rate. If you need client signals on every row, set paymentVerified: true or a minClientSpend threshold (both also filter the results); otherwise most rows come from the public search and carry no client data.

Optional caller session cookies are used only by the fallback route for a single run; they stay out of the dataset, OUTPUT, and logs.

Upwork is a trademark of its owner. This independent Actor is not affiliated with, associated with, or endorsed by Upwork Global Inc.

Best fit

Use this Actor when you need fresh Upwork job cards for monitoring, lead lists, or agent workflows. For multi-board aggregation across Indeed/LinkedIn/Glassdoor, route to Jobs Scraper (multi-board). For Indeed-only detail enrichment, use Indeed Job Scraper (Indeed detail). For Google Jobs SERP cards, use Google Jobs Scraper (Google Jobs SERP).

Workflow story

A freelance agency lead wants new javascript developer posts on a schedule. They save this Actor with searchQueries: ["javascript developer"], sortBy: "recency", and a bounded maxResults. Each execution returns normalized rows with title, budget, skills and permalink, then Zapier/Make routes the best matches into Slack. When the niche is quiet, the Actor completes with VALID_EMPTY and zero job charges instead of inventing rows.

How to scrape Upwork jobs

  1. Open this Actor in Apify Console
  2. Enter one or more searchQueries or an Upwork nx/search/jobs URL in startUrls
  3. Set maxResults for the quality sample
  4. Start the Actor — download JSON / CSV / Excel from the dataset

Input

See the Input tab. Provide at least one keyword or a valid Upwork search URL.

{
"searchQueries": ["javascript developer"],
"maxResults": 5,
"maxPagesPerQuery": 1,
"sortBy": "recency",
"jobType": "all",
"experienceLevel": "all",
"paymentVerified": false
}

Optional secret field cookies: JSON array of {"name","value"} session cookies for this run only (excluded from dataset and OUTPUT).

Output

One dataset row per job:

{
"jobId": "~022100516022389626945",
"title": "Looking for Skilled AI Engineers",
"jobUrl": "https://www.upwork.com/jobs/~022100516022389626945",
"description": "Build agents...",
"jobType": "Hourly",
"budget": "$15-$55 / hr",
"hourlyRange": { "min": 15, "max": 55, "currency": "USD" },
"experienceLevel": "Expert",
"duration": "1 to 3 months",
"postedAt": "2026-01-15T12:00:00.000Z",
"skills": ["Node.js", "AI"],
"clientCountry": null,
"clientPaymentVerified": null,
"clientSpend": null,
"source": "upwork-public-search"
}

Data fields

FieldDescription
jobIdUpwork ciphertext id (~…)
title / jobUrl / descriptionCore posting
jobType / budget / hourlyRange / fixedPriceCompensation
experienceLevel / duration / engagementScope labels
postedAtRaw / postedAtRelative + ISO timestamps
skillsTagged skills
proposalsRange / connectsMarketplace signals (null on public-search rows)
clientCountry / clientCity / clientPaymentVerified / clientSpend / clientRating / clientHires / clientReviewsCountClient signals (null on public-search rows; filled on fallback rows)
sourceupwork-public-search (public search) or fallback-provider (signed-in fallback route)

Pricing

Monetization is Pay per event + platform usage (usage paid by the run user). The live Pricing tab in Apify Console is the current source of truth for event prices.

Typical events:

EventRole
apify-actor-startCharged by the platform at run start: one event per GB of run memory (minimum one)
job-foundPrimary event — one charge per unique job row saved, from either route

The job price is the same for public-search rows and fallback rows, although public-search rows carry no client data (see the table above). Invalid inputs, empty matches and rows dropped by minClientSpend/paymentVerified are not charged job events. Platform usage, including the unblocking proxy used by the public search, is billed to your account as usual. A search served by the fallback route also runs a separate managed job source; that cost is covered by the Actor owner, not added to your bill.

API example

curl "https://api.apify.com/v2/acts/khadinakbar~upwork-job-scraper/runs?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"searchQueries":["javascript developer"],"maxResults":5,"sortBy":"recency"}'

MCP / agent prompt card

Tool: apify--upwork-job-scraper

Example request:

Search Upwork for recent javascript developer jobs and return title, budget, skills, postedAt and jobUrl. Cap results with maxResults.

After completion: read the default dataset, confirm OUTPUT.outcome is COMPLETE or VALID_EMPTY, and reconcile job-found charges with row count. Provenance is in source: upwork-public-search for the public search reader; fallback-provider for fallback rows. Client fields are null on public-search rows; to get them, set paymentVerified: true or minClientSpend. Scope is public job-search cards — not proposals, messages, or private client PII. Paginate with maxPagesPerQuery only when you need depth beyond the first page.

Limits and recovery

Provide specific keywords or a valid Upwork search URL. Confirm OUTPUT.outcome after each execution. Schedule monitoring with bounded maxResults. Source data is current public job-search cards, read by the own public search reader first and the fallback route only when needed. If every route fails for every search, the run fails with UPSTREAM_FAILED instead of reporting success.

Best results

  • Prefer specific niche keywords over broad terms
  • Use sortBy: recency for monitoring pipelines
  • Cap maxResults for cost control
  • Treat VALID_EMPTY as a real no-match

Builder's note

I built this Actor's main route as my own reader for Upwork's public job search, which signed-out visitors can see without an account. That page hides client details, proposal counts and connects, so those fields stay null rather than guessed; a signed-in fallback covers the searches whose filters need them.

This Actor retrieves public Upwork job listings for legitimate market research, lead generation, and personal job monitoring. You are responsible for complying with Upwork's Terms of Service, applicable laws, and your own data-use policies. Use this tool for lawful research and monitoring that respects Upwork's Terms of Service, applicable laws, and your own data-use policies. The authors are not affiliated with Upwork.

Support

Open an issue on the Actor's Issues tab. Sibling routing is covered in Best fit above.