The Job Invite module allows Customers to proactively invite Contractors to work on their posted jobs. This is the reverse of the job-request module where contractors apply to jobs. With job invites, customers can reach out to specific contractors they're interested in working with.
- Customer posts job
- Contractor applies to job
- Customer reviews applications
- Customer accepts/rejects application
- Chat enabled after acceptance
- Customer posts job
- Customer browses contractors
- Customer sends invite to contractor
- Contractor reviews invite
- Contractor accepts/rejects invite
- Chat enabled after acceptance
File: src/db/models/job-invite.model.ts
{
job: ObjectId, // Reference to Job
customer: ObjectId, // Customer who sent the invite
contractor: ObjectId, // Contractor who received the invite
status: "pending" | "accepted" | "rejected" | "cancelled",
message?: string, // Optional message from customer
rejectionReason?: string, // Optional reason for rejection
createdAt: Date,
updatedAt: Date
}Indexes:
(job, contractor)- Unique compound index (prevents duplicate invites)(customer, status)- For customer's sent invites queries(contractor, status)- For contractor's received invites queries
POST /api/job-invite/send/:jobId
Send an invite to a specific contractor for a job.
Auth: Required (Customer only)
Request:
{
"contractorId": "contractor_id",
"message": "I'd like to invite you to work on this project..."
}Response (201):
{
"status": 201,
"message": "Invite sent successfully",
"data": {
"_id": "invite_id",
"job": { /* job details */ },
"contractor": { /* contractor details */ },
"status": "pending",
"message": "I'd like to invite you...",
"createdAt": "2025-11-16T10:00:00Z"
}
}Validations:
- Customer must own the job
- Job must be in "open" status
- Contractor must exist and have "contractor" role
- Contractor must not be suspended
- No duplicate invites allowed
- Contractor must not have already applied to this job
Notifications:
- Sends push notification to contractor
GET /api/job-invite/sent
Get all invites sent by the customer.
Auth: Required (Customer only)
Query Parameters:
jobId(optional) - Filter by specific jobstatus(optional) - Filter by status (pending, accepted, rejected, cancelled)page(optional) - Page number (default: 1)limit(optional) - Items per page (default: 10)
Response (200):
{
"status": 200,
"message": "Invites retrieved successfully",
"data": {
"invites": [
{
"_id": "invite_id",
"job": { /* job details */ },
"contractor": { /* contractor details */ },
"status": "pending",
"createdAt": "2025-11-16T10:00:00Z"
}
],
"total": 25,
"page": 1,
"limit": 10,
"totalPages": 3
}
}DELETE /api/job-invite/:inviteId
Cancel a sent invite (only pending invites can be cancelled).
Auth: Required (Customer only)
Response (200):
{
"status": 200,
"message": "Invite cancelled successfully",
"data": null
}Validations:
- Customer must be the invite sender
- Invite must be in "pending" status
Notifications:
- Sends push notification to contractor
GET /api/job-invite/received
Get all invites received by the contractor.
Auth: Required (Contractor only)
Query Parameters:
status(optional) - Filter by status (pending, accepted, rejected, cancelled)page(optional) - Page number (default: 1)limit(optional) - Items per page (default: 10)
Response (200):
{
"status": 200,
"message": "Invites retrieved successfully",
"data": {
"invites": [
{
"_id": "invite_id",
"job": { /* job details */ },
"customer": { /* customer details */ },
"status": "pending",
"message": "I'd like to invite you...",
"createdAt": "2025-11-16T10:00:00Z"
}
],
"total": 15,
"page": 1,
"limit": 10,
"totalPages": 2
}
}PATCH /api/job-invite/:inviteId/accept
Accept a job invite.
Auth: Required (Contractor only)
Response (200):
{
"status": 200,
"message": "Invite accepted successfully",
"data": {
"invite": { /* invite details */ },
"conversationId": "conversation_id"
}
}Actions:
- Updates invite status to "accepted"
- Creates or retrieves conversation between customer and contractor
- Sends push notification to customer
Validations:
- Contractor must be the invited one
- Invite must be in "pending" status
- Job must still be in "open" status
Notifications:
- Sends push notification to customer
PATCH /api/job-invite/:inviteId/reject
Reject a job invite with optional reason.
Auth: Required (Contractor only)
Request:
{
"rejectionReason": "Not available at this time"
}Response (200):
{
"status": 200,
"message": "Invite rejected successfully",
"data": {
"_id": "invite_id",
"status": "rejected",
"rejectionReason": "Not available at this time",
/* other invite details */
}
}Validations:
- Contractor must be the invited one
- Invite must be in "pending" status
Notifications:
- Sends push notification to customer
GET /api/job-invite/:inviteId
Get details of a specific invite.
Auth: Required (Customer or Contractor involved in the invite)
Response (200):
{
"status": 200,
"message": "Invite retrieved successfully",
"data": {
"_id": "invite_id",
"job": { /* full job details */ },
"customer": { /* customer details */ },
"contractor": { /* contractor details */ },
"status": "pending",
"message": "I'd like to invite you...",
"createdAt": "2025-11-16T10:00:00Z",
"updatedAt": "2025-11-16T10:00:00Z"
}
}Validations:
- User must be either the customer or contractor involved in the invite
pending → accepted
↓
rejected
↓
cancelled (by customer)
Status Descriptions:
pending- Invite sent, awaiting contractor responseaccepted- Contractor accepted the inviterejected- Contractor declined the invitecancelled- Customer cancelled the invite before contractor responded
-
One Invite Per Contractor Per Job: A customer can only send one invite to a specific contractor for a specific job.
-
No Duplicate Invites: If a contractor has already applied to a job, the customer cannot send them an invite for that job.
-
Job Status Check: Invites can only be sent for jobs with "open" status.
-
Contractor Verification: Only users with "contractor" role can receive invites.
-
Suspended Contractors: Cannot send invites to suspended contractors.
-
Ownership Verification:
- Customers can only send invites for their own jobs
- Contractors can only accept/reject invites sent to them
- Customers can only cancel their own sent invites
-
Status Restrictions:
- Only "pending" invites can be accepted, rejected, or cancelled
- Once processed, invites cannot be modified
-
Conversation Creation: When a contractor accepts an invite, a conversation is automatically created (or retrieved if exists) for chat.
- Checks job status before sending invite
- Verifies job ownership
- Prevents inviting contractors who have already applied
- Complementary system (contractors can apply OR be invited)
- Creates conversation when invite is accepted
- Enables communication between customer and contractor
- Sends push notifications for:
- New invite received (to contractor)
- Invite accepted (to customer)
- Invite rejected (to customer)
- Invite cancelled (to contractor)
- Verifies contractor role and status
- Fetches user details for notifications
All endpoints return consistent error responses:
{
"status": 400/401/403/404/500,
"message": "Error description",
"data": null
}Common Error Codes:
400- Bad request (validation errors, duplicate invites, invalid status)401- Unauthorized (missing or invalid token)403- Forbidden (not authorized for this action)404- Not found (job, contractor, or invite not found)500- Internal server error
// 1. Customer posts a job
POST /api/job
{ title: "Fix plumbing", ... }
// 2. Customer browses contractors
GET /api/user?role=contractor&category=plumbing
// 3. Customer sends invite to contractor
POST /api/job-invite/send/job_123
{ contractorId: "contractor_456", message: "I'd like to hire you" }
// 4. Customer checks sent invites
GET /api/job-invite/sent?status=pending
// 5. Customer cancels invite (if needed)
DELETE /api/job-invite/invite_789// 1. Contractor receives notification
// (Push notification via Firebase)
// 2. Contractor checks received invites
GET /api/job-invite/received?status=pending
// 3. Contractor views invite details
GET /api/job-invite/invite_789
// 4. Contractor accepts invite
PATCH /api/job-invite/invite_789/accept
// 5. Contractor starts chatting
GET /api/chat/conversations
POST /api/chat/messages- Customer can send invite to contractor
- Customer cannot send duplicate invites
- Customer cannot invite contractor who already applied
- Customer can view sent invites with filters
- Customer can cancel pending invite
- Contractor can view received invites
- Contractor can accept invite
- Contractor can reject invite with reason
- Conversation created on invite acceptance
- Notifications sent for all actions
- Proper authorization checks
- Pagination works correctly
- Error handling for all edge cases
- Bulk Invites: Allow customers to invite multiple contractors at once
- Invite Expiration: Auto-expire invites after X days
- Invite Templates: Pre-defined message templates for customers
- Contractor Preferences: Allow contractors to set invite preferences
- Invite Analytics: Track invite acceptance rates
- Recommended Contractors: Suggest contractors based on job requirements