Spaces:
Sleeping
Tasks API - Frontend Integration Guide
What Are Tasks?
Tasks = Work items that need to be done (planning layer) Tickets = Work orders assigned to field agents (execution layer)
Flow: Create Task β Convert to Ticket β Assign to Agent β Track Expenses
Endpoints
1. Create Task
POST /api/v1/tasks
Permission: manage_tasks
Request:
{
"project_id": "uuid",
"task_title": "Install fiber at Location X",
"task_description": "Detailed description",
"task_type": "installation", // or "delivery", "maintenance", "survey", etc.
"location_name": "Pole 45, Ngong Road",
"project_region_id": "uuid", // optional
"task_latitude": -1.2921, // optional
"task_longitude": 36.8219, // optional
"priority": "normal", // urgent, high, normal, low
"scheduled_date": "2025-12-01",
"notes": "Optional notes"
}
Response:
{
"id": "uuid",
"project_id": "uuid",
"task_title": "Install fiber at Location X",
"task_description": "...",
"task_type": "installation",
"status": "pending",
"priority": "normal",
"scheduled_date": "2025-12-01",
"is_completed": false,
"is_overdue": false,
"has_location": true,
"project_title": "Project Name",
"region_name": "Nairobi",
"created_by_name": "John Doe",
"created_at": "2025-11-27T10:00:00Z"
}
2. List Tasks
GET /api/v1/tasks?project_id={id}&status={status}&page=1&page_size=50
Permission: view_tasks
Query Params:
project_id- Filter by projectstatus- pending, assigned, in_progress, completed, cancelled, blockedtask_type- installation, delivery, maintenance, etc.priority- urgent, high, normal, lowproject_region_id- Filter by regionscheduled_date- Filter by datesearch- Search in title/descriptionpage- Page number (default: 1)page_size- Items per page (default: 50)
Response:
{
"items": [
{
"id": "uuid",
"task_title": "...",
"status": "pending",
"priority": "high",
"scheduled_date": "2025-12-01",
"is_overdue": false,
"project_title": "Project Name",
"region_name": "Nairobi"
}
],
"total": 150,
"page": 1,
"page_size": 50,
"total_pages": 3
}
3. Get Single Task
GET /api/v1/tasks/{task_id}
Permission: view_tasks
Response: Same as create response
4. Update Task
PUT /api/v1/tasks/{task_id}
Permission: manage_tasks
Request: Same fields as create (all optional)
5. Update Task Status
PATCH /api/v1/tasks/{task_id}/status
Permission: manage_tasks
Request:
{
"status": "in_progress", // pending, assigned, in_progress, completed, cancelled, blocked
"reason": "Optional reason for status change"
}
6. Start Task
POST /api/v1/tasks/{task_id}/start
Permission: manage_tasks
Request:
{
"started_at": "2025-11-27T10:00:00Z", // optional, defaults to now
"notes": "Starting work"
}
7. Complete Task
POST /api/v1/tasks/{task_id}/complete
Permission: manage_tasks
Request:
{
"completed_at": "2025-11-27T15:00:00Z", // optional, defaults to now
"completion_notes": "Work completed successfully"
}
8. Cancel Task
POST /api/v1/tasks/{task_id}/cancel
Permission: manage_tasks
Request:
{
"cancellation_reason": "Customer cancelled order"
}
9. Delete Task
DELETE /api/v1/tasks/{task_id}
Permission: manage_tasks
No request body. Returns 204 No Content.
10. Get Task Stats
GET /api/v1/tasks/stats?project_id={id}
Permission: view_tasks
Query Params:
project_id- Filter by project (optional)project_region_id- Filter by region (optional)
Response:
{
"total_tasks": 150,
"pending_tasks": 20,
"assigned_tasks": 15,
"in_progress_tasks": 30,
"completed_tasks": 80,
"cancelled_tasks": 3,
"blocked_tasks": 2,
"urgent_tasks": 5,
"high_priority_tasks": 25,
"normal_priority_tasks": 100,
"low_priority_tasks": 20,
"overdue_tasks": 8,
"scheduled_today": 5,
"scheduled_this_week": 15,
"avg_completion_time_hours": 48.5,
"completion_rate": 53.33,
"by_task_type": {
"installation": 50,
"delivery": 25,
"maintenance": 30,
"survey": 20
}
}
Task Status Flow
pending β assigned β in_progress β completed
β β
cancelled β---------------------------blocked
Status Values:
pending- Not yet startedassigned- Assigned but not startedin_progress- Currently being worked oncompleted- Finishedcancelled- Cancelledblocked- Blocked by dependencies
Priority Values
urgent- Needs immediate attentionhigh- High prioritynormal- Normal priority (default)low- Low priority
Common Task Types
installation- Install equipment/infrastructuredelivery- Deliver equipment (auto-created from inventory distributions)maintenance- Maintenance worksurvey- Site surveyspickup- Pick up equipmenttesting- Testing/quality checkscustomer_visit- Customer visitstraining- Training sessions
Note: task_type is flexible - you can use any string value
Permissions
| Role | View Tasks | Manage Tasks |
|---|---|---|
| platform_admin | β All | β All |
| project_manager | β Their projects | β Their projects |
| sales_manager | β Their org | β Their org |
| contractor_admin | β Their org | β Their org |
| dispatcher | β Their org | β Their org |
| field_agent | β Assigned to them | β |
| sales_agent | β Related to sales | β |
Auto-Created Tasks
Tasks are automatically created when:
- Inventory Distribution - When distributing inventory to regional hub
- Task type:
delivery - Task title: "Deliver {equipment} to {region}"
- Includes metadata linking back to distribution
- Task type:
Check for auto-created tasks:
// Task has metadata.auto_created = true
if (task.additional_metadata?.auto_created) {
// Show "Auto-created from distribution" badge
// Link to distribution: task.additional_metadata.distribution_id
}
Converting Task to Ticket
Single Task Promotion
POST /api/v1/tickets/from-task
{
"task_id": "uuid",
"priority": "high",
"scheduled_date": "2025-12-01",
"scheduled_time_slot": "morning",
"work_description": "Install fiber cable",
"notes": "Customer prefers morning"
}
Response: Ticket object (see tickets API docs)
Bulk Task Promotion
POST /api/v1/tickets/bulk-from-tasks
{
"task_ids": ["uuid1", "uuid2", "uuid3"],
"priority": "normal",
"notes": "Batch promotion - December deliveries"
}
Response:
{
"total": 3,
"successful": 2,
"failed": 1,
"errors": ["Task uuid3: Already has ticket"],
"created_ticket_ids": ["ticket-uuid1", "ticket-uuid2"]
}
Note: Bulk promotion continues on error - creates as many tickets as possible
Example UI Components
Task List
const { data } = useQuery(['tasks', projectId], () =>
api.get('/tasks', { params: { project_id: projectId } })
);
<TaskList>
{data.items.map(task => (
<TaskCard
key={task.id}
title={task.task_title}
status={task.status}
priority={task.priority}
isOverdue={task.is_overdue}
scheduledDate={task.scheduled_date}
region={task.region_name}
/>
))}
</TaskList>
Task Stats Dashboard
const { data: stats } = useQuery(['task-stats', projectId], () =>
api.get('/tasks/stats', { params: { project_id: projectId } })
);
<StatsGrid>
<StatCard label="Total" value={stats.total_tasks} />
<StatCard label="In Progress" value={stats.in_progress_tasks} />
<StatCard label="Completed" value={stats.completed_tasks} />
<StatCard label="Overdue" value={stats.overdue_tasks} variant="danger" />
<StatCard label="Completion Rate" value={`${stats.completion_rate}%`} />
</StatsGrid>
Create Task Form
const createTask = useMutation(
(data) => api.post('/tasks', data),
{
onSuccess: () => {
queryClient.invalidateQueries(['tasks']);
toast.success('Task created');
}
}
);
<Form onSubmit={handleSubmit}>
<Input name="task_title" required />
<Textarea name="task_description" />
<Select name="task_type" options={taskTypes} />
<Select name="priority" options={priorities} />
<DatePicker name="scheduled_date" />
<RegionSelect name="project_region_id" />
<LocationPicker
onLocationSelect={(lat, lng) => {
setFieldValue('task_latitude', lat);
setFieldValue('task_longitude', lng);
}}
/>
<Button type="submit">Create Task</Button>
</Form>
Task Actions
// Start task
const startTask = useMutation((taskId) =>
api.post(`/tasks/${taskId}/start`, { notes: 'Starting work' })
);
// Complete task
const completeTask = useMutation((taskId) =>
api.post(`/tasks/${taskId}/complete`, {
completion_notes: 'Work done'
})
);
// Cancel task
const cancelTask = useMutation((taskId) =>
api.post(`/tasks/${taskId}/cancel`, {
cancellation_reason: 'Customer cancelled'
})
);
// Convert single task to ticket
const convertToTicket = useMutation((data) =>
api.post('/tickets/from-task', data)
);
// Bulk convert tasks to tickets
const bulkConvertToTickets = useMutation((data) =>
api.post('/tickets/bulk-from-tasks', data),
{
onSuccess: (result) => {
toast.success(`${result.successful} tickets created`);
if (result.failed > 0) {
toast.warning(`${result.failed} tasks failed to convert`);
}
queryClient.invalidateQueries(['tasks']);
queryClient.invalidateQueries(['tickets']);
}
}
);
Bulk Task Promotion UI
// Task list with bulk selection
const [selectedTasks, setSelectedTasks] = useState([]);
const handleBulkPromote = () => {
bulkConvertToTickets.mutate({
task_ids: selectedTasks,
priority: 'normal',
notes: 'Batch promotion'
});
};
<TaskList>
<BulkActions>
<Checkbox
checked={selectedTasks.length === tasks.length}
onChange={handleSelectAll}
label="Select All"
/>
<Button
onClick={handleBulkPromote}
disabled={selectedTasks.length === 0}
>
Promote {selectedTasks.length} to Tickets
</Button>
</BulkActions>
{tasks.map(task => (
<TaskCard
key={task.id}
task={task}
selected={selectedTasks.includes(task.id)}
onSelect={(id) => toggleSelection(id)}
/>
))}
</TaskList>
// Show bulk result
{bulkResult && (
<Alert>
<p>β
{bulkResult.successful} tickets created</p>
{bulkResult.failed > 0 && (
<>
<p>β {bulkResult.failed} failed</p>
<ul>
{bulkResult.errors.map((error, i) => (
<li key={i}>{error}</li>
))}
</ul>
</>
)}
</Alert>
)}
Error Handling
Common Errors:
403- Insufficient permissions404- Task not found400- Validation error (check response.detail)500- Server error
Example:
try {
await api.post('/tasks', taskData);
} catch (error) {
if (error.response?.status === 403) {
toast.error('You don\'t have permission to create tasks');
} else if (error.response?.status === 400) {
toast.error(error.response.data.detail);
} else {
toast.error('Failed to create task');
}
}
Quick Reference
Base URL: /api/v1/tasks
Endpoints:
POST /tasks- CreateGET /tasks- ListGET /tasks/stats- Stats (BEFORE /{task_id})GET /tasks/{id}- Get onePUT /tasks/{id}- UpdatePATCH /tasks/{id}/status- Update statusPOST /tasks/{id}/start- StartPOST /tasks/{id}/complete- CompletePOST /tasks/{id}/cancel- CancelDELETE /tasks/{id}- Delete
Task β Ticket Promotion:
POST /tickets/from-task- Convert single taskPOST /tickets/bulk-from-tasks- Convert multiple tasks (NEW!)
Key Fields:
task_title(required)project_id(required)task_type(optional, flexible string)status(pending/assigned/in_progress/completed/cancelled/blocked)priority(urgent/high/normal/low)scheduled_date(optional)location_name,task_latitude,task_longitude(optional)
Computed Fields:
is_completed- Booleanis_overdue- Booleanhas_location- Booleanduration_days- Number (if completed)
That's it! π