The Upwork API will not let you send proposals. Here is the one that will.

Ned Thomas pointed Claude Code at our API and built a pipeline that writes a brand new site for every job he bids on, then submits the bid with the link already inside. From scanner match to the client's inbox is twelve seconds.

Ned Thomas Ned Thomas · GigRadar API user
Reconstructed with his permission

Watch it submit a proposal

A scanner match, a coding agent, and a bid on Upwork, start to finish. Nothing loads from YouTube until you press play.

The same run, inside Claude Code

A simulated session, not a live console. Open it on its own.

Not a template with fields swapped. A unique Next.js site, written from scratch for that specific job, deployed to its own URL.

It basically has allowed me to generate entirely custom proposals, links with graphs and also timeline breakdowns plus testimonials, all within 12 seconds of receiving data from GigRadar Scanners. Ned Thomas

Here is the brief one of those pages was built from, and you can open the page it produced.

Full Stack Developer Needed, Healthcare POC AI and Tracking Platform

7 April 2026 · $2,420 fixed

  • Multi tenant healthcare web app tracking patient care plans and progress
  • Role based authentication with admin, provider and staff views
  • React and Next.js front end, Node.js and NestJS back end, PostgreSQL
  • A POC engine with diagnosis based plans, phase progression and conversion tracking

Claude Code wrote and deployed the entire proposal site in under 8 seconds from this brief.

Here is the thing most developers do not realize. The official Upwork API cannot send proposals. Not with any scope, not with any workaround, not with any approval tier. So when Ned says the pipeline auto submits the bid, he is not using Upwork's API. He is using ours.

What the official Upwork API actually does, and does not do

Upwork opened their public GraphQL endpoint at api.upwork.com/graphql in 2023. On paper it looks powerful. You can query job searches, pull client histories, fetch your own profile stats, and read messages from contracts you are already working on.

What you cannot do, and what most growth hackers discover two weeks into a project, is submit a proposal. I went through the full schema last month to confirm this had not quietly changed. It had not.

This is deliberate on Upwork's part, and they have been public about why. Bid spam is the single largest trust issue on the platform, and an open submission API would make it worse.

How GigRadar submits proposals, the Business Manager model

GigRadar is not a partner of Upwork. There is no commercial agreement, no official integration, no private API key from Upwork's engineering team. I want to be exact about this because the wrong version of the story gets told a lot.

What GigRadar operates is a real Upwork Business Manager account. GigRadar owns it as a company, and it sits inside GigRadar's own organization on Upwork the same way any agency's Business Manager account sits inside theirs.

When an agency signs up, they grant our Business Manager access to their own Upwork agency account. That is the same mechanism any agency uses to give a virtual assistant or a hired bidder the ability to see job feeds and submit proposals on their behalf.

There is no scraping. There is no browser extension running on your machine. There is no session cookie from your logged in Upwork account being passed through a cloud.

It is the same action your own hired bidder would take, executed by infrastructure instead of a human clicking through the UI. That is the entire trick, and it is the only legal way to submit proposals programmatically on Upwork right now.

What the API exposes

The endpoints live at api.gigradar.io/public-api/v1/ and authenticate with an X-API-Key header. Four of them matter for Ned's use case.

GigRadar API · core endpoints
# Real-time job feed (your scanner matches)
GET  /public-api/v1/gigs

# Full detail on one job, incl. screening questions
GET  /public-api/v1/opportunities/{id}

# The endpoint that submits a proposal to Upwork
POST /public-api/v1/opportunities/{id}/application

# Register a webhook for new scanner matches
POST /public-api/v1/webhooks

Auth: X-API-Key: <your_key>

GET /gigs returns the job posts your scanners matched, in real time, with the full Upwork payload: title, description, budget, client history, hire rate, Connects cost.

POST /opportunities/{id}/application submits a cover letter and bid amount to Upwork through the Business Manager. This is the one the official API cannot do. It takes the opportunity ID, your cover letter, your rate, and optional screening answers, and returns whether it went through plus the Upwork side proposal ID.

POST /webhooks registers a callback URL, so a new match reaches you instead of you polling for it. Ned's entire pipeline lives inside a single webhook handler.

GET /opportunities/{id} returns full detail on any job, including the screening questions the client attached.

The code you will write

Each piece in the order it runs. The shape is identical in any language; these are the minimum viable versions.

1. The webhook receiver

Your endpoint receives a JSON body with the opportunity ID, title, description and scanner metadata. Validate the signature, hand it to your generation step, return a 200 fast so GigRadar does not retry.

webhook.js · Node + Express
import express from 'express';
const app = express();
app.use(express.json());

app.post('/webhook', async (req, res) => {
  const gig = req.body;
  res.sendStatus(200); // ack fast

  const json = await generateProposal(gig);
  const url  = await deployPage(json);
  await submitToGigRadar(gig.id, json.coverLetter, url, gig.budget);
});

app.listen(3000);

2. The Claude Code session

This is where most developers over engineer. No multi agent orchestration, no RAG, no custom prompt framework. One Claude Code session with a clear instruction: read this job description, scaffold a Next.js project that presents a custom proposal for it, deploy to Vercel, return the URL. Ned's entire system prompt is about 600 tokens.

runClaudeCode.js · spawn a session
import { spawn } from 'child_process';
import fs from 'fs/promises';

async function runClaudeCode(gig) {
  const dir = `/tmp/proposal-${gig.id}`;
  await fs.mkdir(dir, { recursive: true });
  await fs.writeFile(`${dir}/brief.md`, gig.description);

  // Claude Code writes the Next.js project, builds it, deploys it.
  // System prompt lives in ~/.claude/PROPOSAL_PROMPT.md
  return new Promise((resolve, reject) => {
    const cc = spawn('claude', [
      '-p', 'Read brief.md. Scaffold a Next.js proposal site for this job. Deploy to Vercel. Print the URL on the last line.',
      '--allowedTools', 'Write,Bash,Edit'
    ], { cwd: dir });
    let out = '';
    cc.stdout.on('data', d => out += d);
    cc.on('close', () => resolve(out.trim().split('\n').pop()));
    cc.on('error', reject);
  });
}

3. The deploy, handled inside the session

There is no separate deploy step. Claude Code runs the Vercel CLI from inside its own session, captures the URL, and prints it on the last line. Your handler reads stdout.

inside the Claude Code session
# Claude Code runs these itself after it finishes writing the project
# No orchestration code on your side
cd /tmp/proposal-$OPP_ID
npm install
vercel --prod --yes --token $VERCEL_TOKEN
# → https://proposal.nedthomas.co.uk/a5zf4kxxy2cz

4. The submission call

One HTTP call. The response tells you whether the proposal went through, how many Connects it consumed, and the Upwork proposal ID so you can track it later.

submit.py · Python + requests
import requests, os

def submit_to_gigradar(opp_id, cover, page_url, budget):
    body = cover + "\n\nFull proposal with timeline and ROI: " + page_url
    r = requests.post(
        f"https://api.gigradar.io/public-api/v1/opportunities/{opp_id}/application",
        headers={"X-API-Key": os.environ["GIGRADAR_KEY"]},
        json={
            "cover_letter": body,
            "bid_amount": budget,
            "bid_type": "fixed",
            "answers": []
        }
    )
    return r.json()

The question to ask before you build this

I want to be direct about one thing most tutorials skip. Running an automated pipeline against Upwork does not remove your obligation to bid well.

Ned submitting 40 genuinely custom proposals a day at a 22 percent reply rate looks fundamentally different to Upwork's ranking models than someone pushing 200 generic bids at 2 percent. The API is a tool, not a shortcut.

Automation amplifies whatever you are already doing. If your baseline proposals are thoughtful, this is an unfair advantage. If they are not, it is a faster way to lose Connects. Point this pipeline at every job in a category with a lazy prompt and you will still tank your Job Success Score. It works because the bids are actually good.

What you need to start

Three things get you from zero to a working prototype.

  • A GigRadar account with the API add on enabled, and at least one active scanner matching your target jobs.
  • An API key, in your dashboard under Settings, API Access.
  • A host for your webhook receiver and page generator. Vercel, Cloudflare Workers and Render all work.

The minimum setup is one scanner pointed at your niche, filtered for payment verified clients, a minimum budget, and a client hire rate above 60 percent. Ned runs four in parallel on slightly different keyword clusters, because the scanner filters determine the quality of everything downstream. Bad scanner, bad proposals. Claude Code cannot rescue a pipeline pointed at garbage jobs.

Register your webhook at POST /webhooks and within about ten minutes payloads start arriving as jobs appear in the feed. From there you are in your own code, and the only external dependency is the final application call.

Why the agency math changed

The reason I am writing this instead of letting Ned keep his edge is that the economics have quietly shifted, and most people have not caught up.

Two years ago the winning strategy was speed. First five minutes, generic but polished, high volume, low close rate but enough replies to make the math work. Upwork's Uma ranking model has mostly killed that. Speed without relevance gets you buried.

The new winning strategy is relevance at speed, and that is only possible if you automate the relevance work a human cannot do fast enough. Building a custom landing page in 12 seconds is not something a virtual assistant can do. It needs an agent that writes code, and an API that can submit the result without a human touching the Upwork UI.

The agencies winning this quarter figured out the second half of that sentence before their competitors did.

Questions

Can the official Upwork API submit proposals?

No. Upwork's public GraphQL API supports read operations like job search, profile lookup and contract messaging, but has no mutation for submitting a proposal, applying to a job, or consuming Connects. Write operations that spend Connects stay behind Upwork's own UI to prevent bid spam.

How does GigRadar actually submit proposals to Upwork?

Through a real Upwork Business Manager account that GigRadar owns and operates as a company. When your agency signs up, you grant it access the same way you would grant a hired bidder. Every proposal flows through that account under our team's supervision. No scraping, no browser extensions, and no commercial agreement with Upwork.

How fast can a proposal actually go out?

Ned's pipeline runs end to end in 11 to 13 seconds: roughly 5 for Claude Code to scaffold the Next.js project, 3 to 4 for the Vercel deploy, and 3 for the submission call. The webhook itself fires within about 2 seconds of the job appearing in the feed.

What does API access cost?

$100 a month on top of your GigRadar subscription, or $960 a year. It sits on your existing account, so you need at least one active scanner. The add on unlocks webhooks, the gigs feed and the application submission endpoint.

Will an automated pipeline hurt my Job Success Score?

Not if the proposals are good. Upwork's ranking models look at reply rate, hire rate and client feedback. Ned sees 18 to 24 percent reply rates because Claude Code writes a new site for each bid. Point the same pipeline at every job with a lazy prompt and your score will drop. Automation amplifies whatever you are already doing.

Do I need to write code, or can I use a no code tool?

You need code, but not much. Four stages: a webhook receiver, a Claude Code session, a Vercel deploy handled inside that session, and the submission call. A junior engineer ships a working version in an afternoon. No code tools cannot spawn a Claude Code session, which is the piece that makes the per proposal site possible.

Can I test it before subscribing?

Yes. Request a demo, get a sandbox key, and run a cURL call against a test opportunity without spending Connects. The docs at api.gigradar.io/public-api/v1/docs carry the full schema.

Get access to the endpoint Upwork will not give you

Proposals go out through our own Business Manager account. No scraping, no browser extensions.