Version: 1.0 Status: Specification Author: DBBasic Project Date: November 2025
Links: - PyPI: https://pypi.org/project/dbbasic-jobs/ (not yet published) - GitHub: https://github.com/askrobots/dbbasic-jobs (not yet published) - Specification: http://dbbasic.com/jobs-spec
"A job board is three states: open, assigned, done. Everything else is noise."
The critical insight: Most job boards over-complicate simple workflows. Freelancer marketplaces need: post job → receive applications → pick winner → mark complete. That's it.
- Connects, invites, proposals, interviews, contracts, milestones
- Escrow, time tracking, screenshots, weekly limits
- Agency vs individual, fixed vs hourly
- 47 different status states
Result: Takes 30 minutes to post a simple job.
- Post job
- Get email replies
- No tracking, no escrow, no reputation
Result: Spam, scams, no quality control.
1. Client posts job (title, description, budget)
2. Freelancers apply (bid, proposal)
3. Client picks winner
4. Work happens
5. Both parties rate each other
Why this works: - Clear workflow - No artificial complexity - Integrates with reputation system - Simple enough to understand in 2 minutes
id client_id title description budget_min budget_max status category skills created_at updated_at awarded_to completed_at
Fields:
- id: Unique job ID (e.g., "j001", "j002")
- client_id: User who posted the job
- title: Job title (e.g., "Build a Django REST API")
- description: Full job description (markdown supported)
- budget_min: Minimum budget ($) or null for open
- budget_max: Maximum budget ($) or null for open
- status: Job status (open, in_progress, completed, cancelled)
- category: Job category (web_dev, design, writing, etc.)
- skills: Comma-separated required skills
- created_at: When job was posted
- updated_at: When job was last modified
- awarded_to: User ID of selected freelancer (null if not awarded)
- completed_at: When job was marked complete
id client_id title description budget_min budget_max status category skills created_at updated_at awarded_to completed_at
j001 u042 Build Django REST API Need REST API for mobile app... 2000 5000 completed web_dev python,django,rest 2025-10-15T10:00:00 2025-11-01T15:00:00 u055 2025-11-01T15:00:00
j002 u042 Logo design Modern logo for SaaS startup 500 1000 in_progress design illustrator,photoshop 2025-10-20T14:00:00 2025-10-25T09:00:00 u088 null
j003 u099 Write blog posts 10 SEO-optimized blog posts null null open writing seo,content 2025-11-01T08:00:00 2025-11-01T08:00:00 null null
id job_id applicant_id bid_amount proposal status created_at updated_at
Fields:
- id: Unique application ID (e.g., "app001", "app002")
- job_id: Job being applied to
- applicant_id: User applying
- bid_amount: Proposed price ($)
- proposal: Application cover letter/proposal
- status: Application status (pending, accepted, rejected, withdrawn)
- created_at: When application was submitted
- updated_at: When application was last modified
id job_id applicant_id bid_amount proposal status created_at updated_at
app001 j001 u055 3500 I have 5 years Django experience... accepted 2025-10-15T11:00:00 2025-10-16T10:00:00
app002 j001 u077 4200 Senior dev, can start immediately... rejected 2025-10-15T12:00:00 2025-10-16T10:00:00
app003 j002 u088 750 Award-winning designer... accepted 2025-10-20T15:00:00 2025-10-25T09:00:00
app004 j003 u091 800 SEO specialist, 500+ articles... pending 2025-11-01T09:00:00 2025-11-01T09:00:00
app005 j003 u092 1200 Content strategist... pending 2025-11-01T10:00:00 2025-11-01T10:00:00
from dbbasic_jobs import Jobs
# Initialize
jobs = Jobs('/path/to/data')
# Create job
job_id = jobs.create(
client_id='u042',
title='Build Django REST API',
description='Need REST API for mobile app with auth, user profiles, etc.',
budget_min=2000,
budget_max=5000,
category='web_dev',
skills=['python', 'django', 'rest']
)
# Get job
job = jobs.get('j001')
# List jobs
all_jobs = jobs.list(status='open')
my_jobs = jobs.list(client_id='u042')
category_jobs = jobs.list(category='web_dev')
# Update job
jobs.update('j001', status='in_progress', awarded_to='u055')
# Delete job
jobs.delete('j001')
# Apply to job
app_id = jobs.apply(
job_id='j001',
applicant_id='u055',
bid_amount=3500,
proposal='I have 5 years Django experience...'
)
# Get applications for job
apps = jobs.get_applications('j001')
# Get applications by user
my_apps = jobs.get_applications_by_user('u055')
# Award job to applicant
jobs.award(job_id='j001', applicant_id='u055')
# Mark job complete
jobs.complete('j001')
# Cancel job
jobs.cancel('j001')
┌──────────┐
│ OPEN │
└─────┬────┘
│
│ Client awards job
▼
┌──────────────┐
│ IN_PROGRESS │
└──────┬───────┘
│
│ Client marks complete
▼
┌──────────┐
│COMPLETED │
└──────────┘
(From any state)
│
│ Client cancels
▼
┌──────────┐
│CANCELLED │
└──────────┘
VALID_TRANSITIONS = {
'open': ['in_progress', 'cancelled'],
'in_progress': ['completed', 'cancelled'],
'completed': [], # Terminal state
'cancelled': [] # Terminal state
}
┌──────────┐
│ PENDING │
└────┬─────┘
│
├─────→ ACCEPTED (job awarded)
│
├─────→ REJECTED (client declined)
│
└─────→ WITHDRAWN (applicant withdrew)
CATEGORIES = {
'web_dev': 'Web Development',
'mobile_dev': 'Mobile Development',
'design': 'Design & Creative',
'writing': 'Writing & Content',
'marketing': 'Marketing & SEO',
'data_science': 'Data Science & Analytics',
'video': 'Video & Animation',
'audio': 'Audio & Music',
'admin': 'Admin & Support',
'consulting': 'Consulting',
'other': 'Other'
}
jobs.create(
budget_min=1000,
budget_max=1000,
...
)
# Display: "$1,000 fixed price"
jobs.create(
budget_min=1000,
budget_max=5000,
...
)
# Display: "$1,000 - $5,000"
jobs.create(
budget_min=None,
budget_max=None,
...
)
# Display: "Budget: Open to proposals"
Skills stored as comma-separated string:
# Create with skills
jobs.create(
skills=['python', 'django', 'postgresql', 'rest']
)
# Stored as: "python,django,postgresql,rest"
# Search by skill
python_jobs = jobs.search_by_skill('python')
# Search by multiple skills (AND)
jobs_with_skills = jobs.search_by_skills(['python', 'django'])
# api/jobs/create.py
from dbbasic_jobs import Jobs
from dbbasic_web.responses import json_response
def handle(request):
jobs = Jobs('/data')
job_id = jobs.create(
client_id=request.user['id'],
title=request.form['title'],
description=request.form['description'],
budget_min=int(request.form.get('budget_min', 0)) or None,
budget_max=int(request.form.get('budget_max', 0)) or None,
category=request.form['category'],
skills=request.form['skills'].split(',')
)
return json_response({'job_id': job_id})
# api/jobs/apply.py
def handle(request):
jobs = Jobs('/data')
app_id = jobs.apply(
job_id=request.form['job_id'],
applicant_id=request.user['id'],
bid_amount=int(request.form['bid_amount']),
proposal=request.form['proposal']
)
return json_response({'application_id': app_id})
# api/jobs/award.py
def handle(request):
jobs = Jobs('/data')
# Only job owner can award
job = jobs.get(request.form['job_id'])
if job['client_id'] != request.user['id']:
return json_response({'error': 'Unauthorized'}, status=403)
jobs.award(
job_id=request.form['job_id'],
applicant_id=request.form['applicant_id']
)
return json_response({'success': True})
<!-- Job listing card -->
<div class="job-card">
<h3>{{ job.title }}</h3>
<div class="job-meta">
<span class="category">{{ job.category }}</span>
<span class="budget">${{ job.budget_min }} - ${{ job.budget_max }}</span>
<span class="posted">{{ job.created_at | timeago }}</span>
</div>
<p>{{ job.description | truncate(200) }}</p>
<div class="skills">
{% for skill in job.skills %}
<span class="skill-tag">{{ skill }}</span>
{% endfor %}
</div>
<a href="/jobs/{{ job.id }}" class="btn">View Details</a>
</div>
<!-- Application form -->
<form method="POST" action="/api/jobs/apply">
<input type="hidden" name="job_id" value="{{ job.id }}">
<label>Your Bid ($)</label>
<input type="number" name="bid_amount" required>
<label>Proposal</label>
<textarea name="proposal" rows="10" required></textarea>
<button type="submit">Submit Application</button>
</form>
# All jobs
dbbasic jobs list
# By status
dbbasic jobs list --status open
# By category
dbbasic jobs list --category web_dev
# By client
dbbasic jobs list --client u042
dbbasic jobs create \
--client u042 \
--title "Build Django REST API" \
--description "Need REST API for mobile app" \
--budget-min 2000 \
--budget-max 5000 \
--category web_dev \
--skills "python,django,rest"
# Applications for job
dbbasic jobs applications j001
# Applications by user
dbbasic jobs applications --user u055
dbbasic jobs award j001 --to u055
dbbasic jobs complete j001
Auto-discovered by dbbasic-admin:
# Automatic CRUD interface at /admin/jobs/
# Shows:
# - Recent jobs
# - Filter by status, category, budget range
# - Applications per job
# - Award/complete/cancel actions
# - Job analytics (avg budget, time to award, etc.)
After job completion, enable ratings:
from dbbasic_jobs import Jobs
from dbbasic_ratings import Ratings
jobs = Jobs('/data')
ratings = Ratings('/data')
# Mark job complete
jobs.complete('j001')
# Get job details
job = jobs.get('j001')
# Enable rating period
ratings.enable_rating_period(
project_id=job['id'],
user_a=job['client_id'],
user_b=job['awarded_to'],
duration_days=30
)
Show user reputation on job listings:
from dbbasic_jobs import Jobs
from dbbasic_ratings import Ratings
jobs = Jobs('/data')
ratings = Ratings('/data')
# Get job applications with reputation
apps = jobs.get_applications('j001')
for app in apps:
user_rep = ratings.get_reputation(app['applicant_id'])
app['reputation'] = user_rep['overall_score']
Notify followers when new job posted:
from dbbasic_jobs import Jobs
from dbbasic_follows import Follows
from dbbasic_email import Email
jobs = Jobs('/data')
follows = Follows('/data')
email = Email('/data')
# Create job
job_id = jobs.create(...)
# Notify followers
followers = follows.get_followers(client_id)
for follower_id in followers:
email.send(
to=follower_id,
subject=f"New job posted by {client_id}",
body=f"Check out this new job: {job_url}"
)
# Jobs requiring Python
python_jobs = jobs.search_by_skill('python')
# Jobs requiring Python AND Django
python_django_jobs = jobs.search_by_skills(['python', 'django'])
# Jobs with budget $1000-$5000
jobs_in_range = jobs.search_by_budget(min_budget=1000, max_budget=5000)
# All web dev jobs
web_jobs = jobs.list(category='web_dev')
# Search in title and description
results = jobs.search('REST API mobile app')
Add a featured field for promoted listings:
jobs.create(..., featured=True)
# Featured jobs appear first
featured = jobs.list(featured=True)
Save common job templates:
# Save template
jobs.save_template(
name='Django API Project',
template={
'category': 'web_dev',
'skills': ['python', 'django', 'rest'],
'description_template': '...'
}
)
# Use template
jobs.create_from_template('Django API Project', budget_min=2000, ...)
Add milestone support for large projects:
# Create job with milestones
jobs.create(
...,
milestones=[
{'name': 'Database design', 'amount': 1000},
{'name': 'API implementation', 'amount': 2000},
{'name': 'Testing & deployment', 'amount': 500}
]
)
jobs.create(
...,
application_deadline='2025-12-01T23:59:59'
)
# Auto-close applications after deadline
jobs.close_expired_applications()
Target: ~200 lines of Python
dbbasic_jobs/
├── __init__.py # 10 lines
├── jobs.py # 100 lines (CRUD, status management)
├── applications.py # 60 lines (application handling)
├── search.py # 20 lines (filtering, search)
└── cli.py # 10 lines (command-line interface)
Total: ~200 lines
Only job owner can: - Update job - Award job - Mark complete - Cancel job
def award(self, job_id, applicant_id, requesting_user_id):
job = self.get(job_id)
if job['client_id'] != requesting_user_id:
raise PermissionError('Only job owner can award')
# ...
Prevent spam applications:
# Max 10 pending applications per user
pending_apps = self.get_applications_by_user(user_id, status='pending')
if len(pending_apps) >= 10:
raise ValueError('Too many pending applications')
Ensure valid state transitions:
if current_status not in VALID_TRANSITIONS:
raise ValueError(f'Cannot transition from {current_status}')
if new_status not in VALID_TRANSITIONS[current_status]:
raise ValueError(f'Cannot transition from {current_status} to {new_status}')
# Find all open jobs
grep "open" data/jobs.tsv
# Jobs in web_dev category
grep "web_dev" data/jobs.tsv
# Jobs posted by user u042
grep "u042" data/jobs.tsv
# Applications by user u055
grep "u055" data/applications.tsv
# Accepted applications
grep "accepted" data/applications.tsv
Only 4 states: open, in_progress, completed, cancelled
Most job boards have 15+ states that confuse users: - "Under review" - "Interviewing" - "Contract pending" - "On hold" - etc.
We skip all that. You're either working on it or you're not.
Freelancers apply to jobs (pull model), rather than clients inviting freelancers (push model).
Why? Simpler workflow, less spam, better match quality.
Applications include bid amount, allowing: - Price discovery - Competition - Client choice
Works seamlessly with: - dbbasic-ratings (reputation) - dbbasic-accounts (user management) - dbbasic-follows (notifications) - dbbasic-email (updates)
| Feature | Upwork | Fiverr | Craigslist | dbbasic-jobs |
|---|---|---|---|---|
| Lines of Code | ~500K | ~500K | ~50K | ~200 |
| Job Status States | 15+ | 10+ | 2 | 4 |
| Application System | Yes | No (gigs) | No | Yes |
| Reputation | Yes | Yes | No | Yes (via dbbasic-ratings) |
| Escrow | Yes | Yes | No | Optional (integration) |
| TSV Storage | No | No | No | Yes |
| grep-able | No | No | No | Yes |
Version: 1.0 Specification Implementation: Not yet started Expected Size: ~200 lines Dependencies: dbbasic-tsv only
Optional Integrations: - dbbasic-ratings (reputation) - dbbasic-accounts (user management) - dbbasic-email (notifications) - dbbasic-follows (social features)
Last Updated: November 2025