Skip to main content

PJP (Permanent Journey Plan) API

Digitize, optimize, and enforce recurring field sales beats, retail visit schedules, customer check-ins, and territory coverage for field teams.


Overview & Solution​

Managing distributed field forces across thousands of retail outlets requires systematic beat planning. The PJP (Permanent Journey Plan) API enables enterprise managers to design weekly/monthly beat plans, assign outlets to sales representatives, and verify check-in authenticity with GPS geofencing.


Industry Use Cases​

1. FMCG & Pharma Sales Force Automation (SFA)​

  • Beat Management: Set recurring Monday-Saturday outlet visit schedules for medical reps and retail territory managers.

2. Banking, Microfinance & Loan Collections​

  • Daily Field Collection Routes: Plan customer loan recovery visits along optimized spatial paths, tracking completed vs. missed appointments in real time.

3. Telecom & Distributor Merchandising​

  • Retail Visibility Auditing: Track promotional banner placements and merchandising compliance during scheduled distributor visits.


API reference​

Field Force Mobility Execution APIs · Requires Intelomatic tenant

Field definitions: Plan, Visit

Endpoints​

MethodPathPurpose
POST/api/app-base/plans/createCreate a plan (with nested visits)
GET/api/app-base/plans/{plan_guid}Get a plan
POST/api/app-base/plans/searchSearch plans
PUT/api/app-base/plans/{plan_guid}Update a plan
DELETE/api/app-base/plans/{plan_guid}Delete a plan
GET/api/app-base/visits/{visit_guid}Get a visit
POST/api/app-base/visits/searchSearch visits
PUT/api/app-base/visits/{visit_guid}Update a visit
DELETE/api/app-base/visits/{visit_guid}Delete a visit

All requests require a bearer token and the api-key query parameter:

Authorization: Bearer <access_token>
?api-key=<apiKey>

Create a plan (with nested visits)​

POST /api/app-base/plans/create

Request Parameters

ParameterInTypeRequiredDescriptionExample
plan_assignee_usernamebodystringNo
plan_namebodystringNoName for the plan.
visitsbodyarrayNo
visits[]bodyobjectNo
visits[].visit_target_guidbodystringNoMust belong to a customer already assigned to visit_assignee_username (directly or via an ALL assignment). Customers assigned to another user cannot be planned for this assignee.
visits[].visit_assignee_usernamebodystringNoThe plan's own assignee - sent as a username here (contrast with the standalone POST /visits, where this same field name takes an employee_guid instead).
visits[].visit_datebodystringNoMust be today or later (UTC).
visits[].visit_prioritybodystringNoPriority, sent as a code string (the example sends "3").
visits[].visit_estimate_timebodystringNoEstimated visit duration, sent as a string (the example sends "30").
visits[].visit_objectivebodystringNoPurpose of the visit, free text.
visits[].visit_orderbodyintegerNoPosition of the visit in the day's sequence, starting at 0.
plan_datebodystringNoMust be today or later (UTC).
configbodyobjectNo
config.timeZoneOffsetbodyintegerNoClient time-zone offset in minutes (the example -420 is UTC+7).

Request body

{
"plan_assignee_username": "apidemo_newuser_9",
"plan_name": "test plan",
"visits": [
{
"visit_target_guid": "6aa0bb299521e3582ef555f8",
"visit_assignee_username": "apidemo_newuser_9",
"visit_date": "2026-09-25T16:59:59.999Z",
"visit_priority": "3",
"visit_estimate_time": "30",
"visit_objective": "test",
"visit_order": 0
},
{
"visit_target_guid": "6aa3d2b3d1d2d6ccfde198cd",
"visit_assignee_username": "apidemo_newuser_9",
"visit_date": "2026-09-25T16:59:59.999Z",
"visit_priority": "3",
"visit_estimate_time": "30",
"visit_objective": "test",
"visit_order": 1
}
],
"plan_date": "2026-09-25T16:59:59.999Z",
"config": {
"timeZoneOffset": -420
}
}

Response Schema

200 OK

{
"plan_guid": "6ab386e8e2e38f375be67f45",
"plan_assignee_username": "apidemo_newuser_9",
"plan_name": "test plan",
"visits": [
{
"visit_target_guid": "6aa0bb299521e3582ef555f8",
"visit_assignee_username": "apidemo_newuser_9",
"visit_date": "2026-09-25T16:59:59.999Z",
"visit_priority": "3",
"visit_estimate_time": 30,
"visit_objective": "test",
"visit_order": 0,
"__customerInfo": {
"customer_guid": "6aa0bb299521e3582ef555f8",
"customer_full_name": "My Customer",
"Title": "My Customer",
"customer_address": "Mulshi, Pune, MAHARASHTRA - 412108",
"customer_postal_code": "412108",
"customer_category": "Default",
"customer_is_active": 1,
"customer_assigned_team": "2",
"customer_assignee": "all"
},
"Title": "Visit 1790150375739",
"visit_status": 1,
"visit_contact_full_name": "My Customer",
"visit_contact_phone_number": "919000000002",
"visit_contact_email": "[email protected]",
"visit_address": "Mulshi, Pune, MAHARASHTRA - 412108",
"visit_administrative_1": "33",
"visit_administrative_2": "138",
"visit_administrative_3": "14204",
"visit_postal_code": "412108",
"visit_customer_category": "Default",
"visit_location": "{\n \"type\": \"Point\",\n \"coordinates\": [\n 73.55986487231496,\n 18.462925230845542\n ]\n}",
"visit_boundary": null,
"validated": true,
"notified": true,
"__isValid": true,
"__dataErrors": [],
"visit_plan_guid": "6ab386e8e2e38f375be67f45",
"visit_created_from": 2,
"visit_last_modified_from": 2,
"Id": "f3fc45bd-3790-4c97-b795-2dc59931a919",
"visit_guid": "6ab386eae2e38f35d9e67f75",
"isSuccess": true
},
{
"visit_target_guid": "6aa3d2b3d1d2d6ccfde198cd",
"visit_assignee_username": "apidemo_newuser_9",
"visit_date": "2026-09-25T16:59:59.999Z",
"visit_priority": "3",
"visit_estimate_time": 30,
"visit_objective": "test",
"visit_order": 1,
"__customerInfo": {
"customer_guid": "6aa3d2b3d1d2d6ccfde198cd",
"customer_full_name": "New customer",
"Title": "New customer",
"customer_address": "ABC",
"customer_postal_code": "451331",
"customer_category": null,
"customer_is_active": 1,
"customer_assigned_team": "2",
"customer_assignee": "all"
},
"Title": "Visit 1790150375740",
"visit_status": 1,
"visit_contact_full_name": "New customer",
"visit_contact_phone_number": null,
"visit_contact_email": null,
"visit_address": "ABC",
"visit_administrative_1": "26",
"visit_administrative_2": "570",
"visit_administrative_3": "13563",
"visit_postal_code": "451331",
"visit_customer_category": null,
"visit_location": "{\n \"type\": \"Point\",\n \"coordinates\": [\n 75.97974198425027,\n 21.888548035168114\n ]\n}",
"visit_boundary": null,
"validated": true,
"notified": true,
"__isValid": true,
"__dataErrors": [],
"visit_plan_guid": "6ab386e8e2e38f375be67f45",
"Id": "6fc95dff-c53b-4ec5-b34d-99c48f3637e7",
"visit_guid": "6ab386ede2e38f44f7e67f76",
"isSuccess": true
}
],
"plan_date": "2026-09-25T16:59:59.999Z",
"plan_created_from": 2,
"plan_last_modified_from": 2,
"Id": "4837cc76-5ae0-4b6a-ba46-5e0c7cf4f827",
"Path": "/<internal storage path>"
}
FieldTypeDescription
plan_guidstringIdentifier of the plan just created. Use it to read, update or delete the plan.
plan_assignee_usernamestringThe field user this plan belongs to.
plan_namestringThe plan name as stored - the value you sent in plan_name.
visitsarrayOne entry per visit created with the plan, each expanded with the customer details resolved from visit_target_guid.
visits[]object
visits[].visit_target_guidstring
visits[].visit_assignee_usernamestring
visits[].visit_datestring
visits[].visit_prioritystring
visits[].visit_estimate_timeintegerEstimated visit duration - sent as a string, returned as a number.
visits[].visit_objectivestring
visits[].visit_orderintegerPosition of the visit in the day's sequence, as sent.
visits[].__customerInfoobjectThe full customer record resolved from visit_target_guid (shape: Customer). Trimmed to its identifying fields in the example above for brevity.
visits[].__customerInfo.customer_guidstring
visits[].__customerInfo.customer_full_namestring
visits[].__customerInfo.Titlestring
visits[].__customerInfo.customer_addressstring
visits[].__customerInfo.customer_postal_codestring
visits[].__customerInfo.customer_categorystring
visits[].__customerInfo.customer_is_activeinteger
visits[].__customerInfo.customer_assigned_teamstring
visits[].__customerInfo.customer_assigneestring
visits[].TitlestringGenerated by the platform (Visit <timestamp>) - do not send.
visits[].visit_statusintegerNumeric status code - 1 = New.
visits[].visit_contact_full_namestringCopied from the target customer's contact details, along with the other visit_contact_* fields.
visits[].visit_contact_phone_numberstring
visits[].visit_contact_emailstring
visits[].visit_addressstringCopied from the target customer.
visits[].visit_administrative_1stringCopied from the target customer's administrative codes (visit_administrative_1..3).
visits[].visit_administrative_2string
visits[].visit_administrative_3string
visits[].visit_postal_codestring
visits[].visit_customer_categorystring
visits[].visit_locationstringCopied from the target customer, as a GeoJSON Point string.
visits[].visit_boundarystring
visits[].validatedbooleanWhether this visit has been validated.
visits[].notifiedbooleanWhether the assignee has been notified of this visit.
visits[].__isValidbooleanWhether this visit passed validation. See __dataErrors for what failed when false.
visits[].__dataErrorsarrayWhat failed validation for this visit, if __isValid is false. Empty when the visit is valid.
visits[].visit_plan_guidstringThe plan this visit belongs to.
visits[].visit_created_frominteger
visits[].visit_last_modified_frominteger
visits[].Idstring
visits[].visit_guidstringIdentifier of this visit. Generated by the platform - do not send it on create.
visits[].isSuccessbooleanWhether this individual visit was created successfully. The plan call can return 200 OK while one visit fails - check this flag per visit.
plan_datestringThe day the plan covers.
plan_created_fromintegerChannel the plan was created from: 1 Mobile, 2 Web, 3 Others.
plan_last_modified_fromintegerChannel of the last change.
IdstringRecord ID of the plan. The plan endpoints address it by plan_guid, not by this value.
PathstringInternal storage location. Read-only.

Response - some visits are invalid (errorCode 3001)

{
"result": -1,
"resultId": "gx5isi129d",
"data": {
"plan_assignee_username": "apidemo_newuser_9",
"plan_date": "2026-09-23T16:59:59.999Z",
"plan_created_from": 2,
"plan_last_modified_from": 2,
"plan_name": "Plan for apidemo_newuser_9 created on 23/09/2026",
"Path": "/<internal storage path>"
},
"errorMessage": "Some visits are invalid.",
"message": "Some visits are invalid.",
"errorDetails": [
{
"data": {
"visit_target_category": "Audit",
"visit_target_guid": "6aa3d2b3d1d2d6ccfde198cd",
"visit_assignee_username": "apidemo_newuser_9",
"visit_date": "2026-09-23T16:59:59.999Z",
"visit_status": 1,
"visit_priority": "Medium",
"visit_objective": "Audit",
"visit_estimate_time": 10,
"__customerInfo": {
"customer_guid": "6aa3d2b3d1d2d6ccfde198cd",
"customer_full_name": "New customer",
"Title": "New customer",
"customer_address": "ABC",
"customer_postal_code": "451331",
"customer_assignee": "all",
"customer_assigned_team": "2"
},
"Title": "Visit 1790149244060",
"visit_contact_full_name": "New customer",
"visit_address": "ABC",
"visit_postal_code": "451331",
"visit_location": "{\"type\":\"Point\",\"coordinates\":[75.97974198425027,21.888548035168114]}",
"validated": true,
"notified": true,
"__isValid": true,
"__dataErrors": []
},
"isValid": false,
"isSuccess": false,
"message": "A visit already exists for this customer on this date",
"details": [
{
"visit_date": "A visit already exists for this customer on this date"
}
]
}
],
"errorCode": 3001
}
FieldTypeDescription
resultinteger-1 when the plan could not be created as sent.
resultIdstringCorrelation ID for this call. Quote it when reporting a problem to support.
dataobjectThe plan as the platform prepared it. No plan_guid is returned in this case.
data.plan_assignee_usernamestring
data.plan_datestring
data.plan_created_frominteger
data.plan_last_modified_frominteger
data.plan_namestring
data.Pathstring
errorMessagestringSummary of the failure.
messagestringSame text as errorMessage.
errorDetailsarrayOne entry per visit that failed.
errorDetails[]object
errorDetails[].dataobjectThe visit as the platform resolved it, including the customer details from visit_target_guid.
errorDetails[].data.visit_target_categorystring
errorDetails[].data.visit_target_guidstring
errorDetails[].data.visit_assignee_usernamestring
errorDetails[].data.visit_datestring
errorDetails[].data.visit_statusinteger
errorDetails[].data.visit_prioritystring
errorDetails[].data.visit_objectivestring
errorDetails[].data.visit_estimate_timeinteger
errorDetails[].data.__customerInfoobjectThe target customer record (trimmed to identifying fields in this example).
errorDetails[].data.__customerInfo.customer_guidstring
errorDetails[].data.__customerInfo.customer_full_namestring
errorDetails[].data.__customerInfo.Titlestring
errorDetails[].data.__customerInfo.customer_addressstring
errorDetails[].data.__customerInfo.customer_postal_codestring
errorDetails[].data.__customerInfo.customer_assigneestring
errorDetails[].data.__customerInfo.customer_assigned_teamstring
errorDetails[].data.Titlestring
errorDetails[].data.visit_contact_full_namestring
errorDetails[].data.visit_addressstring
errorDetails[].data.visit_postal_codestring
errorDetails[].data.visit_locationstring
errorDetails[].data.validatedboolean
errorDetails[].data.notifiedboolean
errorDetails[].data.__isValidboolean
errorDetails[].data.__dataErrorsarray
errorDetails[].isValidbooleanfalse for a visit that failed.
errorDetails[].isSuccessbooleanfalse - the visit was not created.
errorDetails[].messagestringWhy the visit failed.
errorDetails[].detailsarrayField-level reasons, keyed by field name.
errorDetails[].details[]object
errorDetails[].details[].visit_datestring
errorCodeintegerNumeric error code. 3001 = some visits are invalid.

Get a plan​

GET /api/app-base/plans/{plan_guid}

Request Parameters

ParameterInTypeRequiredDescriptionExample
plan_guidpathstringYesPlan GUID6aa13d2e620f0c4efa094477

Response Schema

200 OK

{
"plan_guid": "6aa13d2e620f0c4efa094477",
"Id": "f3d2c4b9-0d46-426d-ac3f-7400eedc94fd",
"Layer": "PLAN",
"Path": "/<internal storage path>",
"ParentPath": "/<internal storage path>",
"CreatedDate": "2026-09-09T11:04:14Z",
"CreatedUser": "apidemo_ss",
"ModifiedDate": "2026-09-09T11:04:14Z",
"ModifiedUser": "apidemo_ss",
"Title": "ZFLCFX_PLAN",
"plan_assignee_username": "apidemo_ss",
"plan_date": "2026-09-10T00:00:59Z",
"plan_name": "Plan for apidemo_ss created on 09/09/2026",
"plan_cancel_status": "Empty",
"plan_is_repeated": false
}
FieldTypeDescription
plan_guidstringThe plan you requested - same value you sent in the path.
IdstringRecord ID of the plan in the PLAN layer. Not used by the plan endpoints, which address the plan by plan_guid.
LayerstringLayer the record lives in, always PLAN.
PathstringInternal storage location. Read-only.
ParentPathstringInternal storage location of the parent node. Read-only.
CreatedDatestringWhen the plan was created, UTC.
CreatedUserstringAccount that created the plan.
ModifiedDatestringWhen the plan was last changed, UTC.
ModifiedUserstringAccount that last changed the plan.
TitlestringReturned as the internal layer name rather than a per-record label.
plan_assignee_usernamestringThe field user this plan belongs to.
plan_datestringThe day the plan covers.
plan_namestringLabel generated by the platform from the assignee and the plan date.
plan_cancel_statusstringCancellation state of the plan. Empty means it has not been cancelled.
plan_is_repeatedbooleanWhether this is a repeating plan (plan_repeat/plan_cycle) rather than a single date.

This call does not return the plan's visits. It returns the plan record only - the visit_guids returned on create are what you have to read each visit's detail.

Search plans​

POST /api/app-base/plans/search

Request Parameters

ParameterInTypeRequiredDescriptionExample
skipbodyintegerNo
takebodyintegerNo
plan_assignee_usernamebodystringNo
sortBybodyarrayNo
sortBy[]bodyobjectNo
sortBy[].FieldbodystringNo
sortBy[].DirectionbodystringNo

Request body

{
"skip": 0,
"take": 20,
"plan_assignee_username": "<username>",
"sortBy": [
{
"Field": "CreatedDate_pdt",
"Direction": "DESC"
}
]
}

skip and take are paging properties:

  • skip - number of records to skip before the page starts. 0 is the first page.
  • take - maximum number of records returned in this page. -1 returns every match, which is only safe on small datasets.

The response reports total, the number of records matching the query regardless of paging - keep requesting pages until skip + take reaches it.

Response Schema

200 OK

{
"data": [
{
"Id": "f3d2c4b9-0d46-426d-ac3f-7400eedc94fd",
"Layer": "PLAN",
"Path": "/<internal storage path>",
"ParentPath": "/<internal storage path>",
"CreatedDate": "2026-09-09T11:04:14Z",
"CreatedUser": "apidemo_ss",
"ModifiedDate": "2026-09-09T11:04:14Z",
"ModifiedUser": "apidemo_ss",
"Title": "ZFLCFX_PLAN",
"plan_guid": "6aa13d2e620f0c4efa094477",
"plan_assignee_username": "apidemo_ss",
"plan_date": "2026-09-10T00:00:59Z",
"plan_name": "Plan for apidemo_ss created on 09/09/2026",
"plan_cancel_status": "Empty",
"plan_is_repeated": false
}
],
"total": 2
}
FieldTypeDescription
dataarrayThe page of matching plans. Each entry carries the same fields as GET /plans/{plan_guid} - use plan_guid to address one of them.
data[]object
data[].Idstring
data[].Layerstring
data[].Pathstring
data[].ParentPathstring
data[].CreatedDatestring
data[].CreatedUserstring
data[].ModifiedDatestring
data[].ModifiedUserstring
data[].Titlestring
data[].plan_guidstring
data[].plan_assignee_usernamestring
data[].plan_datestring
data[].plan_namestring
data[].plan_cancel_statusstring
data[].plan_is_repeatedboolean
totalintegerTotal number of plans matching the query, ignoring skip and take - use it to drive paging.

Search results do not include visits, the same as GET /plans/{plan_guid} - each entry is the plan record only.

Update a plan​

PUT /api/app-base/plans/{plan_guid}

Request Parameters

ParameterInTypeRequiredDescriptionExample
plan_guidpathstringYesPlan GUID6aa13d2e620f0c4efa094477
plan_namebodystringNo

Request body

{
"plan_name": "Updated plan name"
}

Delete a plan​

DELETE /api/app-base/plans/{plan_guid}

Request Parameters

ParameterInTypeRequiredDescriptionExample
plan_guidpathstringYesPlan GUID

Response Schema

200 OK

{
"count": 1
}
FieldTypeDescription
countintegerNumber of records actually deleted. 1 = the record was found and removed, 0 = nothing matched the ID. The call returns 200 OK either way, so check this value to confirm the delete.

Get a visit​

GET /api/app-base/visits/{visit_guid}

Request Parameters

ParameterInTypeRequiredDescriptionExample
visit_guidpathstringYesVisit GUID

Search visits​

POST /api/app-base/visits/search

Request Parameters

ParameterInTypeRequiredDescriptionExample
skipbodyintegerNo
takebodyintegerNo
visit_assignee_usernamebodystringNo
visit_datebodyobjectNo
visit_date.betweenbodyarrayNo
visit_date.between[]bodystringNo
searchConfigbodyobjectNo
searchConfig.includeCustomerbodybooleanNo
searchConfig.customerFieldsbodyarrayNo
searchConfig.customerFields[]bodystringNo

Request body

{
"skip": 0,
"take": -1,
"visit_assignee_username": "<username>",
"visit_date": {
"between": [
"2026-07-25T17:00:00.000Z",
"2026-08-01T16:59:59.999Z"
]
},
"searchConfig": {
"includeCustomer": true,
"customerFields": [
"customer_full_name",
"customer_phone_number",
"customer_email",
"customer_address",
"customer_guid"
]
}
}

skip and take are paging properties:

  • skip - number of records to skip before the page starts. 0 is the first page.
  • take - maximum number of records returned in this page. -1 returns every match, which is only safe on small datasets.

The response reports total, the number of records matching the query regardless of paging - keep requesting pages until skip + take reaches it.

Update a visit​

PUT /api/app-base/visits/{visit_guid}

Request Parameters

ParameterInTypeRequiredDescriptionExample
visit_guidpathstringYesVisit GUID

Delete a visit​

DELETE /api/app-base/visits/{visit_guid}

Request Parameters

ParameterInTypeRequiredDescriptionExample
visit_guidpathstringYesVisit GUID

Response Schema

200 OK

{
"count": 1
}
FieldTypeDescription
countintegerNumber of records actually deleted. 1 = the record was found and removed, 0 = nothing matched the ID. The call returns 200 OK either way, so check this value to confirm the delete.

Example​

curl -X POST '$BASE_URL/api/app-base/plans/create?api-key=$API_KEY' \
-H 'Authorization: Bearer $TOKEN' \
-H 'Content-Type: application/json' \
-d '{"plan_assignee_username": "apidemo_newuser_9", "plan_name": "test plan", "visits": [{"visit_target_guid": "6aa0bb299521e3582ef555f8", "visit_assignee_username": "apidemo_newuser_9", "visit_date": "2026-09-25T16:59:59.999Z", "visit_priority": "3", "visit_estimate_time": "30", "visit_objective": "test", "visit_order": 0}, {"visit_target_guid": "6aa3d2b3d1d2d6ccfde198cd", "visit_assignee_username": "apidemo_newuser_9", "visit_date": "2026-09-25T16:59:59.999Z", "visit_priority": "3", "visit_estimate_time": "30", "visit_objective": "test", "visit_order": 1}], "plan_date": "2026-09-25T16:59:59.999Z", "config": {"timeZoneOffset": -420}}'