Visits API
The Visits API handles the complete visit lifecycle for field force operations, including visit creation, assignment, check-in/check-out, form submissions, and approval workflows.
Visit Workflow
Section titled “Visit Workflow”1. Create Visit (Admin/Auto) → 2. Assign Users → 3. Check-In → 4. Submit Forms → 5. Check-Out → 6. Approve/RejectEndpoints Overview
Section titled “Endpoints Overview”| Endpoint | Method | Platform | Description |
|---|---|---|---|
/office/v1/visits | POST | Admin | Create a new visit |
/office/v1/visits/:id/assign | POST | Admin | Assign users to visit |
/app/v1/visits/self | POST | Mobile | Create self-visit |
/app/v1/visits/:id/check-in | POST | Mobile | Check in to visit |
/app/v1/visits/:id/submit-form | POST | Mobile | Submit form (legacy) |
/app/v1/visits/:id/submit-form-v2 | POST | Mobile | Submit form (v2) |
/app/v1/visits/:id/check-out | POST | Mobile | Check out from visit |
/app/v1/visits/me | GET | Mobile | Get my visits |
/office/v1/visits | GET | Admin | List all visits |
/office/v1/visits/:id | GET | Both | Get visit details |
/app/v1/visits/me/:id | GET | Mobile | Get my visit details |
/app/v1/visits/active | GET | Mobile | Get active visit |
/office/v1/visits/:id/force-close | PUT | Admin | Force close visit |
/office/v1/visits/:id/cancel | PUT | Admin | Cancel visit |
Visit Management
Section titled “Visit Management”Create Visit (Admin)
Section titled “Create Visit (Admin)”Create a new visit and assign it to outlets and users.
POST /office/v1/visitsRequest Body:
{ "outlet_id": 123, "project_id": 45, "team_id": 67, "visit_date": "2024-02-20", "user_ids": [10, 11, 12], "submissions": [ { "form_id": 5, "is_required": true, "order": 1 } ]}Response:
{ "code": "VISIT_CREATED", "message": "Visit created successfully", "data": { "id": 789, "outlet": { "id": 123, "name": "Store ABC" }, "project": { "id": 45, "name": "Q1 Campaign" }, "status": "scheduled", "visit_date": "2024-02-20", "assignees": [ { "user_id": 10, "name": "John Doe" } ] }}Assign Users to Visit
Section titled “Assign Users to Visit”Add or update user assignments for a visit.
POST /office/v1/visits/:id/assignRequest Body:
{ "user_ids": [10, 11, 12]}Create Self-Visit (Mobile)
Section titled “Create Self-Visit (Mobile)”Field users can create their own visit when at an outlet.
POST /app/v1/visits/selfRequest Body:
{ "outlet_id": 123, "project_id": 45, "notes": "Unscheduled visit for urgent restocking"}Example (React Native):
import { apiClient } from '@core/services';
async function createSelfVisit(outletId, projectId, notes) { const response = await apiClient.post('/app/v1/visits/self', { outlet_id: outletId, project_id: projectId, notes: notes }); return response.data;}Check-In / Check-Out
Section titled “Check-In / Check-Out”Check In to Visit
Section titled “Check In to Visit”Start a visit by checking in with GPS location and photo.
POST /app/v1/visits/:id/check-inRequest Body:
{ "latitude": -6.2088, "longitude": 106.8456, "check_in_photo": "base64_encoded_image_or_url", "notes": "Arrived at store"}Response:
{ "code": "VISIT_CHECKED_IN", "message": "Check-in successful", "data": { "id": 789, "status": "in_progress", "checked_in_at": "2024-02-20T09:15:00Z", "check_in_location": { "latitude": -6.2088, "longitude": 106.8456 } }}Example (React Native):
import { apiClient } from '@core/services';import * as Location from 'expo-location';
async function checkInToVisit(visitId, photo) { // Get current location const location = await Location.getCurrentPositionAsync({});
const response = await apiClient.post(`/app/v1/visits/${visitId}/check-in`, { latitude: location.coords.latitude, longitude: location.coords.longitude, check_in_photo: photo, notes: 'Checked in successfully' });
return response.data;}Check Out from Visit
Section titled “Check Out from Visit”Complete a visit by checking out with GPS location and photo.
POST /app/v1/visits/:id/check-outRequest Body:
{ "latitude": -6.2088, "longitude": 106.8456, "check_out_photo": "base64_encoded_image_or_url", "notes": "Visit completed"}Form Submissions
Section titled “Form Submissions”Submit Form (V2)
Section titled “Submit Form (V2)”Submit a form with answers during a visit. This is the current recommended version.
POST /app/v1/visits/:id/submit-form-v2Request Body:
{ "form_id": 5, "items": [ { "submission_id": 101, "status": "completed", "answers": { "field_1": "Answer text", "field_2": ["option_a", "option_b"], "field_3": 42 } } ]}Example (React Native):
import { apiClient } from '@core/services';
async function submitVisitForm(visitId, formId, answers) { const response = await apiClient.post( `/app/v1/visits/${visitId}/submit-form-v2`, { form_id: formId, items: [ { submission_id: null, // or existing submission ID for updates status: 'completed', answers: answers } ] } );
return response.data;}Visit Queries
Section titled “Visit Queries”Get My Visits (Mobile)
Section titled “Get My Visits (Mobile)”Retrieve visits assigned to the current user.
GET /app/v1/visits/meQuery Parameters:
status- Filter by status:scheduled,in_progress,completed,cancelledoutlet_id- Filter by outlet IDproject_id- Filter by project IDdate_from- Filter from date (YYYY-MM-DD)date_to- Filter to date (YYYY-MM-DD)page- Page number (default: 1)limit- Items per page (default: 10)
Example:
GET /app/v1/visits/me?status=scheduled&page=1&limit=20Example (React Native with TanStack Query):
import { useQuery } from '@tanstack/react-query';import { apiClient } from '@core/services';
function useMyVisits(filters) { return useQuery({ queryKey: ['visits', 'me', filters], queryFn: async () => { const response = await apiClient.get('/app/v1/visits/me', { params: filters }); return response.data; }, networkMode: 'offlineFirst' // Offline-first for mobile });}
// Usage in componentfunction VisitsScreen() { const { data, isLoading } = useMyVisits({ status: 'scheduled', page: 1, limit: 20 });
// Render visits list...}Get All Visits (Admin)
Section titled “Get All Visits (Admin)”Retrieve all visits with comprehensive filtering.
GET /office/v1/visitsQuery Parameters:
status- Filter by statusoutlet_id- Filter by outlet IDproject_id- Filter by project IDteam_id- Filter by team IDuser_id- Filter by assigned user IDdate_from- Filter from datedate_to- Filter to datepage- Page numberlimit- Items per page
Get Visit Details
Section titled “Get Visit Details”Retrieve detailed information about a specific visit.
GET /office/v1/visits/:idGET /app/v1/visits/me/:idResponse:
{ "code": "SUCCESS", "message": "Visit retrieved successfully", "data": { "id": 789, "outlet": { "id": 123, "name": "Store ABC", "address": "Jl. Example No. 123" }, "project": { "id": 45, "name": "Q1 Campaign" }, "status": "completed", "visit_date": "2024-02-20", "checked_in_at": "2024-02-20T09:15:00Z", "checked_out_at": "2024-02-20T11:30:00Z", "assignees": [ { "user_id": 10, "name": "John Doe", "email": "john@example.com" } ], "submissions": [ { "id": 101, "form_id": 5, "title": "Product Audit Form", "status": "completed", "approval_status": "approved", "submitted_at": "2024-02-20T10:00:00Z" } ], "logs": [ { "action": "check_in", "timestamp": "2024-02-20T09:15:00Z", "user": "John Doe" } ] }}Get Active Visit
Section titled “Get Active Visit”Get the currently active (in-progress) visit for the authenticated user.
GET /app/v1/visits/activeResponse:
{ "code": "SUCCESS", "message": "Active visit retrieved", "data": { "id": 789, "outlet": { "id": 123, "name": "Store ABC" }, "status": "in_progress", "checked_in_at": "2024-02-20T09:15:00Z" }}Example (React Native):
import { useQuery } from '@tanstack/react-query';import { apiClient } from '@core/services';
function useActiveVisit() { return useQuery({ queryKey: ['visits', 'active'], queryFn: async () => { const response = await apiClient.get('/app/v1/visits/active'); return response.data.data; }, networkMode: 'offlineFirst', staleTime: 30000 // 30 seconds });}Submissions Management
Section titled “Submissions Management”Get Visit Submissions
Section titled “Get Visit Submissions”Retrieve all submissions for a specific visit.
GET /office/v1/visits/:id/submissionsGET /app/v1/visits/me/:id/submissionsResponse:
{ "code": "SUCCESS", "message": "Submissions retrieved", "data": [ { "id": 101, "form_id": 5, "title": "Product Audit Form", "status": "completed", "approval_status": "pending", "is_required": true, "submitted_at": "2024-02-20T10:00:00Z", "answers": { "field_1": "Answer", "field_2": ["option_a"] } } ]}Get Submission Details
Section titled “Get Submission Details”Retrieve detailed information about a specific submission.
GET /office/v1/visits/:visit_id/submissions/:submission_idGET /app/v1/visits/me/:visit_id/submissions/:submission_idApprove Submission
Section titled “Approve Submission”Approve a pending submission (admin only).
POST /office/v1/visits/:visit_id/submissions/:submission_id/approveRequest Body:
{ "notes": "Looks good, approved"}Add Submission to Visit
Section titled “Add Submission to Visit”Add an additional form submission to an existing visit.
POST /office/v1/visits/:id/submissionsRequest Body:
{ "form_id": 8, "is_required": false}Remove Submission from Visit
Section titled “Remove Submission from Visit”Remove a submission from a visit (if not yet submitted).
DELETE /office/v1/visits/:visit_id/submissions/:submission_idAdmin Actions
Section titled “Admin Actions”Force Close Visit
Section titled “Force Close Visit”Administratively close a visit that’s stuck in progress.
PUT /office/v1/visits/:id/force-closeRequest Body:
{ "reason": "User forgot to check out, visit confirmed complete"}Cancel Visit
Section titled “Cancel Visit”Cancel a scheduled or in-progress visit.
PUT /office/v1/visits/:id/cancelRequest Body:
{ "reason": "Outlet temporarily closed"}Visit Status Flow
Section titled “Visit Status Flow”scheduled → in_progress → completed ↓ ↓cancelled force_closed- scheduled: Visit created but not yet started
- in_progress: User has checked in
- completed: User has checked out normally
- cancelled: Admin cancelled the visit
- force_closed: Admin force-closed stuck visit
Common Use Cases
Section titled “Common Use Cases”Mobile: Daily Visit Workflow
Section titled “Mobile: Daily Visit Workflow”// 1. Get today's scheduled visitsconst { data: visits } = useMyVisits({ status: 'scheduled', date_from: today, date_to: today});
// 2. Check in when arrivingawait checkInToVisit(visitId, checkInPhoto);
// 3. Submit required formsawait submitVisitForm(visitId, formId, answers);
// 4. Check out when leavingawait checkOutFromVisit(visitId, checkOutPhoto);Admin: Monitor Visit Progress
Section titled “Admin: Monitor Visit Progress”// Get in-progress visits for a teamconst { data } = useVisitsQuery({ team_id: 5, status: 'in_progress', page: 1, limit: 50});
// Check submission approval queueconst { data: submissions } = useSubmissionsQuery({ approval_status: 'pending', team_id: 5});Error Codes
Section titled “Error Codes”| Code | Description | Solution |
|---|---|---|
VISIT_NOT_FOUND | Visit ID doesn’t exist | Verify visit ID |
VISIT_ALREADY_CHECKED_IN | Already checked in | Check visit status |
VISIT_NOT_STARTED | Cannot check out before check-in | Check in first |
SUBMISSION_REQUIRED | Required forms not submitted | Complete required forms |
INVALID_VISIT_STATUS | Action not allowed in current status | Check visit workflow |
LOCATION_REQUIRED | GPS location missing | Enable location services |
Best Practices
Section titled “Best Practices”- Offline Support: Use
offlineFirstnetwork mode for mobile queries - Location Accuracy: Request high accuracy for check-in/check-out GPS
- Photo Compression: Compress check-in/check-out photos before upload
- Form Validation: Validate form answers client-side before submission
- Active Visit Check: Always check for active visit before creating new
- Status Polling: Poll active visit status every 30 seconds
- Submission Caching: Cache submission drafts in MMKV for offline editing
Related Endpoints
Section titled “Related Endpoints”- Submissions API - Form submission management
- Sales Orders API - Sales order creation during visits
- Products API - Product scanning and lookup
Platform Support: Web (Admin) + Mobile (Field Users)
Authentication: Required (BearerAuth)
RBAC Permissions: visit:create, visit:view, submission:approve