About
Credit
State of the Upwork continues work originally published by Aaron Melton at Ascend Automation Agency (May 2025 – April 2026). Aaron's a Maker School GOAT and we're carrying the legacy forward. Methodology, taxonomy, and report structure follow his open-source framework. AI-keyword tracking, AI-generated narrative, and the public CSV/JSON API are additions.
Methodology
- Daily scrape of every automation-related Upwork job posting via the
jupri/upworkApify actor. - Keyword matching is substring-based on job title + description (case-insensitive). A job mentioning Zapier AND n8n counts in both buckets.
- Average rate is dragged by outliers; we publish median and 25th/75th percentile rates alongside it.
- Rate buckets: Ultra-Premium $150-500, Premium $100-150, Expert $75-100, Experienced $50-75, Mid-Tier $40-50, Entry $25-40.
- Pre-May-2026 historical trend lines come from Aaron's published markdown tables; SOTU's own data starts May 2026.
Keywords tracked
Every job title and description is checked against this exact list. Substring match, case-insensitive. The SOTU additions are tracked starting May 2026; pre-May-2026 historical numbers don't have these.
Platforms Aaron's original
| Bucket | Match phrases (substring, case-insensitive) |
|---|---|
| Zapier | zapier |
| Make.com | make.commake comintegromat |
| n8n | n8n |
| Power Automate | power automatemicrosoft power automatems power automate |
Applications Aaron's original
| Bucket | Match phrases (substring, case-insensitive) |
|---|---|
| HubSpot | hubspot |
| Airtable | airtable |
| Salesforce | salesforce |
| GoHighLevel | gohighlevelgo high levelhighlevel |
| Notion | notion |
| Monday.com | monday.commonday com |
| ClickUp | clickup |
| Asana | asana |
| Trello | trello |
| Jira | jira |
| Slack | slack |
| Google Sheets | google sheetsgoogle sheet |
| Excel | excelmicrosoft excel |
| QuickBooks | quickbooks |
| Xero | xero |
| Zoho | zoho |
Generic CRM mentions Aaron's original
Counted as a separate "CRM demand" signal alongside dedicated CRM apps.
AI tier Added by SOTU · new May 2026
Categories tracked starting May 2026 to give the AI-native automation segment its own trendline.
| Bucket | Match phrases (substring, case-insensitive) |
|---|---|
| Claude / Anthropic | claudeanthropic |
| OpenAI / GPT | openaigpt-4gpt-5gpt 4gpt 5chatgpt |
| AI Agents | ai agentautonomous agentagentic |
| RAG | ragretrieval augmentedretrieval-augmented |
| Vector DBs | pineconeweaviatechromaqdrantvector databasevector db |
| LangChain | langchainlanggraph |
| Voice AI | vapiretellelevenlabsvoice aivoice agent11labs |
| AI Builders | lindybardeenrelevance aigumloopcrewain8n ai |
| MCP | mcpmodel context protocol |
| Embeddings | embeddingembeddings |
The keyword list is intentionally locked so month-over-month comparisons stay valid across the full archive.
Public data API
Three endpoints, all open (no auth, no rate limit, CORS open), all edge-cached for one hour.
/api/data.jsonLatest published month as JSON. Add ?month=YYYY-MM for any past month.
/api/data.csvSame data as a spreadsheet-friendly CSV (anonymized, no job IDs).
/rss.xmlRSS 2.0 feed announcing each new monthly publish — plug into Feedly, Reeder, etc.
Examples
# Latest month
curl https://upwork.redwaterrev.com/api/data.json
# Specific month
curl 'https://upwork.redwaterrev.com/api/data.json?month=2026-04'
# Download CSV
curl -O https://upwork.redwaterrev.com/api/data.csv?month=2026-04// Latest month — runs from any browser, no auth needed
const r = await fetch('https://upwork.redwaterrev.com/api/data.json');
const month = await r.json();
console.log(`${month.total_jobs} jobs at avg $${month.avg_rate}/hr`);
console.log('Top platforms:', month.platforms);
console.log('AI tier:', month.ai_keywords);import requests
r = requests.get('https://upwork.redwaterrev.com/api/data.json',
params={'month': '2026-04'})
data = r.json()
print(f"{data['total_jobs']:,} jobs, avg ${data['avg_rate']}/hr")
for platform, count in sorted(data['platforms'].items(),
key=lambda x: -x[1]):
print(f" {platform}: {count}")import pandas as pd
# Pull last 12 months into one DataFrame
months = ['2025-07','2025-08','2025-09','2025-10','2025-11','2025-12',
'2026-01','2026-02','2026-03','2026-04','2026-05','2026-06']
rows = [pd.read_json(f'https://upwork.redwaterrev.com/api/data.json'
f'?month={m}', typ='series') for m in months]
df = pd.DataFrame(rows).set_index('month')
# Plot Zapier vs n8n vs Make.com over the year
platforms = pd.json_normalize(df['platforms']).set_index(df.index)
platforms[['Zapier','n8n','Make.com']].plot()Response shape
The JSON response is a single object with these fields. Every counts-style field is a JSON object keyed by the canonical name shown in the keyword tables above.
month— "YYYY-MM"total_jobs,avg_rate,median_rate,high_paying_countplatforms,applications,ai_keywords— objects of{name: count}rate_buckets— counts per tier (ultra_premium, premium, expert, …)high_paying_examples— array of top 20 $75+ jobs (title + rate + summary, anonymized)
Limitations
- Jobs may mention multiple platforms (overlap exists).
- Generic automation jobs without platform mentions are excluded from platform counts but still appear in totals.
- Rates are posted maximums, not actual contracted rates.
- Sample is limited to Upwork — not the broader freelance market.
- Substring matching can catch incidental mentions (e.g. "excel" inside "excellent"). We accept the noise to maintain continuity with Aaron's taxonomy.
Bugs, ideas, suggestions
Best place is to DM Nick on Maker School. Or fill out the feedback form — it pings Nick directly with whatever you've got: bug report, feature request, data correction, whatever.