{"openapi":"3.0.0","info":{"title":"FieldCamp AI Web API","version":"1.0.0","description":"Field service management platform API documentation","contact":{"name":"FieldCamp AI"}},"servers":[{"url":"https://app.fieldcamp.ai","description":"Current environment"}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT token obtained from /api/auth/login"}},"schemas":{"SuccessResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Success"},"data":{"type":"object"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Error message"},"data":{"nullable":true,"example":null},"error":{"nullable":true,"example":null}}},"UnauthorizedError":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Unauthorized"},"data":{"nullable":true,"example":null}}},"MongoObjectId":{"type":"string","pattern":"^[a-fA-F0-9]{24}$","example":"507f1f77bcf86cd799439011"},"PaginationParams":{"type":"object","properties":{"page":{"type":"integer","minimum":1,"default":1},"limit":{"type":"integer","minimum":1,"maximum":100,"default":10},"search":{"type":"string"},"sortBy":{"type":"string"},"sortOrder":{"type":"string","enum":["asc","desc"],"default":"desc"}}},"PhoneNumber":{"type":"object","properties":{"countryCode":{"type":"string","example":"+1"},"number":{"type":"string","example":"5551234567"},"countryIdentifier":{"type":"string","example":"US","description":"ISO country code identifier"}}},"Address":{"type":"object","properties":{"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zip":{"type":"string"},"country":{"type":"string"},"lat":{"type":"number","format":"double"},"lng":{"type":"number","format":"double"}}},"FilterObject":{"type":"object","description":"Dynamic filter key-value pairs","additionalProperties":true},"SortingObject":{"type":"object","properties":{"field":{"type":"string"},"order":{"type":"string","enum":["asc","desc"]}}},"LoginRequest":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email","example":"user@example.com"},"password":{"type":"string","format":"password","minLength":6,"example":"password123"},"deviceInfo":{"type":"object","properties":{"deviceId":{"type":"string"},"deviceType":{"type":"integer","description":"1=iOS, 2=Android, 3=Web","default":3},"deviceToken":{"type":"string","nullable":true}}}}},"LoginResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Login successful."},"data":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"token":{"type":"string","description":"JWT auth token"},"role":{"type":"object","properties":{"name":{"type":"string"},"permissions":{"type":"object"},"id":{"$ref":"#/components/schemas/MongoObjectId"}}},"isOnboarded":{"type":"boolean"},"isTeamMember":{"type":"boolean"},"timezone":{"type":"string"},"currency":{"type":"string"},"currencySymbol":{"type":"string"},"companyInfo":{"type":"object"},"integrationStatus":{"type":"object","properties":{"stripe_connect":{"type":"boolean"},"stripe_enabled":{"type":"boolean"}}}}}}},"SignupRequest":{"type":"object","required":["name","email","password"],"properties":{"name":{"type":"string","example":"John Doe"},"email":{"type":"string","format":"email","example":"john@example.com"},"password":{"type":"string","format":"password","minLength":6}}},"AuthTokenResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"token":{"type":"string","description":"JWT auth token"}}}}},"UserProfile":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"phoneNumber":{"$ref":"#/components/schemas/PhoneNumber"},"address":{"$ref":"#/components/schemas/Address"},"role":{"type":"object"},"isTeamMember":{"type":"boolean"},"status":{"type":"string","enum":["Active","Inactive","Deleted"]}}},"Role":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"permissions":{"type":"object"},"createdBy":{"$ref":"#/components/schemas/MongoObjectId"}}},"Skill":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"createdBy":{"$ref":"#/components/schemas/MongoObjectId"}}},"DetailedAddress":{"type":"object","properties":{"street":{"type":"string","example":"123 Main St"},"city":{"type":"string","example":"Austin"},"state":{"type":"string","example":"TX"},"country":{"type":"string","example":"US"},"zipCode":{"type":"string","example":"78701"},"formattedAddress":{"type":"string","example":"123 Main St, Austin, TX 78701"},"houseNumber":{"type":"string"},"latitude":{"type":"number","format":"double"},"longitude":{"type":"number","format":"double"},"plusCode":{"type":"string"}}},"ClientCreateRequest":{"type":"object","description":"Create or update a client. One of `firstName` or `email` is required. When `isFromCopilot` is true and an existing client is found by email/phone, the existing record is updated and `action: \"updated\"` is returned.\n","properties":{"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"},"email":{"type":"string","format":"email","example":"john@example.com"},"phoneNumber":{"$ref":"#/components/schemas/PhoneNumber"},"companyName":{"type":"string"},"propertyAddress":{"$ref":"#/components/schemas/DetailedAddress"},"billingAddress":{"$ref":"#/components/schemas/DetailedAddress"},"companyAddress":{"$ref":"#/components/schemas/DetailedAddress"},"clientType":{"type":"string","enum":["Residential","Commercial"]},"stage":{"type":"string","enum":["New Lead","Contacted","Qualified","Proposal","Inspection","Negotiation","Won","Lost","Active Client","Inactive Client"]},"status":{"type":"string","description":"Client status (used in updates)"},"taxNumber":{"type":"string"},"website":{"type":"string"},"notes":{"type":"string"},"notificationPreferences":{"type":"object","description":"Notification preference settings for the client","additionalProperties":true},"isFromCopilot":{"type":"boolean","description":"When true, triggers deduplication — if a client with the same email or phone already exists, that record is updated instead of creating a duplicate.\n","default":false},"preferredTechnicianIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"jobFormIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"properties":{"type":"array","description":"Custom properties","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"value":{"type":"string","description":"Property value (string or JSON-encoded array for multi-select)"}}}}}},"ClientResponse":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"recordId":{"type":"string","format":"uuid"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","format":"email"},"phoneNumber":{"$ref":"#/components/schemas/PhoneNumber"},"companyName":{"type":"string"},"propertyAddress":{"$ref":"#/components/schemas/DetailedAddress"},"billingAddress":{"$ref":"#/components/schemas/DetailedAddress"},"companyAddress":{"$ref":"#/components/schemas/DetailedAddress"},"clientType":{"type":"string"},"stage":{"type":"string"},"status":{"type":"string"},"taxNumber":{"type":"string"},"website":{"type":"string"},"notes":{"type":"string"},"notificationPreferences":{"type":"object","additionalProperties":true},"properties":{"type":"array","items":{"type":"object"}},"preferredTechnicianIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"jobFormIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"jobCount":{"type":"integer","description":"Only present in list responses when job aggregation is enabled"},"jobTotalAmount":{"type":"number","description":"Only present in list responses when job aggregation is enabled"},"createdBy":{"$ref":"#/components/schemas/MongoObjectId"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"JobItem":{"type":"object","description":"Line item for a job (renamed from LineItem to avoid confusion with document line items)","properties":{"itemId":{"$ref":"#/components/schemas/MongoObjectId","description":"Reference to the inventory/service item"},"itemName":{"type":"string","example":"Lawn Mowing"},"description":{"type":"string"},"quantity":{"type":"number","example":1},"price":{"type":"number","example":50,"description":"Unit price (not 'rate')"},"total":{"type":"number","example":50},"orderIndex":{"type":"integer","description":"Display order index"},"type":{"type":"string","description":"Item type"},"taxIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"exemptFromTax":{"type":"boolean","default":false}}},"JobCreateRequest":{"type":"object","required":["clientId"],"description":"Create a new job. Only `clientId` is required. Note: there is NO `jobTitle` field — jobs are identified by `jobNumber`. Schedule fields are flat (not wrapped in jobSchedule). Use `assignedToTeams` (not `assignedTo`) to assign team members. Use `jobItems` (not `lineItems`) for line items.\n","properties":{"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"jobNumber":{"type":"string","description":"Job number (auto-generated if not provided)"},"jobPhone":{"$ref":"#/components/schemas/PhoneNumber","description":"Phone number for the job location"},"jobAddress":{"$ref":"#/components/schemas/DetailedAddress"},"assignedToTeams":{"type":"array","description":"Team member IDs assigned to this job","items":{"$ref":"#/components/schemas/MongoObjectId"}},"jobType":{"type":"string","enum":["one-off","recurring","multi-day"],"default":"one-off"},"jobStatus":{"type":"string","description":"Job status","enum":["draft","scheduled","unscheduled","in-progress","on-my-way","closed","ai_queued","overdue","incomplete"]},"startDateTime":{"type":"string","format":"date-time","description":"ISO 8601 datetime for job start (flat field, not inside jobSchedule)"},"endDateTime":{"type":"string","format":"date-time","description":"ISO 8601 datetime for job end"},"timezone":{"type":"string","example":"America/Chicago","description":"IANA timezone string"},"scheduleLater":{"type":"boolean","default":false,"description":"Create job without a scheduled date"},"anyTime":{"type":"boolean","default":false,"description":"Job can be done at any time on the scheduled date"},"serviceDuration":{"type":"integer","description":"Expected service duration in seconds"},"repeatOptions":{"type":"object","description":"Recurrence configuration (for recurring job type)","additionalProperties":true},"jobFormIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Job form IDs attached to this job"},"jobItems":{"type":"array","description":"Line items for this job (field name is jobItems, not lineItems)","items":{"$ref":"#/components/schemas/JobItem"}},"visits":{"type":"array","description":"Visit objects for multi-day or recurring jobs","items":{"type":"object"}},"subTotal":{"type":"number","format":"float"},"discount":{"type":"number","format":"float","default":0},"discountType":{"type":"integer","description":"1=percentage, 2=fixed","enum":[1,2],"default":1},"tax":{"type":"number","format":"float","default":0},"total":{"type":"number","format":"float"},"priority":{"type":"string","enum":["low","medium","high","critical"],"default":"medium"},"skillsId":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Required skill IDs"},"jobTypeId":{"$ref":"#/components/schemas/MongoObjectId","description":"Job type category ID"},"equipmentIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Equipment IDs required for this job"},"billToClientId":{"$ref":"#/components/schemas/MongoObjectId","description":"Billing client ID (if different from job client)"},"requestId":{"$ref":"#/components/schemas/MongoObjectId","description":"Source request ID (if job was created from a request)"},"depositId":{"$ref":"#/components/schemas/MongoObjectId"},"depositAmount":{"type":"number","format":"float"},"isAiScheduled":{"type":"boolean","default":false,"description":"Whether this job was scheduled by the AI Dispatcher"},"notes":{"type":"string","description":"Internal notes (stored as internalNotes.note)"},"properties":{"type":"array","items":{"type":"object"}}}},"JobResponse":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"recordId":{"type":"string","format":"uuid"},"jobNumber":{"type":"string","description":"Auto-generated job number"},"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"clientName":{"type":"string"},"jobPhone":{"$ref":"#/components/schemas/PhoneNumber"},"jobAddress":{"$ref":"#/components/schemas/DetailedAddress"},"assignedToTeams":{"type":"array","description":"Team member IDs assigned to this job","items":{"$ref":"#/components/schemas/MongoObjectId"}},"jobType":{"type":"string","enum":["one-off","recurring","multi-day"]},"jobStatus":{"type":"string","enum":["draft","scheduled","unscheduled","in-progress","on-my-way","closed","ai_queued","overdue","incomplete"]},"startDateTime":{"type":"string","format":"date-time"},"endDateTime":{"type":"string","format":"date-time"},"timezone":{"type":"string"},"scheduleLater":{"type":"boolean"},"anyTime":{"type":"boolean"},"priority":{"type":"string","enum":["low","medium","high","critical"]},"jobItems":{"type":"array","items":{"$ref":"#/components/schemas/JobItem"}},"subTotal":{"type":"number"},"discount":{"type":"number"},"tax":{"type":"number"},"total":{"type":"number"},"skillsId":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"equipmentIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"notes":{"type":"string"},"visits":{"type":"array","items":{"type":"object"}},"properties":{"type":"array","items":{"type":"object"}},"createdBy":{"$ref":"#/components/schemas/MongoObjectId"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"RequestItem":{"type":"object","properties":{"itemId":{"$ref":"#/components/schemas/MongoObjectId"},"itemName":{"type":"string","example":"Plumbing Service"},"description":{"type":"string"},"quantity":{"type":"number","example":1},"rate":{"type":"number","example":75},"taxIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"exemptFromTax":{"type":"boolean","default":false}}},"RequestTax":{"type":"object","properties":{"taxId":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string","example":"Sales Tax"},"rate":{"type":"number","example":8.25},"taxType":{"type":"integer","description":"1=Exclusive, 2=Inclusive","enum":[1,2]},"applyOnAllItems":{"type":"boolean","default":false}}},"RequestCreateRequest":{"type":"object","required":["clientId"],"description":"Create a service request. Only `clientId` is required. Use `items` (not `requestItems`) and `taxes` (not `requestTaxes`) for line items and taxes. Stage enum values are slugs (e.g. \"new_request\"), not display names.\n","properties":{"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"stage":{"type":"string","enum":["new_request","unscheduled","overdue","inspection_scheduled","inspection_complete","quote_created","quote_sent","converted","lost_no_response","lost_reject_quote","cancelled","duplicate"],"default":"new_request"},"source":{"type":"string","enum":["manual","online_booking"],"default":"manual"},"description":{"type":"string","default":""},"urgency":{"type":"string","enum":["low","medium","high","critical"],"default":"medium"},"requestAddress":{"$ref":"#/components/schemas/DetailedAddress"},"requestPhone":{"$ref":"#/components/schemas/PhoneNumber"},"preferredContactMethod":{"type":"string","enum":["phone","email","text"],"default":"phone"},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"},"availabilityDate1":{"type":"string","format":"date","description":"First alternate availability date"},"availabilityDate2":{"type":"string","format":"date","description":"Second alternate availability date"},"scheduleLater":{"type":"boolean","default":false},"anyTime":{"type":"boolean","default":false},"requirements":{"type":"string"},"specialInstructions":{"type":"string"},"tags":{"type":"array","items":{"type":"string"},"default":[]},"timezone":{"type":"string","example":"America/Chicago"},"estimatedValue":{"type":"number","format":"float","default":0},"items":{"type":"array","description":"Request line items (field name is 'items', not 'requestItems')","items":{"$ref":"#/components/schemas/RequestItem"}},"taxes":{"type":"array","description":"Request taxes (field name is 'taxes', not 'requestTaxes')","items":{"$ref":"#/components/schemas/RequestTax"}},"notes":{"type":"string"},"assignedTo":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"properties":{"type":"array","items":{"type":"object"}}}},"RequestResponse":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"recordId":{"type":"string","format":"uuid"},"requestNumber":{"type":"integer"},"requestNumberDisplay":{"type":"string","example":"REQ #1"},"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"clientName":{"type":"string"},"stage":{"type":"string","enum":["new_request","unscheduled","overdue","inspection_scheduled","inspection_complete","quote_created","quote_sent","converted","lost_no_response","lost_reject_quote","cancelled","duplicate"]},"stageName":{"type":"string","description":"Human-readable stage display name"},"source":{"type":"string","enum":["manual","online_booking"]},"urgency":{"type":"string","enum":["low","medium","high","critical"]},"description":{"type":"string"},"requestAddress":{"$ref":"#/components/schemas/DetailedAddress"},"items":{"type":"array","description":"Request line items (field name is 'items', not 'requestItems')","items":{"$ref":"#/components/schemas/RequestItem"}},"taxes":{"type":"array","description":"Request taxes (field name is 'taxes', not 'requestTaxes')","items":{"$ref":"#/components/schemas/RequestTax"}},"subtotal":{"type":"number"},"taxTotal":{"type":"number"},"total":{"type":"number"},"notes":{"type":"string"},"assignedTo":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"properties":{"type":"array","items":{"type":"object"}},"createdBy":{"$ref":"#/components/schemas/MongoObjectId"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"VisitCreateRequest":{"type":"object","required":["jobId","technicianId","scheduledStart","scheduledEnd"],"properties":{"jobId":{"$ref":"#/components/schemas/MongoObjectId"},"technicianId":{"$ref":"#/components/schemas/MongoObjectId"},"scheduledStart":{"type":"string","format":"date-time","description":"ISO datetime for visit start"},"scheduledEnd":{"type":"string","format":"date-time","description":"ISO datetime for visit end"},"status":{"type":"string","enum":["scheduled","unscheduled","in_progress","complete","incomplete"],"default":"scheduled"},"suggestionId":{"type":"string","description":"AI Dispatcher suggestion ID"}}},"VisitResponse":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"recordId":{"type":"string","format":"uuid"},"jobId":{"$ref":"#/components/schemas/MongoObjectId"},"teamId":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"visitStartDateTime":{"type":"string","format":"date-time"},"visitEndDateTime":{"type":"string","format":"date-time"},"visitStatus":{"type":"string"},"serviceDuration":{"type":"integer","description":"Duration in seconds"},"scheduleLater":{"type":"boolean"},"anyTime":{"type":"boolean"},"status":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"DocumentLineItem":{"type":"object","required":["itemType"],"properties":{"name":{"type":"string","example":"Service Call"},"description":{"type":"string"},"quantity":{"type":"number","example":1},"rate":{"type":"number","example":100},"itemType":{"type":"integer","description":"REQUIRED. Item type: 1=Product, 2=Service, 3=Labor","enum":[1,2,3]},"taxIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"exemptFromTax":{"type":"boolean","default":false},"action":{"type":"integer","description":"PATCH only — item action: 1=add, 2=update, 3=delete","enum":[1,2,3]}}},"DocumentCreateRequest":{"type":"object","required":["documentType","title"],"description":"Create an invoice (documentType=1) or estimate (documentType=2). Both `documentType` and `title` are required. `documentNumber` is auto-generated and does not need to be provided.\n","properties":{"documentType":{"type":"integer","description":"REQUIRED. 1=Invoice, 2=Estimate","enum":[1,2]},"title":{"type":"string","description":"REQUIRED. Document title/heading","example":"Spring Lawn Service Invoice"},"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"documentPreFix":{"type":"string","description":"Prefix for the document number (e.g. \"INV-\")"},"date":{"type":"string","format":"date","description":"Document issue date (field is 'date', not 'issueDate')"},"dueDate":{"type":"string","format":"date"},"documentSubType":{"type":"integer","default":0,"description":"Document sub-type integer"},"shippingInfo":{"type":"object","description":"Shipping information JSON","additionalProperties":true},"shippingAmount":{"type":"number","format":"float","default":0},"markup":{"type":"number","format":"float","default":0},"markupType":{"type":"integer","description":"1=percentage, 2=fixed","enum":[1,2],"default":1},"markupAppliedOn":{"type":"string","description":"What the markup is applied on"},"discount":{"type":"number","format":"float","default":0},"discountType":{"type":"integer","description":"1=percentage, 2=fixed","enum":[1,2],"default":1},"depositAmount":{"type":"number","format":"float"},"paymentStatus":{"type":"string","enum":["unpaid","paid","partially_paid","overdue","cancelled"],"default":"unpaid"},"paymentTerms":{"type":"string","description":"Payment terms (e.g. \"Net 30\")"},"paymentInstructions":{"type":"string"},"attachments":{"type":"array","items":{"type":"object"}},"comments":{"type":"string"},"privateNotes":{"type":"string"},"declaration":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"isMultiOption":{"type":"boolean","default":false,"description":"Enable multi-option estimate mode"},"estimateOptions":{"type":"array","description":"Estimate options when isMultiOption=true. Each option has its own line items, totals, and approval status.\n","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"displayColor":{"type":"string"},"sortOrder":{"type":"integer"},"isDefault":{"type":"boolean"},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/DocumentLineItem"}}}}},"assignedTo":{"type":"array","description":"Assigned user IDs","items":{"$ref":"#/components/schemas/MongoObjectId"}},"templateId":{"$ref":"#/components/schemas/MongoObjectId","description":"Document template ID"},"jobId":{"$ref":"#/components/schemas/MongoObjectId"},"notes":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/DocumentLineItem"}},"taxes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"rate":{"type":"number"},"taxType":{"type":"integer","enum":[1,2]}}}}}},"DocumentUpdateRequest":{"type":"object","required":["documentId"],"properties":{"documentId":{"$ref":"#/components/schemas/MongoObjectId"},"documentType":{"type":"integer","enum":[1,2]},"paymentStatus":{"type":"string","enum":["unpaid","paid","partially_paid","overdue","cancelled"]},"actionStatus":{"type":"integer","description":"Estimate approval: 0=pending, 1=approved, 2=declined","enum":[0,1,2]},"items":{"type":"array","description":"Line items. Each item can have an `action` field: 1=add, 2=update, 3=delete.\n","items":{"$ref":"#/components/schemas/DocumentLineItem"}},"notes":{"type":"string"},"dueDate":{"type":"string","format":"date"},"date":{"type":"string","format":"date","description":"Issue date (field is 'date', not 'issueDate')"},"discount":{"type":"number"},"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"jobId":{"$ref":"#/components/schemas/MongoObjectId"},"taxes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"rate":{"type":"number"}}}}}},"DocumentResponse":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"recordId":{"type":"string","format":"uuid"},"documentNumber":{"type":"string"},"documentType":{"type":"integer","description":"1=Invoice, 2=Estimate"},"title":{"type":"string"},"documentPreFix":{"type":"string"},"documentSubType":{"type":"integer"},"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"clientName":{"type":"string"},"jobId":{"$ref":"#/components/schemas/MongoObjectId"},"date":{"type":"string","format":"date","description":"Issue date (field is 'date', not 'issueDate')"},"dueDate":{"type":"string","format":"date"},"paymentStatus":{"type":"string","enum":["unpaid","paid","partially_paid","overdue","cancelled"]},"paidAmount":{"type":"number","format":"float"},"items":{"type":"array","items":{"$ref":"#/components/schemas/DocumentLineItem"}},"subtotal":{"type":"number"},"taxTotal":{"type":"number"},"inclusiveTaxAmount":{"type":"number"},"exclusiveTaxAmount":{"type":"number"},"total":{"type":"number"},"discount":{"type":"number"},"depositAmount":{"type":"number"},"isMultiOption":{"type":"boolean"},"shippingAmount":{"type":"number"},"markup":{"type":"number"},"comments":{"type":"string"},"privateNotes":{"type":"string"},"notes":{"type":"string"},"paymentTerms":{"type":"string"},"paymentInstructions":{"type":"string"},"createdBy":{"$ref":"#/components/schemas/MongoObjectId"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"InventoryTransferRequest":{"type":"object","required":["fromWarehouseId","toWarehouseId","inventoryId","quantity"],"description":"Transfer inventory from one warehouse to another","properties":{"fromWarehouseId":{"$ref":"#/components/schemas/MongoObjectId","description":"Source warehouse ID"},"toWarehouseId":{"$ref":"#/components/schemas/MongoObjectId","description":"Destination warehouse ID (must differ from fromWarehouseId)"},"inventoryId":{"$ref":"#/components/schemas/MongoObjectId","description":"Inventory record ID to transfer"},"quantity":{"type":"number","description":"Quantity to transfer (must be > 0 and <= available quantity)"},"note":{"type":"string","description":"Optional transfer note"}}},"InventoryItemResponse":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"description":{"type":"string"},"price":{"type":"number"},"cost":{"type":"number","format":"float","description":"Purchase/cost price"},"currency":{"type":"string","example":"USD"},"type":{"type":"string"},"categoryIds":{"type":"array","description":"Array of category IDs (replaces single 'category' string)","items":{"$ref":"#/components/schemas/MongoObjectId"}},"preferredVendor":{"type":"object","description":"Preferred vendor details","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"email":{"type":"string","format":"email"}}},"unitOfMeasurement":{"type":"object","description":"Unit of measurement object (not just a string)","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"abbreviation":{"type":"string"}}},"taxIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"trackInventory":{"type":"boolean"},"sku":{"type":"string","description":"Stock Keeping Unit identifier"},"binLocation":{"type":"string","description":"Physical bin/shelf location in warehouse"},"lowStockAlert":{"type":"number","description":"Threshold quantity that triggers a low-stock alert"},"isBillable":{"type":"boolean","description":"Whether the item is billable to clients"},"duration":{"type":"string","description":"Service duration (for service-type items)"},"bookingType":{"type":"string","description":"Booking type for service items"},"status":{"type":"string","example":"available"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}},"security":[{"BearerAuth":[]}],"tags":[{"name":"Auth & Security","description":"Authentication, login, signup, password reset"},{"name":"User & Roles","description":"User management, roles, skills, settings"},{"name":"System & Configuration","description":"Company info, regions, views, properties, metadata"},{"name":"Teams","description":"Team member management, invitations, scheduling"},{"name":"Products & Inventory","description":"Products, services, inventory, equipment, categories"},{"name":"Clients & Customer Portal","description":"Client management, drive, customer portal"},{"name":"Documents & Templates","description":"Documents, templates, estimates, invoices"},{"name":"Payments & Billing","description":"Stripe, payment options, deposits, purchase orders"},{"name":"Requests","description":"Service requests, quotes, and request pipeline"},{"name":"Jobs & Visits","description":"Jobs, visits, scheduling, dispatch, forms"},{"name":"Items & Inventory","description":"Inventory items, products, and services catalog"},{"name":"Communication","description":"Notifications, emails, SMS, conversations"},{"name":"Workflows & Automation","description":"Workflow builder, triggers, executions"},{"name":"Analytics & Reporting","description":"Analytics dashboards, charts, reports"},{"name":"AI & Chat","description":"AI dispatcher, chat, skills, copilot"},{"name":"Team Tracking & Live Pulse","description":"Real-time tracking, Traccar, sessions"},{"name":"Integrations & Addons","description":"Twilio, Jobber, QuickBooks, addons"}],"paths":{"/api/addons/addon-connect":{"post":{"summary":"Connect an addon","description":"Connects (installs) a specified addon type for the authenticated user. Creates a new addon record or updates an existing one.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["addonConnectType"],"properties":{"addonConnectType":{"type":"string","description":"The addon type to connect","example":"online_booking"}}}}}},"responses":{"200":{"description":"Addon connected successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"}}}}}]}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/addons/addon-disconnect":{"post":{"summary":"Disconnect an addon","description":"Disconnects a specified addon type for the authenticated user. For AI Dispatcher, also clears all activation-related fields.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["addonDisonnectType"],"properties":{"addonDisonnectType":{"type":"string","description":"The addon type to disconnect","example":"online_booking"}}}}}},"responses":{"200":{"description":"Addon disconnected successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"}}}}}]}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/addons/ai-call-connect":{"post":{"summary":"Connect the AI Call addon","description":"Connects the AI Call addon by acquiring an AI call token and purchasing a Twilio phone number. Includes automatic token retry logic.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"AI Call addon connected successfully with Twilio data","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"twilioData":{"type":"object"}}}}}]}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error or Twilio service failure"}}}},"/api/addons/ai-call-disconnect":{"post":{"summary":"Disconnect the AI Call addon","description":"Disconnects the AI Call addon by releasing the Twilio phone number and clearing addon data. Includes automatic token retry logic.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"AI Call addon disconnected successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"}}}}}]}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/addons/ai-dispatcher/generate-otp":{"post":{"summary":"Generate new OTP for AI Dispatcher activation","description":"Manager/Admin endpoint to regenerate an OTP for a user's AI Dispatcher activation. Returns the OTP directly so manager can send via any platform. Requires BearerAuth (admin or manager role).","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userEmail"],"properties":{"userEmail":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"New OTP generated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"email":{"type":"string"},"otp":{"type":"string"},"generatedAt":{"type":"string","format":"date-time"}}}}}}}]}}}},"400":{"description":"userEmail is required"},"401":{"description":"Unauthorized - missing or invalid token"},"403":{"description":"Forbidden - insufficient role"},"404":{"description":"No pending activation request found"},"500":{"description":"Failed to generate OTP"}}}},"/api/addons/ai-dispatcher/request-activation":{"post":{"summary":"Request AI Dispatcher activation","description":"Submits an activation request for the AI Dispatcher addon. Auto-generates an OTP and sends an email to the manager with the activation code.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userEmail"],"properties":{"userEmail":{"type":"string","format":"email"},"userName":{"type":"string"},"companyName":{"type":"string"}}}}}},"responses":{"200":{"description":"Activation request submitted successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"requestedAt":{"type":"string","format":"date-time"},"email":{"type":"string","format":"email"}}}}}}}]}}}},"400":{"description":"User email required, addon not installed, or already activated"},"401":{"description":"Unauthorized"},"500":{"description":"Failed to submit activation request"}}}},"/api/addons/ai-dispatcher/verify-otp":{"post":{"summary":"Verify OTP to activate AI Dispatcher","description":"User verifies the OTP code to complete AI Dispatcher addon activation. Clears the OTP after successful verification.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["otp"],"properties":{"otp":{"type":"string","description":"6-digit OTP code","example":"123456"}}}}}},"responses":{"200":{"description":"AI Dispatcher activated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"isActivated":{"type":"boolean"},"activatedAt":{"type":"string","format":"date-time"}}}}}}}]}}}},"400":{"description":"OTP required, invalid OTP, already activated, or no OTP generated"},"401":{"description":"Unauthorized"},"404":{"description":"AI Dispatcher addon not found"},"500":{"description":"Failed to verify OTP"}}}},"/api/addons/ai-status":{"get":{"summary":"Get AI addon status by type","description":"Returns the status and details of a specific addon by type for the authenticated user.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"type","required":true,"schema":{"type":"string"},"description":"The addon type to check status for","example":"ai_call"}],"responses":{"200":{"description":"Addon status retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"addon":{"type":"object"}}}}}]}}}},"400":{"description":"Addon type is required"},"401":{"description":"Unauthorized"},"404":{"description":"Addon not found"},"500":{"description":"Failed to fetch addon status"}}}},"/api/addons/calls-connect":{"post":{"summary":"Connect the Calls addon","description":"Connects (installs) the Calls addon for the authenticated user. Creates a new addon record or reactivates an existing one.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Calls addon connected successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string"},"isConnected":{"type":"boolean"},"addonId":{"$ref":"#/components/schemas/MongoObjectId"}}}}}]}}}},"401":{"description":"Unauthorized or invalid token"},"500":{"description":"Internal server error"}}}},"/api/addons/calls-disconnect":{"post":{"summary":"Disconnect the Calls addon","description":"Disconnects the Calls addon, deletes the associated TwiML App, clears Twilio data, and deactivates all phone numbers.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Calls addon disconnected successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string"},"isConnected":{"type":"boolean"}}}}}]}}}},"401":{"description":"Unauthorized or invalid token"},"500":{"description":"Internal server error"}}}},"/api/addons/crm-settings-connect":{"post":{"summary":"Connect the CRM Settings addon","description":"Connects the CRM Settings addon for the authenticated user and propagates connection to child records.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"CRM Settings addon connected successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"}}}}}]}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/addons/crm-settings-disconnect":{"post":{"summary":"Disconnect the CRM Settings addon","description":"Disconnects the CRM Settings addon for the authenticated user and propagates disconnection to child records.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"CRM Settings addon disconnected successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"}}}}}]}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/addons/job-forms-connect":{"post":{"summary":"Connect the Job Forms addon","description":"Connects the Job Forms addon for the authenticated user and propagates connection to child records.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Job Forms addon connected successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"}}}}}]}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/addons/job-forms-disconnect":{"post":{"summary":"Disconnect the Job Forms addon","description":"Disconnects the Job Forms addon for the authenticated user and propagates disconnection to child records.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Job Forms addon disconnected successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"}}}}}]}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/addons/status":{"get":{"summary":"Get all addon statuses","description":"Returns connection and activation status for all addons (AI Call, Job Forms, CRM Settings, Online Booking, Service Area, Customer Portal, Calls, Multi-Day Job, Job Logs, Timesheet, AI Dispatcher) and widget settings.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"isTeamMember","schema":{"type":"string","enum":["true","false"]},"description":"Whether the requesting user is a team member"}],"responses":{"200":{"description":"Addon statuses retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"aiCall":{"type":"object","properties":{"isConnected":{"type":"boolean"}}},"jobForms":{"type":"object","properties":{"isConnected":{"type":"boolean"}}},"crmSettings":{"type":"object","properties":{"isConnected":{"type":"boolean"}}},"aiDispatcher":{"type":"object","properties":{"isConnected":{"type":"boolean"},"isActivated":{"type":"boolean"}}}}}}}]}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/agent-memory":{"get":{"summary":"Get agent memory files","description":"Retrieves memory files for a specific agent (server-to-server, API key auth)","tags":["AI & Dispatcher"],"parameters":[{"in":"query","name":"agentId","required":true,"schema":{"type":"string"},"description":"Agent identifier"}],"responses":{"200":{"description":"Memory files retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Sync agent memory files","description":"Upserts memory files for one or more agents (server-to-server, API key auth)","tags":["AI & Dispatcher"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["agentId","memories"],"properties":{"agentId":{"type":"string","description":"Agent identifier"},"memories":{"type":"array","items":{"type":"object","properties":{"filename":{"type":"string"},"content":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Memory files synced successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai-dispatcher-settings/{id}":{"put":{"summary":"Update AI Dispatcher settings","description":"Updates AI Dispatcher settings by ID","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"AI Dispatcher settings ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"dispatchMode":{"type":"string","description":"Dispatch mode (e.g., immediate)"},"maxDriveTimeMinutes":{"type":"integer","description":"Maximum drive time in minutes"},"allowOvertime":{"type":"boolean","description":"Whether overtime is allowed"}}}}}},"responses":{"200":{"description":"Settings updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai-dispatcher-settings":{"get":{"summary":"Get AI Dispatcher settings","description":"Fetches AI Dispatcher settings for the authenticated organization","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Settings retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"Settings not found"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Create AI Dispatcher settings","description":"Creates new AI Dispatcher settings for the organization","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"dispatchMode":{"type":"string","description":"Dispatch mode"},"maxDriveTimeMinutes":{"type":"integer","description":"Maximum drive time in minutes"},"allowOvertime":{"type":"boolean","description":"Whether overtime is allowed"}}}}}},"responses":{"200":{"description":"Settings created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Settings already exist"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai-dispatcher/callback":{"post":{"summary":"AI Dispatcher callback","description":"Callback endpoint for AI Dispatcher to POST results after job optimization processing. Handles single, batch, and reshuffled assignments.","tags":["AI & Dispatcher"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string"},"fsmJobId":{"type":"string"},"status":{"type":"string","enum":["success","failed","rejected","timeout"]},"result":{"type":"object","properties":{"suggestionId":{"type":"string"},"suggestion":{"type":"object"},"isBatch":{"type":"boolean"},"assignments":{"type":"array","items":{"type":"object"}},"unassignedReason":{"type":"string"}}},"metadata":{"type":"object"}}}}}},"responses":{"200":{"description":"Callback processed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized - missing or invalid webhook secret"},"404":{"description":"Job not found"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"summary":"Callback health check","description":"Returns health status of the callback endpoint","tags":["AI & Dispatcher"],"responses":{"200":{"description":"Endpoint is healthy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/ai-dispatcher/cleanup":{"delete":{"summary":"Clean up AI Dispatcher data","description":"Removes stale or expired AI Dispatcher suggestions and related data","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"jobIds":{"type":"array","items":{"type":"string"},"description":"Array of job IDs to clean up"},"visitIds":{"type":"array","items":{"type":"string"},"description":"Array of visit IDs to clean up"}}}}}},"responses":{"200":{"description":"Cleanup completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"summary":"Get AI Dispatcher cleanup status","description":"Health check for the cleanup endpoint","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Cleanup status retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai-dispatcher/decisions":{"post":{"summary":"Submit dispatch decision","description":"Accept or reject an AI Dispatcher suggestion for a single job or visit","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["decision"],"properties":{"jobId":{"type":"string","description":"FieldCamp job ID (for one-off jobs)"},"visitId":{"type":"string","description":"FieldCamp visit ID (for recurring job visits)"},"decision":{"type":"string","enum":["ACCEPTED","REJECTED"]},"note":{"type":"string"},"rejectionReasons":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Decision processed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid input or already decided"},"404":{"description":"Job or visit not found"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"summary":"Bulk dispatch decisions","description":"Accept or reject multiple jobs/visits with chunking for large batches","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["decision"],"properties":{"jobIds":{"type":"array","items":{"type":"string"},"description":"Array of job IDs (for one-off jobs)"},"visitIds":{"type":"array","items":{"type":"string"},"description":"Array of visit IDs (for recurring visits)"},"decision":{"type":"string","enum":["ACCEPTED","REJECTED"]},"note":{"type":"string"},"rejectionReasons":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Bulk decision processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid input"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"summary":"Decisions health check","description":"Returns health status of the decisions endpoint","tags":["AI & Dispatcher"],"responses":{"200":{"description":"Endpoint is healthy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/ai-dispatcher/events":{"get":{"summary":"Stream AI Dispatcher events","description":"Server-Sent Events endpoint for real-time AI Dispatcher updates and notifications","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"token","required":true,"schema":{"type":"string"},"description":"JWT authentication token"}],"responses":{"200":{"description":"SSE stream established","content":{"text/event-stream":{"schema":{"type":"string"}}}},"401":{"description":"Authentication token required or invalid"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai-dispatcher/resync-visit":{"post":{"summary":"Re-sync rejected visits","description":"Resets rejected visit status and re-dispatches to AI Dispatcher. Supports single visit, bulk visits, or all rejected visits for a job.","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"visitId":{"type":"string","description":"Single visit ID to re-sync"},"visitIds":{"type":"array","items":{"type":"string"},"description":"Array of visit IDs for bulk re-sync"},"jobId":{"type":"string","description":"Parent job ID to re-sync all rejected visits"}}}}}},"responses":{"200":{"description":"Visits reset and dispatched","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"No eligible visits or missing IDs"},"403":{"description":"Not authorized"},"404":{"description":"Visit or job not found"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai-dispatcher/sync-visit":{"post":{"summary":"Sync accepted visit suggestion","description":"Called by AI Dispatcher when a suggestion is accepted. Updates the visit and parent job with the accepted schedule.","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["visitId","technicianId","scheduledStart","scheduledEnd"],"properties":{"visitId":{"type":"string","description":"FieldCamp visit ID to update"},"technicianId":{"type":"string","description":"Technician to assign"},"scheduledStart":{"type":"string","format":"date-time","description":"ISO datetime for scheduled start"},"scheduledEnd":{"type":"string","format":"date-time","description":"ISO datetime for scheduled end"},"status":{"type":"string","description":"Visit status (e.g. scheduled)"},"suggestionId":{"type":"string","description":"AI Dispatcher suggestion ID for sync"},"jobId":{"type":"string","description":"Parent job ID (for one-off jobs)"}}}}}},"responses":{"200":{"description":"Visit synced successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing required fields"},"403":{"description":"Not authorized"},"404":{"description":"Visit not found"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai-skills/{id}":{"get":{"summary":"Get AI skill by ID","description":"Retrieves a single AI skill by its ID","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"AI Skill ID"}],"responses":{"200":{"description":"Skill retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"Skill not found"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"summary":"Update AI skill","description":"Updates an existing custom AI skill by ID","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"AI Skill ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"instructions":{"type":"string"}}}}}},"responses":{"200":{"description":"Skill updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"Skill not found"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"summary":"Delete AI skill","description":"Deletes a custom AI skill by ID","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"AI Skill ID"}],"responses":{"200":{"description":"Skill deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"Skill not found"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai-skills/{id}/toggle":{"patch":{"summary":"Toggle AI skill enabled/disabled","description":"Toggles the enabled state of an AI skill by ID","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"AI Skill ID"}],"responses":{"200":{"description":"Skill toggled successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"404":{"description":"Skill not found"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai-skills":{"get":{"summary":"List AI skills","description":"Retrieves all AI skills for the authenticated organization","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"},"description":"Search term for filtering skills"},{"in":"query","name":"type","schema":{"type":"string","enum":["official","custom"]},"description":"Filter by skill type"}],"responses":{"200":{"description":"Skills retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Create AI skill","description":"Creates a new custom AI skill","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","description"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"instructions":{"type":"string"}}}}}},"responses":{"201":{"description":"Skill created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"409":{"description":"Skill with this name already exists"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai-skills/seed":{"post":{"summary":"Seed official AI skills","description":"Seeds official AI skills for the organization (idempotent)","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"responses":{"201":{"description":"Skills seeded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai-skills/upload":{"post":{"summary":"Upload AI skill file","description":"Uploads a .skill or .md file containing AI skill definition with YAML frontmatter","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":".skill or .md file with YAML frontmatter"}}}}}},"responses":{"201":{"description":"Skill uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid file or skill data"},"409":{"description":"Skill with this name already exists"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/ai/dynamic-slots":{"post":{"summary":"Generate dynamic or recurring scheduling slots","description":"Calls the Python AI API to generate available time slots for scheduling jobs. Supports both one-off dynamic slots and recurring slot generation.","tags":["AI & Chat"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["address"],"properties":{"address":{"type":"string"},"duration":{"type":"number"},"num_working_days":{"type":"number"},"travel_time":{"type":"number","default":0},"technician_ids":{"type":"array","items":{"type":"string"}},"fromDate":{"type":"string","format":"date"},"toDate":{"type":"string","format":"date"},"isRecurring":{"type":"boolean"},"visits":{"type":"number"},"interval":{"type":"string"},"frequency":{"type":"string"},"total_visits":{"type":"number"},"duration_details":{"type":"object"},"drivemode":{"type":"string"},"start_first_job_at_shift_time":{"type":"boolean"},"job_shift":{"type":"array","items":{"type":"string"}},"travel_service":{"type":"string","default":"haversine"}}}}}},"responses":{"200":{"description":"Slot suggestions generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing required fields"},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/ai/team-suggestions":{"post":{"summary":"Get AI-powered team member suggestions for a job","description":"Returns ranked technician recommendations based on address, skills, preferred date, and duration. Supports both one-off and recurring job suggestions.","tags":["AI & Chat"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["address"],"properties":{"address":{"type":"string"},"latitude":{"type":"number"},"longitude":{"type":"number"},"skills":{"type":"array","items":{"type":"string"}},"preferred_date":{"type":"string","format":"date-time"},"duration_minutes":{"type":"number"},"isRecurring":{"type":"boolean"},"recurringOptions":{"type":"object","properties":{"technician_id":{"type":"string"},"frequency":{"type":"string"},"duration":{"type":"object"}}}}}}}},"responses":{"200":{"description":"Team suggestions generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing required fields"},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/ai/template-slots":{"post":{"summary":"Find available template-based scheduling slots","description":"Searches for available 30-day scheduling slots based on a job template with day configurations and crew requirements.","tags":["AI & Chat"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["template"],"properties":{"template":{"type":"object","required":["dayConfigurations"],"properties":{"name":{"type":"string"},"dayConfigurations":{"type":"array","items":{"type":"object","properties":{"dayNumber":{"type":"number"},"duration":{"type":"number"},"crewMembers":{"type":"array","items":{"type":"object"}}}}}}},"technicianIds":{"type":"array","items":{"type":"string"}},"workingDaysOnly":{"type":"boolean","default":true},"searchDays":{"type":"number","default":30},"startDate":{"type":"string","format":"date"},"travel_service":{"type":"string","default":"haversine"}}}}}},"responses":{"200":{"description":"Template slots generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing required template configuration"},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/auth/check-email":{"post":{"tags":["Auth & Security"],"summary":"Check if email is available for registration","description":"Validates email format, checks against disposable domains, and verifies if email is already registered","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","example":"user@example.com"}}}}}},"responses":{"200":{"description":"Email check result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"exists":{"type":"boolean"},"blocked":{"type":"boolean"},"reason":{"type":"string"}}}}}}}},"400":{"description":"Email is required"},"500":{"description":"Server error"}}}},"/api/auth/complete-onboarding-step":{"post":{"tags":["Auth & Security"],"summary":"Mark an onboarding step as complete","description":"Updates user onboarding or business setup step progress","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"stepId":{"type":"string"},"stepType":{"type":"string","enum":["onboarding","business"]},"skipAll":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Step updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing stepId or stepType"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"},"500":{"description":"Server error"}}}},"/api/auth/complete-signup":{"post":{"tags":["Auth & Security"],"summary":"Complete user signup with onboarding data","description":"Finalizes signup by generating industry-specific fake data and completing onboarding setup","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"industry":{"type":"string"},"customIndustry":{"type":"string"},"country":{"type":"string","default":"USA"},"country_code":{"type":"string","default":"US"}}}}}},"responses":{"200":{"description":"Signup completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/auth/get-user-id":{"get":{"tags":["Auth & Security"],"summary":"Get current user ID from JWT token","description":"Decodes and verifies JWT token to return the authenticated user ID","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"User ID returned","content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"$ref":"#/components/schemas/MongoObjectId"}}}}}},"401":{"description":"Missing, invalid, or expired token"}}}},"/api/auth/google-calendar":{"get":{"tags":["Auth & Security"],"summary":"Handle Google Calendar OAuth callback","description":"Processes the OAuth2 callback from Google, exchanges code for tokens, and stores calendar credentials","security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"code","schema":{"type":"string"},"description":"OAuth authorization code from Google"},{"in":"query","name":"state","schema":{"type":"string"},"description":"Base64-encoded state with user context"}],"responses":{"200":{"description":"OAuth callback processed (returns HTML that communicates with opener window)"},"400":{"description":"Missing code or state"},"500":{"description":"Server error"}}}},"/api/auth/login":{"post":{"tags":["Auth & Security"],"summary":"Login with email and password","description":"Authenticates user and returns JWT token with user profile data","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"Login successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"400":{"description":"Invalid email or password format"},"401":{"description":"Invalid credentials or inactive account"},"404":{"description":"User not found"},"500":{"description":"Server error"}}},"delete":{"tags":["Auth & Security"],"summary":"Delete fake/demo data","description":"Removes all fake data (visits, jobs, clients, items) created for the authenticated user","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Fake data deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/auth/logout":{"post":{"tags":["Auth & Security"],"summary":"Logout current device","description":"Logs out the user from the current device by updating device login status","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Logged out successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Invalid token"},"500":{"description":"Server error"}}},"delete":{"tags":["Auth & Security"],"summary":"Logout from all devices","description":"Logs out the user from all active devices","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Logged out from all devices","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Invalid token"},"500":{"description":"Server error"}}}},"/api/auth/quickbooks":{"get":{"tags":["Auth & Security"],"summary":"QuickBooks OAuth callback (demo mode)","description":"Handles QuickBooks integration OAuth flow. Currently returns a demo response.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"OAuth callback processed (returns HTML)"}}}},"/api/auth/signup":{"post":{"tags":["Auth & Security"],"summary":"Register a new user account","description":"Creates a new user with company setup and optional fake data generation","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}}},"responses":{"200":{"description":"Signup successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"400":{"description":"Invalid input or email validation failed"},"409":{"description":"Email already exists"},"429":{"description":"Too many signup attempts from this IP"},"500":{"description":"Server error"}}}},"/api/certifications":{"get":{"summary":"List certifications","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"}}],"responses":{"200":{"description":"Certifications retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"post":{"summary":"Create certification","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"}}}}}},"responses":{"200":{"description":"Certification created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/chat-v2/dashboard":{"get":{"summary":"Get dashboard analytics","description":"Returns dashboard analytics data including jobs by status, revenue, team workload, overdue counts, and job type breakdown","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"filters","schema":{"type":"string"},"description":"JSON-encoded filter state from FilterBar"}],"responses":{"200":{"description":"Dashboard data loaded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"No authorization header"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/chat-v2/sessions/{id}/messages":{"get":{"summary":"Get paginated messages for a chat session","description":"Returns paginated messages for a specific chat session from Firebase. Supports cursor-based and offset pagination. Admin/SuperAdmin can view any session.","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Chat session ID"},{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number (offset pagination)"},{"in":"query","name":"limit","schema":{"type":"integer","default":30},"description":"Messages per page (max 100, use -1 for all)"},{"in":"query","name":"order","schema":{"type":"string","enum":["asc","desc"],"default":"asc"},"description":"Sort order by timestamp"},{"in":"query","name":"after","schema":{"type":"string"},"description":"Cursor - return messages after this message ID (cursor pagination)"},{"in":"query","name":"before","schema":{"type":"string"},"description":"Cursor - return messages before this message ID (cursor pagination)"}],"responses":{"200":{"description":"Messages retrieved successfully"},"401":{"description":"Authorization header or token missing"},"403":{"description":"Insufficient permissions"},"404":{"description":"Session not found"},"500":{"description":"Server error"}}}},"/api/chat-v2/sessions/list":{"get":{"summary":"List chat sessions (paginated, mobile-friendly)","description":"Returns paginated chat sessions for the authenticated user. Admin/SuperAdmin can view all org sessions.","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":20},"description":"Sessions per page (max 50, use -1 for all)"},{"in":"query","name":"userId","schema":{"type":"string"},"description":"Filter by specific user ID (admin/superAdmin only)"}],"responses":{"200":{"description":"Sessions retrieved successfully"},"401":{"description":"Authorization header or token missing"},"403":{"description":"Insufficient permissions"},"500":{"description":"Server error"}}}},"/api/chat-v2/sessions":{"get":{"summary":"List chat sessions","description":"Lists all chat sessions for the authenticated user from Firebase","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Sessions retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Create chat session","description":"Creates a new chat session for the authenticated user","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"responses":{"201":{"description":"Session created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/chat-v2/suggestions":{"get":{"summary":"Get chat suggestions","description":"Returns contextual chat commands, suggestions based on org data, and trial info for the authenticated user","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Suggestions retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"No authorization header"},"404":{"description":"User not found"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/chat-v2/translate":{"post":{"summary":"Translate chat query","description":"Translates a user query to a structured module type and translated query via the translate API","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"object","properties":{"content":{"type":"string","description":"User message to translate"}}},"responseId":{"type":"string","description":"Client-generated response ID"}}}}}},"responses":{"200":{"description":"Translation result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"moduleType":{"type":"string"},"translatedQuery":{"type":"string"},"originalQuery":{"type":"string"}}}}}}}},"500":{"description":"Server error"}}}},"/api/chat-v2/upload":{"post":{"summary":"Upload chat attachment","description":"Uploads a file to S3 for chat attachments. Supports images (PNG, JPEG, WebP), PDFs, and CSVs up to 10MB.","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"File to upload (PNG, JPEG, WebP, PDF, CSV)"}}}}}},"responses":{"200":{"description":"File uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing file or unsupported type"},"401":{"description":"No authorization header"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/chat-widget/add-text":{"post":{"summary":"Add text to knowledge base","description":"Adds text content to the chat widget knowledge base via the Python API","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","description":"Text content to add"},"title":{"type":"string"},"source":{"type":"string"},"replace_existing":{"type":"string","default":"false"}}}}}},"responses":{"200":{"description":"Text added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Text is required"},"401":{"description":"Authorization required"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/chat-widget/add-url":{"post":{"summary":"Add URL to knowledge base","description":"Adds a URL as a knowledge source for the chat widget via the Python API","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"URL to add as knowledge source"},"replace_existing":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"URL added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"URL is required"},"401":{"description":"Authorization required"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/chat-widget/ask":{"post":{"summary":"Ask chat widget question","description":"Sends a question to the FAQ assistant and streams the response via SSE","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question"],"properties":{"question":{"type":"string","description":"Question to ask the FAQ assistant"},"style_prompt_id":{"type":"string","description":"Style prompt ID for response formatting"}}}}}},"responses":{"200":{"description":"SSE stream established","content":{"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"Question is required"},"401":{"description":"Authorization required"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/chat-widget/history":{"get":{"summary":"Get chat widget history","description":"Retrieves chat history for the widget from the Python FAQ assistant API","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Chat history retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization required"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/chat-widget/knowledge-sharing":{"get":{"summary":"List knowledge sources","description":"Lists all knowledge sharing documents/sources for the authenticated user","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Knowledge sources retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization required"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Add knowledge source","description":"Adds a knowledge source (text, file, or URL) to the chat widget knowledge base","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["source"],"properties":{"source":{"type":"string","enum":["text","document","file","website","url","status"],"description":"Source type"},"url":{"type":"string"},"document_id":{"type":"string"},"status":{"type":"string","enum":["active","inactive"]}}}},"multipart/form-data":{"schema":{"type":"object","required":["source"],"properties":{"source":{"type":"string"},"text":{"type":"string"},"title":{"type":"string"},"file":{"type":"string","format":"binary"},"replace_existing":{"type":"string"}}}}}},"responses":{"200":{"description":"Knowledge source added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid source type or missing fields"},"401":{"description":"Authorization required"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"summary":"Update document status","description":"Updates the status of a knowledge sharing document or source","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"document_id":{"type":"string"},"source":{"type":"string"},"status":{"type":"string","enum":["active","inactive"]}}}}}},"responses":{"200":{"description":"Status updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing required fields"},"401":{"description":"Authorization required"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"summary":"Delete knowledge source","description":"Deletes a knowledge sharing document by document ID or source","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"document_id","schema":{"type":"string"},"description":"Document ID to delete"},{"in":"query","name":"source","schema":{"type":"string"},"description":"Source identifier to delete"}],"responses":{"200":{"description":"Document deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization required"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/chat/history":{"get":{"summary":"Get chat history","description":"Retrieves chat message history for the authenticated user with in-memory caching","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Chat history retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization header or token missing"},"403":{"description":"Invalid token"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/chat":{"post":{"summary":"Send chat message","description":"Sends a chat message and streams the AI response from the Python backend via SSE","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question"],"properties":{"question":{"type":"string","description":"User message or question"},"isAnalytics":{"type":"boolean","description":"Whether to use analytics endpoint"},"isCanvas":{"type":"boolean","description":"Whether to use email template canvas"},"emailContent":{"type":"string"},"id":{"type":"string"},"task":{"type":"string"},"tone":{"type":"string"},"isTaskMode":{"type":"boolean"},"task_timezone":{"type":"string"}}}}}},"responses":{"200":{"description":"SSE stream established","content":{"text/plain":{"schema":{"type":"string"}}}},"401":{"description":"Authorization header or token missing"},"500":{"description":"Server error"}}}},"/api/chat/update":{"put":{"summary":"Update chat message content","description":"Updates the content of an existing chat message by message ID","tags":["Chat & Conversations"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["messageId","content"],"properties":{"messageId":{"type":"string","description":"Message ID to update"},"content":{"type":"string","description":"New content for the message"}}}}}},"responses":{"200":{"description":"Chat content updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing required parameters"},"401":{"description":"Invalid or missing token"},"404":{"description":"Chat not found"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/clients/{id}/document-recipients":{"get":{"summary":"Resolve document email recipients for a client","description":"Given a clientId and docType (invoice|quote|report|renewal), returns\nthe list of `client_contact` records that have opted in to receive\nthat document type (via the `receivesInvoices`/`receivesQuotes`/\n`receivesReports`/`receivesRenewals` boolean fields on the contact).\nReturns an empty array if no contacts match — the caller is expected\nto fall back to the client's primary email so existing tenants\n(without any `client_contact` records) keep working.\n","tags":["Clients & Customer Portal"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"docType","required":true,"schema":{"type":"string","enum":["invoice","quote","estimate","report","renewal"]}}],"responses":{"200":{"description":"Recipient list"}}}},"/api/clients/{id}":{"get":{"summary":"Get a client by ID","description":"Fetch a single client with all details including addresses, highlight widgets (totalJobs, openRequests, totalInvoiced, outstandingAmount)","tags":["Clients & Customer Portal"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The client ID or recordId"}],"responses":{"200":{"description":"Client fetched successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Client fetched successfully"},"data":{"allOf":[{"$ref":"#/components/schemas/ClientResponse"},{"type":"object","properties":{"totalJobs":{"type":"integer","description":"Number of active jobs"},"openRequests":{"type":"integer","description":"Number of open requests"},"totalInvoiced":{"type":"number","description":"Total invoiced amount"},"outstandingAmount":{"type":"number","description":"Outstanding balance"}}}]}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Client not found"}}},"put":{"summary":"Update a client by ID","description":"Update an existing client. Tracks field change history.","tags":["Clients & Customer Portal"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The client ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientCreateRequest"}}}},"responses":{"200":{"description":"Client updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Client updated successfully"},"data":{"$ref":"#/components/schemas/ClientResponse"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Client not found or unauthorized"}}},"delete":{"summary":"Delete a client by ID","description":"Soft-delete a client. Use forceDelete=true query param to cascade-delete associated jobs.","tags":["Clients & Customer Portal"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The client ID"},{"in":"query","name":"forceDelete","schema":{"type":"boolean","default":false},"description":"Force delete even if client has jobs"}],"responses":{"200":{"description":"Client deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Client deleted successfully"},"data":{"type":"object","properties":{"deletedJobCount":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Client not found"},"409":{"description":"Client has jobs - use forceDelete"}}}},"/api/clients/bulk-stage":{"post":{"summary":"Bulk update the stage of multiple clients","description":"Update the `stage` field for many clients in one request. Mirrors the safe-field-only behaviour of the single PUT endpoint, so imported clients with empty required fields (e.g. firstName) can still be moved through the pipeline.","tags":["Clients & Customer Portal"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["clientIds","newStage"],"properties":{"clientIds":{"type":"array","items":{"type":"string"}},"newStage":{"type":"string"}}}}}},"responses":{"200":{"description":"Bulk update completed (partial success possible)"}}}},"/api/clients":{"get":{"summary":"List clients with filtering, pagination, and grouping","tags":["Clients & Customer Portal"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number (1-10000)"},{"in":"query","name":"limit","schema":{"type":"integer","default":30},"description":"Items per page (-1 for all, max 500)"},{"in":"query","name":"stage","schema":{"type":"string","enum":["leads","pipeline","clients","active"]},"description":"Filter by stage"},{"in":"query","name":"tab","schema":{"type":"string"},"description":"Navigation tab filter"},{"in":"query","name":"view","schema":{"type":"string","enum":["viewAssigned","own","all"]},"description":"View mode"},{"in":"query","name":"groupBy","schema":{"type":"string"},"description":"Group results by field"},{"in":"query","name":"groupsPerPage","schema":{"type":"integer","default":5},"description":"Number of groups per page (grouped view)"},{"in":"query","name":"recordsPerGroup","schema":{"type":"integer","default":5},"description":"Number of records per group"},{"in":"query","name":"groupPage","schema":{"type":"integer","default":1},"description":"Which page of groups to display"},{"in":"query","name":"groupPagination","schema":{"type":"string"},"description":"JSON string — per-group pagination state: { groupKey: { page, total } }"},{"in":"query","name":"filters[]","schema":{"type":"string"},"description":"Advanced filter array. Pass as filters[N][field], filters[N][filterCondition], filters[N][condition], filters[N][value]. Each element filters by a field.\n"},{"in":"query","name":"sorting[]","schema":{"type":"string"},"description":"Sorting array. Pass as sorting[N][field], sorting[N][condition].\n"},{"in":"query","name":"searchView","schema":{"type":"string"},"description":"Custom aggregation pipeline (JSON)"},{"in":"query","name":"analyticsDate","schema":{"type":"string"},"description":"Filter by analytics date"},{"in":"query","name":"analyticsStage","schema":{"type":"string"},"description":"Filter by analytics stage"}],"responses":{"200":{"description":"Clients fetched successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Clients fetched successfully"},"data":{"type":"object","properties":{"clients":{"type":"array","items":{"$ref":"#/components/schemas/ClientResponse"}},"total":{"type":"integer","example":42}}}}}}}},"400":{"description":"Invalid parameter format"},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new client","description":"Create a new client. One of `firstName` or `email` is required (HTTP 403 if both are missing). When `isFromCopilot: true`, an existing client with matching email/phone will be updated instead of creating a duplicate — the response will include `action: \"updated\"` in that case.\n","tags":["Clients & Customer Portal"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientCreateRequest"}}}},"responses":{"201":{"description":"Client created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Client created successfully"},"data":{"type":"object","properties":{"client":{"$ref":"#/components/schemas/ClientResponse"},"token":{"type":"string","description":"JWT token for the client"},"action":{"type":"string","enum":["created","updated"]}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Missing required fields"}}},"delete":{"summary":"Bulk delete clients","tags":["Clients & Customer Portal"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["clientIds"],"properties":{"clientIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"forceDelete":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Clients deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid or empty client IDs array"},"401":{"description":"Unauthorized"},"409":{"description":"Clients have jobs - use forceDelete"}}}},"/api/command":{"get":{"summary":"Get WebSocket config","description":"Returns WebSocket configuration URL for the command interface","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"clientId","required":true,"schema":{"type":"string"},"description":"Client identifier"}],"responses":{"200":{"description":"WebSocket config retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Client ID required"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Send command via SSE","description":"SSE fallback for sending commands when WebSocket is unavailable","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"Command message"},"conversationId":{"type":"string"},"responseId":{"type":"string"}}}}}},"responses":{"200":{"description":"SSE stream established","content":{"text/event-stream":{"schema":{"type":"string"}}}},"500":{"description":"Server error"}}}},"/api/company-schedule":{"get":{"summary":"Get company business schedule","description":"Retrieves the active company schedule including weekly hours and specific hours configuration.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Schedule fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing token"},"404":{"description":"No schedule found"},"500":{"description":"Server error"}}},"post":{"summary":"Create or update company business schedule","description":"Creates a new company schedule or updates an existing one. Uses upsert logic based on the company's parentId.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["weeklySchedule"],"properties":{"weeklySchedule":{"type":"array","description":"Array of weekly schedule entries for each day","items":{"type":"object"}},"specificHours":{"type":"array","description":"Array of specific hour overrides (holidays, special hours)","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"Schedule updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"201":{"description":"Schedule created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - weeklySchedule is required and must be an array"},"401":{"description":"Unauthorized - Invalid or missing token"},"404":{"description":"Company info not found"},"500":{"description":"Server error"}}},"put":{"summary":"Update company business schedule","description":"Updates an existing schedule or creates one if it does not exist (upsert). Supports partial updates.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"weeklySchedule":{"type":"array","description":"Array of weekly schedule entries for each day","items":{"type":"object"}},"specificHours":{"type":"array","description":"Array of specific hour overrides (holidays, special hours)","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"Schedule updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"201":{"description":"Schedule created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing token"},"404":{"description":"Company info not found"},"500":{"description":"Server error"}}},"delete":{"summary":"Delete company business schedule","description":"Soft deletes a company schedule by setting its status to Inactive.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The schedule record ID to delete"}],"responses":{"200":{"description":"Schedule deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - Schedule ID is required"},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error"}}}},"/api/companyBusinessInfo":{"get":{"summary":"Get company business information","description":"Fetches company information along with business schedule (weekly hours and specific hours) and currency details.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Company business info fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Authorization header or token missing"},"500":{"description":"Server error - Failed to fetch company business info"}}}},"/api/companyInfo/{id}":{"put":{"summary":"Update company information","description":"Updates an existing company information record by ID. Supports logo removal and replacement.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The company info record ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"businessName":{"type":"string"},"address":{"type":"string"},"country":{"type":"string"},"phoneNumber":{"type":"string"},"email":{"type":"string"},"website":{"type":"string"},"companyName":{"type":"string"},"companySummary":{"type":"string"},"companyLogo":{"type":"string"},"removeCompanyLogo":{"type":"boolean","description":"Set to true to remove the existing company logo"},"currency":{"type":"string"},"timezone":{"type":"string"},"mileageTrackingEnabled":{"type":"boolean"},"mileageUnit":{"type":"string"},"mileageRateValue":{"type":"number"},"mileageStartLocation":{"type":"string"},"includeReturnTrip":{"type":"boolean"},"defaultHourlyRate":{"type":"number"},"includeTravel":{"type":"boolean"},"overtimeMultiplier":{"type":"number"},"weekendMultiplier":{"type":"number"},"roundToNearestMinutes":{"type":"integer"},"costTrackingEnabled":{"type":"boolean"},"includeOverhead":{"type":"boolean"},"overheadPercentage":{"type":"number"}}}}}},"responses":{"200":{"description":"Company info updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Authorization header or token missing"},"404":{"description":"Company info not found"},"500":{"description":"Server error - Failed to update company info"}}}},"/api/companyInfo":{"get":{"summary":"Get company information","description":"Fetches company information for the authenticated user. Optionally includes currency data.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"withCurrencyData","schema":{"type":"string","enum":[true,false]},"description":"Include list of available currencies in the response"}],"responses":{"200":{"description":"Company info fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Authorization header or token missing"},"500":{"description":"Server error - Failed to fetch company info"}}},"post":{"summary":"Create or update company information","description":"Creates a new company information record with business details, mileage tracking, and cost settings.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"businessName":{"type":"string"},"address":{"type":"string"},"country":{"type":"string"},"phoneNumber":{"type":"string"},"email":{"type":"string"},"website":{"type":"string"},"companyName":{"type":"string"},"companySummary":{"type":"string"},"currency":{"type":"string"},"timezone":{"type":"string"},"industry":{"type":"string"},"customIndustry":{"type":"string"},"mileageTrackingEnabled":{"type":"boolean"},"mileageUnit":{"type":"string"},"mileageRateValue":{"type":"number"},"mileageStartLocation":{"type":"string"},"includeReturnTrip":{"type":"boolean"},"defaultHourlyRate":{"type":"number"},"includeTravel":{"type":"boolean"},"overtimeMultiplier":{"type":"number"},"weekendMultiplier":{"type":"number"},"roundToNearestMinutes":{"type":"integer"},"costTrackingEnabled":{"type":"boolean"},"includeOverhead":{"type":"boolean"},"overheadPercentage":{"type":"number"}}}}}},"responses":{"201":{"description":"Company info created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Authorization header or token missing"},"500":{"description":"Server error - Failed to create company info"}}}},"/api/companyInfo/scrape":{"post":{"summary":"Scrape company info from website URL","description":"Scrapes company information from a provided website URL using an external Python API service.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["website"],"properties":{"website":{"type":"string","description":"The website URL to scrape company information from"},"timezone":{"type":"string","description":"Timezone for the scraping context"}}}}}},"responses":{"200":{"description":"Scraping successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - Website URL is required"},"401":{"description":"Unauthorized - Authorization header or token missing"},"500":{"description":"Server error - Failed to scrape website"}}}},"/api/conversations":{"get":{"summary":"Fetch all conversations for the authenticated user","tags":["Communication"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Conversations fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization header or token missing"},"403":{"description":"Invalid token"},"500":{"description":"Failed to fetch conversations"}}}},"/api/createTranslation":{"post":{"summary":"Transcribe audio via FieldCamp Agents speech-to-text","description":"Uploads an audio file and transcribes it using the FieldCamp Agents speech-to-text service","tags":["AI & Dispatcher"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["audio_file"],"properties":{"audio_file":{"type":"string","format":"binary","description":"Audio file to transcribe"}}}}}},"responses":{"200":{"description":"Audio transcribed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Audio file is required"},"402":{"description":"AI credits exhausted"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"summary":"Check speech-to-text API status","description":"Checks if the FieldCamp Agents speech-to-text service is configured","tags":["AI & Dispatcher"],"responses":{"200":{"description":"API status retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/custom-objects/{slug}":{"get":{"summary":"List records for a custom object","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"slug","required":true,"schema":{"type":"string"},"description":"Custom object type slug"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":25}},{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"status","schema":{"type":"string"}},{"in":"query","name":"linkedRecordId","schema":{"type":"string"}},{"in":"query","name":"sortField","schema":{"type":"string","default":"createdAt"}},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"post":{"summary":"Create a record for a custom object","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"slug","required":true,"schema":{"type":"string"},"description":"Custom object type slug"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object"},"status":{"type":"string"},"linkedRecords":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/custom-objects":{"get":{"summary":"List custom object types","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/delete-user":{"post":{"summary":"Delete user account","description":"Soft-deletes the authenticated user's account by setting the status to 'Deleted'.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"User account deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - user already deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/deposits/payment":{"get":{"summary":"Get deposit payment history","description":"Retrieves payment history for a specific deposit, including total paid amount.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"depositId","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"ID of the deposit"}],"responses":{"200":{"description":"Deposit payment history retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing depositId"},"401":{"description":"Unauthorized"},"404":{"description":"Deposit not found"},"500":{"description":"Internal server error"}}},"post":{"summary":"Record a deposit payment","description":"Records a payment against a deposit, updates paid amount and payment status, and creates corresponding document payment history entry.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["depositId","amount","date"],"properties":{"depositId":{"$ref":"#/components/schemas/MongoObjectId"},"amount":{"type":"number","description":"Payment amount (must be positive)"},"date":{"type":"string","format":"date-time"},"method":{"type":"string","description":"Payment method (e.g., Cash, Credit Card)"},"note":{"type":"string"},"transactionDetails":{"type":"object"}}}}}},"responses":{"200":{"description":"Deposit payment recorded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Validation error or exceeds required amount"},"401":{"description":"Unauthorized"},"404":{"description":"Deposit not found"},"500":{"description":"Internal server error"}}},"delete":{"summary":"Delete a deposit payment","description":"Soft-deletes a deposit payment and updates the deposit's paid amount accordingly. Cannot delete payments already used by an invoice.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"paymentId","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"ID of the payment to delete"}],"responses":{"200":{"description":"Deposit payment deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Payment already used by invoice or missing paymentId"},"401":{"description":"Unauthorized"},"404":{"description":"Payment or deposit not found"},"500":{"description":"Internal server error"}}}},"/api/deposits":{"get":{"summary":"Get deposit details","description":"Retrieves deposit records by ID, source document ID, or job ID, including payment history.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"id","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Get deposit by ID"},{"in":"query","name":"sourceDocumentId","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Get deposit by source document (estimate) ID"},{"in":"query","name":"jobId","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Get deposit by job ID"}],"responses":{"200":{"description":"Deposit(s) retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing query parameter"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}},"post":{"summary":"Create or update a deposit","description":"Creates a new deposit record for an estimate or updates an existing one if not yet used by an invoice.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sourceDocumentId","depositType","requiredAmount"],"properties":{"sourceDocumentId":{"$ref":"#/components/schemas/MongoObjectId"},"depositType":{"type":"integer","description":"1 = percentage, 2 = fixed"},"depositPercent":{"type":"number"},"requiredAmount":{"type":"number"},"depositDate":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"Deposit created or updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Validation error or deposit already used"},"401":{"description":"Unauthorized"},"404":{"description":"Source document not found"},"500":{"description":"Internal server error"}}},"put":{"summary":"Mark deposit as used by an invoice","description":"Marks a deposit as used and links it to the specified invoice.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["depositId","invoiceId"],"properties":{"depositId":{"$ref":"#/components/schemas/MongoObjectId"},"invoiceId":{"$ref":"#/components/schemas/MongoObjectId"},"usedAmount":{"type":"number","description":"Amount used (defaults to paidAmount)"}}}}}},"responses":{"200":{"description":"Deposit marked as used successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Deposit already fully consumed or validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Deposit or invoice not found"},"500":{"description":"Internal server error"}}},"delete":{"summary":"Release a deposit","description":"Releases a deposit by marking it as unused, typically when an invoice is deleted.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"depositId","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"ID of the deposit to release"}],"responses":{"200":{"description":"Deposit released successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing depositId"},"401":{"description":"Unauthorized"},"404":{"description":"Deposit not found"},"500":{"description":"Internal server error"}}}},"/api/documents/{id}":{"put":{"summary":"Update a document by ID","description":"Update an existing document using path parameter ID (bridges to PATCH)","tags":["Documents & Templates"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The document ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"documentType":{"type":"integer","enum":[1,2],"description":"1=Invoice, 2=Estimate"},"status":{"type":"string","enum":["draft","sent","viewed","accepted","declined","paid","overdue","cancelled","partially_paid"]},"items":{"type":"array","items":{"$ref":"#/components/schemas/DocumentLineItem"}},"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"jobId":{"$ref":"#/components/schemas/MongoObjectId"},"notes":{"type":"string"},"dueDate":{"type":"string","format":"date"},"issueDate":{"type":"string","format":"date"},"discount":{"type":"number"},"taxes":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"rate":{"type":"number"}}}}}}}}},"responses":{"200":{"description":"Document updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"document":{"$ref":"#/components/schemas/DocumentResponse"}}}}}}}},"400":{"description":"Document ID is required"},"401":{"description":"Unauthorized"}}},"patch":{"summary":"Partially update a document by ID","description":"Partially update an existing document using path parameter ID","tags":["Documents & Templates"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The document ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"documentType":{"type":"integer","enum":[1,2]},"status":{"type":"string","enum":["draft","sent","viewed","accepted","declined","paid","overdue","cancelled","partially_paid"]},"items":{"type":"array","items":{"$ref":"#/components/schemas/DocumentLineItem"}},"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"jobId":{"$ref":"#/components/schemas/MongoObjectId"},"notes":{"type":"string"},"dueDate":{"type":"string","format":"date"},"issueDate":{"type":"string","format":"date"},"discount":{"type":"number"}}}}}},"responses":{"200":{"description":"Document updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"document":{"$ref":"#/components/schemas/DocumentResponse"}}}}}}}},"400":{"description":"Document ID is required"},"401":{"description":"Unauthorized"}}},"delete":{"summary":"Delete a document by ID","description":"Delete a single document using path parameter ID","tags":["Documents & Templates"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The document ID"}],"responses":{"200":{"description":"Document deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Document ID is required"},"401":{"description":"Unauthorized"}}}},"/api/documents/analytics":{"get":{"summary":"Get document analytics","description":"Retrieve analytics data for documents including totals, status breakdowns, and monthly trends","tags":["Documents & Templates"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"documentType","required":true,"schema":{"type":"integer"},"description":"Document type (1=Invoice, 2=Estimate)"},{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Filter start date"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"Filter end date"}],"responses":{"200":{"description":"Document analytics fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"documentType parameter is required"},"401":{"description":"Unauthorized"}}}},"/api/documents/getDocumentNumber":{"get":{"summary":"Get next document number","description":"Retrieve the next available document number for a given document type","tags":["Documents & Templates"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"documentType","schema":{"type":"integer"},"description":"Document type (1=Invoice, 2=Estimate)"}],"responses":{"200":{"description":"Document number retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"}}}},"/api/documents":{"get":{"summary":"List documents","description":"Retrieve a list of documents for the authenticated user","tags":["Documents & Templates"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"documentType","required":true,"schema":{"type":"integer","enum":[1,2]},"description":"REQUIRED — Document type: 1=Invoice, 2=Estimate. Returns 400 if missing."},{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number for pagination"},{"in":"query","name":"limit","schema":{"type":"integer","default":10},"description":"Number of items per page"},{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Filter documents from this date"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"Filter documents up to this date"},{"in":"query","name":"view","schema":{"type":"string"},"description":"View mode filter"},{"in":"query","name":"searchView","schema":{"type":"string"},"description":"Custom MongoDB aggregation pipeline JSON for saved views"},{"in":"query","name":"groupBy","schema":{"type":"string"},"description":"Group results by field"},{"in":"query","name":"groupsPerPage","schema":{"type":"integer","default":5},"description":"Number of groups per page (grouped view)"},{"in":"query","name":"recordsPerGroup","schema":{"type":"integer","default":5},"description":"Number of records per group"},{"in":"query","name":"groupPage","schema":{"type":"integer","default":1},"description":"Which page of groups to display"},{"in":"query","name":"filters[]","schema":{"type":"string"},"description":"Advanced filter array. Pass as filters[N][field], etc."},{"in":"query","name":"sorting[]","schema":{"type":"string"},"description":"Sorting array. Pass as sorting[N][field], sorting[N][condition]."}],"responses":{"200":{"description":"Documents retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"Documents fetched successfully"},"data":{"type":"object","properties":{"documents":{"type":"array","items":{"$ref":"#/components/schemas/DocumentResponse"}},"total":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new document","description":"Create a new invoice (documentType=1) or estimate (documentType=2). Both `documentType` and `title` are required. `documentNumber` is auto-generated. Include `itemType` (1=Product, 2=Service, 3=Labor) for each line item.\n","tags":["Documents & Templates"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentCreateRequest"}}}},"responses":{"201":{"description":"Document created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"document":{"$ref":"#/components/schemas/DocumentResponse"}}}}}}}},"401":{"description":"Unauthorized"}}},"patch":{"summary":"Update a document","description":"Update an existing document by ID. For line items, each item can include an `action` field: 1=add, 2=update, 3=delete. For multi-option estimates, send `optionIds` with `actionStatus` to approve/decline specific options.\n","tags":["Documents & Templates"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentUpdateRequest"}}}},"responses":{"200":{"description":"Document updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"document":{"$ref":"#/components/schemas/DocumentResponse"}}}}}}}},"401":{"description":"Unauthorized"}}},"delete":{"summary":"Delete documents","description":"Soft delete one or more documents by IDs","tags":["Documents & Templates"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["documentIds"],"properties":{"documentIds":{"type":"array","items":{"type":"string"},"description":"Array of document IDs to delete"}}}}}},"responses":{"200":{"description":"Documents deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"}}}},"/api/documents/sequence":{"get":{"summary":"Get the next invoice & estimate numbers","description":"Returns the next document number that will be assigned for invoices and estimates.","tags":["Documents & Templates"],"security":[{"BearerAuth":[]}]},"put":{"summary":"Set the starting number for an invoice/estimate sequence","description":"Sets the next document number for the given document type (1=Invoice, 2=Estimate).","tags":["Documents & Templates"],"security":[{"BearerAuth":[]}]}},"/api/dynamic/{objectSlug}/{id}/convert":{"post":{"summary":"Convert dynamic object record","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"objectSlug","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ruleId"],"properties":{"ruleId":{"type":"string"},"additionalData":{"type":"object"}}}}}},"responses":{"201":{"description":"Conversion completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/dynamic/{objectSlug}/{id}":{"get":{"summary":"Get dynamic object record by ID","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"objectSlug","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"}}],"responses":{"200":{"description":"Record retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Record not found"},"500":{"description":"Server error"}}},"put":{"summary":"Update dynamic object record","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"objectSlug","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Record updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Record not found"},"500":{"description":"Server error"}}},"delete":{"summary":"Delete dynamic object record","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"objectSlug","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"}}],"responses":{"200":{"description":"Record deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Record not found"},"500":{"description":"Server error"}}}},"/api/dynamic/{objectSlug}":{"get":{"summary":"List dynamic object records","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"objectSlug","required":true,"schema":{"type":"string"}},{"in":"query","name":"page","schema":{"type":"integer"}},{"in":"query","name":"limit","schema":{"type":"integer"}},{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"status","schema":{"type":"string"}}],"responses":{"200":{"description":"Records retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Object type not found"},"500":{"description":"Server error"}}},"post":{"summary":"Create dynamic object record","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"objectSlug","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"201":{"description":"Record created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Object type not found"},"500":{"description":"Server error"}}}},"/api/email-sms-templates":{"get":{"summary":"Fetch email/SMS templates by document type and template type","tags":["Communication"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"documentType","schema":{"type":"integer","default":2},"description":"1 = Invoice, 2 = Estimate"},{"in":"query","name":"templateType","schema":{"type":"string","default":"email","enum":["email","sms"]},"description":"Template type"},{"in":"query","name":"all","schema":{"type":"string","enum":["true","false"]},"description":"Set to true to get all templates with pagination"},{"in":"query","name":"id","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Fetch a specific template by ID"},{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number (when all=true)"},{"in":"query","name":"limit","schema":{"type":"integer","default":30},"description":"Items per page (when all=true)"}],"responses":{"200":{"description":"Email/SMS template(s) fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"},"500":{"description":"Failed to fetch email/sms template"}}},"post":{"summary":"Create a new email/SMS template","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["documentType","templateType","name"],"properties":{"documentType":{"type":"integer","description":"1 = Invoice, 2 = Estimate"},"templateType":{"type":"string","enum":["email","sms"]},"name":{"type":"string"},"subject":{"type":"string"},"body":{"type":"string"},"attachMainPdf":{"type":"boolean","default":true},"attachOptionPdfs":{"type":"boolean","default":false},"isDefault":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Email/SMS template created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing required fields"},"401":{"description":"Unauthorized"},"500":{"description":"Failed to create email/sms template"}}},"put":{"summary":"Update an existing email/SMS template","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"documentType":{"type":"integer"},"templateType":{"type":"string"},"name":{"type":"string"},"subject":{"type":"string"},"body":{"type":"string"},"attachMainPdf":{"type":"boolean"},"attachOptionPdfs":{"type":"boolean"},"isDefault":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Email/SMS template updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing required fields"},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"},"500":{"description":"Failed to update email/sms template"}}},"delete":{"summary":"Soft-delete one or more email/SMS templates","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateIds"],"properties":{"templateIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Array of template IDs to delete"}}}}}},"responses":{"200":{"description":"Template(s) deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Template IDs must be provided as a non-empty array"},"401":{"description":"Unauthorized"},"500":{"description":"Failed to delete email/sms template"}}}},"/api/emails/actions":{"post":{"summary":"Perform bulk or single email actions (delete, markRead, markUnread)","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["delete","markRead","markUnread"],"description":"Action to perform on emails"},"messageIds":{"type":"array","items":{"type":"string"},"description":"Array of message IDs for bulk actions"},"singleMessageId":{"type":"string","description":"Single message ID for individual actions"}}}}}},"responses":{"200":{"description":"Action completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid message IDs or invalid action"},"401":{"description":"No authorization header"},"500":{"description":"Failed to process action"}}}},"/api/emails/download":{"post":{"summary":"Download an email attachment from S3","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["s3Key","filename"],"properties":{"s3Key":{"type":"string","description":"S3 object key for the attachment"},"filename":{"type":"string","description":"Original filename for the download"}}}}}},"responses":{"200":{"description":"File downloaded successfully","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Missing required parameters"},"401":{"description":"No authorization header or invalid token"},"500":{"description":"Failed to download file"}}}},"/api/emails":{"get":{"summary":"Get paginated email threads for the authenticated user","tags":["Communication"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"email","schema":{"type":"string"},"description":"Filter by client email address"},{"in":"query","name":"chMessageId","schema":{"type":"string"},"description":"Filter by channel message ID"},{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Number of threads per page"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search query across subject, body, from, and to fields"},{"in":"query","name":"unread","schema":{"type":"string","enum":["true","false"]},"description":"Filter unread emails only"}],"responses":{"200":{"description":"Email threads fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"No authorization header or invalid token"},"500":{"description":"Failed to fetch email threads"}}}},"/api/files":{"post":{"summary":"Upload files to S3 and create file records","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["files","moduleType","moduleId"],"properties":{"files":{"type":"array","items":{"type":"string","format":"binary"}},"moduleType":{"type":"string"},"moduleId":{"type":"string"},"summary":{"type":"string"},"fileId":{"type":"string"}}}}}},"responses":{"200":{"description":"Files uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"}}},"get":{"summary":"Get files by module type and optional filters","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"moduleType","required":true,"schema":{"type":"string"}},{"in":"query","name":"moduleId","schema":{"type":"string"}},{"in":"query","name":"fileId","schema":{"type":"string"}}],"responses":{"200":{"description":"Files fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"}}}},"/api/forgot-pass":{"post":{"tags":["Auth & Security"],"summary":"Request password reset or reset password with token","description":"Multi-mode endpoint: forgotPassMode=0 generates reset token and sends email, forgotPassMode=1 resets password using token","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","forgotPassMode"],"properties":{"email":{"type":"string","format":"email"},"newPassword":{"type":"string","format":"password"},"token":{"type":"string","description":"Reset token (required for mode 1)"},"forgotPassMode":{"type":"integer","enum":[0,1],"description":"0 = send reset email, 1 = reset password with token"}}}}}},"responses":{"200":{"description":"Reset email sent or password updated"},"400":{"description":"Invalid or expired token"},"404":{"description":"User not found"},"500":{"description":"Server error"}}}},"/api/forgot-pass/validate-token":{"post":{"tags":["Auth & Security"],"summary":"Validate a password reset token","description":"Checks if a password reset token exists and has not expired","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"Password reset token from email link"}}}}}},"responses":{"200":{"description":"Token validation result","content":{"application/json":{"schema":{"type":"object","properties":{"isValid":{"type":"boolean"},"message":{"type":"string"}}}}}},"400":{"description":"Token is required"},"500":{"description":"Server error"}}}},"/api/form-fields/search":{"get":{"summary":"Search across modules for TABLE field data source linking","tags":["Form Fields"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"module","required":true,"schema":{"type":"string","enum":["items","clients","vendors","teamMembers","customObject:*"]},"description":"Module to search (customObject prefix for custom objects)"},{"in":"query","name":"query","required":true,"schema":{"type":"string","minLength":1},"description":"Search query string (minimum 1 character)"},{"in":"query","name":"field","schema":{"type":"string"},"description":"Optional specific field to search"},{"in":"query","name":"limit","schema":{"type":"integer","default":10,"minimum":1,"maximum":50},"description":"Maximum results to return"}],"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"results":{"type":"array"},"total":{"type":"integer"}}}}}}}},"400":{"description":"Missing or invalid parameters"},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/gmail/{id}/markRead":{"post":{"summary":"Mark a Gmail message as read by removing the UNREAD label","tags":["Communication"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Gmail message ID"}],"responses":{"200":{"description":"Email marked as read successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization header or token missing"},"403":{"description":"Invalid token"},"404":{"description":"Gmail not connected"},"500":{"description":"Failed to mark email as read"}}}},"/api/gmail/{id}":{"delete":{"summary":"Move a Gmail message to trash","tags":["Communication"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"Gmail message ID"}],"responses":{"200":{"description":"Email deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"No authorization header or invalid token"},"403":{"description":"Insufficient permissions to delete email"},"404":{"description":"Gmail not connected"},"500":{"description":"Failed to delete email"}}}},"/api/gmail/forward":{"post":{"summary":"Forward a Gmail message to another recipient with optional attachments","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["messageId","forwardTo","subject"],"properties":{"messageId":{"type":"string","description":"Original message ID to forward"},"forwardTo":{"type":"string","format":"email","description":"Recipient email address to forward to"},"subject":{"type":"string","description":"Email subject line"},"from":{"type":"string","format":"email","description":"Sender email address"},"content":{"type":"string","description":"Additional message content to prepend"},"cc":{"type":"string","description":"CC recipients (comma-separated)"},"bcc":{"type":"string","description":"BCC recipients (comma-separated)"},"attachments":{"type":"array","items":{"type":"string","format":"binary"},"description":"File attachments"}}}}}},"responses":{"200":{"description":"Email forwarded successfully with email data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"No authorization header or invalid token / reconnect required"},"404":{"description":"Gmail not connected"},"500":{"description":"Failed to forward email"}}}},"/api/gmail/reply":{"post":{"summary":"Reply to a Gmail message with optional attachments","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["messageId","content","to","subject"],"properties":{"messageId":{"type":"string","description":"Original message ID to reply to"},"content":{"type":"string","description":"Reply body content"},"to":{"type":"string","format":"email","description":"Recipient email address"},"subject":{"type":"string","description":"Reply subject line"},"cc":{"type":"string","description":"CC recipients (comma-separated)"},"bcc":{"type":"string","description":"BCC recipients (comma-separated)"},"attachments":{"type":"array","items":{"type":"string","format":"binary"},"description":"File attachments"}}}}}},"responses":{"200":{"description":"Reply sent successfully with email data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"No authorization header or invalid token / reconnect required"},"404":{"description":"Gmail not connected"},"500":{"description":"Failed to send reply"}}}},"/api/gmail":{"get":{"summary":"Fetch Gmail messages from connected Gmail account filtered by client emails","tags":["Communication"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"email","schema":{"type":"string","format":"email"},"description":"Optional client email to filter messages from a specific sender"}],"responses":{"200":{"description":"Gmail messages fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"No authorization header or invalid token / reconnect required"},"404":{"description":"Gmail not connected"},"429":{"description":"Gmail API quota exceeded"},"500":{"description":"Failed to fetch Gmail messages"}}}},"/api/gmail/smartReply":{"post":{"summary":"Generate an AI-powered smart reply for an email","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email_address","email_content"],"properties":{"email_address":{"type":"string","format":"email","description":"Email address for context"},"email_content":{"type":"string","description":"Original email content to generate a reply for"},"tone":{"type":"string","description":"Desired tone for the reply (e.g. professional, casual, friendly)"}}}}}},"responses":{"200":{"description":"Smart reply generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Request body is empty or missing required parameters"},"401":{"description":"No authorization header or invalid token"},"500":{"description":"Failed to generate smart reply"}}}},"/api/i18n/translate":{"post":{"summary":"Translate a short label string into one or more locales (Google Translate, with cache)","tags":["I18n"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","description":"Source text (single short label)"},"from":{"type":"string","description":"Source locale","default":"en"},"to":{"type":"array","items":{"type":"string"},"description":"Target locales (defaults to all supported except `from`)"}}}}}},"responses":{"200":{"description":"{ translations: { ar: '...', th: '...' }, cacheHits: { ar: true, ... }, providerAvailable: true }"},"400":{"description":"Validation error"}}}},"/api/inbox":{"post":{"summary":"Fetch unified inbox items from the Python backend with optional filters","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"page":{"type":"integer","description":"Page number"},"limit":{"type":"integer","description":"Items per page"},"filters":{"type":"object","description":"Filter criteria for inbox items"}}}}}},"responses":{"200":{"description":"Inbox items fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization header or token missing"},"500":{"description":"Internal server error"}}}},"/api/integrations/disconnect":{"post":{"summary":"Disconnect an integration","description":"Disconnects a specified integration (Google, QuickBooks, or Stripe Connect) for the authenticated user.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["integrationType"],"properties":{"integrationType":{"type":"string","description":"The type of integration to disconnect","example":"google_gmail"},"isStripeDisable":{"type":"boolean","description":"For Stripe Connect - whether to enable or disable payment processing"}}}}}},"responses":{"200":{"description":"Integration disconnected successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized or invalid token"},"404":{"description":"Integration or payment option not found"},"500":{"description":"Failed to disconnect integration"}}}},"/api/integrations/email":{"get":{"summary":"Get connected integration email","description":"Returns the email address associated with the user's connected Google Gmail integration.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Email fetched successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"email":{"type":"string","format":"email"}}}}}]}}}},"401":{"description":"Authorization header or token missing"},"403":{"description":"Invalid token"},"404":{"description":"No connected email found"},"500":{"description":"Failed to fetch integration email"}}}},"/api/integrations/quick-books":{"post":{"summary":"Generate QuickBooks connection URL","description":"Creates a JWT token and generates a QuickBooks connection URL for the authenticated user.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"QuickBooks connection URL generated","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"},"url":{"type":"string","format":"uri"}}}}}]}}}},"404":{"description":"User not found"},"500":{"description":"Failed to generate QuickBooks connection URL"}}}},"/api/integrations/status-scope":{"get":{"summary":"Get integration statuses with access scope details","description":"Returns connection status and full access scope for all integrations including Google, Stripe, and QuickBooks.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Integration statuses with scope information","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"status":{"type":"object","properties":{"google_gmail":{"type":"boolean"},"google_calendar":{"type":"boolean"},"stripe_connect":{"type":"boolean"},"quickbooks":{"type":"boolean"}}},"full_access":{"type":"object","properties":{"google_gmail":{"type":"boolean"},"google_calendar":{"type":"boolean"}}}}}}}]}}}},"401":{"description":"Unauthorized"},"500":{"description":"Failed to fetch integration status"}}}},"/api/integrations/status":{"get":{"summary":"Get integration connection statuses","description":"Returns the connection status of all active integrations for the authenticated user.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Map of integration types to connection boolean","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"boolean"}}}}},"401":{"description":"Unauthorized"},"500":{"description":"Failed to fetch integration statuses"}}}},"/api/integrations/stripe-connect/auth-url":{"get":{"summary":"Generate Stripe Connect OAuth authorization URL","description":"Generates a Stripe Connect OAuth URL with CSRF state parameter for the authenticated user.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Stripe Connect authorization URL generated","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Stripe Connect OAuth authorization URL"}}}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/integrations/update-settings":{"get":{"summary":"Get integration settings","description":"Fetches the settings for a specified integration type.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"integrationType","required":true,"schema":{"type":"string"},"description":"The type of integration to fetch settings for"}],"responses":{"200":{"description":"Settings fetched successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"settings":{"type":"object"}}}}}]}}}},"401":{"description":"Authorization header missing or invalid token"},"404":{"description":"Integration not found"},"500":{"description":"Failed to fetch integration settings"}}},"post":{"summary":"Update integration settings","description":"Merges and updates settings for a specified integration type.","tags":["Integrations & Addons"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["integrationType","settings"],"properties":{"integrationType":{"type":"string"},"settings":{"type":"object"}}}}}},"responses":{"200":{"description":"Settings updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"settings":{"type":"object"}}}}}]}}}},"401":{"description":"Authorization header missing or invalid token"},"404":{"description":"Integration not found"},"500":{"description":"Failed to update integration settings"}}}},"/api/inventory/items":{"get":{"summary":"List non-inventoried product items","description":"Retrieve a paginated list of non-inventoried product-type items (type=Product, trackInventory=false, status=available). The response envelope uses `result` (not `items`) and `metaData` (not flat pagination fields). See `InventoryItemResponse` schema for full field list including `cost`, `currency`, `duration`, `categoryIds`, and `bookingType`.\n","tags":["Items & Inventory"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":10},"description":"Items per page"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by item name or description"}],"responses":{"200":{"description":"Items fetched successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"result":{"type":"array","description":"Array of inventory item objects (field is 'result', not 'items')","items":{"$ref":"#/components/schemas/InventoryItemResponse"}},"metaData":{"type":"object","description":"Pagination metadata (field is 'metaData', not flat fields)","properties":{"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"},"totalPages":{"type":"integer"}}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/api/inventory":{"get":{"summary":"List inventory records with filtering and pagination","description":"Returns paginated inventory records. Each record includes item details, warehouse info, category info, and stock levels. Use `lowStock=true` to filter items at or below their low-stock alert threshold.\n","tags":["Items & Inventory"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":10},"description":"Items per page (-1 for all)"},{"in":"query","name":"id","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Filter by a specific inventory record ID"},{"in":"query","name":"warehouse","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Filter by warehouse ID"},{"in":"query","name":"category","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Filter by category ID"},{"in":"query","name":"type","schema":{"type":"string"},"description":"Filter by warehouse type"},{"in":"query","name":"lowStock","schema":{"type":"boolean"},"description":"When true, returns only items at or below their lowStockAlert threshold"}],"responses":{"200":{"description":"Inventory records fetched successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"result":{"type":"array","items":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"quantity":{"type":"number"},"sku":{"type":"string"},"binLocation":{"type":"string"},"lowStockAlert":{"type":"number"},"isBillable":{"type":"boolean"},"status":{"type":"string"},"item":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"description":{"type":"string"},"type":{"type":"string"},"price":{"type":"number"},"currency":{"type":"string"}}},"warehouse":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"location":{"type":"string"},"type":{"type":"string"},"status":{"type":"string"}}},"categories":{"type":"array","items":{"type":"object"}}}}},"metaData":{"type":"object","properties":{"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"},"totalPages":{"type":"integer"},"lowStockCount":{"type":"integer"}}}}}}}}}},"400":{"description":"Invalid parameter format"},"401":{"description":"Unauthorized"}}},"post":{"summary":"Transfer inventory between warehouses","description":"Transfer a quantity of an inventory item from one warehouse to another. `fromWarehouseId` and `toWarehouseId` must differ. The source inventory must have sufficient quantity.\n","tags":["Items & Inventory"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryTransferRequest"}}}},"responses":{"200":{"description":"Inventory transferred successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid parameters (same warehouse, insufficient quantity, etc.)"},"401":{"description":"Unauthorized"}}},"delete":{"summary":"Bulk delete inventory records","description":"Soft-delete inventory records. The response has three categories: - `deletedImmediately` — items with a single location, deleted right away (trackInventory set to false) - `requiresConfirmation` — items in multiple locations that need a follow-up confirmation call - `blockedItems` — items currently used in active jobs (cannot be deleted)\n","tags":["Items & Inventory"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["deleteIds"],"properties":{"deleteIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Array of inventory record IDs to delete"},"currentWarehouseId":{"$ref":"#/components/schemas/MongoObjectId","description":"Optional — current warehouse context for multi-location reporting"}}}}}},"responses":{"200":{"description":"Bulk delete result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"deletedImmediately":{"type":"array","description":"Items soft-deleted immediately","items":{"type":"object"}},"requiresConfirmation":{"type":"array","description":"Multi-location items requiring a separate confirmation","items":{"type":"object"}},"blockedItems":{"type":"array","description":"Items blocked from deletion because they are in active jobs","items":{"type":"object"}}}}}}}}},"400":{"description":"Missing or invalid deleteIds"},"401":{"description":"Unauthorized"}}}},"/api/job-forms/{id}":{"get":{"summary":"Get a single job form by ID","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Job form ID"}],"responses":{"200":{"description":"Job form fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Form ID is required"},"401":{"description":"Unauthorized"},"404":{"description":"Form not found"},"500":{"description":"Server error"}}}},"/api/job-forms/{id}/submit":{"post":{"summary":"Submit a job form with data and optional signature upload","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Job form ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"jobId":{"$ref":"#/components/schemas/MongoObjectId"},"visitId":{"$ref":"#/components/schemas/MongoObjectId"},"data":{"type":"object","description":"Form field values keyed by field name"}}}}}},"responses":{"200":{"description":"Form submitted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Form ID or data is required"},"401":{"description":"Unauthorized"},"404":{"description":"Form or visit not found"},"500":{"description":"Server error"}}}},"/api/job-forms/all":{"get":{"summary":"Get all active job forms without pagination","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"type","schema":{"type":"string"},"description":"Filter by form type (e.g. Job)"}],"responses":{"200":{"description":"Job forms fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/job-forms/email":{"post":{"summary":"Email a completed job form to a recipient","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["recipientEmail"],"properties":{"recipientEmail":{"type":"string","format":"email"},"formId":{"$ref":"#/components/schemas/MongoObjectId"},"subject":{"type":"string"},"message":{"type":"string"},"jobData":{"type":"string","description":"JSON string of job data"},"selectedForm":{"type":"string","description":"JSON string of selected form"},"formValues":{"type":"string","description":"JSON string of form values"},"userData":{"type":"string","description":"JSON string of user data"},"visitData":{"type":"string","description":"JSON string of visit data"},"attachments":{"type":"array","items":{"type":"string","format":"binary"}}}}},"application/json":{"schema":{"type":"object","required":["recipientEmail"],"properties":{"recipientEmail":{"type":"string","format":"email"},"formId":{"$ref":"#/components/schemas/MongoObjectId"},"subject":{"type":"string"},"message":{"type":"string"},"jobData":{"type":"object"},"selectedForm":{"type":"object"},"formValues":{"type":"object"},"userData":{"type":"object"},"visitData":{"type":"object"}}}}}},"responses":{"200":{"description":"Email sent successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing required field recipientEmail"},"401":{"description":"Unauthorized"},"500":{"description":"Server error or email service not configured"}}}},"/api/job-forms":{"post":{"summary":"Create a new job form","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","formSchema"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"formSchema":{"type":"object"},"type":{"type":"string","default":"Job"},"attachToNewJobs":{"type":"boolean","default":false},"attachToNewProductService":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Job form created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"get":{"summary":"List job forms with pagination","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"status","schema":{"type":"string","default":"Active"}},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":10}}],"responses":{"200":{"description":"Job forms fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"put":{"summary":"Update an existing job form","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"title":{"type":"string"},"description":{"type":"string"},"formSchema":{"type":"object"},"type":{"type":"string"},"attachToNewJobs":{"type":"boolean"},"attachToNewProductService":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Job form updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Form ID is required"},"401":{"description":"Unauthorized"},"404":{"description":"Form not found"},"500":{"description":"Server error"}}},"delete":{"summary":"Delete job form(s) (soft delete)","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"id","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Single form ID to delete"}],"requestBody":{"description":"Bulk delete with array of IDs","content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}}}}}}},"responses":{"200":{"description":"Form(s) deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Form IDs required"},"401":{"description":"Unauthorized"},"404":{"description":"Form not found"},"500":{"description":"Server error"}}}},"/api/job-logs-templates/{id}/clone":{"post":{"summary":"Clone a job log template","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Source template ID to clone"}],"responses":{"201":{"description":"Template cloned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"},"500":{"description":"Server error"}}}},"/api/job-logs-templates/{id}":{"get":{"summary":"Get a single job log template by ID","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Template ID"}],"responses":{"200":{"description":"Template fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"},"500":{"description":"Server error"}}}},"/api/job-logs-templates/all":{"get":{"summary":"Get all active job log templates for dropdowns","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Templates fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/job-logs-templates":{"get":{"summary":"List job log templates with pagination","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":30}},{"in":"query","name":"search","schema":{"type":"string"}}],"responses":{"200":{"description":"Templates fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"post":{"summary":"Create a custom job log template","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","fields"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"fields":{"type":"array","items":{"type":"object"}},"billingMode":{"type":"integer","description":"0 = Shared, 1 = Per-Team"}}}}}},"responses":{"201":{"description":"Template created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Name and fields are required"},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"put":{"summary":"Update a job log template","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"description":{"type":"string"},"fields":{"type":"array","items":{"type":"object"}},"billingMode":{"type":"integer"}}}}}},"responses":{"200":{"description":"Template updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Template ID is required"},"401":{"description":"Unauthorized"},"403":{"description":"Cannot edit system templates"},"404":{"description":"Template not found"},"500":{"description":"Server error"}}},"delete":{"summary":"Soft delete job log template(s)","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"id","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Single template ID to delete"}],"requestBody":{"description":"Bulk delete with array of IDs","content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}}}}}}},"responses":{"200":{"description":"Template(s) deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Template ID(s) required or templates in use"},"401":{"description":"Unauthorized"},"403":{"description":"Cannot delete system templates"},"404":{"description":"Template not found"},"500":{"description":"Server error"}}}},"/api/job-logs-templates/seed":{"post":{"summary":"Seed system job log templates (admin only)","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"force","schema":{"type":"string","enum":["true","false"]},"description":"Set to true to delete and recreate existing system templates"}],"responses":{"200":{"description":"System templates already exist"},"201":{"description":"System templates created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/job-schedule-templates":{"get":{"summary":"Fetch all job schedule templates with pagination","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":10},"description":"Use -1 to fetch all"},{"in":"query","name":"search","schema":{"type":"string"}}],"responses":{"200":{"description":"Job schedule templates fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"post":{"summary":"Create a new job schedule template","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","totalDays","scheduleData"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"scheduleData":{"type":"array","items":{"type":"object"}},"totalDays":{"type":"integer","minimum":1}}}}}},"responses":{"201":{"description":"Job schedule template created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"put":{"summary":"Update a job schedule template","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateId","name"],"properties":{"templateId":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"description":{"type":"string"},"scheduleData":{"type":"array","items":{"type":"object"}},"totalDays":{"type":"integer"}}}}}},"responses":{"200":{"description":"Job schedule template updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"},"500":{"description":"Server error"}}},"delete":{"summary":"Delete job schedule templates (soft delete)","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["templateIds"],"properties":{"templateIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}}}}}}},"responses":{"200":{"description":"Template(s) deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Template IDs are required"},"401":{"description":"Unauthorized"},"404":{"description":"Some templates not found"},"500":{"description":"Server error"}}}},"/api/job-settings":{"get":{"summary":"Fetch job table settings and saved views for the current user","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Job settings fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"User not found"},"500":{"description":"Server error"}}},"post":{"summary":"Save job table settings and/or saved views","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"jobTableSettings":{"type":"object","description":"Table display settings"},"jobSavedViews":{"type":"array","items":{"type":"object"}},"viewWithId":{"type":"object","description":"View to create or update"},"viewId":{"type":"string","description":"View ID to delete"}}}}}},"responses":{"200":{"description":"Job settings saved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/jobs/{id}":{"get":{"summary":"Get a job by ID","description":"Retrieves a single job by its ID, proxying to the parent jobs list endpoint.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The job ID"}],"responses":{"200":{"description":"Job fetched successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/JobResponse"}},"total":{"type":"integer"}}}}}}}},"400":{"description":"Bad request - Job ID is required"},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error"}}},"put":{"summary":"Update a job by ID","description":"Updates a job by its ID, injecting the jobId into the request body and proxying to the parent PATCH handler.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The job ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"jobTitle":{"type":"string"},"jobStatus":{"type":"string"},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItem"}},"jobSchedule":{"$ref":"#/components/schemas/JobSchedule"},"assignedTo":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"jobAddress":{"$ref":"#/components/schemas/DetailedAddress"},"notes":{"type":"string"},"jobType":{"type":"string","enum":["oneOff","recurring"]},"properties":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"Job updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"job":{"$ref":"#/components/schemas/JobResponse"}}}}}}}},"400":{"description":"Bad request - Job ID is required"},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error"}}},"patch":{"summary":"Update a job by ID (alias for PUT)","description":"Same as PUT - updates a job by its ID.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The job ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"jobTitle":{"type":"string"},"jobStatus":{"type":"string"},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItem"}},"jobSchedule":{"$ref":"#/components/schemas/JobSchedule"},"assignedTo":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"jobAddress":{"$ref":"#/components/schemas/DetailedAddress"},"notes":{"type":"string"},"properties":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"Job updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"job":{"$ref":"#/components/schemas/JobResponse"}}}}}}}},"400":{"description":"Bad request - Job ID is required"},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error"}}},"delete":{"summary":"Delete a job by ID","description":"Deletes a single job by its ID, proxying to the parent DELETE handler with the jobId wrapped in an array.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The job ID"}],"responses":{"200":{"description":"Job deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - Job ID is required"},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error"}}}},"/api/jobs/{id}/visits":{"get":{"summary":"Get visits for a job","description":"Retrieves all active visits for a specific job, including team member details.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The job ID"}],"responses":{"200":{"description":"Visits fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - Job ID is required"},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error - Failed to fetch visits"}}}},"/api/jobs/assign-all":{"post":{"summary":"Assign jobs to team members","description":"Forwards job assignment requests to the Python backend for bulk or single visit assignment.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"visitIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Array of visit IDs for bulk assignment"},"visitId":{"$ref":"#/components/schemas/MongoObjectId","description":"Single visit ID for individual assignment"},"jobId":{"$ref":"#/components/schemas/MongoObjectId","description":"Job ID for single assignment"},"type":{"type":"string","description":"Assignment type"}}}}}},"responses":{"200":{"description":"Jobs assigned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - Either visitIds array or visitId is required"},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error - Failed to assign jobs"}}}},"/api/jobs/conflicts/check-combined":{"post":{"summary":"Check combined schedule and visit conflicts","description":"Combined conflict check for team member weeklySchedule/specificHours availability and existing job/visit overlaps.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["teamMemberIds","visits"],"properties":{"teamMemberIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"visits":{"type":"array","items":{"type":"object","properties":{"visitStartDateTime":{"type":"string","format":"date-time"},"visitEndDateTime":{"type":"string","format":"date-time"}}}},"excludeJobId":{"$ref":"#/components/schemas/MongoObjectId"},"companyTimezone":{"type":"string","example":"America/New_York"}}}}}},"responses":{"200":{"$ref":"#/components/responses/SuccessResponse"},"400":{"description":"Invalid request parameters"},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/jobs/conflicts/check-team-schedule":{"post":{"summary":"Check team schedule conflicts with timezone support","description":"Check for scheduling conflicts based on team member weeklySchedule, specificHours, and existing visit overlaps with UTC-to-local timezone conversion.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["teamMemberIds","visits"],"properties":{"teamMemberIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"visits":{"type":"array","items":{"type":"object","properties":{"visitStartDateTime":{"type":"string","format":"date-time"},"visitEndDateTime":{"type":"string","format":"date-time"}}}},"timezone":{"type":"string","example":"Asia/Kolkata"},"excludeJobId":{"$ref":"#/components/schemas/MongoObjectId"}}}}}},"responses":{"200":{"$ref":"#/components/responses/SuccessResponse"},"400":{"description":"Invalid request parameters"},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/jobs/conflicts/check":{"post":{"summary":"Check schedule conflicts","description":"Check for scheduling conflicts when creating or updating a job by evaluating team member availability against existing visits.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["teamMemberIds","visits"],"properties":{"teamMemberIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"visits":{"type":"array","items":{"type":"object","properties":{"visitStartDateTime":{"type":"string","format":"date-time"},"visitEndDateTime":{"type":"string","format":"date-time"}}}},"excludeJobId":{"$ref":"#/components/schemas/MongoObjectId"}}}}}},"responses":{"200":{"$ref":"#/components/responses/SuccessResponse"},"400":{"description":"Invalid request parameters"},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/jobs/file":{"post":{"summary":"Create or find a job file folder","description":"Finds an existing system-generated job folder for a client or creates a new one if none exists.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["moduleId"],"properties":{"moduleId":{"$ref":"#/components/schemas/MongoObjectId"},"moduleType":{"type":"string","default":"clients"}}}}}},"responses":{"200":{"description":"Job folder found or created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - Client ID (moduleId) is required"},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error - Failed to handle job folder"}}}},"/api/jobs/nextJobNumber":{"get":{"summary":"Get next available job number","description":"Retrieves the next available job number by finding the maximum existing numeric job number and incrementing it.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Next job number fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error - Failed to fetch next job number"}}}},"/api/jobs/review-job-visit-address":{"post":{"summary":"Update a job's address","description":"Updates the job address for a specific job, typically used during address review workflows.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jobId","jobAddress"],"properties":{"jobId":{"$ref":"#/components/schemas/MongoObjectId"},"jobAddress":{"type":"object","description":"The updated job address object"}}}}}},"responses":{"200":{"description":"Job address updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error"}}}},"/api/jobs":{"get":{"summary":"List jobs with filtering, pagination, and grouping","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"id","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Fetch a single job by ID"},{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":10},"description":"Items per page (-1 for all)"},{"in":"query","name":"clientId","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Filter by client ID"},{"in":"query","name":"startDate","schema":{"type":"string","format":"date"},"description":"Filter start date"},{"in":"query","name":"endDate","schema":{"type":"string","format":"date"},"description":"Filter end date"},{"in":"query","name":"view","schema":{"type":"string","enum":["viewAssigned","own","all"]},"description":"View mode"},{"in":"query","name":"stage","schema":{"type":"string"},"description":"Filter by job status/stage"},{"in":"query","name":"groupBy","schema":{"type":"string"},"description":"Group results by field"},{"in":"query","name":"groupsPerPage","schema":{"type":"integer","default":5},"description":"Number of groups per page (grouped view)"},{"in":"query","name":"recordsPerGroup","schema":{"type":"integer","default":5},"description":"Number of records per group"},{"in":"query","name":"groupPage","schema":{"type":"integer","default":1},"description":"Which page of groups to display"},{"in":"query","name":"groupPagination","schema":{"type":"string"},"description":"JSON string — per-group pagination state: { groupKey: { page, total } }"},{"in":"query","name":"searchView","schema":{"type":"string"},"description":"Custom MongoDB aggregation pipeline JSON for saved views"},{"in":"query","name":"filters[]","schema":{"type":"string"},"description":"Advanced filter array. Pass as filters[N][field], filters[N][filterCondition], filters[N][condition], filters[N][value].\n"},{"in":"query","name":"sorting[]","schema":{"type":"string"},"description":"Sorting array. Pass as sorting[N][field], sorting[N][condition]."}],"responses":{"200":{"description":"Jobs fetched successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/JobResponse"}},"total":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new job","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCreateRequest"}}}},"responses":{"201":{"description":"Job created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"job":{"$ref":"#/components/schemas/JobResponse"}}}}}}}},"401":{"description":"Unauthorized"}}},"patch":{"summary":"Update an existing job","description":"Update an existing job. Use `assignedToTeams` (not `assignedTo`) and `jobItems` (not `lineItems`). Schedule fields are flat top-level fields (`startDateTime`, `endDateTime`, `timezone`) — there is no `jobSchedule` wrapper object. There is no `jobTitle` field.\n","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jobId"],"properties":{"jobId":{"$ref":"#/components/schemas/MongoObjectId"},"jobStatus":{"type":"string","enum":["draft","scheduled","unscheduled","in-progress","on-my-way","closed","ai_queued","overdue","incomplete"]},"jobItems":{"type":"array","description":"Line items (field is jobItems, not lineItems)","items":{"$ref":"#/components/schemas/JobItem"}},"startDateTime":{"type":"string","format":"date-time","description":"ISO 8601 datetime (flat field, not inside jobSchedule)"},"endDateTime":{"type":"string","format":"date-time"},"timezone":{"type":"string","example":"America/Chicago"},"scheduleLater":{"type":"boolean"},"anyTime":{"type":"boolean"},"repeatOptions":{"type":"object","additionalProperties":true},"assignedToTeams":{"type":"array","description":"Team member IDs (field is assignedToTeams, not assignedTo)","items":{"$ref":"#/components/schemas/MongoObjectId"}},"jobAddress":{"$ref":"#/components/schemas/DetailedAddress"},"priority":{"type":"string","enum":["low","medium","high","critical"]},"skillsId":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"equipmentIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"notes":{"type":"string"}}}}}},"responses":{"200":{"description":"Job updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"job":{"$ref":"#/components/schemas/JobResponse"}}}}}}}},"401":{"description":"Unauthorized"}}},"delete":{"summary":"Bulk delete jobs","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jobIds"],"properties":{"jobIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}}}}}}},"responses":{"200":{"description":"Jobs deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"}}}},"/api/jobs/view-token":{"get":{"summary":"Validate a job view token","description":"Validates a tiny token for visit access and returns the stored authentication token. Called when someone clicks an email link to view a job.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"token","required":true,"schema":{"type":"string"},"description":"The tiny token to validate"},{"in":"query","name":"type","required":true,"schema":{"type":"string","enum":["visit"]},"description":"The token type (currently only 'visit')"},{"in":"query","name":"visitId","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The visit ID to validate against the token"}],"responses":{"200":{"description":"Token validated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - Token, type, or visitId missing"},"401":{"description":"Unauthorized - Invalid or expired token"},"500":{"description":"Server error - Failed to validate token"}}}},"/api/metadata/capabilities":{"get":{"summary":"Get available capabilities for an object type","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"objectSlug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of capabilities with availability status"}}}},"/api/metadata/field-definitions":{"get":{"summary":"List field definitions for an object type","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"objectSlug","required":true,"schema":{"type":"string"},"description":"Object type slug"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"post":{"summary":"Create a field definition","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["objectSlug","name","label","type"],"properties":{"objectSlug":{"type":"string"},"name":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"},"isRequired":{"type":"boolean"},"isVisible":{"type":"boolean"},"defaultValue":{"type":"string"},"options":{"type":"object"},"validationRules":{"type":"object"},"position":{"type":"integer"},"relationSettings":{"type":"object"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"put":{"summary":"Update a field definition","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"label":{"type":"string"},"isRequired":{"type":"boolean"},"isVisible":{"type":"boolean"},"isReadOnly":{"type":"boolean"},"defaultValue":{"type":"string"},"options":{"type":"object"},"validationRules":{"type":"object"},"position":{"type":"integer"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"delete":{"summary":"Delete a field definition","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Field definition ID"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/metadata/object-definitions":{"get":{"summary":"List object definitions","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"post":{"summary":"Create a custom object definition","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slug","nameSingular","namePlural"],"properties":{"slug":{"type":"string"},"nameSingular":{"type":"string"},"namePlural":{"type":"string"},"icon":{"type":"string"},"color":{"type":"string"},"objectProfile":{"type":"string"},"isChildObject":{"type":"boolean"},"parentObjectSlug":{"type":"string"},"showInSidebar":{"type":"boolean"},"settings":{"type":"object"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"put":{"summary":"Update a custom object definition","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"nameSingular":{"type":"string"},"namePlural":{"type":"string"},"icon":{"type":"string"},"color":{"type":"string"},"showInSidebar":{"type":"boolean"},"settings":{"type":"object"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"delete":{"summary":"Delete a custom object definition","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Object definition ID"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/metadata/pipeline-configs":{"get":{"summary":"Get pipeline configuration for an object type","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"objectSlug","required":true,"schema":{"type":"string"},"description":"Object type slug"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"post":{"summary":"Create a pipeline configuration","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["objectSlug","pipelineName","fieldName"],"properties":{"objectSlug":{"type":"string"},"pipelineName":{"type":"string"},"fieldName":{"type":"string"},"stages":{"type":"array","items":{"type":"object"}},"transitions":{"type":"array","items":{"type":"object"}},"computedRules":{"type":"object"},"isDefault":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"put":{"summary":"Update a pipeline configuration","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"objectSlug":{"type":"string"},"stages":{"type":"array","items":{"type":"object"}},"transitions":{"type":"array","items":{"type":"object"}},"computedRules":{"type":"object"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/metadata/pipeline-configs/toggle-requirement":{"post":{"summary":"Toggle pipeline stage requirement","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["objectSlug","fromValue","toValue","requirement"],"properties":{"objectSlug":{"type":"string"},"fromValue":{"type":"string"},"toValue":{"type":"string"},"requirement":{"type":"string"},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/metadata/pipeline-configs/validate-transition":{"post":{"summary":"Validate pipeline stage transition","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["objectSlug","fromValue","toValue"],"properties":{"objectSlug":{"type":"string"},"fromValue":{"type":"string"},"toValue":{"type":"string"},"record":{"type":"object"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/metadata/relation-targets":{"get":{"summary":"List relation targets for a parent object","description":"Returns the set of object slugs that can be picked as the\n`targetObject` of a relationship slot when authoring the layout\nfor the given parent. Includes:\n  - REVERSE relations: custom objects with a `relation` field whose\n    `relationSettings.targetObject` equals the parent slug. These\n    are eligible because the custom-object list endpoint scopes by\n    `linkedRecordId`.\n  - OUTGOING relations: target slugs referenced by the parent's\n    OWN relation fields (`relationSettings.targetObject` on a\n    field whose `objectSlug` equals the parent). Without these,\n    the Inspector's \"Related object\" dropdown for an auto-\n    generated related-table couldn't pre-select the field's\n    configured target — the SelectPicker would render \"— none —\"\n    even though the layout JSON had the right value.\n  - SCHEDULABLE custom-object → jobs reverse link: when a custom\n    object has `settings.schedulable === true`, jobs scheduled for\n    its records store a `targetRecordId` + `targetObjectSlug` pivot\n    on the job rather than a relation field. For a schedulable\n    custom object parent, the endpoint adds `job` to the `custom`\n    (reverse) array so the Inspector dropdown surfaces it even when\n    other relation fields exist on the parent (which would skip the\n    fallback list that normally contains `job`). The entry carries\n    the sentinel fieldName `__schedulable_pivot__` to distinguish it\n    from real relation-field-backed entries. The reverse direction\n    (`job` parent → schedulable custom object as outgoing target)\n    is intentionally not surfaced — the runtime fetch has no\n    backlink today and would render an empty table.\nStandard relations (job/task/visit/etc.) are computed client-side via\n`getStandardRelationTargetsForParent` since the URL builders that\ndefine them live in the client bundle.\n","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"parentSlug","required":true,"schema":{"type":"string"},"description":"Slug of the parent object (e.g. \"client\", \"job\")."}],"responses":{"200":{"description":"Successful operation"}}}},"/api/metadata/seed":{"post":{"summary":"Seed default metadata for organization","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/my-views/{id}":{"get":{"summary":"Get a specific user view","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"View record ID"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/my-views":{"get":{"summary":"List user views","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"post":{"summary":"Create a new view","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["viewRecordId"],"properties":{"viewRecordId":{"type":"string"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/notifications/{id}":{"put":{"summary":"Mark a specific notification as read","tags":["Communication"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Notification ID"}],"responses":{"200":{"description":"Notification updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"No authorization header"},"500":{"description":"Failed to update notification"}}}},"/api/notifications/counts":{"get":{"summary":"Get total, unread, and read notification counts for the authenticated user","tags":["Communication"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Notification counts fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"500":{"description":"Failed to fetch notification counts"}}}},"/api/notifications/filters":{"get":{"summary":"Get notification filter options with counts grouped by type","tags":["Communication"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Notification filters fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"No authorization header"},"500":{"description":"Failed to fetch notification filters"}}}},"/api/notifications":{"get":{"summary":"Get paginated notifications for the authenticated user","tags":["Communication"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":3},"description":"Number of notifications per page"},{"in":"query","name":"type","schema":{"type":"string"},"description":"Filter by notification type (e.g. \"All\" returns all types)"}],"responses":{"200":{"description":"Notifications fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"No authorization header"},"500":{"description":"Failed to fetch notifications"}}},"put":{"summary":"Mark all notifications as read or unread for the authenticated user","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["markAllRead","markAllUnread"],"description":"Action to perform on all notifications"}}}}}},"responses":{"200":{"description":"All notifications updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"No authorization header"},"500":{"description":"Failed to update notifications"}}},"post":{"summary":"Create a new notification","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type"],"properties":{"type":{"type":"string","description":"Notification type"},"title":{"type":"string"},"message":{"type":"string"},"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"redirectUrl":{"type":"string"},"priority":{"type":"string","default":"low"},"status":{"type":"string","default":"active"},"visitId":{"type":"string"},"jobNumber":{"type":"string"},"visitStartDateTime":{"type":"string","format":"date-time"},"visitEndDateTime":{"type":"string","format":"date-time"},"metadata":{"type":"object"}}}}}},"responses":{"200":{"description":"Notification created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing type"},"401":{"description":"No authorization header"},"404":{"description":"Client not found"},"500":{"description":"Failed to create notification"}}}},"/api/notifications/send":{"post":{"summary":"Send push notifications to one or more users or device tokens","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","message"],"properties":{"title":{"type":"string","description":"Notification title"},"message":{"type":"string","description":"Notification message body"},"data":{"type":"object","description":"Additional data payload"},"userIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Array of user IDs to send notifications to"},"manualDeviceTokens":{"type":"array","items":{"type":"string"},"description":"Manual device tokens for direct push"},"deviceType":{"type":"integer","enum":[1,2],"description":"1 = iOS, 2 = Android"}}}}}},"responses":{"200":{"description":"Push notification sent successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Title and message are required"},"401":{"description":"Unauthorized"},"500":{"description":"Failed to send push notification"}}}},"/api/option-presets/{id}":{"get":{"summary":"Get a single option preset","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Option preset ID"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"put":{"summary":"Update option preset","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Option preset ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"displayColor":{"type":"string"},"isDefault":{"type":"boolean"},"sections":{"type":"array","items":{"type":"object"}},"items":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"delete":{"summary":"Delete option preset","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Option preset ID"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/option-presets":{"get":{"summary":"List option presets","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":10}},{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"category","schema":{"type":"string"}}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"post":{"summary":"Create option preset","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"displayColor":{"type":"string"},"isDefault":{"type":"boolean"},"sections":{"type":"array","items":{"type":"object"}},"items":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"201":{"description":"Preset created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"delete":{"summary":"Bulk delete option presets","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["presetIds"],"properties":{"presetIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/payment/options":{"get":{"summary":"Get payment options","description":"Retrieves all active payment options for the authenticated user's account.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Payment options fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - invalid or missing token"},"500":{"description":"Internal server error"}}}},"/api/payment/options/update":{"patch":{"summary":"Update a payment option","description":"Updates the enabled status of a specific payment option.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["paymentOptionId"],"properties":{"paymentOptionId":{"$ref":"#/components/schemas/MongoObjectId"},"isPaymentMethodEnable":{"type":"integer","enum":[0,1],"description":"Whether the payment method is enabled"}}}}}},"responses":{"200":{"description":"Payment option updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing required fields"},"401":{"description":"Unauthorized"},"404":{"description":"Payment option not found"},"500":{"description":"Internal server error"}}}},"/api/payment/stripe/cards/{id}":{"delete":{"summary":"Remove a saved card","description":"Detaches a payment method (card) from a customer on the connected Stripe account.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"},"description":"The Stripe payment method ID to remove"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["clientId"],"properties":{"clientId":{"$ref":"#/components/schemas/MongoObjectId"}}}}}},"responses":{"200":{"description":"Card removed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing fields or Stripe not connected"},"401":{"description":"Unauthorized"},"404":{"description":"Customer not found"},"500":{"description":"Internal server error"}}}},"/api/payment/stripe/cards/add":{"post":{"summary":"Add a card to a client","description":"Attaches a payment method (card) to a customer on the connected Stripe account and sets it as the default payment method.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["clientId","paymentMethodId"],"properties":{"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"paymentMethodId":{"type":"string","description":"Stripe payment method ID (pm_xxx)"}}}}}},"responses":{"200":{"description":"Card added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing fields, Stripe not connected, customer not found, or card error"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/payment/stripe/cards/init":{"get":{"summary":"Initialize card setup for a client","description":"Returns connectedAccountId and creates or retrieves a Stripe customer on the connected account. Frontend uses connectedAccountId to initialize Stripe.js.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The client ID to initialize card setup for"}],"responses":{"200":{"description":"Card setup initialized successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Client ID missing or Stripe not connected"},"401":{"description":"Unauthorized"},"404":{"description":"Client not found"},"500":{"description":"Internal server error"}}}},"/api/payment/stripe/cards/list":{"get":{"summary":"List saved cards for a client","description":"Retrieves all payment methods (cards) for a client on the connected Stripe account, including default payment method indicator.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The client ID to list cards for"}],"responses":{"200":{"description":"Payment methods retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Client ID missing or Stripe not connected"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/payment/stripe/charge-saved-card":{"post":{"summary":"Charge a saved card","description":"Charges a saved payment method for a document (invoice/estimate) payment. Records the payment transaction and updates document payment status.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["documentId","clientId","amount"],"properties":{"documentId":{"$ref":"#/components/schemas/MongoObjectId"},"clientId":{"$ref":"#/components/schemas/MongoObjectId"},"amount":{"type":"number","description":"Amount to charge (must be > 0 and <= remaining balance)"},"paymentMethodId":{"type":"string","description":"Optional Stripe payment method ID; uses default if omitted"},"sendReceipt":{"type":"boolean"},"note":{"type":"string"}}}}}},"responses":{"200":{"description":"Payment charged successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Validation error, card declined, or Stripe not connected"},"401":{"description":"Unauthorized"},"404":{"description":"Document not found"},"500":{"description":"Internal server error"}}}},"/api/payment/stripe/connect-completed":{"get":{"summary":"Check Stripe Connect onboarding status","description":"Verifies whether a Stripe Connect account has completed onboarding by checking if details have been submitted. Updates the payment option status accordingly.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"accountId","required":true,"schema":{"type":"string"},"description":"The Stripe Connect account ID to check"}],"responses":{"200":{"description":"Stripe account status checked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Account ID is required"},"401":{"description":"Unauthorized"},"404":{"description":"Payment option not found"},"500":{"description":"Internal server error"}}}},"/api/payment/stripe/create-checkout-session":{"post":{"summary":"Create a Stripe checkout session","description":"Creates a Stripe Checkout Session for invoice or estimate payment, including support for deposit payments. Returns a checkout URL for redirect.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount","documentId"],"properties":{"amount":{"type":"number","description":"Payment amount"},"documentId":{"$ref":"#/components/schemas/MongoObjectId"},"documentType":{"type":"integer","description":"1 = Invoice, 2 = Estimate"},"documentNumber":{"type":"string"},"documentPreFix":{"type":"string"},"isCustomerView":{"type":"boolean"},"isClientView":{"type":"boolean"},"tinyToken":{"type":"string"},"token":{"type":"string"},"linkAttachments":{"type":"string"},"isDepositPayment":{"type":"boolean","default":false},"depositInfo":{"type":"object","nullable":true}}}}}},"responses":{"200":{"description":"Checkout session created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing fields, invalid amount, or Stripe not connected"},"401":{"description":"Unauthorized"},"404":{"description":"Document not found"},"500":{"description":"Internal server error"}}}},"/api/payment/stripe/onboard":{"post":{"summary":"Start Stripe Connect onboarding","description":"Creates or reuses a Stripe Connect standard account and generates an onboarding link for the user to complete their Stripe setup.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"returnUrl":{"type":"string","description":"URL to redirect to after onboarding completes"}}}}}},"responses":{"200":{"description":"Stripe onboarding link generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Return URL is required"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/payment/stripe/refresh-onboarding":{"get":{"summary":"Refresh Stripe onboarding link","description":"Generates a new Stripe account onboarding link and redirects the user. Used as the refresh URL during Stripe Connect onboarding flow.","tags":["Payments & Billing"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"accountId","required":true,"schema":{"type":"string"},"description":"The Stripe Connect account ID"}],"responses":{"302":{"description":"Redirects to the new Stripe onboarding link"},"400":{"description":"Account ID is required"},"404":{"description":"Payment option not found"},"500":{"description":"Internal server error"}}}},"/api/payment/stripe/webhook/payments":{"post":{"summary":"Stripe Connect payments webhook","description":"Handles payment events from Stripe Connect (connected accounts) including payment_intent.succeeded, payment_intent.payment_failed, and charge.succeeded. Records payments and updates document statuses idempotently.","tags":["Payments & Billing"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Webhook processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Webhook signature verification failed"},"500":{"description":"Webhook processing failed"}}}},"/api/payment/stripe/webhook":{"post":{"summary":"Stripe Connect account webhook","description":"Receives Stripe webhook events for Connect account updates (e.g., account.updated). Verifies the webhook signature and updates payment option status when onboarding completes.","tags":["Payments & Billing"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Webhook received successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Webhook signature verification failed"},"500":{"description":"Internal server error"}}}},"/api/properties":{"get":{"summary":"List properties","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"module_type","schema":{"type":"string","enum":["product_properties","team_properties","job_properties","request_properties"]},"description":"Property module type filter"},{"in":"query","name":"input_type","schema":{"type":"string","enum":["Category","Hierarchy"]},"description":"Input type filter (product_properties only)"}],"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"post":{"summary":"Create property","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string"},"type":{"type":"string"},"options":{"type":"array","items":{"type":"object"}},"module_type":{"type":"string"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"put":{"summary":"Update property","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","name","type"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"options":{"type":"array","items":{"type":"object"}},"module_type":{"type":"string"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}},"delete":{"summary":"Delete property","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string"},"module_type":{"type":"string"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/property/option":{"post":{"summary":"Manage property options (add/update/delete select field options)","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["propertyId","name"],"properties":{"propertyId":{"type":"string"},"name":{"type":"string"},"color":{"type":"string"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/property/options/reorder":{"post":{"summary":"Reorder property select options","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["propertyId","options"],"properties":{"propertyId":{"type":"string"},"options":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/regions/country":{"get":{"summary":"List all countries","description":"Fetches all available countries with their ISO codes and flag identifiers.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Countries fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error - Failed to fetch countries"}}}},"/api/regions/currency":{"get":{"summary":"List all currencies","description":"Fetches all available currencies with their code, name, and symbol.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Currencies fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error - Failed to fetch currencies"}}}},"/api/regions/state":{"get":{"summary":"List states/provinces by country","description":"Fetches all states or provinces for a given country, identified by countryId.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"countryId","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The country ID to fetch states for"}],"responses":{"200":{"description":"States fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - Country ID is required"},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error - Failed to fetch states"}}}},"/api/relations/resolve-names":{"post":{"summary":"Resolve display names for a batch of relation record ids","description":"Given a list of { targetSlug, ids } groups, returns a flat { id -> displayName } map for the ids that exist within the caller's tenant. Used by list/detail UIs to render relation chips with human-readable labels in a single round-trip.\nSupersedes the legacy per-target ?limit=200 list calls, which both overfetched and silently failed when a referenced id sat outside the first 200 rows.\n","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"object","required":["targetSlug","ids"],"properties":{"targetSlug":{"type":"string","description":"System slug (client, job, team_member, …) or custom object slug."},"ids":{"type":"array","items":{"type":"string"}}}}}}}}}},"responses":{"200":{"description":"Map of id to display name. Ids that don't resolve are omitted."}}}},"/api/requests/{id}":{"get":{"summary":"Get a request by ID","description":"Fetch a single request with all details including items, taxes, and client info","tags":["Requests"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The request ID"}],"responses":{"200":{"description":"Request fetched successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"request":{"$ref":"#/components/schemas/RequestResponse"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Request not found"}}},"patch":{"summary":"Update a request by ID","description":"Update an existing request using path parameter ID","tags":["Requests"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The request ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"stage":{"type":"string","enum":["New","Assessment","In Progress","Action Required","Completed","Cancelled"]},"requestItems":{"type":"array","items":{"$ref":"#/components/schemas/RequestItem"}},"requestTaxes":{"type":"array","items":{"$ref":"#/components/schemas/RequestTax"}},"notes":{"type":"string"},"assignedTo":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}}}}}}},"responses":{"200":{"description":"Request updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"request":{"$ref":"#/components/schemas/RequestResponse"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Request not found"}}}},"/api/requests":{"get":{"summary":"List requests with filtering, pagination, and grouping","tags":["Requests"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":30},"description":"Items per page (-1 for all)"},{"in":"query","name":"id","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Fetch a single request by ID"},{"in":"query","name":"clientId","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Filter requests by client ID"},{"in":"query","name":"tab","schema":{"type":"string","enum":["requests","pipeline","archived"]},"description":"Navigation tab — controls which stage group is shown"},{"in":"query","name":"stage","schema":{"type":"string","enum":["new_request","unscheduled","overdue","inspection_scheduled","inspection_complete","quote_created","quote_sent","converted","lost_no_response","lost_reject_quote","cancelled","duplicate"]},"description":"Filter by request stage slug value"},{"in":"query","name":"stages","schema":{"type":"string"},"description":"JSON array string of stage slugs to filter by (overrides `stage`)"},{"in":"query","name":"view","schema":{"type":"string","enum":["viewAssigned","own","all"]},"description":"View mode"},{"in":"query","name":"groupBy","schema":{"type":"string"},"description":"Group results by field"},{"in":"query","name":"groupsPerPage","schema":{"type":"integer","default":5},"description":"Number of groups per page"},{"in":"query","name":"recordsPerGroup","schema":{"type":"integer","default":5},"description":"Number of records per group"},{"in":"query","name":"groupPage","schema":{"type":"integer","default":1},"description":"Which page of groups to display"},{"in":"query","name":"groupPagination","schema":{"type":"string"},"description":"JSON per-group pagination state: { groupKey: { page, total } }"},{"in":"query","name":"includeItems","schema":{"type":"boolean"},"description":"Include request line items in the response"},{"in":"query","name":"includeTaxes","schema":{"type":"boolean"},"description":"Include request taxes in the response"},{"in":"query","name":"includeAll","schema":{"type":"boolean"},"description":"Shorthand for includeItems=true and includeTaxes=true"},{"in":"query","name":"filters[]","schema":{"type":"string"},"description":"Advanced filter array. Pass as filters[N][field], filters[N][filterCondition], filters[N][condition], filters[N][value].\n"},{"in":"query","name":"sorting[]","schema":{"type":"string"},"description":"Sorting array. Pass as sorting[N][field], sorting[N][condition]."}],"responses":{"200":{"description":"Requests fetched successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"requests":{"type":"array","items":{"$ref":"#/components/schemas/RequestResponse"}},"total":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new request","tags":["Requests"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestCreateRequest"}}}},"responses":{"201":{"description":"Request created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"request":{"$ref":"#/components/schemas/RequestResponse"}}}}}}}},"401":{"description":"Unauthorized"}}},"patch":{"summary":"Update an existing request","description":"Update a request. The identifier field is `id` (NOT `requestId`) — sending `requestId` will result in a 400 error. Use `items` (not `requestItems`) and `taxes` (not `requestTaxes`). Stage values are slugs (e.g. \"new_request\"), not display names.\n","tags":["Requests"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"$ref":"#/components/schemas/MongoObjectId","description":"REQUIRED — the request ID to update (field name is 'id', NOT 'requestId')"},"stage":{"type":"string","enum":["new_request","unscheduled","overdue","inspection_scheduled","inspection_complete","quote_created","quote_sent","converted","lost_no_response","lost_reject_quote","cancelled","duplicate"]},"source":{"type":"string","enum":["manual","online_booking"]},"urgency":{"type":"string","enum":["low","medium","high","critical"]},"description":{"type":"string"},"requestAddress":{"$ref":"#/components/schemas/DetailedAddress"},"requestPhone":{"$ref":"#/components/schemas/PhoneNumber"},"preferredContactMethod":{"type":"string","enum":["phone","email","text"]},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"},"scheduleLater":{"type":"boolean"},"anyTime":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"estimatedValue":{"type":"number"},"items":{"type":"array","description":"Request line items (field name is 'items', not 'requestItems')","items":{"$ref":"#/components/schemas/RequestItem"}},"taxes":{"type":"array","description":"Request taxes (field name is 'taxes', not 'requestTaxes')","items":{"$ref":"#/components/schemas/RequestTax"}},"notes":{"type":"string"},"assignedTo":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}}}}}}},"responses":{"200":{"description":"Request updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"request":{"$ref":"#/components/schemas/RequestResponse"}}}}}}}},"401":{"description":"Unauthorized"}}},"delete":{"summary":"Bulk delete requests","tags":["Requests"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["requestIds"],"properties":{"requestIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"forceDelete":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Requests deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"}}}},"/api/reset-password":{"post":{"tags":["Auth & Security"],"summary":"Reset user password","description":"Resets a user password either manually or with auto-generated password. Used for team member password resets.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"newPassword":{"type":"string","format":"password"},"passwordType":{"type":"string","enum":["manual","auto"],"description":"manual = auto-generate password, otherwise use provided newPassword"}}}}}},"responses":{"200":{"description":"Password reset successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing email or password"},"404":{"description":"User not found"},"500":{"description":"Server error"}}}},"/api/role/{id}":{"put":{"summary":"Update a role","description":"Updates an existing role's name, description, permissions, and base permissions.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The role ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Updated role name"},"description":{"type":"string","description":"Updated role description"},"permissions":{"type":"array","description":"Updated list of permission identifiers","items":{"type":"string"}},"base_permissions":{"type":"object","description":"Updated base permission template"}}}}}},"responses":{"200":{"description":"Role updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Role"}}}]}}}},"400":{"description":"Bad request - missing role ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Role not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"summary":"Get a role by ID","description":"Retrieves a single role by its ID.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The role ID"}],"responses":{"200":{"description":"Role retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Role"}}}]}}}},"400":{"description":"Bad request - missing role ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Role not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/role/check-name":{"get":{"summary":"Check if role name is available","description":"Checks whether a role name already exists within the organization, including system-defined preset roles.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"name","required":true,"schema":{"type":"string"},"description":"The role name to check for availability"}],"responses":{"200":{"description":"Name availability check result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"exists":{"type":"boolean","description":"Whether the role name already exists"},"message":{"type":"string","nullable":true,"description":"Message if the name exists"}}}}}]}}}},"400":{"description":"Bad request - role name is required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/role":{"get":{"summary":"List all roles","description":"Retrieves all roles accessible to the authenticated user, including default roles and roles created within their organization.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Roles retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Role"}}}}]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Create a new role","description":"Creates a new custom role with the specified name, permissions, and description.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Unique name for the role"},"permissions":{"type":"array","description":"List of permission identifiers","items":{"type":"string"}},"description":{"type":"string","description":"Description of the role"},"base_permissions":{"type":"object","description":"Base permission template for the role"}}}}}},"responses":{"201":{"description":"Role created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Role"}}}]}}}},"400":{"description":"Bad request - role name already exists or missing required fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"summary":"Delete a role","description":"Deletes a non-default role by ID. Fails if team members are currently assigned to the role.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["role_id"],"properties":{"role_id":{"type":"string","description":"The ID of the role to delete","$ref":"#/components/schemas/MongoObjectId"}}}}}},"responses":{"200":{"description":"Role deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - role has assigned members","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/send-email":{"post":{"summary":"Send an email via Gmail API integration","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","to","subject","body"],"properties":{"from":{"type":"string","format":"email","description":"Sender email address"},"to":{"type":"string","format":"email","description":"Recipient email address"},"subject":{"type":"string","description":"Email subject line"},"body":{"type":"string","description":"HTML email body content"}}}}}},"responses":{"200":{"description":"Email sent successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization header or token missing"},"403":{"description":"Invalid token"},"404":{"description":"Gmail not connected"},"500":{"description":"Failed to send email"}}}},"/api/send-new-email":{"post":{"summary":"Send a new email with attachments via Gmail or Outlook (multipart/form-data)","tags":["Communication"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["to","subject","content"],"properties":{"to":{"type":"string","format":"email","description":"Recipient email address"},"subject":{"type":"string","description":"Email subject line"},"content":{"type":"string","description":"Email body content"},"cc":{"type":"string","description":"CC recipients (comma-separated)"},"bcc":{"type":"string","description":"BCC recipients (comma-separated)"},"messageId":{"type":"string","description":"Message ID to update email status after sending"},"attachments":{"type":"array","items":{"type":"string","format":"binary"},"description":"File attachments"}}}}}},"responses":{"200":{"description":"Email sent successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization header or token missing / reconnect required"},"403":{"description":"Invalid token"},"404":{"description":"No email provider connected"},"500":{"description":"Failed to send email"}}}},"/api/service-area/{id}":{"get":{"summary":"Get service area by ID","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"}}],"responses":{"200":{"description":"Service area retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Service area not found"},"500":{"description":"Server error"}}},"put":{"summary":"Update service area by ID","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string"},"type":{"type":"string"}}}}}},"responses":{"200":{"description":"Service area updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Service area not found"},"500":{"description":"Server error"}}},"patch":{"summary":"Get team members for a service area","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"}}],"responses":{"200":{"description":"Team members retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Service area not found"},"500":{"description":"Server error"}}}},"/api/service-area/capacity":{"get":{"summary":"Get service area capacity summary","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"serviceAreaId","required":true,"schema":{"type":"string"}},{"in":"query","name":"date","required":true,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Capacity summary retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"post":{"summary":"Check service area capacity for a time slot","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["serviceAreaId","date","timeSlot"],"properties":{"serviceAreaId":{"type":"string"},"date":{"type":"string","format":"date"},"timeSlot":{"type":"object"}}}}}},"responses":{"200":{"description":"Capacity check completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/service-area":{"get":{"summary":"List service areas","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Service areas retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"post":{"summary":"Create service area","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string"},"type":{"type":"string","enum":["radius","zipcode_cities","polygon"]}}}}}},"responses":{"201":{"description":"Service area created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"delete":{"summary":"Delete service area","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"}}}}}},"responses":{"200":{"description":"Service area deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/service-area/status":{"post":{"summary":"Update service area status","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"}}}}}},"responses":{"200":{"description":"Service area status updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/service-area/verification":{"post":{"summary":"Verify service area address","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["address"],"properties":{"address":{"type":"string"}}}}}},"responses":{"200":{"description":"Address verification completed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/skills/{id}":{"put":{"summary":"Update a skill","description":"Updates an existing skill's name, description, certifications, technician assignments, and job type associations.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The skill ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Updated skill name"},"description":{"type":"string","description":"Updated skill description"},"certifications":{"type":"array","description":"Updated list of certification names","items":{"type":"string"}},"selectedTechnicians":{"type":"array","description":"Updated list of technician user IDs","items":{"type":"string"}},"selectedJobTypes":{"type":"array","description":"Updated list of job type IDs","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Skill updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"skill":{"$ref":"#/components/schemas/Skill"}}}}}]}}}},"400":{"description":"Bad request - invalid skill ID or missing name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Skill not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"summary":"Delete a skill","description":"Permanently deletes a skill by its ID.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The skill ID"}],"responses":{"200":{"description":"Skill deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - invalid skill ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Skill not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/skills":{"get":{"summary":"List all skills","description":"Retrieves all skills for the authenticated user's organization with optional pagination, filtering, and sorting.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer"},"description":"Page number for pagination"},{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Number of items per page"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search term to filter skills by name"}],"responses":{"200":{"description":"Skills retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"skills":{"type":"array","items":{"$ref":"#/components/schemas/Skill"}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"},"hasMore":{"type":"boolean"}}}}}}}]}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Create a new skill","description":"Creates a new skill with optional certifications, technician assignments, and job type associations.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Name of the skill"},"description":{"type":"string","description":"Description of the skill"},"certifications":{"type":"array","description":"List of certification names to associate","items":{"type":"string"}},"selectedTechnicians":{"type":"array","description":"List of technician user IDs to assign","items":{"type":"string"}},"selectedJobTypes":{"type":"array","description":"List of job type IDs to associate","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Skill created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"skill":{"$ref":"#/components/schemas/Skill"}}}}}]}}}},"400":{"description":"Bad request - skill name is required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"summary":"Delete skills (bulk)","description":"Soft-deletes multiple skills by setting their status to 'deleted'.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["skillIds"],"properties":{"skillIds":{"type":"array","description":"Array of skill IDs to delete","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Skills deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - skill IDs must be a non-empty array","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/system-settings":{"get":{"summary":"Get system settings","description":"Fetches system settings with optional filtering by category, settingKey, and search. Supports pagination. Returns a single setting when settingKey is specified.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"category","schema":{"type":"string"},"description":"Filter by settings category"},{"in":"query","name":"settingKey","schema":{"type":"string"},"description":"Fetch a specific setting by key"},{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number for pagination"},{"in":"query","name":"limit","schema":{"type":"integer","default":10},"description":"Number of results per page"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search across label, settingKey, and description"}],"responses":{"200":{"description":"System settings fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error - Failed to fetch system settings"}}},"post":{"summary":"Create a new system setting","description":"Creates a new system setting record (e.g., document prefixes, visit colors). Setting key must be unique per user/company.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["settingKey","category","type","settings"],"properties":{"settingKey":{"type":"string","description":"Unique key identifier for the setting"},"label":{"type":"string","description":"Human-readable label for the setting"},"category":{"type":"string","description":"Category grouping for the setting"},"type":{"type":"string","description":"Type of setting (e.g., json, string, number)"},"settings":{"type":"object","description":"The actual settings data (JSON)"},"description":{"type":"string","description":"Description of what this setting controls"}}}}}},"responses":{"201":{"description":"System setting created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - Missing required fields or setting key already exists"},"401":{"description":"Unauthorized - Invalid or missing token"},"500":{"description":"Server error - Failed to create system setting"}}},"put":{"summary":"Update an existing system setting","description":"Updates an existing system setting by settingKey. Supports full replacement or partial merge of the settings JSON.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["settingKey"],"properties":{"settingKey":{"type":"string","description":"The setting key to update"},"label":{"type":"string"},"category":{"type":"string"},"type":{"type":"string"},"settings":{"type":"object","description":"The settings data to update"},"description":{"type":"string"},"updateType":{"type":"string","enum":["full","partial"],"default":"full","description":"'full' replaces all fields, 'partial' merges settings JSON"}}}}}},"responses":{"200":{"description":"System setting updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing token"},"404":{"description":"System setting not found"},"500":{"description":"Server error - Failed to update system setting"}}},"delete":{"summary":"Delete a system setting","description":"Soft deletes a system setting by setting its status to Deleted.","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"settingKey","required":true,"schema":{"type":"string"},"description":"The setting key to delete"}],"responses":{"200":{"description":"System setting deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - Setting key is required"},"401":{"description":"Unauthorized - Invalid or missing token"},"404":{"description":"System setting not found"},"500":{"description":"Server error - Failed to delete system setting"}}}},"/api/team-settings":{"get":{"summary":"Get team table settings","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Team settings retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"post":{"summary":"Save team table settings","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"teamTableSettings":{"type":"object"},"teamSavedViews":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"Team settings saved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/team-tracking/create-device":{"post":{"summary":"Create Traccar device for team member","description":"Creates a new tracking device in Traccar for a single team member or bulk creates devices for all team members without one. Only account owners can create devices.","tags":["Team Tracking & Live Pulse"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"teamMemberId":{"$ref":"#/components/schemas/MongoObjectId"},"createAll":{"type":"boolean","default":false,"description":"If true, creates devices for all team members without one"}}}}}},"responses":{"200":{"description":"Device created or already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Missing team member ID or Traccar not configured"},"401":{"description":"Authorization header or token missing"},"403":{"description":"Invalid token or user is not an account owner"},"404":{"description":"User or team member not found"},"500":{"description":"Failed to create device or connect to tracking service"}}}},"/api/team-tracking/members":{"get":{"summary":"Get trackable team members","description":"Returns a paginated list of team members (including the account owner) with their tracking status, device IDs, and last known positions.","tags":["Team Tracking & Live Pulse"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","default":100,"maximum":100},"description":"Maximum number of team members to return"},{"in":"query","name":"offset","schema":{"type":"integer","default":0},"description":"Number of team members to skip for pagination"}],"responses":{"200":{"description":"List of team members with tracking info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization header or token missing"},"403":{"description":"Invalid token"},"500":{"description":"Internal server error"}}}},"/api/team-tracking/reports":{"get":{"summary":"Get tracking reports for a device","description":"Retrieves daily tracking reports from Traccar for a specific device. Supports route, stops, trips, and summary report types with enhanced data processing.","tags":["Team Tracking & Live Pulse"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"deviceId","required":true,"schema":{"type":"string"},"description":"Traccar device ID"},{"in":"query","name":"type","schema":{"type":"string","enum":["route","stops","trips","summary"],"default":"route"},"description":"Type of report to generate"},{"in":"query","name":"date","schema":{"type":"string","format":"date"},"description":"Date for the report (defaults to today)"}],"responses":{"200":{"description":"Report data returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Device ID is required"},"401":{"description":"Authorization header or token missing"},"403":{"description":"Invalid token"},"404":{"description":"Traccar not configured"},"500":{"description":"Failed to fetch report from Traccar"}}}},"/api/team-tracking/session":{"get":{"summary":"Get Traccar session details","description":"Returns the Traccar token, WebSocket URL, and API URL for the authenticated user's organization. Resolves the token from the user or their parent account owner.","tags":["Team Tracking & Live Pulse"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Traccar session details returned"},"401":{"description":"Authorization header or token missing"},"403":{"description":"Invalid token"},"404":{"description":"Traccar token not configured for organization"},"500":{"description":"Internal server error"}}}},"/api/team-tracking/visits":{"get":{"summary":"Get visits for tracking","description":"Retrieves processed visit data for team tracking, including current, upcoming, and past visits with location coordinates and timing information.","tags":["Team Tracking & Live Pulse"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"teamMemberId","schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Filter visits for a specific team member"},{"in":"query","name":"date","schema":{"type":"string","format":"date"},"description":"Date to fetch visits for (defaults to today)"}],"responses":{"200":{"description":"Visits data with stats and timing info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization header or token missing"},"403":{"description":"Invalid token"},"500":{"description":"Internal server error"}}}},"/api/teams/{id}":{"get":{"summary":"Get team member details","description":"Fetch a single team member by ID, including role, skills, and service areas.","tags":["Teams"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Team member ID"},{"in":"query","name":"isEdit","schema":{"type":"boolean"},"description":"When true, bypasses active/team member status filters"}],"responses":{"200":{"description":"Team member fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Team member not found"},"500":{"description":"Internal server error"}}},"put":{"summary":"Update team member","description":"Update a team member's details including name, email, role, skills, schedule, labor rates, and multi-day travel settings.","tags":["Teams"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Team member ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"phoneNumber":{"type":"object"},"color":{"type":"string"},"roleId":{"$ref":"#/components/schemas/MongoObjectId"},"skillIds":{"type":"array","items":{"type":"string"}},"status":{"type":"string"},"address":{"type":"object"},"serviceAreaIds":{"type":"array","items":{"type":"string"}},"hourlyRate":{"type":"number"},"overtimeRate":{"type":"number"},"emergencyRate":{"type":"number"},"useBusinessSchedule":{"type":"boolean"},"weeklySchedule":{"type":"array"},"isAssignableToJobs":{"type":"boolean"},"maxJobsOnNormalDay":{"type":"integer"},"multiDayTravelEnabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Team updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Team not found"},"500":{"description":"Internal server error"}}}},"/api/teams/accept-invitation/{id}":{"post":{"summary":"Accept team invitation","description":"Accept a team member invitation by ID. Validates invitation expiration (48 hours) and updates invitation status.","tags":["Teams"],"security":[],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Team member ID from invitation link"}],"responses":{"200":{"description":"Invitation accepted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"User ID is required or invitation link has expired"},"404":{"description":"User not found"},"500":{"description":"Internal server error"}}}},"/api/teams/check-conflicts":{"post":{"summary":"Check team member schedule conflicts","description":"Check for conflicts between team member's schedule changes and existing AI-dispatched visits. Supports both date-specific unavailable slots and weekly schedule changes (90-day lookahead).","tags":["Teams"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["teamMemberId"],"properties":{"teamMemberId":{"$ref":"#/components/schemas/MongoObjectId"},"unavailableSlots":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"startTime":{"type":"string"},"endTime":{"type":"string"},"reason":{"type":"string"}}}},"weeklyScheduleChanges":{"type":"array","items":{"type":"object","properties":{"dayOfWeek":{"type":"integer"},"dayName":{"type":"string"},"unavailablePeriods":{"type":"array","items":{"type":"object","properties":{"startTime":{"type":"string"},"endTime":{"type":"string"}}}}}}},"timezone":{"type":"string"}}}}}},"responses":{"200":{"description":"Conflict check completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Team member ID is required"},"401":{"description":"Unauthorized"},"404":{"description":"Team member not found"},"500":{"description":"Internal server error"}}}},"/api/teams/get-all":{"get":{"summary":"Get all team members (simplified list)","description":"Fetch all active team members with roles, skills, and service areas. Supports view filtering for assigned team, assigned self, or all.","tags":["Teams"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"view","schema":{"type":"string","enum":["assignedTeam","assignedSelf"]},"description":"Filter view mode"}],"responses":{"200":{"description":"Teams fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - No authorization header"},"500":{"description":"Internal server error"}}}},"/api/teams":{"get":{"summary":"List team members","description":"Fetch team members with filtering, pagination, sorting, and grouping support.","tags":["Teams"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number (1-10000)"},{"in":"query","name":"limit","schema":{"type":"integer","default":10},"description":"Items per page (-1 for all, max 500)"},{"in":"query","name":"view","schema":{"type":"string","enum":["own","all"]},"description":"View mode"},{"in":"query","name":"documentCreators","schema":{"type":"boolean"},"description":"Filter for Admin/Dispatcher roles only"},{"in":"query","name":"assignableOnly","schema":{"type":"boolean"},"description":"Filter for job-assignable team members only"},{"in":"query","name":"groupBy","schema":{"type":"string"},"description":"Group results by field"},{"in":"query","name":"searchView","schema":{"type":"string"},"description":"Custom aggregation pipeline (JSON encoded)"}],"responses":{"200":{"description":"Teams fetched successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Invalid parameter format"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}},"post":{"summary":"Create/invite team member","description":"Create a new team member and send an invitation email or SMS.","tags":["Teams"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","passwordMethod"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"phoneNumber":{"type":"object"},"color":{"type":"string"},"passwordMethod":{"type":"string","enum":["manual","email","sms"]},"role":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"}}},"skillIds":{"type":"array","items":{"type":"string"}},"schedule":{"type":"object"},"address":{"type":"object"},"serviceAreaIds":{"type":"array","items":{"type":"string"}},"useBusinessSchedule":{"type":"boolean"},"weeklySchedule":{"type":"array"},"isAssignableToJobs":{"type":"boolean"},"maxJobsOnNormalDay":{"type":"integer"}}}}}},"responses":{"200":{"description":"Team created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Validation error or email already exists"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}},"put":{"summary":"Bulk update team member status","description":"Update the status of multiple team members at once.","tags":["Teams"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["updateIds","newStatus"],"properties":{"updateIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"newStatus":{"type":"string"}}}}}},"responses":{"200":{"description":"Team status updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}},"delete":{"summary":"Bulk delete/deactivate team members","description":"Delete one or more team members by ID. Archives data before deletion.","tags":["Teams"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["deleteIds"],"properties":{"deleteIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}}}}}}},"responses":{"200":{"description":"Team members deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"DeleteIds array is required"},"401":{"description":"Unauthorized"},"404":{"description":"One or more team members not found"},"500":{"description":"Internal server error"}}}},"/api/teams/set-password/{id}":{"post":{"summary":"Set password for invited team member","description":"Set password for a team member during the invitation flow. Updates invitation status to completed and activates the account.","tags":["Teams"],"security":[],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Team member ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"type":"string","format":"password"}}}}}},"responses":{"200":{"description":"Password set successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"User ID or password is required"},"404":{"description":"User not found"},"500":{"description":"Internal server error"}}}},"/api/terms-conditions":{"get":{"summary":"List terms and conditions","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer"}},{"in":"query","name":"limit","schema":{"type":"integer"}},{"in":"query","name":"search","schema":{"type":"string"}}],"responses":{"200":{"description":"Terms and conditions retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"post":{"summary":"Create terms and conditions","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label","content"],"properties":{"label":{"type":"string"},"content":{"type":"string"}}}}}},"responses":{"201":{"description":"Terms and conditions created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"put":{"summary":"Update terms and conditions","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["termsId","label","content"],"properties":{"termsId":{"$ref":"#/components/schemas/MongoObjectId"},"label":{"type":"string"},"content":{"type":"string"}}}}}},"responses":{"200":{"description":"Terms and conditions updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Terms and conditions not found"},"500":{"description":"Server error"}}},"delete":{"summary":"Delete terms and conditions","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["termsIds"],"properties":{"termsIds":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}}}}}}},"responses":{"200":{"description":"Terms and conditions deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/traccar/register":{"post":{"summary":"Register device with Traccar","description":"Registers the account owner as a user in Traccar and obtains a session token. Only account owners (users without a parentId) can register. If the user already exists in Traccar, links the existing account.","tags":["Team Tracking & Live Pulse"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Successfully registered or already configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Authorization header or token missing"},"403":{"description":"Invalid token or user is not an account owner"},"404":{"description":"User not found"},"500":{"description":"Failed to connect to tracking service or create account"}}}},"/api/tracking-token":{"post":{"summary":"Generate tracking token","description":"Generates a JWT tracking token for a given user.","tags":["Team Tracking & Live Pulse"],"security":[{"BearerAuth":[]}]}},"/api/tracking-token/validate":{"post":{"summary":"Validate tracking token and get visit data","description":"Validates a JWT tracking token and returns visit/tracking data for the associated client within the specified date range. Uses MongoDB aggregation to join jobs, visits, and team members.","tags":["Team Tracking & Live Pulse"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","startDate","endDate"],"properties":{"token":{"type":"string","description":"JWT tracking token to validate"},"startDate":{"type":"string","format":"date-time","description":"Start of date range"},"endDate":{"type":"string","format":"date-time","description":"End of date range"}}}}}},"responses":{"200":{"description":"Token valid, tracking data returned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Invalid or expired token"},"500":{"description":"Internal server error"}}}},"/api/tracking-token/visit-details/{id}":{"get":{"summary":"Get visit details by tracking token","description":"Retrieves detailed visit information including job details, team members with skills, file attachments with presigned URLs, and created-by user info. Uses MongoDB aggregation pipeline.","tags":["Team Tracking & Live Pulse"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"Visit ID to fetch details for"}],"responses":{"200":{"description":"Visit details returned successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Visit ID is required"},"404":{"description":"Visit not found"},"500":{"description":"Error fetching visit details"}}}},"/api/transcribe":{"post":{"summary":"Transcribe audio file","description":"Transcribes an audio file using the Python transcription API","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["audio_file"],"properties":{"audio_file":{"type":"string","format":"binary","description":"Audio file to transcribe"},"type":{"type":"string","description":"Audio file type (e.g., mp3)"}}}}}},"responses":{"200":{"description":"Audio transcribed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Audio file is required"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/user-settings":{"get":{"summary":"Get user settings","description":"Retrieves the authenticated user's client table settings and saved views.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"User settings retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Update user settings","description":"Updates the authenticated user's client table settings, saved views, and custom views.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"clientTableSettings":{"type":"object","description":"Table display settings (columns, pagination, etc.)"},"clientSavedViews":{"type":"array","description":"Array of saved view configurations","items":{"type":"object"}},"viewWithId":{"type":"object","description":"View object with ID for create/update operations"},"viewId":{"type":"string","description":"View ID for deletion operations"}}}}}},"responses":{"200":{"description":"User settings updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/user/calendar-preferences":{"get":{"summary":"Get user calendar preferences","description":"Retrieves the current user's calendar display preferences","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Calendar preferences retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"eventDisplay":{"type":"object","properties":{"preset":{"type":"string"},"fields":{"type":"array","items":{"type":"string"}},"density":{"type":"string"}}},"starredView":{"type":"string"},"jobTray":{"type":"object"},"boardLayout":{"type":"object"}}}}}},"401":{"description":"Unauthorized - missing or invalid token"},"500":{"description":"Internal server error"}}},"put":{"summary":"Update user calendar preferences","description":"Updates the current user's calendar display preferences (merges with existing)","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"eventDisplay":{"type":"object"},"starredView":{"type":"string"},"jobTray":{"type":"object"},"boardLayout":{"type":"object"}}}}}},"responses":{"200":{"description":"Calendar preferences updated successfully"},"400":{"description":"Bad request - invalid preference data"},"401":{"description":"Unauthorized - missing or invalid token"},"500":{"description":"Internal server error"}}}},"/api/user/change-password":{"put":{"summary":"Change user password","description":"Allows an authenticated user to change their password by providing the current password and a new password.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["currentPassword","newPassword"],"properties":{"currentPassword":{"type":"string","description":"The user's current password"},"newPassword":{"type":"string","description":"The new password to set"},"isTeamMemberResetPass":{"type":"boolean","description":"Flag indicating if this is a team member password reset"}}}}}},"responses":{"200":{"description":"Password changed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - current password is incorrect","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/user/dynamic-detail-objects":{"patch":{"summary":"Toggle the dynamic detail view for one object slug","description":"Adds or removes a single objectSlug from the current user's\n`dynamicDetailObjects` array. Only writable by parent users\n(`isTeamMember=false`); team members receive 403 because they\ninherit their parent's value at session hydration.\n","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["objectSlug","enabled"],"properties":{"objectSlug":{"type":"string","example":"client"},"enabled":{"type":"boolean","description":"true → use dynamic view; false → use classic"}}}}}},"responses":{"200":{"description":"Updated dynamicDetailObjects array"},"400":{"description":"Bad request — missing/invalid objectSlug or enabled"},"401":{"description":"Unauthorized"},"403":{"description":"Team members cannot modify their parent's preference"},"500":{"description":"Internal server error"}}}},"/api/user/onboarding":{"put":{"summary":"Update onboarding status","description":"Marks the authenticated user's onboarding as completed.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Onboarding status updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized - missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/user/tooltip-preference":{"patch":{"summary":"Update tooltip display preferences","description":"Updates the tooltip visibility preference for a specific module for the authenticated user.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["moduleKey","showTooltip"],"properties":{"moduleKey":{"type":"string","description":"The module identifier for the tooltip preference","enum":["myView","clients","salesPipeline","jobs","items","inventory","analytics","calendar","calendarRoute","estimates","invoices","workflow","team","jobForm","tax","contract","serviceArea","onlineBooking","categories","priceBundles"]},"showTooltip":{"type":"boolean","description":"Whether to show the tooltip for the specified module"}}}}}},"responses":{"200":{"description":"Tooltip preference updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Bad request - invalid module key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/user/update":{"put":{"summary":"Update user profile","description":"Updates the authenticated user's profile information including name, email, phone number, and calendar preferences.","tags":["User & Roles"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"User's display name"},"email":{"type":"string","format":"email","description":"User's email address"},"phoneNumber":{"type":"string","description":"User's phone number"},"calendarDragDropConfirmation":{"type":"boolean","description":"Whether to show drag-and-drop confirmation in calendar"}}}}}},"responses":{"200":{"description":"Profile updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"user":{"$ref":"#/components/schemas/UserProfile"},"newToken":{"type":"string","description":"Updated JWT token reflecting profile changes"}}}]}}}},"400":{"description":"Bad request - email already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/view-modules":{"post":{"summary":"Translate a view query via AI module","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string"}}}}}},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}}}}},"/api/visits/{id}":{"put":{"summary":"Update a visit by ID","description":"Update an existing visit using path parameter ID (bridges to PATCH /api/jobs/visits)","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The visit ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"visitStartDateTime":{"type":"string","format":"date-time"},"visitEndDateTime":{"type":"string","format":"date-time"},"visitStatus":{"type":"string","enum":["scheduled","unscheduled","in_progress","complete","incomplete"]},"teamId":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}},"scheduleLater":{"type":"boolean"},"anyTime":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Visit updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"visit":{"$ref":"#/components/schemas/VisitResponse"}}}}}}}},"400":{"description":"Visit ID is required"},"401":{"description":"Unauthorized"}}},"patch":{"summary":"Partially update a visit by ID","description":"Partially update an existing visit using path parameter ID","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The visit ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"visitStartDateTime":{"type":"string","format":"date-time"},"visitEndDateTime":{"type":"string","format":"date-time"},"visitStatus":{"type":"string","enum":["scheduled","unscheduled","in_progress","complete","incomplete"]},"teamId":{"type":"array","items":{"$ref":"#/components/schemas/MongoObjectId"}}}}}}},"responses":{"200":{"description":"Visit updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"visit":{"$ref":"#/components/schemas/VisitResponse"}}}}}}}},"400":{"description":"Visit ID is required"},"401":{"description":"Unauthorized"}}},"delete":{"summary":"Delete a visit by ID","description":"Delete a single visit using path parameter ID","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/MongoObjectId"},"description":"The visit ID"}],"responses":{"200":{"description":"Visit deleted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Visit ID is required"},"401":{"description":"Unauthorized"}}}},"/api/visits":{"get":{"summary":"Health check","description":"Returns API health status","tags":["Jobs & Visits"],"responses":{"200":{"description":"API is healthy"}}},"post":{"summary":"Create a visit","description":"Create a new visit for a job. Used by AI Dispatcher when syncing accepted suggestions.","tags":["Jobs & Visits"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisitCreateRequest"}}}},"responses":{"200":{"description":"Visit created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":"object","properties":{"visit":{"$ref":"#/components/schemas/VisitResponse"}}}}}}}},"400":{"description":"Missing required fields"},"401":{"description":"Unauthorized"},"404":{"description":"Job not found"}}}},"/api/voice-note/transcribe":{"post":{"summary":"Transcribe voice note","description":"Forwards voice note audio to the Python backend for transcription","tags":["AI & Dispatcher"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["audio_file"],"properties":{"audio_file":{"type":"string","format":"binary","description":"Voice note audio file to transcribe"}}}}}},"responses":{"200":{"description":"Voice note transcribed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"description":"Audio file is required"},"401":{"description":"No authorization header"},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/widget-setting":{"get":{"summary":"Get widget settings","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Widget settings retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}},"post":{"summary":"Create or update widget settings","tags":["System & Configuration"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"welcomeMessage":{"type":"string"},"position":{"type":"string"},"color":{"type":"string"}}}}}},"responses":{"200":{"description":"Widget settings saved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Server error"}}}},"/api/workflows":{"get":{"summary":"List all workflows with statistics","tags":["Workflows & Automation"],"security":[{"BearerAuth":[]}],"parameters":[{"in":"query","name":"status","schema":{"type":"string","enum":["active","inactive","draft","paused"]},"description":"Filter workflows by status"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search workflows by name"}],"responses":{"200":{"description":"Workflows fetched successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"workflows":{"type":"array","items":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"recordId":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"status":{"type":"string"},"triggerType":{"type":"string"},"totalExecutions":{"type":"integer"},"successfulExecutions":{"type":"integer"},"failedExecutions":{"type":"integer"},"lastExecuted":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}},"stats":{"type":"object","properties":{"totalWorkflows":{"type":"integer"},"activeWorkflows":{"type":"integer"},"totalExecutions":{"type":"integer"},"successRate":{"type":"integer"}}}}}}}]}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new workflow","tags":["Workflows & Automation"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["nodes"],"properties":{"nodes":{"type":"array","items":{"type":"object"},"description":"Workflow nodes (must include a trigger node)"},"connections":{"type":"array","items":{"type":"object"}},"name":{"type":"string"},"description":{"type":"string"},"isTemplate":{"type":"boolean"},"templateId":{"type":"string"}}}}}},"responses":{"200":{"description":"Workflow created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"workflowId":{"$ref":"#/components/schemas/MongoObjectId"},"workflow":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"status":{"type":"string"},"triggerType":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}}}}}}]}}}},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}},"put":{"summary":"Update workflow status","tags":["Workflows & Automation"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workflowId","status"],"properties":{"workflowId":{"$ref":"#/components/schemas/MongoObjectId"},"status":{"type":"string","enum":["active","inactive","draft","paused"]}}}}}},"responses":{"200":{"description":"Workflow status updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"workflow":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/MongoObjectId"},"name":{"type":"string"},"status":{"type":"string"},"triggerType":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}]}}}},"400":{"description":"Invalid status or missing fields"},"401":{"description":"Unauthorized"},"404":{"description":"Workflow not found"}}}}}}