the upwork api won't let you send proposals. here's the one that will.

The short version

  • Upwork's public GraphQL API has no mutation for submitting a proposal. You can read jobs and profiles but you cannot apply or consume Connects through it.
  • The GigRadar API exposes POST /opportunities/{id}/application, which submits a real proposal to Upwork through GigRadar's own Upwork Business Manager account. No scraping, no browser extensions.
  • A GigRadar user named Ned Thomas wired this endpoint into a Claude Code pipeline that writes a brand new Next.js site for every job, then submits the bid with the URL inside. Total time: 12 seconds.
  • This article rebuilds Ned's pipeline in four stages: a webhook receiver, a Claude Code session, a Vercel deploy, and the submission call. Afternoon-scale build.
  • The interactive tool below generates a complete cURL command for the GigRadar application endpoint using your opportunity ID and cover letter.

Last week a GigRadar user named Ned Thomas sent me a message that stopped me mid-coffee. He had pointed Claude Code at our API and built a pipeline that generates a brand new, one-off landing page for every Upwork client he bids on.

Not a template with fields swapped. A unique Next.js site, written from scratch for that specific job, deployed to its own URL. From the scanner firing to the bid landing in the client's inbox, the whole thing takes 12 seconds.

Ned Thomas
Ned Thomas Built a 12-second proposal pipeline using Claude Code and GigRadar's API
"It basically has allowed me to generate entirely custom proposals, links with graphs and also timeline breakdowns + testimonials, all within 12 seconds of receiving data from GigRadar Scanners."Ned Thomas, GigRadar API user

You can see a live example of the output he shared at proposal.nedthomas.co.uk/a5zf4kxxy2cz. Here's roughly what the client sees when they click the link inside Ned's Upwork proposal.

proposal.nedthomas.co.uk/a5zf4kxxy2cz

Full-Stack Developer Needed – Healthcare POC AI & Tracking Platform

7 April 2026 · $2,420 fixed

Key requirements

  • Build multi-tenant healthcare web app tracking patient care plans and progress
  • Implement role-based authentication with admin, provider, and staff views
  • Use React/Next.js frontend, Node.js/NestJS backend, PostgreSQL database
  • Create POC engine with diagnosis-based plans, phase progression, and conversion tracking
  • $2000 fixed budget for MVP with clean, executable implementation

Architecture · Phase 1

PostgreSQL Database
Tenant isolation schema with role-based access (admin/provider/staff)
AWS HIPAA-Ready Infra
Secure cloud foundation with data encryption and audit trails
Authentication System
Role-based access control with tenant-scoped permissions

Live proposal page. Claude Code wrote and deployed the entire site in under 8 seconds from the Upwork job brief.

Here's the thing most developers don't realize. The official Upwork API cannot send proposals.

Not with any scope, not with any workaround, not with any approval tier. Upwork's public GraphQL API lets you read jobs, fetch profiles, and pull messaging metadata, but the mutation for submitting a proposal simply does not exist in the schema.

So when Ned says the pipeline "auto-submits the bid," he isn't using Upwork's API. He's using ours.

12s
scanner hit to submitted bid
22%
reply rate on custom proposals
40
bids per day, fully automated

what the official upwork api actually does (and doesn't do)

Upwork opened their public GraphQL endpoint at https://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're already working on. What you cannot do, and what most growth hackers discover two weeks into a project, is submit a proposal.

OperationOfficial Upwork GraphQLGigRadar API
Search jobs✓ marketplaceJobPostingsSearch✓ GET /gigs (real-time scanner hits)
Read job detail + questions✓ read-only✓ GET /opportunities/{id}
Submit a proposal✗ no mutation exists✓ POST /opportunities/{id}/application
Consume Connects programmatically✗ UI-gated✓ via application endpoint
Real-time new-job webhooks✗ poll only✓ POST /webhooks
Answer screening questions✗ UI only✓ in application payload

I went through the full Upwork GraphQL schema last month to confirm this hadn't quietly changed. It hadn't.

This is deliberate on Upwork's part, and they've 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 to upwork (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 actually operates is a real Upwork Business Manager account. GigRadar owns that account as a company, and it sits inside GigRadar's own organization on Upwork the same way any agency's BM account sits inside theirs.

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

The Business Manager flow

Your agency
Your Upwork agency account. You grant GigRadar's BM access the same way you'd grant a hired bidder.
GigRadar BM
A real Upwork Business Manager account owned by GigRadar. Operated under our team's supervision.
Upwork
Receives the proposal as if a human bidder on your team submitted it through the UI.

Same mechanism any agency uses to hire a virtual assistant or outsourced bidder. Just executed by GigRadar's infrastructure instead of a human clicking through the UI.

Every proposal that gets submitted through the GigRadar API flows through our Business Manager account under our own team's supervision. 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, just executed by GigRadar's 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 gigradar api exposes

The endpoints live at https://api.gigradar.io/public-api/v1/ and they authenticate with a simple 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 have matched in real time. The response includes 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 on your behalf via GigRadar's Business Manager. This is the endpoint that does what the official Upwork API cannot.

It accepts the opportunity ID from the scanner payload, your rendered cover letter body, your proposed rate, and optional answers to screening questions. The response confirms whether the submission went through and returns the Upwork-side proposal ID.

POST /webhooks lets you register a callback URL. GigRadar will POST to it every time a new job matches a scanner, so you don't need to poll.

Ned's entire pipeline lives inside a single webhook handler. Request in, Claude Code generates and deploys a unique landing page, application call out, done.

GET /opportunities/{id} returns the full detail on any job, including the screening questions the client attached to the post. You'll need this to render custom answers in your pipeline.

ned's pipeline, reconstructed (with claude code)

I asked Ned for permission to reverse-engineer his setup so other agencies could build something similar. He was generous with the details.

The important thing to understand up front: Ned is not calling the Claude API from a script. He is running Claude Code as the execution engine, and Claude Code is the thing that actually writes, builds, and deploys a brand new Next.js project for every proposal.

That design choice is the whole reason his proposals feel different from every other "AI-generated" bid on Upwork. His pipeline does not fill a template. It writes a new site.

1
Scanner match fires a webhook ~0s
GigRadar posts the full job payload (title, description, client history, budget, Connects cost) to Ned's endpoint. No polling, no rate limits, no Upwork scraping.
2
Claude Code scaffolds a brand new Next.js project ~5s
A Claude Code session reads the job description and writes a unique Next.js site from scratch: hero copy specific to the client's problem, a Gantt timeline based on the scope, relevant testimonials. Not a template with fields swapped — real code in a fresh directory.
3
Claude Code deploys it to Vercel ~3s
Claude Code runs the deploy command itself. Vercel returns a URL at proposal.nedthomas.co.uk/[slug]. Each proposal ends up at its own unique slug.
4
Proposal submitted via GigRadar API ~3s
A POST to /opportunities/{id}/application sends a short cover letter ending with the Vercel URL. The client opens Upwork, sees a branded site built just for their job, and replies more often than to plain text.

Total wall-clock time: 11 to 13 seconds. Ned runs this across roughly 40 jobs a day and sees reply rates in the 18 to 24 percent range. That's roughly 3x the Upwork median for his category, on fully automated bids.

the actual code you'll write

I'll walk through each piece in the order it runs. The shape is identical in any language, but the snippets below are the minimum viable versions in JavaScript and Python.

1. the webhook receiver

Your endpoint receives a JSON body with the opportunity ID, job title, description, and scanner metadata. Validate the signature header, hand the payload to your generation step, return a 200 fast so GigRadar doesn't 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 try to over-engineer. You don't need a multi-agent orchestration, you don't need RAG, you don't need a custom prompt framework. You need one Claude Code session with a clear system instruction: "read this job description, scaffold a Next.js project that presents a custom proposal for it, then deploy to Vercel and 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 by claude code)

There is no separate deploy step. Claude Code runs the Vercel CLI directly from inside its session, captures the returned URL, and prints it on the last line of its output. Your handler just 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 to GigRadar. The response tells you whether the proposal went through, how many Connects were 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()

Free Tool

GigRadar API Payload Builder

Fill in the fields below to generate a complete cURL command for the POST /opportunities/{id}/application endpoint. Copy and paste into your terminal to test the call.

the discipline question you should ask before building 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 with a 22 percent reply rate looks fundamentally different to Upwork's ranking models than someone pushing 200 generic bids a day with a 2 percent reply rate. The API is a tool, not a shortcut.

Automation amplifies whatever you are already doing. If your baseline proposals are thoughtful, this setup is an unfair advantage. If they are not, it is a faster way to lose Connects. Ned's 22 percent reply rate comes from Claude Code actually writing a new site for each bid, not from the pipeline being fast.

If you build Ned's pipeline and you point it at every job in a category with a lazy prompt, you will still tank your Job Success Score and get deprioritized in Upwork's feed. The pipeline works because the bids are actually good.

what you need to start

Three things get you from zero to a working prototype.

  1. A GigRadar account with the API plan enabled. The plan sits on top of the Scanner tier, so you need at least one active scanner matching your target jobs.
  2. An API key. You'll find it in your GigRadar dashboard under Settings, API Access.
  3. A host for your webhook receiver and page generator. Vercel, Cloudflare Workers, and Render all work fine.

The minimum scanner setup is one Scanner pointed at jobs in your niche. Filter for payment-verified clients, minimum budget, and client hire rate above 60 percent.

Ned runs four scanners in parallel targeting slightly different keyword clusters. This matters because the scanner filters determine the quality of the payload your pipeline operates on.

Bad scanner, bad proposals. Claude Code cannot rescue a pipeline pointed at garbage jobs.

Once the scanner is live, you register your webhook URL at POST /webhooks and point it at your endpoint. Within about 10 minutes you'll start receiving payloads as matching jobs appear in the Upwork feed.

From there you're in your own code, and the only external dependency you have is the final /opportunities/{id}/application call.

why this changes the agency math

The reason I'm writing this article instead of letting Ned keep his edge is that the economics have quietly shifted for agencies on Upwork. Most people haven't caught up.

Two years ago, the winning strategy was speed. First 5 minutes, generic-but-polished proposal, high volume, low close rate but high enough reply count to make the math work.

Upwork's Uma ranking model has mostly killed that approach. Speed without relevance gets you buried.

The new winning strategy is relevance at speed. That's only possible if you automate the parts of the relevance work that a human can't do fast enough.

Building a custom landing page in 12 seconds is not something a VA can do. It requires an agent that can write code (Claude Code) and an API that can submit the resulting bid without a human touching the Upwork UI (GigRadar).

The agencies I've seen win this quarter are the ones that figured out the second half of that sentence before their competitors did. The GigRadar API is the piece that makes the submission leg work through a real Business Manager account instead of a scraper.

For growth hackers building on Upwork

Get API access to the endpoint Upwork won't give you

GigRadar's public API submits proposals to Upwork through our own Business Manager account. No scraping, no browser extensions. Battle-tested on 3,000+ agencies.

Request API Access →

frequently asked questions

Can the official Upwork API submit proposals?

No. Upwork's public GraphQL API at api.upwork.com/graphql 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 remain locked 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 our BM access to your Upwork agency account the same way you'd grant a hired bidder or VA. Every proposal flows through our Business Manager under our team's supervision. No scraping, no browser extensions, no commercial agreement with Upwork is involved.

How fast can I actually get a proposal submitted?

Ned Thomas's pipeline runs end-to-end in 11 to 13 seconds: roughly 5 seconds for Claude Code to scaffold a new Next.js project, 3 to 4 seconds for the Vercel deploy, and 3 seconds for the GigRadar API call to submit the bid to Upwork via our Business Manager. The webhook itself fires within roughly 2 seconds of the job appearing in the Upwork feed.

What does GigRadar API access cost?

API access sits on top of the Scanner tier. You need at least one active scanner matching your target jobs, and the API add-on unlocks webhooks, the gigs feed, and the application submission endpoint. Pricing is on the request-demo page and depends on expected daily volume.

Will running this 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 on automated bids because Claude Code actually writes a new site for each one. If you point the same pipeline at every job with a lazy prompt, your JSS 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 to write code, but not much of it. The pipeline is four stages: a webhook receiver, a Claude Code session, a Vercel deploy (handled inside Claude Code), and the GigRadar submission call. A junior engineer can ship a working version in an afternoon. No-code tools cannot spawn a Claude Code session, which is the piece that makes the per-proposal Next.js site possible.

Can I test the API before subscribing?

Yes. The payload builder tool earlier in this article generates a valid cURL command against the application endpoint. Once you request a demo and get a sandbox API key, you can run the cURL against a test opportunity without spending Connects. The docs at api.gigradar.io/public-api/v1/docs have the full schema.

Ready for your Upwork success story? Book a demo with GigRadar below!
Book a Demo
FAQ

Most Popular
Questions

Get a more consistent and cost-effective lead generator for your Upwork agency.

Ask a Question

What does GigRadar API access cost?

API access sits on top of the Scanner tier. You need at least one active scanner matching your target jobs, and the API add-on unlocks webhooks, the gigs feed, and the application submission endpoint. Pricing depends on expected daily volume. Request a demo at gigradar.io/request-demo for current pricing.

Can I test the GigRadar API before subscribing?

Yes. The payload builder tool in this article generates a valid cURL command against the application endpoint. Once you request a demo and get a sandbox API key, you can run the cURL against a test opportunity without spending Connects. The docs at api.gigradar.io/public-api/v1/docs have the full schema.

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

You need to write code, but not much of it. The pipeline is four stages: a webhook receiver, a Claude Code session, a Vercel deploy (handled inside Claude Code), and the GigRadar submission call. A junior engineer can ship a working version in an afternoon. No-code tools cannot spawn a Claude Code session, which is the piece that makes the per-proposal Next.js site possible.

Will running an automated proposal 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 on automated bids because Claude Code actually writes a new site for each one. If you point the same pipeline at every job with a lazy prompt, your JSS will drop. Automation amplifies whatever you are already doing.

How fast can I actually get a proposal submitted via the GigRadar API?

Ned Thomas's pipeline runs end-to-end in 11 to 13 seconds: roughly 5 seconds for Claude Code to scaffold a new Next.js project, 3 to 4 seconds for the Vercel deploy, and 3 seconds for the GigRadar API call to submit the bid to Upwork via our Business Manager. The webhook itself fires within roughly 2 seconds of the job appearing in the Upwork feed.

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 our BM access to your Upwork agency account the same way you'd grant a hired bidder or VA. Every proposal flows through our Business Manager under our team's supervision. No scraping, no browser extensions, no commercial agreement with Upwork is involved.

Can the official Upwork API submit proposals?

No. Upwork's public GraphQL API at api.upwork.com/graphql 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 remain locked behind Upwork's own UI to prevent bid spam.

Arcticles

Read more posts

We will assign one of our crew members to your team immediately