Spaces:
Sleeping
Ticket Completion API Guide
Overview
Dynamic completion forms generated from project requirements. Each project defines its own photo and activation requirements.
Workflow
1. Agent arrives β Record arrival
2. Get completion checklist β See what's required
3. Upload photos β Multiple times, different types
4. Submit activation data β Equipment serials, etc.
5. Complete ticket β PM reviews and invoices
1. Get Completion Checklist
Endpoint: GET /api/v1/tickets/{ticket_id}/completion-checklist
Response:
{
"ticket_id": "uuid",
"project_id": "uuid",
"completion_percentage": 45.5,
"is_photos_complete": false,
"is_activation_complete": true,
"is_complete": false,
"photo_items": [
{
"id": "photo_Speedtest",
"type": "photo",
"photo_type": "Speedtest",
"label": "Speed test collected",
"required": true,
"min_photos": 1,
"max_photos": 1,
"uploaded_count": 0,
"uploaded_photos": [],
"status": "pending"
}
],
"field_items": [
{
"id": "field_ont_serial",
"type": "field",
"field_name": "ont_serial",
"label": "ONT Serial Number",
"data_type": "text",
"required": true,
"value": "HW12345678",
"status": "complete"
}
]
}
2. Upload Photos
Endpoint: POST /api/v1/tickets/{ticket_id}/upload-photos
Content-Type: multipart/form-data
Form Data:
photo_type: string (matches photo_items[].photo_type from checklist)files: file[] (image files)
Example:
const formData = new FormData();
formData.append('photo_type', 'Speedtest');
formData.append('files', file1);
formData.append('files', file2); // If multiple allowed
await api.post(`/tickets/${ticketId}/upload-photos`, formData);
Response:
{
"success": true,
"message": "Photos updated successfully. Activation data still required.",
"ticket_id": "uuid",
"checklist": { /* updated checklist */ }
}
Notes:
- Can call multiple times for different photo types
- Photos stored with
image_type="completion" - Description includes photo type:
[Speedtest] Completion photo for ticket
3. Submit Activation Data
Endpoint: POST /api/v1/tickets/{ticket_id}/activation-data
Payload:
{
"ont_serial": "HW12345678",
"router_serial": "RT98765432",
"installation_notes": "Installed on second floor"
}
Field names must match field_items[].field_name from checklist.
Response:
{
"success": true,
"message": "Activation data updated successfully",
"ticket_id": "uuid",
"checklist": { /* updated checklist */ }
}
Notes:
- Data stored in
ticket.completion_data(JSONB) - Used to create subscription on invoicing
- Equipment serials tracked in inventory
4. Complete Ticket
Endpoint: POST /api/v1/tickets/{ticket_id}/complete
Payload:
{
"work_notes": "Installation completed successfully. Customer trained on WiFi setup.",
"force_complete": false
}
Response:
{
"success": true,
"message": "Ticket completed successfully",
"ticket_id": "uuid",
"subscription_id": "uuid", // If created
"equipment_installed": ["HW12345678", "RT98765432"]
}
Validation:
- All required photos uploaded
- All required fields filled
- Agent must have arrived at location
- Set
force_complete: trueto bypass (admin only)
Notes:
- Ticket status β
completed - Subscription NOT auto-created (PM does this on invoicing)
- Equipment marked as installed in inventory
Error Responses
400 Bad Request:
{
"detail": {
"message": "Photo validation failed",
"errors": [
"Speedtest: Requires 1 photo, got 0",
"ODU outdoor image: Requires 1 photo, got 0"
]
}
}
404 Not Found:
{
"detail": "Ticket not found"
}
Progressive Completion
Agent can complete in any order:
- Upload some photos β Save
- Upload more photos later β Save
- Fill activation data β Save
- Upload remaining photos β Save
- Complete ticket β Final
Each step updates completion_percentage in checklist.
Example Flow
// 1. Get checklist
const checklist = await api.get(`/tickets/${ticketId}/completion-checklist`);
// 2. Render form from checklist
checklist.photo_items.forEach(item => {
renderPhotoUpload(item.photo_type, item.label, item.required);
});
checklist.field_items.forEach(item => {
renderField(item.field_name, item.label, item.data_type, item.required);
});
// 3. Upload photos as agent takes them
async function uploadPhoto(photoType, file) {
const formData = new FormData();
formData.append('photo_type', photoType);
formData.append('files', file);
await api.post(`/tickets/${ticketId}/upload-photos`, formData);
// Refresh checklist to show progress
const updated = await api.get(`/tickets/${ticketId}/completion-checklist`);
updateProgress(updated.completion_percentage);
}
// 4. Submit activation data
async function submitActivationData(data) {
await api.post(`/tickets/${ticketId}/activation-data`, data);
// Refresh checklist
const updated = await api.get(`/tickets/${ticketId}/completion-checklist`);
updateProgress(updated.completion_percentage);
}
// 5. Complete ticket
async function completeTicket(workNotes) {
const result = await api.post(`/tickets/${ticketId}/complete`, {
work_notes: workNotes
});
showSuccess('Ticket completed!');
}
Project Requirements Format
Projects define requirements in JSONB fields:
photo_requirements:
[
{
"type": "Speedtest",
"required": true,
"min_photos": 1,
"max_photos": 1,
"description": "Speed test collected"
}
]
activation_requirements:
[
{
"field": "ont_serial",
"label": "ONT Serial Number",
"type": "text",
"required": true,
"placeholder": "HW12345678"
}
]
Different projects = different requirements. Forms adapt automatically.