Upwork's API Can't Be Your Job Feed
Every agent built on Upwork starts at the same place: a live feed of jobs worth bidding on. Score them, filter them, hand your agent the ten that matter instead of the four hundred that do not.
So you go looking for the official route. Upwork has a GraphQL API, and since August 2026 an MCP server at https://mcp.upwork.com/mcp sitting in front of it. Both look like the answer. Neither one is, and there are two separate walls in the way: you probably cannot get access, and access would not permit continuous discovery anyway.
Wall one: the key
Upwork's API is not self-serve. You request a key through the Developer Center, and it stays disabled until it passes review. Most freelancers and small agencies never get one approved, and a review queue is not something you can design a product around.
The MCP looks like a way past that, because it authorizes over OAuth against your own Upwork account with no key at all. It is, but only inside a client Upwork has decided to allow.
We tested this directly on 22/08/2026. Upwork's authorization server advertises dynamic client registration, but registration is an exact-host allowlist. Registering identical client metadata with different callbacks, claude.ai, cursor.com, chatgpt.com and loopback addresses are accepted; claude.com, example.com and uphunt.io come back with invalid_redirect_uri. Upwork's servers do fetch an unlisted client's metadata document, we can see the 200 in our access logs, and then refuse it with "Client not found or disabled", an error that reads like you never registered at all.
A Developer Center key does not rescue this either. It lives in a separate registry: the shared token endpoint returns invalid_client for one, the same response you get for an id you made up.
So the honest summary is that you can use Upwork's MCP by hand, inside somebody else's allowlisted app. You cannot build on it.
Wall two: continuous monitoring is out of scope
Suppose you clear the first wall. find_jobs exists and it searches. Why not point it at a cron and keep an index warm?
Because Upwork's API & MCP Terms of Use say you cannot. Section 4.1, on purpose limitation, permits searching and browsing, then adds:
"For clarity, permission to search or browse Upwork Content authorizes only access reasonably necessary to perform a specific, documented, user-directed task and does not authorize activity designed to enumerate or continuously monitor Upwork's available content corpus."
That is the sentence. Polling every few minutes to maintain a live view of open jobs is enumeration and continuous monitoring. It is not a rate limit you can negotiate around or a quota you can pay to raise. It is a scope boundary.
Section 7.2 closes the obvious workarounds by name, prohibiting "creating multiple Developer Applications for identical or substantially similar purposes; fanning load across multiple accounts, Credentials, or scope tokens; rotating IP addresses or headers to evade limits."
The official surface is built for user-directed work: this user, this job, right now. It was never meant to be your search index.
And rolling your own scraper is worse
The natural next thought is to scrape listings yourself and keep the official API for everything else. Section 5.2 anticipates that. It defines anything obtained through "scraping, crawling, spidering, headless browsing, web-browser plug-ins, optical character recognition ... multi-account fan-out, header spoofing, IP rotation, or any other technology used to access Upwork content outside the Tools" as Non-Official Content, and then prohibits two things in the same breath:
"access, store, display, transfer, or use any Upwork content obtained through [those techniques] ...; combine Upwork Content with Non-Official Content"
Read that second clause carefully if you are building a hybrid. Feeding a scraped job id into an official call is combining the two in the most literal sense available. And Section 1.1 says you accept these terms simply by calling a Tool, so the moment you connect, the scraper you were running beside it stops being a separate concern sitting outside your agreement.
This is the part most teams have not read yet, and it is the part that decides your architecture.
Discovery is a different problem, so it wants a different server
| Upwork API and MCP | UpHunt MCP | |
|---|---|---|
| What it is for | Acting on one specific job or contract | Finding which jobs are worth acting on |
| Getting in | Key review, or an allowlisted client | Your UpHunt API key, generated in seconds |
| Continuous feed | Not permitted (Section 4.1) | Yes, that is its purpose |
| Scoring | None | AI score and a plain-language reason per job |
| Client history | Per contract | Spend, hire rate, open jobs before you bid |
| Runs | Hosted by Upwork | Locally, on your machine |
UpHunt has shipped an MCP server since before Upwork announced theirs, and it does the half the official one deliberately does not: a live, scored, filterable job feed your agent can query as often as it likes, plus the client research you want before committing Connects.
Four tools, all read-only:
search_jobs: search your live AI-scored feed by keyword, budget, job type, and minimum match scoreget_job: full details for any job by URL or ciphertext, including budget, skills, and screening questionsget_client: a buyer's open jobs, hire history, and spend statslist_applied_jobs: what you have already applied to, with scores and current status
Add it to any MCP client. Claude Desktop, Cursor, Cline, Windsurf, and Claude Code all work:
{
"mcpServers": {
"uphunt": {
"command": "npx",
"args": ["-y", "uphunt-mcp"],
"env": {
"UPHUNT_API_KEY": "your_api_key_here"
}
}
}
}
Generate the key in your UpHunt dashboard under Auto-Apply, API and Webhooks, then restart your client. No review queue, no allowlist.
What a real feed has to do that an API call does not
If you have only ever fetched a search endpoint, the gap is easy to underestimate. Continuous discovery is not one call on a timer:
Freshness that survives the first hour. Most jobs collect the bulk of their proposals early. A feed is only useful if a posting reaches you in minutes, which means the ingestion has to run whether or not you happen to be at your desk.
Scoring against your profile, not the market's. A keyword match is not a fit. The useful filter is a score with a reason attached, so you can read ten jobs a day instead of skimming four hundred titles.
Client history before you spend Connects. Total spend, hire rate, how many jobs the buyer has posted and never hired on, what they paid last time. That is what decides whether a good-looking job is worth 21 Connects, and none of it is in a job listing.
The short version
Upwork's official API and its MCP are transaction surfaces: authenticated, user-directed, one action at a time. That is a reasonable thing for them to be, and if you have access, use them for that.
Just do not expect either to be your job search. Upwork wrote that boundary into Section 4.1 on purpose, Section 5.2 closes the workaround, and the allowlist means most builders never reach the question. Point your agent at a server built for discovery instead.
Try the UpHunt MCP server or start free and get your API key.