Upwork Rate & Market Intelligence β Freelance Skill Rates
Pricing
from $475.30 / 1,000 skill benchmarks
Upwork Rate & Market Intelligence β Freelance Skill Rates
Benchmark any freelance skill on Upwork: live demand (open jobs), hourly & fixed rate distribution (median, p25, p75), hourly-vs-fixed split, experience-tier mix, posting freshness and adjacent skills. One clean row per skill.
Pricing
from $475.30 / 1,000 skill benchmarks
Rating
0.0
(0)
Developer
Vitalii Bondarev
Maintained by CommunityActor stats
0
Bookmarked
4
Total users
4
Monthly active users
2 days ago
Last modified
Share
Stop guessing what a freelance skill is worth. Give this Actor a list of skills and it returns one clean benchmark row per skill β live demand, the real rate distribution (median, 25th and 75th percentile, full range), the hourly-vs-fixed split, the experience level clients are actually hiring, how fresh the market is, typical engagement length, and the skills each one is hired alongside.
It is built for people who need to price and position, not scroll job feeds: freelancers setting their rate, agencies scoping a new service line, recruiters and talent teams benchmarking contractor cost, analysts tracking the freelance economy, and AI agents that need structured labor-market data on demand.
What you get β one row per skill
Each skill you pass in produces a single, decision-ready record. No raw-posting dump to clean up yourself β the Actor samples the live market and does the statistics for you.
| Field | What it tells you |
|---|---|
skill | The skill / keyword you benchmarked |
demand_open_jobs | Live demand β the total number of open jobs for this skill right now |
sample_size | How many recent postings were sampled for the rate stats |
hourly_median_usd / fixed_median_usd | Headline median rates, ready for a table |
hourly_rate_usd | Full hourly distribution: median, p25, p75, min, max, mean, count |
hourly_min_usd / hourly_max_usd | Distribution of the posted hourly band edges |
fixed_budget_usd | Full fixed-price budget distribution (same stats) |
job_type_split | Hourly vs fixed counts and shares β how this skill is usually contracted |
experience_mix | Entry / intermediate / expert counts + expert_share (how senior the demand is) |
freshness | Postings in the last 24h / 7d / 30d, newest/oldest age, and an estimated posts-per-day velocity |
engagement_weeks | Typical project length distribution where stated |
adjacent_skills | The skills most often requested alongside this one (with counts and shares) |
scraped_at | ISO timestamp of the run |
Why the percentiles matter
A single "average rate" hides the market. The p25βp75 band tells you where the bulk of
real jobs sit, so a freelancer knows whether their rate is competitive or leaving money on
the table, and an agency knows what a project will actually cost to staff. The
expert_share and experience_mix tell you who the demand is for β a skill that's 70%
expert-level pays very differently from one that's mostly entry-level work.
Why the adjacency map matters
adjacent_skills is the quiet superpower: it shows what each skill is bundled with in real
postings. If you sell "React" and the market keeps pairing it with "TypeScript" and "Next.js",
that's your upsell and your positioning, straight from demand data.
Input
{"skills": ["python developer", "react", "copywriting"],"sampleSize": 100,"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US" }}
| Input | Type | Default | Description |
|---|---|---|---|
skills | array of strings | β | One or more skills/keywords to benchmark. Each runs independently and yields one row. |
sampleSize | integer | 100 | Recent postings sampled per skill for the rate stats (10β500). Demand counts are always the full market total. |
proxyConfiguration | object | RESIDENTIAL / US | Apify proxy used to reach the target. Billed to the run owner. |
Tip: start with a handful of skills and sampleSize 100 for a fast, cheap read. Bump
sampleSize toward 300β500 when you want tighter, more stable percentiles on a high-volume
skill.
Example output (abridged)
{"skill": "python developer","demand_open_jobs": 1880,"sample_size": 100,"hourly_median_usd": 38.0,"fixed_median_usd": 750.0,"expert_share": 0.41,"hourly_rate_usd": { "median": 38.0, "p25": 25.0, "p75": 60.0, "min": 12.0, "max": 150.0, "count": 63 },"job_type_split": { "hourly": 63, "fixed": 37, "hourly_share": 0.63, "fixed_share": 0.37 },"experience_mix": { "entry": 18, "intermediate": 41, "expert": 41, "expert_share": 0.41 },"freshness": { "posted_24h": 22, "posted_7d": 71, "est_posts_per_day": 9.4 },"adjacent_skills": [ { "skill": "Django", "count": 31, "share": 0.31 }, { "skill": "AWS", "count": 24, "share": 0.24 } ]}
How it works
The Actor reaches Upwork's public job search through the site's own visitor API. Upwork is a protected target β it requires a residential proxy for reliable access β so the Actor routes every request through the Apify RESIDENTIAL proxy on a sticky session, obtains a short-lived visitor token the same way the public site does, and then queries the public job-search endpoint for each skill. It samples the most recent postings (so the freshness and velocity numbers reflect the live market), computes the distributions, and writes one row per skill.
Only public job-posting data is collected β titles, descriptions, skills, budgets, experience tier, post time and the search-wide demand count. No client-account or applicant data is requested.
Pricing
This Actor is pay-per-result: you are charged per skill successfully benchmarked. A skill that returns no live postings is not charged. Apify platform/proxy usage during the run is billed to your own account by Apify, as with any Actor.
Common uses
- Set your freelance rate against the real p25βp75 band for your skill.
- Scope an agency service line β see demand, senior-share and typical project budget before you staff it.
- Benchmark contractor cost for a hiring or finance team.
- Track the freelance economy β run the same skill list on a schedule and watch demand and rates move.
- Feed an AI agent structured, on-demand labor-market data via the Apify API or MCP.
FAQ
Does this need my Upwork login? No. It uses only the public job search; you never provide credentials.
Why a residential proxy? The target requires it for reliable access. It's the default and billed to your run.
Can I benchmark non-tech skills? Yes β pass any skill or keyword (copywriting,
video editing, bookkeeping, voice over, β¦). The same benchmark is computed for all.
How fresh is the data? Every run is live. The freshness block tells you exactly how
recent the sampled postings are.
Rate this Actor
If Upwork Rate & Market Intelligence saved you time, please rate it on the Apify Store. A rating takes a few seconds and helps other people find a working scraper. If something did not work, open an issue on the Actor page and we will fix it.
Usage statistics
This Actor creates a small, content-free summary at the end of each run. It is used only to monitor reliability and improve this Actor. A copy is saved as USAGE_STATS in your own Apify key-value store, so you can see the exact record created for your run.
Set disableUsageStats to true in the input to opt out. Nothing is sent then; your USAGE_STATS record only says that statistics were disabled.
Only these fields are recorded:
- schema version, Actor name and build number;
- UTC start and finish hour (not a precise timestamp);
- run duration, number of results and time to the first result, each as a coarse range;
- whether the result was empty, the end status, and an error type from a fixed list;
- memory setting and counts of charged events;
- names of the input fields you set, never their values;
- the selected option for input fields that offer a fixed list of choices (for example a sort order).
We do not collect input text, search terms, URLs, domains, usernames, email addresses, names, proxy credentials, tokens, scraped records, output items, raw error messages, stack traces, or hashes of any of those values. Records are kept for no longer than 13 months, used only as aggregated operational statistics, and never sold or shared.
Additional fields (Phase 2)
This Actor also records your Apify user ID, whether Apify marks the account as paying, the size range of list inputs, the selected country when the input offers a fixed list of countries, and one category from a fixed Actor taxonomy. We use these fields only for aggregate reliability, repeat-use and cross-Actor analysis; reports suppress any cell with fewer than five distinct users.
The same disableUsageStats: true input flag turns these fields off too. The user ID is removed after 13 months; we do not export, sell, share, or attempt to re-identify this data.
Run-outcome signals (v2)
To learn whether a run did what it was asked to do, the record also holds a few more coarse ranges and yes/no flags. None of them contains content:
- the result limit you asked for (a range, when the input has one) and what share of it was delivered;
- results delivered per input item you listed (a range);
- output quality as ranges: how fully the result fields were filled, the share of rows that look like errors, the share of duplicate rows, and how many different fields appeared. These are counted in memory while results are saved; no result content is kept;
- how the run was started (console, API, schedule, webhook, another Actor);
- how it ended: stopped by you, timed out, reached the requested limit, stopped by the charge limit, and how many times the platform moved the run;
- if this Actor reports it: how many items to process worked or failed (ranges) and one failure reason from a fixed list;
- a short code made from the names of the input fields you set, never their values.
Repeat-run fingerprint (v2)
When your Apify user ID is recorded (see above), the record also holds an 8-character one-way code made from your input (proxy settings left out) and this Actor's name. It only lets us see that the same account ran the same input again soon after an unsatisfying run; we never see the input itself. It is stored only in the database, never published, and reports use it in aggregate with the same five-user minimum. It is the one exception to the statement above that no hashes are collected, and disableUsageStats: true turns it off.