Upwork Scraper API & MCP for AI Agents | FetchLayer
Unofficial Third-party API
3 endpoints live

Upwork data.
Without an Upwork account.

Job postings & budgets Client spend & ratings Your Upwork search URL MCP ready

Job postings with parsed budgets, skills, durations, client spend and proposal ranges, as clean JSON via REST or MCP. Search by keyword, or paste an Upwork search URL.

Free tier included No credit card No scraper maintenance

Trusted by developers shipping real products.

246,915 served today

POST /upwork/search-jobs
200 OK

Request

$ curl -X POST \
  https://api.fetchlayer.dev/upwork/search-jobs \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"query":"python developer","limit":5}'

Response

{
  "totalJobs": 5, "enrichedJobs": 0,
  "jobs": [{
    "title": "Full Stack Developer for Web App",
    "jobType": "hourly",
    "budget": { "type": "hourly",
      "min": 15, "max": 100, "currency": "USD" },
    "proposals": null,
    "proposalsRange": { "label": "50+" },
    "clientPaymentVerified": true,
    "enriched": false
  }], "pagesFetched": 1
}

Used by developers at

Individual developers or teams — not official partnerships

Built for feedback workflows

Fetch the public signal. Use it wherever your team works.

What the client is paying, ready to sort

Every posting’s budget arrives as numbers you can rank on: the fixed price, or the hourly range with its currency, alongside the exact string Upwork displayed. Sort a day of postings by what the work is worth without writing a parser for "$15.00-$100.00/hr".

Your Upwork search, as an API call

Paste the search page address you already built on Upwork as searchUrl, and its filters come across as-is. No re-expressing a saved search as a dozen parameters, and no guessing at the spelling of a budget band.

The client, not just the posting

Location, whether their payment method is verified, their feedback score and how many reviews it rests on, and what they have spent on Upwork to date — so you can rank demand by who is actually paying rather than by who is posting.

The whole posting, not the preview

Title and full body, the skills asked for, category, experience level and expected duration — and on a posting you open in detail, its screening questions and the client’s hire history. Enough to decide whether the work is worth bidding on without opening Upwork.

Posting times you can order by

Every posting carries an ISO timestamp, so a day’s new work sorts by when it landed rather than by "Posted 1 hour ago". Where Upwork published only that relative label, the response says the timestamp was derived from it — so you know which ones to trust to the second.

How much competition a posting has

The exact proposal count where Upwork publishes one, and Upwork’s own band — "50+", "10 to 15" — where it does not. They are separate fields on purpose, so a posting nobody has applied to never looks the same as one whose count was simply not published.

FetchLayer Research Chat

Ask what the freelance market is paying—then export it.

Prefer not to write code? Describe the work in FetchLayer Research Chat and get a table back, with the postings whose detail was not available marked as such rather than quietly averaged in.

  • Ask questions in plain English
  • Analyze themes, sentiment, and pain points
  • Download the structured results as CSV or JSON
Search Upwork in Chat

No code or setup required · Uses your FetchLayer credits

Upwork research

Find Upwork postings for "python developer" from payment-verified clients, and give me the hourly and fixed budgets, the skills asked for and how many proposals each has.

Y

Twenty-eight postings. Hourly rates cluster at $20–45/hr, with two outliers to $100. Eleven are fixed-price, median $600. Nine came back with an exact proposal count; the rest show a range like "50+", and those are marked so you do not read them as zero.

upwork-postings.csv

28 postings · 9 columns · ready to download

CSV

From raw feedback to action

One request is the start of the workflow.

Store the structured response, run it on a schedule, pass it to an LLM, or feed it into the system your team already uses. FetchLayer handles retrieval so you can focus on the outcome.

  1. 01

    Search, or paste a search

    Send a keyword with filters, or the address of an Upwork search page you already built. One of the two is required, so a request never turns into an unfiltered sweep of everything by accident.

  2. 02

    Read enrichedJobs first

    Compare it to the number of postings you got back. Equal, and every row is the full record. Lower, and the rest are listing-level — filter on each row's enriched flag before you aggregate anything.

  3. 03

    Go deeper on the ones that matter

    Send a posting's id or URL to job-detail for its exact time, exact proposal count, screening questions and the client's hire history. It is best-effort and says so: a 503 means retry, not that the posting is gone.

MCP & agents

Live

Your AI already knows how to use this.

Use the Upwork endpoint directly from your MCP-compatible agent. Ask it to retrieve public feedback, identify patterns, and turn the result into a useful brief without writing integration glue.

1
Pick your editor
2
Agent discovers 3 tools
3
Query any platform instantly
connect your agent ~30s

Pick your editor and we hand you the config with your key already in it — no JSON to hand-edit, no wrapper code.

Get your free API key

Free tier · no credit card · any MCP host

New chat fetchlayer connected

What are clients paying for Python work on Upwork right now?

Used fetchlayer.upwork_search_jobs

Five postings on the first page. Hourly budgets run $15–100/hr; fixed-price ones from $10 to $2,760. Four clients are payment-verified. enrichedJobs was 0 of 5, so exact proposal counts were not available on this pass — the ranges are there ("50+", "10 to 15") and I have not treated any of them as zero.

Ask a follow-up…

Works with every MCP-compatible tool

Cursor
Claude
VS Code
Windsurf
Cline
Hermes
OpenClaw
Kiro
No wrapper code
All 3 endpoints exposed as tools
Official & maintained
Free to install

Use cases

What teams build on public freelance demand.

Postings, budgets and the clients behind them, as rows you can sum. Here is what that is for.

01

Freelance rate benchmarking

Budgets arrive as numbers, not display strings, so a week of postings for a skill sums into a real hourly and fixed-price distribution instead of a pile of text to regex.

pricingresearchreporting
02

Demand tracking by skill

Run the same search on a schedule and watch which skills, durations and experience levels clients are actually posting for — and which have gone quiet.

trendscrondatasets
03

Agency lead sourcing

Filter to payment-verified clients with real spend history, and work the postings where the budget and the track record both stand up.

salesagenciesleads
04

Competitive bid intelligence

Proposal counts and ranges tell you how contested a posting is before you write anything. A "50+" and a null are different answers, and both are labelled.

salesresearchfreelance
05

Job board and newsletter feeds

Reuse a search you already built on Upwork as the query, and turn it into a curated feed with parsed budgets and skill tags on every row.

productscroncontent
06

Talent market reports

Client location, spend and hire history alongside every posting, so a market report is about who is paying rather than only about who is posting.

datasetsreportingresearch

Compare approaches

Spend time on the insight, not the collection layer.

What you needFetchLayerOfficial APIBuild it yourself
Budgets returned as numbersn/aYou parse the string
Incomplete rows labelled per postingn/aYou would not know
Reuse an Upwork search URLn/aYou re-implement filters
No Upwork account requiredn/aYou keep one warm
Ready for AI and MCP workflowsn/aYour integration

FetchLayer provides access to publicly available data. It is not affiliated with, endorsed by, or sponsored by the platform.

Pricing

Start free. Scale when ready.

Pay per request with no commitment, or lock in a flat monthly rate with built-in savings.

Free plan — 30 requests

Verify responses, inspect the API, no credit card required.

Try free

Pay as you go

$1.99

per 1,000 requests

$0.00199 per request · credits never expire

How credits work — no multipliers

One credit = one API call, on every endpoint and platform. No matter how many results come back, you pay for the call, not the output size. Most scraping APIs charge multipliers of 5x, 25x, even 75x per call depending on the target — here it's always 1.

  • No multipliers — 1 request = 1 credit
  • No monthly commitment
  • Credits never expire
  • Same API & MCP access as subscribers
  • ∞ req/min, ∞ API keys
Get API key

Expiry

Credits stay until you use them. No monthly reset, no pressure to hit a quota.

0

Commitments

No subscription, no contract, no minimums. Start, pause, or scale whenever.

100%

Access

Every endpoint and MCP included. Not a limited tier — same data as any subscriber.

API reference

Every parameter, documented.

All requests are POST with a JSON body and Bearer auth header.

POST /upwork/search-jobs

Public job postings by keyword or by an Upwork search URL, with the platform's own filters. Every response reports enrichedJobs against the number of postings returned, so a thin result set is visible rather than assumed complete.

query string Keywords, e.g. "python developer". Give this or searchUrl.
searchUrl string An Upwork search page address — its filters come across as-is.
jobType / experienceLevel string 'hourly' | 'fixed'; 'entry' | 'intermediate' | 'expert'
hourlyRateRange / fixedPriceRange string Budget bands, in Upwork's own spelling.
paymentVerified / clientHistory string Narrow to verified clients, or by how much they have hired before.
location / maxJobAge / sort string Where applicants may be, how new, and 'relevance' or 'newest'.
limit / pages / perPage number Up to 500 postings across up to 20 pages, 1 credit per page.
POST /upwork/job-detail

One posting by id or URL, as its fuller record — the exact posting time, the exact proposal count, screening questions and the client's hire history. Best-effort: when that record is not available, this route answers 503 rather than handing back something thinner than you asked for.

jobId string The posting's id, from a search result. Give this or jobUrl.
jobUrl string The posting's full address, from a search result.
POST /upwork/resolve-url

Sort a pile of pasted Upwork links into postings and searches, and read the search terms straight off a results page. Free — it reports pagesFetched: 0.

url* string Any Upwork job or search address.

Fields marked * are required. Full API reference →

FAQ

Common questions.

What is the Upwork Jobs API?

A REST endpoint and MCP tool that returns public Upwork job postings as structured JSON. You send a keyword or an Upwork search URL with a Bearer token; you get back the postings with their titles, bodies, parsed budgets, skills, durations, client location and client spend. No Upwork account, no HTML to parse.

Do I need an Upwork account?

No. Everything returned is what Upwork publishes publicly on its job search — the same postings anyone can read without signing in. There is no login, no OAuth, and nothing to keep warm.

Can I reuse a search I built on Upwork?

Yes, and it is the fastest way to start. Paste the Upwork search page address as searchUrl and the filters on it come across as-is — job type, experience level, budget band, client history, location, posting age. Or pass query and set the filters as fields. One of the two is required: a request with neither is rejected immediately rather than running an unfiltered search of everything.

Are the budgets usable as numbers?

Yes. budget comes back parsed, not as a display string: {type, amount, min, max, currency, raw}. A fixed-price posting fills amount. An hourly posting with a published range fills min and max. An hourly posting where Upwork published no rate fills neither and keeps raw: "Hourly" — so a null min means no rate was published, not that the rate is zero. raw is always the exact string Upwork showed, so you can check any parse by eye.

Is every field filled in on every posting?

No, and this is the thing to know before you build on it. Each posting comes back with the listing-level fields Upwork puts on its results page, always. The fuller record behind a posting — the exact posting time, the exact proposal count, the client's hire rate and average paid rate — is not always available, and how much of it is varies by hour. Nothing is silently invented to fill the gap.

How do I tell which postings have the fuller record?

One comparison: enrichedJobs against jobs.length. Equal means every posting came back complete. Lower means the rest carry listing-level fields only, and each of those is marked enriched: false so you can filter on the row rather than on the total. notes[] says it in words too. Retrying often returns more.

What happens if I read a thin posting as a complete one?

You get a wrong answer that looks right — a posting with proposals: null read as a posting nobody has applied to. That is why enriched is on every row and why proposalsRange is still there when proposals is not: "50+" is a real answer, and null is not the same as zero. Branch on enriched before you aggregate.

How fast is it?

A page of results measured 24 to 28 seconds against production. Reading several pages in one request takes proportionally longer. If you are building something interactive, fetch one page per request and page through it rather than asking for many at once.

How is it billed?

One credit per results page, reported on every response as pagesFetched. A page costs the same whether it returns three postings or fifty, and a posting that came back without its fuller record costs nothing extra. Parsing an Upwork URL is free — it reports pagesFetched: 0.

Can my AI agent use it?

Yes. All three routes are MCP tools on the same FetchLayer MCP server: upwork_search_jobs, upwork_job_detail and upwork_resolve_url. Tool calls use the same key and are billed exactly like REST, and the agent sees enrichedJobs alongside the postings, so it can tell you when a result set is thin instead of presenting it as complete.

Start pulling Public job postings today.

Get a free API key, make your first request, and put public feedback to work in your own product or research workflow.