Upwork Job Scraper: Scheduled Job Alerts, Qualified Clients
Pricing
from $1.00 / 1,000 results
Upwork Job Scraper: Scheduled Job Alerts, Qualified Clients
New Upwork jobs in seconds, served from a live-updated index — no login, no waiting on a scrape. Filter by client spend, payment-verified, hire rate and budget. Empty runs free. Start from any time; never miss a job. $2 per 1,000, no monthly fee.
Pricing
from $1.00 / 1,000 results
Rating
5.0
(2)
Developer
Hyperbach
Maintained by CommunityActor stats
3
Bookmarked
269
Total users
122
Monthly active users
4 days ago
Last modified
Categories
Share
Scheduled job alerts from Upwork: run this Actor every few minutes and get the new postings from payment-verified clients with real spend — into Slack, email or a webhook through Apify's own integrations. It answers from an index that is already current, so a call takes seconds and an empty check costs nothing. Qualify by client spend, hire rate and budget; start the feed from any moment you choose and never miss a job. $2 per 1,000 results — no monthly fee, no login.
Built for freelancers, agencies, and job-board builders who need to see a posting before everyone else has replied to it. Most scrapers go to Upwork at request time and hand you raw rows. This one is already watching.
What is measured, not claimed
- Freshness. 99.8% of postings are available within 10 minutes of going live, median 2.9 minutes — measured on the live feed over the 7 days to 2026-09-17, not estimated.
- A start point you own.
created_atis a value you pass, not state we keep. Among the published input schemas of the largest Upwork scrapers on the Store (checked 2026-09-06), none exposes one: their time filters are posting time, and their "new jobs only" modes are since-last-run state held on their side. Yours can be replayed, shared between machines, or resumed after downtime — and a job that Upwork lists late is never skipped. - The filters buyers actually use come first. Payment-verified, total spent, client and job scores, hire rate, open jobs, invites — with comparison and range operators:
">=1000","500-2000". 61 filters in total; the ones nobody uses sit at the bottom of the form. - Screening questions captured per job, ready for your proposal generator. AI-extracted skills and urgency for those who want them.
- $2 per 1,000 results, less on paid plans, no monthly fee. Zero-result runs cost nothing, so a tight polling schedule costs no more than a lazy one.
🧭 Three ways to use it
1. Alerts — new jobs, minutes after they post
Set notifications_only: true and schedule the Actor every few minutes. Each run returns only the postings that arrived since your previous run, and can send them straight to Slack, Telegram or a webhook if you paste one URL — see Job alerts in five minutes below. Median time from posting to availability: 2.9 minutes.
{ "notifications_only": true, "buyer_payment_verified": true, "total_spent": ">=1000", "limit": 50 }
2. Research — search the last 90 days, open and closed
Leave notifications_only off (the default) and every filter runs against the whole index for the last 90 days. Jobs stay in the index after they are filled or expire, so a search returns closed postings as well as open ones — what clients asked for and offered to pay across three months, not only what is still live. Built for researchers, analysts and anyone pricing a niche.
{ "skills": "React", "date_posted": "90d", "price": ">=1000", "limit": 1000 }
The posting's text is as it read in its first minutes; its proposal and invite counts and its budget carry the newest reading we have, and refreshed_at says when (see What is captured once, and what is kept current). Whether a job has since closed is not a column — add include_history: true for its status over time, or pass its id to Refresh mode to read it now. For anything older than 90 days, Upwork Jobs History has every posting since November 2024 — the last 12 months or all of it, by category and skill.
3. Job history — add include_history: true, free
Set include_history: true in either mode and every row carries the job's recorded history, at no extra charge:
{ "skills": "React", "date_posted": "7d", "include_history": true, "limit": 200 }
history.readings— the job's state at each moment it changed: status, applicants, interviewing, invites, hires, the client's last activity and the budget. Identical readings are collapsed, so the list is the job's story, not a log of every look.history.edits— each change the client made to the posting itself, and what changed: the title before and after, skills added and removed, the description rewritten.
The history is served from our own database of recorded versions, not re-read from Upwork when you ask. That is why it can reach back to the first time we saw a job — which a scraper that only reads Upwork at run time cannot do.
We re-read every job in the 45-day window on a measured schedule: every 6 hours while it is under two days old, every 2 days until day 7, then once more before day 45. The schedule started on 2026-09-24, and jobs posted since then are re-read from their first hours. Older jobs are being worked through newest first; until the schedule reaches one, it comes back with empty lists. Readings for the jobs we already tracked go back to 2026-08-22.
📈 Who it's for
- Freelancers — poll every few minutes and see matching jobs while they are still fresh, with screening questions ready to drop into your proposal.
- Agencies — filter straight to high-budget, agency-friendly clients with real spend and hire history.
- Job boards & SaaS — power a niche Upwork job board or alerting product on a feed that stays current without you running scrapers.
- Market researchers — track pricing, demand, and skill trends across millions of postings.
🚀 Quick Start
1. Basic Usage
{"limit": 10}
2. With Smart Filtering
{"limit": 20,"skills": "React","price_min": ">=50","buyer_score": ">=4.0","ai_urgency": "Urgent"}
3. Notification Mode (Only New Jobs)
{"notifications_only": true,"limit": 50}
4. Start the feed from a moment you choose
{"created_at": ">=2026-09-01T00:00:00Z","buyer_payment_verified": true,"total_spent": ">=1000","limit": 100}
created_at is when a job entered this index, not when Upwork posted it. Advance it to the newest value you received and call again: you get exactly the jobs added since, including the ones Upwork listed late. The start point is yours to keep, replay, or share between machines — nothing on our side decides your position — the value you pass does.
📏 Check a search before you schedule it — estimate: true
Add estimate: true to any search and the run measures it instead of running it. It returns one row, billed as one result, and moves no feed position:
{ "keywords": "\"brand film\" OR \"product commercial\"", "notifications_only": true, "estimate": true, "estimate_poll_minutes": 15 }
- Volume — how many jobs this exact search would have delivered over the last 28 days: per day, per week and per hour, plus the quietest and the busiest day. The count uses the same filter code a real run uses, so the estimate and the feed cannot disagree.
- Band —
too_narrow(under one job a week),healthy, ortoo_broad(over 30 a day). - Cost per month at each Apify plan's price, for how often you plan to run it (
estimate_poll_minutes) and in the mode you chose. Withnotifications_only: false, a time window polled more often than it is wide re-sends the same jobs, and the estimate says how many times. - Which filter cuts the most — for every filter, how many jobs a day the search would find without it.
- 5 recent matches — title, budget and client spend, so you can see whether the volume is the right jobs.
The run's status message has the summary in one line, and the OUTPUT record has the same row. A relative date filter (date_posted: ">=1h") is measured the way a scheduled feed reads it; an absolute created_at or date_posted is a start point, not part of the flow, and the row says it left it out. With keywords matched by meaning the count is approximate, and the row says so.
🔔 Job alerts in five minutes
Paste one URL into the form, schedule the task, and every run that finds new jobs sends one message with those jobs to your Slack, Telegram, or any webhook. A run that finds nothing sends nothing. Nothing to host, nothing to code, no account with us.
1. The task. Create a Task from this Actor with your filters (buyer_payment_verified: true, total_spent: ">=1000", keywords: "react frontend developer", …) and set notifications_only: true — each run then returns only the postings that arrived since the previous run, so nothing is sent twice.
2. Where the alert goes. Fill in one or more of these in the same form:
- Slack — in Slack: Apps → Manage → Build (api.slack.com/apps) → Create New App → From scratch → Incoming Webhooks → Activate → Add New Webhook to Workspace → pick the channel. Copy the URL that starts with
https://hooks.slack.com/services/…intoslack_webhook_url. The message is a Slack block per job: title as a link, rate or budget, client country, spend, hire rate. - Telegram — message @BotFather,
/newbot, copy the token intotelegram_bot_token. Add the bot to your chat or channel, then get the chat id: message @userinfobot for your own id, or for a channel post once and read the id fromhttps://api.telegram.org/bot<token>/getUpdates(channel ids start with-100). Put it intelegram_chat_id. - Anything else — put your URL in
webhook_url(n8n, Make, Zapier catch hooks, your own backend). It receives a JSONPOST:{ "count": 3, "total": 3, "run_url": "…", "run_id": "…", "dataset_id": "…", "items": [ …up to 100 compact rows: id, title, url, dates, price, category, skills, client spend, hire rate, scores… ] }— the full rows with descriptions are in the dataset. Extra headers, e.g.{"Authorization": "Bearer …"}, go inwebhook_headers.
Your webhook URL and bot token are yours: the run sends to them directly from inside the Actor and they are never stored or seen on our side (the form masks them; our run records keep only "set").
3. The schedule. Task → Schedule. The index behind this Actor takes in new postings about every 3 minutes (a posting is in it a median 3 minutes after Upwork shows it, 5 minutes at the 90th percentile — measured 2026-09-24), so a schedule tighter than that sees nothing new and only costs start events. Every 5–15 minutes is the normal setting: an empty run costs nothing and sends nothing. Keep one schedule per clientId and filter set — two schedules on the same feed share one cursor.
What you get per run: up to 10 jobs in the message (the rest counted, all of them in the dataset), a link to the run. Set notify_when_empty: true if you want a message on empty runs too. A delivery that fails (a wrong URL, a bot not in the chat) is written into the run's status line and never fails the run — the rows are in the dataset either way.
Ready-made: the published task Slack alerts for new Upwork jobs from qualified clients carries the filters — copy it, paste your Slack URL, schedule it.
Apify's own Slack and Gmail integrations (Task → Integrations) still work; they fire on every run, including empty ones, and send a link rather than the jobs.
Why the alerts are complete: notifications_only keeps a cursor on our side per clientId and filter set, at the last row you received. A run returns the postings after it, oldest first; when more match than limit, the run says so (has_more) and the next run continues from the last row, so nothing is skipped and nothing is sent twice. A job that reaches Upwork's search late still arrives on your next run, because the feed is ordered by when a job entered our index, not by when Upwork stamped it.
The first run of a feed. Pass created_at: ">=<a moment>" with notifications_only: true and the first run streams from that moment exactly like every later one: oldest first, has_more, the next run continuing. Without created_at, the first run is one page of the newest limit matches and the older ones are not queued — the run's status line says so. The cursor is keyed on clientId plus every filter and its value; limit and fields are not part of the key, so you can change them freely, and any change to a filter starts a new feed. A clientId you do not set derives from your Apify account, so a manual test run with the same filters advances your production cursor — give production its own clientId.
📤 Output Format
Each dataset item is a flat job record. Real example from a fresh posting (job_score: 94):
{"id": "022053503680858460814","title": "Senior React Native / Cloud Backend Engineer for iOS Video App","url": "https://www.upwork.com/jobs/~022053503680858460814","description": "We are looking for a senior engineer to join a small, fast-moving team building a live iOS app in React Native with heavy cloud backend connectivity and cloud-based video processing. This is not a web app project. We need someone experienced with real iOS apps on real devices, App Store/TestFlight workflows, mobile-to-cloud video uploads, client/server sync, and production-safe feature deployment...","date_posted": "2026-05-10T15:53:24Z","created_at": "2026-05-10T15:59:01Z","price_type": "Hourly","price_min": 70,"price_max": 100,"skills": "React Native, iOS, Mobile App Development, Google Cloud Platform, Python, AI Development","category_name": "Web, Mobile & Software Dev","subcategory_name": "Web Development","experience_level": "Expert","engagement_label": "3 to 6 months","qual_pref_english": "ANY","client_location": "United States","buyer_city": "Miami","buyer_score": 4.93,"buyer_feedback_count": 44,"buyer_payment_verified": true,"jobs_posted": 57,"hires": 55,"hire_rate": 100,"total_spent": 54226.41,"avg_hourly_rate": 55.35,"total_hours": 473,"open_jobs": 1,"ai_clients_technical_understanding": "High","ai_technical_skills": "React Native, iOS app development, App Store/TestFlight workflows, mobile video/photo upload, camera roll/photo library permissions, cloud backend (GCP preferred, AWS acceptable), async jobs/queues/workers, client/server state sync, production logging/debugging, AI-assisted development","ai_inferred_technical_skills": "Slack, Markdown documentation, real device testing","ai_explicit_mention_of_agency": "No Mention","job_score": 94,"job_score_breakdown": {"categories": {"price": { "raw_score": 18, "normalized_score": 18, "weight": 20 },"client_reputation": { "raw_score": 42, "normalized_score": 42, "weight": 42 },"client_spending": { "raw_score": 24, "normalized_score": 24, "weight": 40 },"location": { "raw_score": 10, "normalized_score": 10, "weight": 10 }},"top_contributors": [{ "attribute": "price_min,price_max", "value": "$70-$100/hr", "score": 18, "category": "price" },{ "attribute": "hire_rate", "value": "100% (57 jobs)", "score": 15, "category": "client_reputation" },{ "attribute": "buyer_score", "value": "4.93 (44 reviews)", "score": 15, "category": "client_reputation" },{ "attribute": "avg_hourly_rate", "value": "$55.35/hr", "score": 15, "category": "client_spending" },{ "attribute": "buyer_payment_verified", "value": true, "score": 12, "category": "client_reputation" },{ "attribute": "client_location", "value": "United States", "score": 10, "category": "location" },{ "attribute": "total_spent", "value": "$54,226.41", "score": 6, "category": "client_spending" },{ "attribute": "total_hours", "value": "473 hours", "score": 3, "category": "client_spending" }],"penalties": []},"questions": ["Describe a React Native iOS app you worked on that was live in TestFlight or the App Store. What parts of the client/server flow did you personally build or maintain, especially around photo/video uploads, auth, sync, or backend job status?","Do you personally have an iPhone with a large real photo/video library that you can use for testing? What model and how many photos/videos in your camera roll?","How are you currently using AI as a developer?","How do you prevent regression?"]}
job_score is the rule-based 0–100 rank. Category weights: price (20) + client_reputation (42) + client_spending (40) + premium (5) + location (10). Each category is capped at its weight, then summed and bounded to 0–100.
job_score_breakdown is always included and explains which fields contributed how many points. Use it to build UI that highlights why a job ranked high, or to debug your score-threshold filters. top_contributors is sorted descending; penalties lists negative contributions (e.g., unverified payment, low hire rate on a client with many jobs posted).
questions is the array of Upwork screening questions captured for the job — empty [] for ~83% of postings, 1–6 strings when the client included them. Feed it into your auto-proposal generator.
Empty/missing AI fields are returned as null (not every job goes through full AI enrichment).
For the full list of fields with types and operator support, see Complete Field Reference above.
📸 What is captured once, and what is kept current
Everything a client writes into a posting is recorded when the job first enters the index — typically within minutes of going live (median under 4) — and kept exactly as it read then: title, description, skills, screening questions. The client's history (total spent, hire rate, feedback score, jobs posted) is what the job page showed at posting time, the same numbers you would have weighed before bidding.
The fields that move after posting carry the newest reading we have: client_total_applicants, client_total_invited (interviewing), client_invites_sent, client_unanswered_invites, client_last_activity, and the budget — price, price_min, price_max. refreshed_at says when that reading was taken. When it is null we have not re-read the job yet, and those fields are still the values from its first minutes, when proposals are usually around 1.
We re-read every job in the 45-day window on a measured schedule: every 6 hours while it is under two days old, which is when proposals pile up; every 2 days until day 7; once more before day 45. The schedule started on 2026-09-24 and works newest first, so recent jobs are current and older ones are being reached.
client_invites_sent is worth reading two ways: its value in the job's first minutes tells you whether the posting started invite-first (>=1) or open to everyone (0), and include_history shows how it grew after.
For every reading we have recorded of a job, add include_history: true. To read a job live from Upwork right now, use Refresh mode below.
🔄 Refresh mode — live "get job by id"
The feed serves an index by design; refresh mode is the deliberate exception. Pass refresh_job_ids (up to 50 — job URLs, ~02… ciphers, or numeric ids) and the run fetches those jobs live from Upwork, right now, bypassing the index entirely:
{ "refresh_job_ids": ["https://www.upwork.com/jobs/~021234567890123456789", "021987654321098765432"] }
Each returned item is the job's current state: title, description and budget as they read now, and the live activity counters — applicants, interviewing, invites sent, unanswered invites, hires, last client activity. This is the data that changes after posting — exactly what the snapshot index deliberately does not carry.
Two output shapes. By default (refresh_shape: "raw") each job comes back as its live record, nested — the shape refresh mode has always returned. Set refresh_shape: "flat" and each job comes back as the same flat row the feed returns — the same field names and values (title, price_min, total_spent, hire_rate, skills, questions, …) — plus a live block:
"live": { "status": "ACTIVE", "applicants": 19, "interviewing": 8, "invites_sent": 60,"unanswered_invites": 51, "hired": 0, "last_client_activity": "2026-09-19T10:00:00Z","fetched_at": "2026-09-19T14:02:11Z" }
Use flat when you run both modes on the same jobs and want one shape in your pipeline.
Rules: refresh runs cannot be combined with search filters; each refreshed job bills at the normal result rate. A job Upwork no longer shows comes back as a row with its status — CLOSED, FILLED, PRIVATE or HIDDEN — from our last reading of it, marked status_from: "last_reading" with that reading's time in refreshed_at (in the flat shape: live.status_from and live.fetched_at); it bills like any row. Ids we have no ending recorded for cost nothing and are listed in the run OUTPUT under refreshSummary.not_found. A run of 50 ids takes roughly a minute.
💰 Pricing
$2.00 per 1,000 results. No monthly subscription. No platform usage fees. Runs that return zero results cost nothing.
Paid Apify plans pay less, automatically: $1.70 per 1,000 on Starter, $1.40 on Scale, $1.00 on Business.
limit goes up to 10,000 and the default is 50. A full 10,000-row run is $20 at list price. For a one-time backfill that is the point — the published task Backfill 90 days of Upwork jobs once, then poll does exactly that. For a schedule it is not: set notifications_only: true and each new posting is billed once.
📜 Terms of use and data provenance
What a row is. One public Upwork job posting as Upwork shows it to a visitor: the posting's own fields (title, description, budget or rate, skills, category, posting time) and the client-level figures Upwork prints on it (payment verified, total spent, hires, location, reviews). No freelancer profiles, proposals, messages or account data. Descriptions are the clients' own text.
How it is collected. Every posting entered the index as a public listing, visible on Upwork without an account. We collect without an Upwork account and never with anyone's credentials: the Actor does not ask for yours and never acts on Upwork for you. Upwork does not license its listings to third parties and we do not claim an Upwork licence. We do not give legal advice and do not warrant that a given use is lawful where you are.
Your licence to the output. Under the Apify Actor Terms you keep the intellectual-property rights in the output (§3.2) and it is your Customer Data (§4.3). As the creator (§2.2) we grant you a non-exclusive, perpetual licence to use the rows for your business: alerts, matching, analysis, enrichment, and products built on them, including a job board that shows postings with a link to Upwork. You may not resell or redistribute the rows themselves as a dataset or a feed, and you may not use them to contact people at scale. Compliance with the laws that apply to your use is yours.
Questions about these terms: apify@hyperbach.com.
📖 Reference — Fields, Filters & Syntax
Everything below is the complete manual. Skim it when you're ready to build precise queries.
⚙️ Input Configuration
All parameters are optional.
| Parameter | Type | Description |
|---|---|---|
limit | number | Number of jobs to return (1-10000, default: 50) |
notifications_only | boolean | If true, only returns NEW jobs since last call |
refresh_shape | raw / flat | Refresh mode only: the live record as it comes (default), or the feed's flat row plus a live block — see Refresh mode |
slack_webhook_url, telegram_bot_token + telegram_chat_id, webhook_url (+ webhook_headers) | string | Where to send one message per run with the new jobs — see Job alerts. notify_when_empty (boolean) also sends when nothing was found |
One run returns up to 10,000 rows. limit sets how many, from 1 to 10,000,
and defaults to 50. Rows come back in the order they entered our index, newest
first — not by Upwork's own posting time, which is why date_posted on
consecutive rows does not decrease in step.
The searchable window is the last 90 days. A filtered search reads the most recent 90 days of the index and stops there. Ask for 10,000 and you get 10,000 only if that many postings match inside the window; otherwise you get every match there is. Nothing is held back — the window simply ends. Older history is served by the published dataset, not by this live feed.
On a schedule, set notifications_only: true. The run remembers the newest posting it delivered, per filter set, and the next run starts there, so every posting is billed once and no code holds a cursor. A time-window filter polled often does not do this: date_posted: ">=20 minutes ago" every 4 minutes returns each posting about five times and bills it five times. Use created_at when you want to hold the start point yourself — pass the newest created_at you received, not a clock.
How you get more depends on the mode. With notifications_only: true the
run remembers where it stopped, so each run returns only what arrived since the
previous one, oldest first, and a schedule walks forward through time on its own;
has_more: true there means the next run continues from the last row, nothing is
skipped. The first run of a feed follows the same rule when you pass a
created_at start point; without one it is a single page of the newest limit
matches, and older ones are not queued. With notifications_only: false each run
returns the newest matches in the window; a has_more: true in the output means
more postings match than your limit asked for. A repeat run returns the same newest
page; to go further back, pass the run's nextPage (in OUTPUT and on the status line)
as page_after with the same filters. Each page continues below the last row of the
one before, with no repeats and no gaps, until nextPage is absent — the whole 90-day
window, one page at a time.
A run never asks for more rows than your maxTotalChargeUsd can pay for. The
page is sized to the cap before the feed moves its cursor, so a cap can shorten a
run but never lose a row; the status line says when it did, and the next run
continues.
⚠️ Filter Quality — Read This Before Filtering
Upwork only requires title, description, skills, and price-type from the client. Almost everything else is optional, and clients fill it in inconsistently. If you filter on a field that's empty for most jobs, you exclude all the matching jobs that simply didn't supply the value — not just the non-matching ones.
Field density measured on the last 30 days (~119K postings, 2026-09-17):
- Always-filled (safe to filter):
title,description,skills,price_type,category_name,subcategory_name,experience_level,qual_pref_english,qual_type,client_location,hire_rate,company_size,open_jobs,ai_explicit_mention_of_agency(all ≥96%) - Mostly filled (mild loss):
buyer_contract_date(~82%),buyer_payment_verified(~80%),ai_technical_skills(~79%) - Half-filled (use carefully):
price/price_min/price_max(fixed-price jobs leave hourly bounds null and vice versa),total_spent(~60%),buyer_score(~57%),buyer_feedback_count,industry,engagement_label,ai_duration(~41%) - Sparse (filter at your own risk):
ai_urgency(~18%),ai_deadline(~16%),qual_rising_talent(~5%),qual_portfolio_required(~0%) — most jobs are not tagged
Recommendation: filter on dense fields, then use sparse fields for post-fetch sorting / scoring / display. Combining sparse filters compounds the loss multiplicatively.
📊 Available Filter Fields
You can filter jobs by 61 different fields across these categories:
📋 Essential Job Information
Core job details like title, description, pricing
Available fields: title, description, skills, price_type, price, price_min, price_max, category_name, subcategory_name, date_posted, url, keywords, exclude_keywords
📋 Job Requirements
Experience level, qualifications, and constraints
Available fields: experience_level, engagement_label, engagement_weeks, qual_min_hours_week, qual_min_success_score, qual_pref_english, qual_rising_talent, qual_portfolio_required, qual_type
📋 Client Information
Details about the client posting the job
Available fields: client_location, jobs_posted, hire_rate, avg_hourly_rate, open_jobs, total_spent, hires, active_hires, total_hours, industry, company_size, buyer_city, buyer_feedback_count, buyer_score, buyer_contract_date, buyer_payment_verified, client_total_applicants, client_total_invited, client_unanswered_invites, client_positions_to_hire, client_last_activity, qual_countries
📋 AI-Powered Insights
AI-generated analysis of job requirements and urgency
Available fields: ai_urgency, ai_duration, ai_deadline, ai_technical_skills, ai_inferred_technical_skills, ai_explicit_mention_of_agency, ai_clients_technical_understanding
📋 System Metadata
Internal tracking and system fields
Available fields: id, created_at, questions, job_score, job_score_breakdown
🔥 Most Popular Filters
Measured on the live feed: share of requests that carry each filter.
| Field | Type | Example | Used in | Description |
|---|---|---|---|---|
buyer_payment_verified | boolean | true | 78% | Boolean flag indicating whether the client has verified their payment method on Upwork |
keywords | text | "chatbot" | 75% | Search the job's title, description, skills, categories and AI-generated fields |
total_spent | numeric | ">=1000" | 70% | Total amount the client has spent on Upwork across all their projects |
created_at | date | ">=2026-09-01T00:00:00Z" | 59% | When this job entered our index — a start point you own |
date_posted | date | ">=2026-09-01" | 35% | When the job was posted on Upwork |
category_name | select | "Web, Mobile & Software Dev" | 25% | Upwork's top-level category of the job |
exclude_keywords | text | "wordpress shopify" | 19% | Drop jobs containing any of these words |
buyer_score | numeric | ">=3" | 19% | Score of the client's performance on Upwork |
job_score | numeric | ">=40" | 4% | Rule-based 0–100 score that ranks the job by attractiveness |
subcategory_name | select | "AI Apps & Integration" | 2% | Upwork's subcategory, one level under category_name |
Keywords match by meaning. Describe the work in plain words —
website migration shopify webflow— and a new search returns every job with all those words plus the jobs about the same thing in other words, each with arelevancescore (0-1). A search you already ran keeps matching exactly as before. For an exact word match, write the syntax —react OR vue,"cold email",-wordpress— or setkeywords_match: "strict". The run's status message says which mode ran.exclude_keywordsdrops a job if any listed word appears.
Words, like Upwork's advanced search:
all_words,any_words,none_words,exact_phrase,title_search,skills_search. Each is a list — one word or phrase per entry — and matches the exact words. They combine with each other and withkeywords:{"keywords": "ai voice agent", "any_words": ["vapi", "retell"], "none_words": ["wordpress"]}.
Tip — filter server-side, not after the fact: every filter below is applied before
limitpicks the page, so narrowing costs you nothing and rows you would have discarded never enter your dataset. Comparison and range syntax works on any numeric field —"total_spent": ">=10000","price": "500-2000"— anddate_postedis publication time, so">=10 minutes ago"means what it says.
🎛️ Filter Syntax Guide
Different field types support different filtering options:
📝 Text Fields (skills, title, description)
- Contains search:
"React"→ finds jobs mentioning React - Several words are one phrase:
"product animation"→ that exact phrase, in order. To describe the work in your own words, usekeywords(matched by meaning); for exact rules — words in any order, alternatives, exclusions — useall_words,any_words,none_words - Case-insensitive: automatically handled
- Empty check:
"=null"→ finds jobs with empty/null values - Non-empty check:
"!=null"→ finds jobs with content
🔢 Numeric Fields (price, price_min, buyer_score)
- At least:
"1000"→ 1000 or more (a bare number is a minimum — what a filter means) - Exactly:
"=1000" - Operators:
">=50","<=100",">25","<75" - Ranges:
"500-2000"→ between 500 and 2000 (inclusive) - Upwork's own spellings work too:
"10+"=">=10","over 1,000","under 500","$5,000"
✅ Boolean Fields (buyer_payment_verified, qual_rising_talent, qual_portfolio_required)
- True:
"true"or"1" - False:
"false"or"0"
📅 Date Fields (date_posted, created_at)
- Relative age (easiest):
"7d"= last 7 days,"24h"= last day,"2w"= last two weeks. A bare number means days:"3"= last 3 days. - In words:
"today","yesterday","past 24 hours","this week","last 30 days","3 days ago"— Upwork's filter labels as written - Two-digit dates:
09/20/2026is read (20 cannot be a month);09/10/2026is refused — month-day or day-month? Write2026-09-10 - Exact date:
"2025-01-15" - Operators:
">=2025-01-01","<=2025-12-31"— also work on relative ages ("<=7d"= older than a week) - ISO format:
"2025-01-15T10:30:00Z"
🎯 Select Fields (experience_level, ai_urgency)
- Exact match:
"Intermediate","Urgent" - Case-sensitive: use exact values from field options (e.g.
"Urgent", not"urgent"or"high")
🗂️ Categories and subcategories
category_name, subcategory_name and industry take these exact values, and nothing else. The counts are jobs in the feed over the 90 days to 2026-09-25, most first. To get a whole field of work, set category_name (for example Web, Mobile & Software Dev for software development). It is exact and does not depend on the words in a posting. Use keywords for the task inside it.
category_name | Jobs | subcategory_name values under it |
|---|---|---|
Design & Creative | 103,156 | Video & Animation (45,102), Graphic, Editorial & Presentation Design (30,152), Branding & Logo Design (8,004), Performing Arts (6,443), Art & Illustration (5,652), Product Design (3,590), Audio & Music Production (2,010), Photography (1,886), NFT, AR/VR & Game Art (317) |
Sales & Marketing | 79,466 | Digital Marketing (46,397), Lead Generation & Telemarketing (24,339), Marketing, PR & Brand Strategy (8,730) |
Web, Mobile & Software Dev | 66,449 | Web Development (22,631), Web & Mobile Design (17,718), Ecommerce Development (9,037), Mobile Development (5,349), Scripts & Utilities (3,746), QA Testing (3,090), AI Apps & Integration (1,639), Game Design & Development (979), Other - Software Development (903), Product Management & Scrum (574), Desktop Application Development (513), Blockchain, NFT & Cryptocurrency (270) |
Admin Support | 32,117 | Virtual Assistance (18,269), Data Entry & Transcription Services (5,258), Market Research & Product Reviews (5,222), Project Management (3,368) |
Engineering & Architecture | 18,912 | 3D Modeling & CAD (5,723), Civil & Structural Engineering (2,727), Building & Landscape Architecture (2,623), Contract Manufacturing (2,163), Electrical & Electronic Engineering (2,029), Energy & Mechanical Engineering (1,506), Interior & Trade Show Design (1,289), Physical Sciences (745), Chemical Engineering (107) |
Accounting & Consulting | 17,293 | Accounting & Bookkeeping (7,325), Recruiting & Human Resources (4,091), Management Consulting & Analysis (2,021), Financial Planning (1,755), Other - Accounting & Consulting (1,493), Personal & Professional Coaching (608) |
Writing | 12,276 | Content Writing (5,975), Editing & Proofreading Services (3,187), Professional & Business Writing (1,864), Sales & Marketing Copywriting (1,250) |
Data Science & Analytics | 9,190 | AI & Machine Learning (4,291), Data Analysis & Testing (2,792), Data Extraction/ETL (1,266), Data Mining & Management (841) |
Customer Service | 7,557 | Customer Service & Tech Support (6,746), Community Management & Tagging (811) |
IT & Networking | 6,809 | Network & System Administration (2,127), DevOps & Solution Architecture (2,003), Information Security & Compliance (1,650), ERP/CRM Software (834), Database Management & Administration (195) |
Translation | 6,718 | Translation & Localization Services (5,658), Language Tutoring & Interpretation (1,060) |
Legal | 6,524 | Corporate & Contract Law (5,431), Public Law (493), International & Immigration Law (382), Finance & Tax Law (218) |
industry (the client's industry, 29 values): Tech & IT (19,339), Sales & Marketing (13,387), Media & Entertainment (7,562), Education (5,852), Health & Fitness (5,000), Retail & Consumer Goods (4,812), Art & Design (4,434), HR & Business Services (3,984), Fashion & Beauty (3,945), Real Estate (3,828), Finance & Accounting (3,455), Manufacturing & Construction (2,682), Engineering & Architecture (2,363), Food & Beverage (1,882), Travel & Hospitality (1,580), Automotive (1,316), Legal (1,253), Energy & Utilities (915), Sports & Recreation (911), Science & Medicine (718), Nonprofit (519), Transportation & Warehousing (487), Supply Chain & Logistics (387), Agriculture & Forestry (373), Government & Public Sector (370), Aerospace (185), Aviation (98), Mining (63), Military & Defense (43).
📋 Complete Field Reference
Essential Job Information
| Field | Type | Operators | Examples | Description |
|---|---|---|---|---|
title | text | contains, =null, !=null | Build a React Dashboard with Real-time Analytics, Virtual Assistant for Email Management, Logo Design for Tech Startup | The job posting title as written by the client. Contains the main description of what work needs to be done. |
description | text | contains, =null, !=null | We need an experienced React developer to build a ..., Looking for a Python expert who can integrate machine learning models ... | Full job description text as written by the client. Contains detailed requirements, expectations, and project scope. |
skills | text | contains, =null, !=null | JavaScript, React, Node.js, Python, Machine Learning, TensorFlow | Comma-separated list of required skills and technologies for the job as specified by the client. |
price_type | select | equals | Fixed-price, Hourly | How the job is priced: Fixed Price (one-time payment) or Hourly (paid per hour worked). |
price | numeric | >=, <=, >, <, =, ranges | 500, 1200, 50 | The budget amount for the fixed-price job. |
price_min | numeric | >=, <=, >, <, =, ranges | 25, 50, 100 | Lower end of the job's hourly rate band (Upwork jobs advertise a range like $10-35/hr). Hourly jobs only - fixed-price jobs carry their budget in price and leave this empty. |
price_max | numeric | >=, <=, >, <, =, ranges | 75, 150, 500 | Upper end of the job's hourly rate band (Upwork jobs advertise a range like $10-35/hr). Hourly jobs only - fixed-price jobs carry their budget in price and leave this empty. |
category_name | select | equals | Accounting & Consulting, Admin Support, Customer Service, Data Science & Analytics, Design & Creative, +7 more | Upwork's top-level category of the job. The value must match exactly. One of 12, most jobs first: "Design & Creative", "Sales & Marketing", "Web, Mobile & Software Dev", "Admin Support", "Engineering & Architecture", "Accounting & Consulting", "Writing", "Data Science & Analytics", "Customer Service", "IT & Networking", "Translation", "Legal". |
subcategory_name | select | equals | 3D Modeling & CAD, Accounting & Bookkeeping, AI & Machine Learning, AI Apps & Integration, Art & Illustration, +59 more | Upwork's subcategory, one level under category_name. The value must match exactly. Most common: "Digital Marketing", "Video & Animation", "Graphic, Editorial & Presentation Design", "Lead Generation & Telemarketing", "Web Development", "Virtual Assistance". All 64 values, grouped under their category, are in the README section "Categories and subcategories". |
date_posted | date | >=, <=, >, <, =, equals | 7d, 24h, >=2026-05-01, 2026-05-17 | When the job was posted on Upwork. Use this to find recent opportunities or analyze posting patterns. |
url | text | contains, =null, !=null | https://www.upwork.com/jobs/~01234567890abcdef, https://www.upwork.com/jobs/~987654321fedcba09 | Direct link to the job posting on Upwork. Use this to view the full job details or apply. |
keywords | text | contains | python machine learning, react javascript typescript, design ui ux figma, marketing seo content, data analysis sql python | Search the job's title, description, skills, categories and AI-generated fields. A new search matches by meaning: every job with all your words, plus jobs about the same thing in other words (see keywords_match). a OR b, "quotes" and -word ask for an exact word match: bare words must all appear, OR separates alternatives, quotes keep a phrase, a leading minus drops a word. For a whole field of work, set category_name instead: it is exact (e.g. "Web, Mobile & Software Dev"). |
exclude_keywords | text | contains | wordpress php, data entry copy paste, logo design graphic, social media marketing, excel spreadsheet manual | Drop jobs containing any of these words. Quotes keep a phrase together ("data entry" wordpress drops both), and you can write OR yourself. Searches the same text fields as keywords. |
Job Requirements
| Field | Type | Operators | Examples | Description |
|---|---|---|---|---|
experience_level | select | equals | Entry_level, Expert, Intermediate | Required experience level for the job. Exact values: Entry_level, Intermediate, Expert. |
engagement_label | select | equals | 1 to 3 months, 3 to 6 months, Less than 1 month, More than 6 months | Expected duration or type of engagement (e.g., 1 to 3 months, 3 to 6 months, Less than 1 month, Less than 1 week, More than 6 months). |
engagement_weeks | select | equals | 3, 9, 18, 52 | Project duration in weeks as Upwork encodes it. Values seen: 3, 9, 18, 52. |
qual_min_hours_week | select | equals | 0, 10, 30, 40 | Minimum hours per week the client requires for hourly jobs. Values seen: 0 (none stated), 10, 30, 40. |
qual_min_success_score | select | equals | 0, 80, 90 | Minimum Upwork success score required to apply for the job. |
qual_pref_english | select | equals | ANY, CONVERSATIONAL, FLUENT, NATIVE | Client's preferred English proficiency level for freelancers. |
qual_rising_talent | boolean | equals | True, False | Whether the job is open to Upwork Rising Talent (newer freelancers with potential). |
qual_portfolio_required | boolean | equals | True, False | Whether the client requires a portfolio or work samples to apply. |
qual_type | select | equals | AGENCY, ANY, INDEPENDENT | Type of freelancer the client is looking for: Agency (team/company), Independent (solo freelancer), or Any (no preference). |
Client Information
| Field | Type | Operators | Examples | Description |
|---|---|---|---|---|
client_location | text | contains, =null, !=null | United States, United Kingdom, Canada | Geographic location of the client posting the job. |
jobs_posted | numeric | >=, <=, >, <, =, ranges | 1, 10, >50 | How many jobs this client has posted on Upwork over their whole account — a count, not a date. "10" means 10 or more; use date_posted or created_at for a time window. |
hire_rate | numeric | >=, <=, >, <, =, ranges | 75, 90, 50 | Upwork's own hire rate for the client: the share of their posted jobs that led to a hire, 0-100. It is not hires divided by jobs posted (a job can hire several people). High values mean a client who hires rather than browses. |
avg_hourly_rate | numeric | >=, <=, >, <, =, ranges | 45.50, 75.00, >=100 | Average hourly rate this client typically pays freelancers. Based on their historical hiring patterns. |
open_jobs | numeric | >=, <=, >, <, =, ranges | 0, 2, <5 | Number of jobs the client currently has open/active. |
total_spent | numeric | >=, <=, >, <, =, ranges | 100, 500, >=1000000 | Total amount the client has spent on Upwork across all their projects. |
hires | numeric | >=, <=, >, <, =, ranges | 1, 10, >50 | Total number of freelancers hired by the client. |
active_hires | numeric | >=, <=, >, <, =, ranges | 1, 10, >50 | Number of freelancers currently hired by the client. |
total_hours | numeric | >=, <=, >, <, =, ranges | 100, 500, >=1000 | Total number of hours the client has worked on Upwork across all their projects. |
industry | select | equals | Aerospace, Agriculture & Forestry, Art & Design, Automotive, Aviation, +24 more | The client's industry, as the client set it on Upwork. The value must match exactly. Most common: "Tech & IT", "Sales & Marketing", "Media & Entertainment", "Education", "Health & Fitness", "Retail & Consumer Goods". All 29 values in the last 90 days are in the list. |
company_size | select | equals | 0, 1, 10, 100, 1000, +4 more | Size of the client's company. Predefined option for company size. Select from available choices. |
buyer_city | text | contains, =null, !=null | New York, London, Paris, Tokyo, Sydney, Berlin, Rome, Madrid, Amsterdam, Mumbai, Beijing, Delhi | City where the client is located. |
buyer_feedback_count | numeric | >=, <=, >, <, =, ranges | 1, 10, >=2 | Number of feedbacks the client has received. |
buyer_score | numeric | >=, <=, >, <, =, ranges | >=4.7, 5.0 | Score of the client's performance on Upwork. |
buyer_contract_date | date | >=, <=, >, <, =, equals | >=2024-01-15, 2024-01-20T14:30:00Z, 365d | Date when the client first registered their account on Upwork. Indicates how long the client has been active on the platform. |
buyer_payment_verified | boolean | equals | True, False | Boolean flag indicating whether the client has verified their payment method on Upwork. |
client_total_applicants | numeric | (read-only, not a filter input) | - | Proposals the job has received. The newest reading we have — refreshed_at says when; while it is null this is still the value from the job's first minutes. |
client_total_invited | numeric | (read-only, not a filter input) | - | Freelancers the client has invited to interview. The newest reading we have — refreshed_at says when; while it is null this is still the value from the job's first minutes. |
client_unanswered_invites | numeric | (read-only, not a filter input) | - | Invites the client has sent that are still unanswered. The newest reading we have — refreshed_at says when; while it is null this is still the value from the job's first minutes. |
client_positions_to_hire | numeric | (read-only, not a filter input) | - | How many freelancers the client intends to hire for this job (1 for most postings). |
client_last_activity | date | (read-only, not a filter input) | - | When the client last acted on this job (viewed proposals, sent invites). The newest reading we have — refreshed_at says when; while it is null this is still the value from the job's first minutes. |
qual_countries | array | (read-only, not a filter input) | - | Countries the client restricted applicants to, as listed on the job; empty when the job is open worldwide (85% of postings). |
AI-Powered Insights
| Field | Type | Operators | Examples | Description |
|---|---|---|---|---|
ai_urgency | select | equals | Immediate, Long-Term, Moderately Urgent, Not Urgent, Urgent, Very Urgent | AI-detected urgency level of the job based on language and posting patterns. |
ai_duration | select | equals | Flexible, Flexible Deadline, Long-Term, Mid-Term, Part-Time, Short-Term | AI-detected project duration based on job description analysis. Indicates expected length and type of engagement. |
ai_deadline | select | equals | Fixed Deadline, Flexible Deadline, Immediate Deadline, No Deadline | AI-detected deadline type for the job based on urgency indicators and time-sensitive language in the job description. |
ai_technical_skills | text | contains, =null, !=null | JavaScript, React, Node.js, Python, Django, PostgreSQL, AWS, Docker, Kubernetes | Technical skills explicitly mentioned in the job description, extracted using AI. These are skills directly stated by the client as requirements or preferences. |
ai_inferred_technical_skills | text | contains, =null, !=null | Git, REST APIs, Testing, Database Design, Security, Responsive Design, SEO | Technical skills inferred by AI from the job description context, even when not explicitly mentioned. These are skills likely needed based on project requirements and industry patterns. |
ai_explicit_mention_of_agency | select | equals | Agencies Welcome, No Agencies, No Mention | AI-detected explicit mention of agency preferences in the job posting. Indicates whether the client welcomes agencies, prefers individual freelancers, or has no specific preference. |
ai_clients_technical_understanding | select | equals | Expert, High, Low, Moderate | AI-detected assessment of the client's technical understanding based on how they describe their project requirements. Helps identify whether the client has deep technical knowledge, moderate understanding, basic knowledge, or expert-level expertise in the domain. |
System Metadata
| Field | Type | Operators | Examples | Description |
|---|---|---|---|---|
id | text | contains, =null, !=null | 022053503680858460814, 021987654321098765432 | Unique identifier for the job posting on Upwork. Use this to track specific jobs or avoid duplicates. |
created_at | date | >=, <=, >, <, =, equals | 2024-01-20T14:30:00Z | When this job entered our index — a start point you own. Pass >= the newest created_at you have received and you get exactly the jobs added since, including ones Upwork listed late. Replayable and shareable, unlike a vendor-held 'since last run' state. |
questions | array | (read-only, not a filter input) | ['Name one long-tail keyword you would target for a UK mortgage and investment app and explain why?', 'Share a specific SEO or ASO result you have driven. Include the numbers?'], ['Are you willing to undergo a background check, in accordance with local law/regulations?', 'How soon can be available for work?', 'What is your level of proficiency in English?', 'Briefly describe your experience with Oracle Fusion HCM'] | Upwork screening questions captured for the job, as written by the client. Empty array [] for ~83% of postings (jobs without screening questions). When present, contains 1-6 question strings. Feed straight into a proposal-answer generator to pre-write answers, or surface as an application checklist. |
job_score | numeric | >=, <=, >, <, =, ranges | >=50, >=70, 60-90 | Rule-based 0–100 score that ranks the job by attractiveness. Weighted across price (20), client reputation (42), client spending (40), premium status (5), and location (10). Because 82 of those 100 points come from client reputation and spending, a threshold mostly selects for good clients rather than good briefs. |
job_score_breakdown | object | (read-only, not a filter input) | - | Always-included explanation of the job_score: per-category raw/normalized/weight, ranked top_contributors, and penalties. Use to surface why a job ranked high in UI, or to debug threshold filters. See Output Format below. |
🎯 Smart Usage Patterns
💼 Freelancer Job Alerts
// High-value React jobs from quality clients{"notifications_only": true,"skills": "React","price_min": ">=60","buyer_score": ">=4.0","buyer_payment_verified": true,"ai_urgency": "Urgent"}// Remote-friendly design jobs{"keywords": "design ui ux figma","price_type": "Fixed-price","price": "500-5000"}
🏢 Agency Lead Generation
// Jobs explicitly welcoming agencies or teams{"ai_explicit_mention_of_agency": "Agencies Welcome","price_min": ">=100","total_spent": ">=10000"}// Enterprise projects with team requirements{"ai_explicit_mention_of_agency": "Agencies Welcome","engagement_label": "More than 6 months","company_size": "1000","ai_clients_technical_understanding": "High"}// High-value development projects for agencies{"ai_explicit_mention_of_agency": "Agencies Welcome","keywords": "react node.js typescript","price": ">=25000","ai_duration": "Long-Term","buyer_score": ">=4.5"}
🔍 Market Research & Analysis
// Track AI/ML job trends and pricing{"keywords": "machine learning artificial intelligence","date_posted": ">=2025-01-01","limit": 100}// Monitor mobile app development market{"skills": "React Native","price_type": "Fixed-price","date_posted": ">=2024-12-01","experience_level": "Intermediate"}// Analyze client spending patterns{"total_spent": ">=100000","hire_rate": ">=90","buyer_payment_verified": true,"limit": 50}
🎯 Specialized Niches
// Blockchain & crypto projects with quality clients{"keywords": "blockchain cryptocurrency solidity ethereum","exclude_keywords": "scam pyramid scheme","ai_clients_technical_understanding": "High","price_min": ">=50"}// Technical writing for clients who understand the work{"keywords": "technical writing documentation","ai_clients_technical_understanding": "Moderate","experience_level": "Intermediate","price_min": ">=30","qual_pref_english": "FLUENT"}// Urgent fixes for immediate delivery{"ai_urgency": "Immediate","ai_deadline": "Fixed Deadline","keywords": "bug fix maintenance urgent","ai_duration": "Short-Term","price_min": ">=40","date_posted": ">=2025-01-01"}
🚀 Notification & Automation
// Daily new job alerts for Python developers{"notifications_only": true,"skills": "Python","price_min": ">=45","buyer_score": ">=3.5","limit": 20}// Weekend side project hunting{"engagement_label": "Less than 1 month","price_type": "Fixed-price","price": "1000-10000"}
⚡ Performance
- Typical run: ~8 seconds end to end for 100 results. Most of that is Apify container start-up — the query behind it returns in well under a second.
- Jobs are served from a pre-indexed store, not scraped live per run. That's why a 100-result run takes seconds rather than minutes, and why filters run against the whole corpus instead of one page of search results. The posting is indexed once; its moving counts and budget are re-read on a schedule and served from the same store — see "What is captured once, and what is kept current" above.
🚨 Common Issues & Solutions
Issue: "No jobs returned"
- Solution: Check your filters aren't too restrictive
- Tip: Start with broader filters and narrow down
Issue: "Same jobs appearing"
-
Solution: Use
notifications_only: truefor new jobs only -
Tip: Cursor tracking is automatic per user session
-
Tip: Use higher
limitvalues to reduce request frequency
🔗 Integration Examples
Webhook Integration
// Set up webhook to receive new job notificationsconst webhook = await apifyClient.webhooks().create({eventTypes: ['ACTOR.RUN.SUCCEEDED'],requestUrl: 'https://your-app.com/webhook/new-jobs'});
Slack Bot Integration
// Post new jobs to Slack channelconst run = await client.actor('hyperbach/upwork-scraper-ai').call({notifications_only: true,skills: 'React',price_min: '>=50'});const { items } = await client.dataset(run.defaultDatasetId).listItems();items.forEach(job => {slack.postMessage({channel: '#job-alerts',text: `New ${job.skills} job: ${job.title} - $${job.price_min}+/hr`});});jobs.items.forEach(job => {slack.postMessage({channel: '#job-alerts',text: `🚀 New ${job.skills} job: ${job.title} - $${job.price_min}+/hr`});});
📊 Need historical data?
For research, market sizing or model training, the live scraper isn't the right tool. Upwork Jobs History has every Upwork posting since November 2024 — over 3 million — with the full description, budget, skills, and the client's spend and hires as they stood on the day the job was posted. Take the last 3, 6 or 12 months or all of it, narrowed by category and skill; a free estimate prices any selection before you buy it.
A one-time download of the 2024–2025 dataset in CSV, JSON, Parquet, SQLite and DuckDB is also on Gumroad.
🆘 Support
Found an issue or have a feature request? Open an issue on the Issues tab. We respond within ~48 hours.
Contact: apify@hyperbach.com