{"openapi":"3.0.3","info":{"title":"AfyaLead Operations Tool - API","version":"1.0.0","description":"Backend API for the AfyaLead internal operations tool. Admins manage users, clients and tasks; employees update the status of tasks assigned to them.\n\n- Get a token via `POST /api/auth/login` and send it as `Authorization: Bearer <token>`.\n- Register as an employee via `POST /api/auth/register`. There is no public admin signup.\n- Admin accounts are created manually with the seed script (`npm run db:seed`)."},"servers":[{"url":"/"}],"tags":[{"name":"Auth","description":"Registration and login"},{"name":"Users","description":"User management (admin only)"},{"name":"Clients","description":"Client management (admin only)"},{"name":"Tasks","description":"Task creation, listing and status updates"},{"name":"Metrics","description":"Admin dashboard metrics"},{"name":"System","description":"Health checks and API docs"}],"paths":{"/api/health":{"get":{"tags":["System"],"summary":"Health check","description":"Checks that the API and its database connection are healthy.","responses":{"200":{"description":"API and database are healthy","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]},"database":{"type":"string","enum":["connected","unreachable"]},"timestamp":{"type":"string","format":"date-time"}}}}}},"503":{"description":"Database unreachable"}}}},"/api/auth/register":{"post":{"tags":["Auth"],"summary":"Register an employee account","description":"Creates a new account with the role \"employee\". Admin accounts cannot be created here.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fullName","email","password"],"properties":{"fullName":{"type":"string","example":"Jane Doe"},"email":{"type":"string","format":"email","example":"jane@example.com"},"password":{"type":"string","format":"password","minLength":6,"example":"secret123"},"token":{"type":"string","description":"Optional invitation token (from a user invite).","example":"abc123..."}}}}}},"responses":{"201":{"description":"Account created","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"integer"},"fullName":{"type":"string"},"email":{"type":"string"},"role":{"type":"string","enum":["employee"]}}}}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}},"409":{"description":"Email already registered","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}}}}},"/api/auth/login":{"post":{"tags":["Auth"],"summary":"Login","description":"Authenticates a user and returns a JWT plus the user profile. Redirect by role is handled by the frontend.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email","example":"admin@afyalead.com"},"password":{"type":"string","format":"password","example":"Admin@123456"}}}}}},"responses":{"200":{"description":"Login successful","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"JWT access token"},"user":{"type":"object","properties":{"id":{"type":"integer"},"fullName":{"type":"string"},"email":{"type":"string"},"role":{"type":"string","enum":["admin","employee"]}}}}}}}},"401":{"description":"Invalid credentials","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}},"403":{"description":"Account deactivated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}}}}},"/api/users":{"get":{"tags":["Users"],"summary":"List all users","description":"Returns every user (admin and employee) with their task count.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of users","content":{"application/json":{"schema":{"type":"object","properties":{"users":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"full_name":{"type":"string","example":"Jane Doe"},"email":{"type":"string","format":"email","example":"jane@example.com"},"role":{"type":"string","enum":["admin","employee"],"example":"employee"},"status":{"type":"string","enum":["active","deleted"],"example":"active"},"created_at":{"type":"string","format":"date-time"},"task_count":{"type":"integer","example":4}}}}}}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}}}}},"/api/users/invite":{"post":{"tags":["Users"],"summary":"Invite a new user","description":"Sends an email invitation so the person can register as an employee.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","example":"new-hire@example.com"}}}}}},"responses":{"201":{"description":"Invitation sent","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"email":{"type":"string"},"inviteUrl":{"type":"string"}}}}}},"409":{"description":"Email already registered or already invited","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}}}}},"/api/users/{id}":{"delete":{"tags":["Users"],"summary":"Delete a user","description":"Soft-deletes an employee account (sets status to \"deleted\"). Admin accounts cannot be deleted.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"User id"}],"responses":{"200":{"description":"User deleted","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"id":{"type":"integer"}}}}}},"400":{"description":"Cannot delete self or an admin","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}}}}},"/api/users/{id}/reactivate":{"post":{"tags":["Users"],"summary":"Reactivate a user","description":"Sets a previously deleted/deactivated user back to \"active\".","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"User id"}],"responses":{"200":{"description":"User reactivated","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"id":{"type":"integer"},"status":{"type":"string","enum":["active"]}}}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}}}}},"/api/clients":{"get":{"tags":["Clients"],"summary":"List all clients","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of clients","content":{"application/json":{"schema":{"type":"object","properties":{"clients":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Nairobi Clinic"},"email":{"type":"string","format":"email","nullable":true},"phone":{"type":"string","example":"+254712345678","nullable":true},"company":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}}}}}}}},"post":{"tags":["Clients"],"summary":"Create a client","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"Nairobi Clinic"},"email":{"type":"string","format":"email","example":"info@nairobidinic.com"},"phone":{"type":"string","example":"+254712345678"},"company":{"type":"string","example":"Nairobi Clinic Ltd"}}}}}},"responses":{"201":{"description":"Client created","content":{"application/json":{"schema":{"type":"object","properties":{"client":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Nairobi Clinic"},"email":{"type":"string","format":"email","nullable":true},"phone":{"type":"string","example":"+254712345678","nullable":true},"company":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"name is required","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}}}}},"/api/tasks":{"get":{"tags":["Tasks"],"summary":"List all tasks (admin)","description":"Returns all tasks. Filter by `clientId`, `employeeId` and/or `status` via query parameters.","security":[{"bearerAuth":[]}],"parameters":[{"name":"clientId","in":"query","required":false,"schema":{"type":"integer"},"description":"Filter by client id"},{"name":"employeeId","in":"query","required":false,"schema":{"type":"integer"},"description":"Filter by assigned employee id"},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["received","in_progress","completed"]},"description":"Filter by task status"}],"responses":{"200":{"description":"List of tasks","content":{"application/json":{"schema":{"type":"object","properties":{"tasks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"title":{"type":"string","example":"Follow up on lead"},"description":{"type":"string","example":"Call the client about the new offer","nullable":true},"status":{"type":"string","enum":["received","in_progress","completed"],"example":"received"},"due_date":{"type":"string","format":"date","example":"2026-08-15","nullable":true},"created_at":{"type":"string","format":"date-time","example":"2026-08-04T10:00:00Z"},"updated_at":{"type":"string","format":"date-time"},"assigned_to":{"type":"integer","example":3},"assignee_name":{"type":"string","example":"Jane Doe"},"assignee_email":{"type":"string","format":"email"},"client_id":{"type":"integer","example":2},"client_name":{"type":"string","example":"Nairobi Clinic"},"client_email":{"type":"string","format":"email","nullable":true}}}}}}}}}}},"post":{"tags":["Tasks"],"summary":"Create and assign a task (admin)","description":"Creates a task, assigns it to one employee, tags it with a client, and emails the assignee.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","clientId","assignedTo"],"properties":{"title":{"type":"string","example":"Follow up on lead"},"description":{"type":"string","example":"Call the client about the new offer"},"clientId":{"type":"integer","example":2},"assignedTo":{"type":"integer","example":3},"dueDate":{"type":"string","format":"date","example":"2026-08-15"}}}}}},"responses":{"201":{"description":"Task created and assigned","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"type":"object","properties":{"id":{"type":"integer","example":1},"title":{"type":"string","example":"Follow up on lead"},"description":{"type":"string","example":"Call the client about the new offer","nullable":true},"status":{"type":"string","enum":["received","in_progress","completed"],"example":"received"},"due_date":{"type":"string","format":"date","example":"2026-08-15","nullable":true},"created_at":{"type":"string","format":"date-time","example":"2026-08-04T10:00:00Z"},"updated_at":{"type":"string","format":"date-time"},"assigned_to":{"type":"integer","example":3},"assignee_name":{"type":"string","example":"Jane Doe"},"assignee_email":{"type":"string","format":"email"},"client_id":{"type":"integer","example":2},"client_name":{"type":"string","example":"Nairobi Clinic"},"client_email":{"type":"string","format":"email","nullable":true}}}}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}},"404":{"description":"Employee or client not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}}}}},"/api/tasks/mine":{"get":{"tags":["Tasks"],"summary":"List tasks assigned to the current user (employee)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Tasks assigned to the authenticated employee","content":{"application/json":{"schema":{"type":"object","properties":{"tasks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"title":{"type":"string","example":"Follow up on lead"},"description":{"type":"string","example":"Call the client about the new offer","nullable":true},"status":{"type":"string","enum":["received","in_progress","completed"],"example":"received"},"due_date":{"type":"string","format":"date","example":"2026-08-15","nullable":true},"created_at":{"type":"string","format":"date-time","example":"2026-08-04T10:00:00Z"},"updated_at":{"type":"string","format":"date-time"},"assigned_to":{"type":"integer","example":3},"assignee_name":{"type":"string","example":"Jane Doe"},"assignee_email":{"type":"string","format":"email"},"client_id":{"type":"integer","example":2},"client_name":{"type":"string","example":"Nairobi Clinic"},"client_email":{"type":"string","format":"email","nullable":true}}}}}}}}}}}},"/api/tasks/{id}/status":{"patch":{"tags":["Tasks"],"summary":"Update task status (employee)","description":"Updates the status of a task assigned to the authenticated employee. Allowed values: received, in_progress, completed.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"},"description":"Task id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["received","in_progress","completed"],"example":"in_progress"}}}}}},"responses":{"200":{"description":"Status updated","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"status":{"type":"string","enum":["received","in_progress","completed"]}}}}}},"400":{"description":"Invalid status","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}},"403":{"description":"Task not assigned to you","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}},"404":{"description":"Task not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Invalid email or password"}}}}}}}}},"/api/metrics":{"get":{"tags":["Metrics"],"summary":"Admin dashboard metrics","description":"Returns total users, total employees, total tasks assigned, total tasks completed, and a breakdown by status.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Metrics","content":{"application/json":{"schema":{"type":"object","properties":{"metrics":{"type":"object","properties":{"total_users":{"type":"integer","example":12},"total_employees":{"type":"integer","example":10},"total_admins":{"type":"integer","example":2},"total_tasks_assigned":{"type":"integer","example":45},"total_tasks_completed":{"type":"integer","example":20},"tasks_received":{"type":"integer","example":10},"tasks_in_progress":{"type":"integer","example":15}}}}}}}}}}},"/api/swagger":{"get":{"tags":["System"],"summary":"OpenAPI specification","description":"Returns the OpenAPI 3.0 JSON spec for this API (used by the Swagger UI at /docs).","responses":{"200":{"description":"OpenAPI JSON document"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"security":[{"bearerAuth":[]}]}