- Python 3.9+
- Anthropic API key (Claude)
- ~15 minutes
cd /Users/growthgod/.openclaw/workspace/upwork_scripting_apppython3 -m venv venv
source venv/bin/activatepip install -r requirements.txtcp .env.example .envEdit .env and add your Claude API key:
CLAUDE_API_KEY=sk-ant-...
Get your key from: https://console.anthropic.com/account/keys
The database auto-initializes on first run, but you can manually init:
python -c "from db.database import init_db; init_db()"This creates upwork.db with all tables and indexes.
python main.pyOutput:
INFO:root:π― Starting Upwork Auto-Apply System
INFO:root:π‘ API Server: http://0.0.0.0:8000
INFO:root:π§ Initializing Upwork Auto-Apply System...
INFO:root:β
Database initialized at /path/to/upwork.db
INFO:root:β° Scheduler started (cycle every 30 min)
INFO:root:β
System ready
# In another terminal
curl http://localhost:8000/health
# {"status":"ok"}
curl http://localhost:8000/api/stats
# {"new_jobs":0,"filtered_out":0,"pending_proposal":0,...}curl -X POST http://localhost:8000/api/cycle/runThis runs:
- Monitor feeds β fetch new jobs
- Filter jobs β score against whitelist/blacklist
- Generate proposals β Claude API creates personalized text
Feed Monitoring
- Polls every 30 minutes
- Monitors 5 Upwork RSS feeds (landing pages, python, scraping, zapier, API)
Job Filtering
- Fixed price: minimum $150
- Hourly: minimum $25/hr
- Rejects jobs with no budget
Whitelist Keywords (points-based)
- automation, python, script, api, integrate
- landing page, webflow, react, next.js
- scraping, pipeline, workflow, zapier, make.com
- Min score to proceed: 2 keywords
Proposal Generation
- Max length: 1000 characters
- Tone: professional but personable
- Claude model: 3.5 Sonnet (latest)
Edit config.py:
# More aggressive filtering (higher keyword score needed)
FILTER_WHITELIST_MIN_SCORE = 3
# Increase budget minimum
BUDGET_FILTERS = {
"fixed_min": 250, # $250+ only
"hourly_min": 35, # $35/hr only
"allow_no_budget": False,
}
# Add your own keywords
KEYWORD_WHITELIST.extend([
"your skill here",
"another specialty",
])
# Poll more/less frequently (in minutes)
FEED_POLL_INTERVAL = 15 # Check every 15 min instead of 30Get all jobs with their current status.
curl http://localhost:8000/api/jobs
# Response:
{
"jobs": [
{
"id": "12345",
"title": "Build landing page",
"budget": "$300",
"status": "pending_proposal",
"filter_score": 3,
...
}
]
}Filter by status:
curl "http://localhost:8000/api/jobs?status=pending_proposal"Status values: new, filtered_out, pending_proposal, proposal_ready, approved, sent
Get all proposals (with linked job info).
curl http://localhost:8000/api/proposals
# Response:
{
"proposals": [
{
"id": 1,
"job_id": "12345",
"title": "Build landing page",
"proposal_text": "Hi! I saw you need...",
"status": "pending",
"generated_at": "2026-02-18T03:21:58",
...
}
]
}Filter by status:
curl "http://localhost:8000/api/proposals?status=pending"Status values: pending, approved, rejected, sent
Approve a proposal (marks as ready to send).
curl -X POST "http://localhost:8000/api/proposals/1/approve?notes=Looks+great"
# Response:
{
"status": "approved",
"proposal_id": 1
}Reject a proposal.
curl -X POST "http://localhost:8000/api/proposals/1/reject?notes=Not+interested"
# Response:
{
"status": "rejected",
"proposal_id": 1
}Manually trigger a full monitoring cycle.
curl -X POST http://localhost:8000/api/cycle/run
# Runs: Monitor feeds β Filter jobs β Generate proposalsSystem statistics.
curl http://localhost:8000/api/stats
# Response:
{
"new_jobs": 5,
"filtered_out": 12,
"pending_proposal": 3,
"proposal_ready": 2,
"proposals_pending": 2,
"proposals_approved": 0,
"proposals_sent": 0
}# Terminal 1: Run server
python main.py
# Terminal 2: Monitor logs in real-time
tail -f upwork.db # (or check console output)2026-02-18 03:21:45 - modules.feed_monitor - INFO - π Starting feed monitor cycle...
2026-02-18 03:21:45 - modules.feed_monitor - INFO - π‘ Fetching: https://www.upwork.com/ab/feed/jobs/rss?q=landing+page...
2026-02-18 03:21:48 - modules.feed_monitor - INFO - β
New job: Build responsive landing page for SaaS...
2026-02-18 03:21:48 - modules.feed_monitor - INFO - π Feed summary: 2 new, 8 duplicates
2026-02-18 03:21:48 - modules.job_filter - INFO - π― Starting job filter cycle...
2026-02-18 03:21:48 - modules.job_filter - INFO - π Processing 2 new jobs...
2026-02-18 03:21:48 - modules.job_filter - INFO - π Filtering: Build responsive landing page...
2026-02-18 03:21:48 - modules.job_filter - INFO - β
Budget OK: Fixed price: $500 (meets min $150)
2026-02-18 03:21:48 - modules.job_filter - INFO - π Whitelist score: 3 (matched: landing page, react, api)
2026-02-18 03:21:48 - modules.job_filter - INFO - β
PASS: Score 3 >= 2
2026-02-18 03:21:48 - modules.job_filter - INFO - β
Filter cycle complete: 1 passed, 1 filtered
2026-02-18 03:21:55 - modules.proposal_generator - INFO - π Starting proposal generation cycle...
2026-02-18 03:21:55 - modules.proposal_generator - INFO - π Generating proposals for 1 jobs...
2026-02-18 03:21:55 - modules.proposal_generator - INFO - π€ Generating proposal for: Build responsive landing page...
2026-02-18 03:21:58 - modules.proposal_generator - INFO - β
Proposal generated (756 chars)
2026-02-18 03:21:58 - modules.proposal_generator - INFO - πΎ Proposal saved for job ab123cd
2026-02-18 03:21:58 - modules.proposal_generator - INFO - β¨ Proposal generation cycle complete: 1 generated, 0 failed
# Open SQLite shell
sqlite3 upwork.db
# List all jobs
SELECT id, title, budget, status, filter_score FROM jobs;
# Count by status
SELECT status, COUNT(*) FROM jobs GROUP BY status;
# Exit
.quit-- High-scoring jobs pending proposals
SELECT id, title, filter_score FROM jobs
WHERE status = 'pending_proposal'
ORDER BY filter_score DESC;
-- Proposals waiting for approval
SELECT p.id, j.title, p.generated_at FROM proposals p
JOIN jobs j ON p.job_id = j.id
WHERE p.status = 'pending';
-- Recent feed activity
SELECT feed_url, fetched_at, new_jobs FROM feed_log
ORDER BY fetched_at DESC
LIMIT 10;If port 8000 is taken:
# Edit config.py
API_PORT = 8001 # or another portError: 401 Unauthorized
- Check CLAUDE_API_KEY in
.env - Verify key from: https://console.anthropic.com/account/keys
- Ensure key has sufficient quota
- Upwork RSS feeds sometimes delay
- Try manual cycle:
curl -X POST http://localhost:8000/api/cycle/run - Check if feeds are accessible in browser
If you see "database is locked":
# Stop the server (Ctrl+C)
# Then:
rm upwork.db
python main.py # Will reinitialize- Run the system β let it monitor feeds for a few cycles
- Review proposals β check
/api/proposals?status=pendingto see Claude's work - Test approval flow β approve/reject proposals to understand the workflow
- Customize filters β adjust
config.pybased on what jobs you're getting - Build dashboard β Phase 2: React frontend for easier proposal review
Check README.md for full documentation.