Query recent Upwork job listings with AI quality scores built in. Every result includes scores — no extra calls required.
Pass your API key as a Bearer token on every request. Generate keys in Settings → API Keys.
Authorization: Bearer vw_your_api_key_here
Responses are always { data: [...], count: N } on success, or { error: '...' } with an appropriate HTTP status on failure. Free accounts also include quotaRemaining, freshnessDelayHours, newerJobsMatched and note in each response (see freshness).count is the number of jobs in this response, not the total number of jobs matching the filters.
https://kttkatrmvlzsepgprqqd.supabase.co/functions/v1/public-jobsReturns recent Upwork job listings matching your filters. Jobs are continuously ingested and scored as they are posted, and are usually available within 1–2 minutes of going live on Upwork. Paid keys see them at that point; free keys only see jobs older than 24 hours (see freshness).
Example request
curl https://kttkatrmvlzsepgprqqd.supabase.co/functions/v1/public-jobs \ -H "Authorization: Bearer vw_your_key" \ -G \ -d keywords=react \ -d jobType=fixed \ -d minQuickWin=7 \ -d sort=quick_win \ -d limit=20
Example response (free account)
{
"data": [
{
"id": "vw_b797267068b246bfa8da8722",
"title": "Build a Next.js dashboard with Supabase backend",
"category": "Web Development",
"jobType": "fixed",
"budget": 800,
"budgetMax": null,
"experienceLevel": "Intermediate",
"duration": "Less than 1 month",
"connects": 11,
"hoursPerWeek": null,
"skills": ["Next.js", "TypeScript", "Supabase", "Tailwind CSS"],
"clientLocation": "United States",
"clientPaymentVerified": true,
"clientTotalSpent": 42000,
"clientHireRate": 78,
"clientRating": 4.9,
"clientAvgRate": null,
"description": "Build a clean admin dashboard...",
"upworkUrl": "https://www.upwork.com/jobs/~01abc...",
"scores": {
"quickWin": 8,
"scopeClarity": 9,
"redFlags": 10,
"effortHours": 14
},
"postedAt": "2026-06-08T09:31:00+00:00",
"receivedAt": "2026-06-08T09:32:14.000+00:00"
}
],
"count": 1,
"quotaRemaining": 99,
"freshnessDelayHours": 24,
"newerJobsMatched": 37,
"note": "Fresh jobs (<24h old) are limited to paid users only. 37 newer jobs matched your filters ..."
}keywordsstringFilter by job title keyword (e.g. react, shopify). Case-insensitive partial match.categorystringFilter by the exact category string returned on a job, for example Web Development, AI & Machine Learning, or QA & Testing. Categories originate upstream and may expand over time.jobTypefixed | hourlyFilter by contract type.experienceLevelEntry Level | Intermediate | ExpertFilter by the client's requested experience level.minBudgetnumberMinimum budget in USD. Uses the project budget for fixed-price jobs and the minimum hourly rate for hourly jobs. Hourly jobs that do not publish a rate have no budget value at all, so they are excluded whenever this is set.minQuickWininteger 0–10Only return jobs at or above this Quick Win score. Set to 7 to filter for high-quality quick wins only.sortnewest | quick_win | budgetSort order. Default: newest. budget sorts highest first, but jobs with no published budget sort ahead of the rest, so pair it with minBudget to skip those.limitinteger 1–100Maximum number of results to return. Default: 50. Free accounts are charged only for jobs actually returned.Every job returned by this endpoint has been scored by an LLM. The scores object is included on every result. Individual fields may be null when the source data or estimate is unavailable.
scores.quickWininteger 0–10Fixed price + clear bounded scope + completable in one session. Higher = faster money with less negotiation risk.scores.scopeClarityinteger 0–10How precisely the deliverable is defined. Low scores indicate vague requirements and scope creep risk.scores.redFlagsinteger 0–10Absence of bad signals. 10 = clean client with no red flags. Low scores mean proceed with caution.scores.effortHoursnumber | nullRealistic estimated hours to complete the job.Jobs are ingested and scored continuously and are usually queryable within 1–2 minutes of going live on Upwork. How much of that speed your key gets depends on the plan.
Paid keys: every job as soon as it is scored, with no delay.
Free keys: only jobs more than 24 hours old. Anything newer is withheld regardless of your filters.
Free responses carry three extra fields so the delay is visible rather than silent: freshnessDelayHours (currently 24), newerJobsMatched, the number of jobs that matched your filters inside the delay window and were held back, and a plain-language note. An empty data array alongside a non-zero newerJobsMatched means your filters are fine and the matches are simply too recent for a free key.
Anything time-sensitive, bidding early or alerting, needs a paid key. Polling more often on a free key cannot get inside the 24-hour window; a webhook is the fastest path once you are on a paid plan.
| Free | Paid | |
|---|---|---|
| Job freshness | Older than 24h only | Real time |
| Results per day | 100 | Unlimited |
| Max per request | 100 | 100 |
| Per-minute limit | None | None |
| Quota reset | Midnight UTC | — |
| API keys | Multiple | Multiple |
The daily quota is per account, not per key — creating additional API keys does not increase your limit. There is no application-level per-minute throttle, but clients should avoid unnecessary high-frequency polling.
The live API uses an approximately seven-day rolling job window. It currently returns only the first matching page (up to 100 jobs); date cursors, pagination, and access to the cold archive are not available through this endpoint.
Rather than polling the API, webhooks push a job payload to your endpoint when a matching job is ingested — usually within 1–2 minutes of it posting on Upwork. Available on paid plans. Webhooks are configured per filter: open a filter's alert settings in the dashboard, enable the Webhook channel, and paste your endpoint URL. Use Preview alert to send a sample payload immediately.
Payload shape
{
"event": "job.matched",
"matchedAt": "2026-06-08T09:32:14.000Z",
"filterName": "React fixed-price",
"match": {
"scoreQuickWin": 8,
"scoreScopeClarity": 9,
"scoreRedFlags": 10,
"effortEstimateHours": 14,
"reasoning": "Clear bounded deliverable..."
},
"job": {
"id": "vw_b797267068b246bfa8da8722",
"title": "Build a Next.js dashboard",
"description": "Build a clean admin dashboard...",
"url": "https://www.upwork.com/jobs/~01abc...",
"type": "fixed",
"contractType": null,
"budget": { "min": 800, "max": null, "currency": "USD", "display": "$800" },
"duration": "Less than 1 month",
"hoursPerWeek": null,
"experienceLevel": "Intermediate",
"connectsRequired": 11,
"skills": ["Next.js", "TypeScript"],
"categories": ["Web Development"],
"postedAt": "2026-06-08T09:31:00+00:00",
"questions": ["Share a similar dashboard you've built", "What's your availability this week?"]
},
"client": {
"id": "1234567890123456789",
"location": "United States",
"locationRestriction": null,
"paymentVerified": true,
"totalSpent": 42000,
"jobsPosted": 31,
"hires": 24,
"hireRate": 78,
"rating": 4.9,
"reviewCount": 19,
"avgHourlyRate": null,
"rankScore": 5,
"rankLabel": "Excellent"
}
}job.questions and client.locationRestriction are frequently empty — not every job carries screening questions or a hiring-location restriction, and coverage varies by source. Treat both as "present when available," not guaranteed. client.id is Upwork's stable ID for the client, so you can group jobs by client. It is present on nearly every job, but can be null.
Requests carry the X-Vibeworker-Event: job.matched header. Preview deliveries include "preview": true in the payload. Delivery is currently a single best-effort POST with no automatic retries or signature. Your endpoint should respond quickly, be idempotent, and treat the URL as a secret. Anyone who knows it can send requests to it, so ignore payloads that don't match this shape.
https://tryvibeworker.com/api/v1/profile-auditSend a public Upwork freelancer profile URL and get back the full audit: the parsed profile, an overall score, sub-scores, quick wins, critiques and rewrites for the title and overview, and in-demand skills to add, based on this week's job postings. It is the same audit as the free profile audit, returned as raw JSON for you to render however you like.
Example request
curl -X POST https://tryvibeworker.com/api/v1/profile-audit \
-H "Authorization: Bearer vw_your_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.upwork.com/freelancers/~01abc..."}'Example response
{
"data": {
"profile_url": "https://www.upwork.com/freelancers/~01abc...",
"slug": "q3X9vB2kLmP0",
"audited_at": "2026-10-19T09:32:14.000Z",
"profile": {
"name": "Jane D.",
"title": "Full-Stack Developer",
"location": "Lahore, Pakistan",
"hourlyRate": "$35.00",
"overview": "I build web apps...",
"proofPoints": "96% Job Success · Top Rated · 4.9 from 41 reviews · 58 jobs",
"skills": ["React", "Node.js", "PostgreSQL"],
"stats": { "jss": 96, "badge": "Top Rated", "rating": 4.9, "reviews": 41,
"jobs": 58, "hours": 2140, "hireAgain": null,
"memberSince": "2019", "responsiveness": null },
"projects": [{ "title": "SaaS billing dashboard", "description": "...", "url": "..." }],
"workHistory": [{ "title": "Next.js marketplace build", "clientQuote": "..." }],
"employment": [{ "role": "Senior Developer", "company": "Acme" }],
"linkChecks": [{ "url": "https://...", "status": 404, "ok": false }]
},
"report": {
"score": 68,
"subScores": { "titleClarity": 60, "overviewHook": 52, "skillsMarketFit": 81, "ratePositioning": null },
"quickWins": [
{ "severity": "bad", "label": "Portfolio link is broken", "detail": "..." },
{ "severity": "good", "label": "Strong Job Success Score" }
],
"sections": {
"title": { "critique": "...", "rewrite": "..." },
"overview": { "critique": "...", "rewrite": "...", "flagged": ["passionate about"] },
"skills": { "critique": "...", "add": ["Next.js", "Supabase"] }
},
"summary": "..."
}
}
}urlstring · requiredA public Upwork freelancer profile URL (https://www.upwork.com/freelancers/~...). Query strings are ignored.analysisbooleanDefault true. Set to false to get only the parsed profile, with no analysis. report and slug come back null, fetched_at replaces audited_at, and the call is about twice as fast.slugstring | nullID of the saved audit.profileobjectThe profile as read from Upwork. Fields that are missing on the profile are omitted or null. proofPoints is one line combining Job Success, badges, verifications and client feedback tags.profile.photoUrlstring | nullThe freelancer's profile photo, as a direct image URL on Upwork. null when the profile has no photo.report.scoreinteger 0–100Overall profile score.report.subScoresobjectTitle clarity, overview hook and skills market fit, each 0–100. ratePositioning is always null: the rate is advice in report.sections.rate, not part of the score.report.quickWinsarraySpecific findings, each with a severity of good, warn or bad.report.sectionsobjectCritique and suggested rewrite for the title and overview, generic phrases flagged in the overview, in-demand skills to add (paired with skills to remove when the profile is at Upwork's 20-skill limit), and optional advice on the hourly rate.Each call fetches the live profile and runs the audit. A full audit usually takes 10 to 30 seconds, and a profile-only call ("analysis": false) usually takes 5 to 20. Set your client timeout to at least 120 seconds. Agency accounts can fetch 50 profiles per day, with or without analysis.
Errors
400bad requestMissing or invalid profile URL.401unauthorizedMissing, invalid or revoked API key.403forbiddenThe account is not on the Agency plan.422unprocessableThe profile is private, members-only, or cannot be read publicly.504timeoutThe profile could not be fetched in time. Retry in a minute.