Documentation Index
Fetch the complete documentation index at: https://docs.sensorup.com/llms.txt
Use this file to discover all available pages before exploring further.
Field Service Management Guide
The Field Service Management (FSM) API enables mobile workflows through issue tracking, task management, and dynamic data collection forms. FSM integrates four subgraphs: su-assets (form subjects), su-issues (issue tracking), su-workflow (workflow orchestration), and su-forms (data collection).
Overview
FSM enables field teams to:
- Track issues and work items requiring attention
- Execute workflows with defined states and transitions
- Complete tasks using dynamic XForms
- Reference asset profiles as subjects for field work
- Collect structured data through mobile applications
- Manage multi-step inspection campaigns
Core Concepts
Asset profiles (su-assets) define the subjects that field teams work on during mobile workflows. These are NOT the same as CAM assets:
- Asset Profile: Schema definition for form subjects (e.g., “Well Inspection”, “Equipment Survey”)
- Asset: Instance of a profile used as a form subject
- Properties: Attributes collected about the subject
- Relationships: Connections between assets and other entities
Asset profiles serve as the foundation for tasks and forms in mobile applications.
Issues
Issues represent trackable work items requiring attention or resolution:
- Issue Types:
ISSUE, TASK, MULTITASK, SUBTASK
- Status:
OPEN, IN_PROGRESS, RESOLVED, CLOSED, CANCELLED
- Subject: The entity the issue relates to (e.g., an asset profile instance)
- Trigger: What caused the issue (e.g., a detection, event, or manual creation)
Tasks
Tasks are specific work to be performed by field personnel:
- Form-based Tasks: Tasks with an associated XForm for data collection
- General Tasks: Simple work items without form requirements
- Multi-tasks: Parent tasks coordinating multiple subtasks
- Subtasks: Individual tasks within a multi-task campaign
Dynamic data collection forms based on the ODK XForms standard:
- Controls: Input fields (text, select, date, location, etc.)
- Groups: Logical sections organizing related questions
- Repeats: Repeating sections for multiple observations
- Constraints: Validation rules and calculations
- Relevance: Conditional display logic
Workflows
State machines defining the lifecycle of issues and tasks:
- States: Valid statuses an issue can have
- Transitions: Allowed movements between states
- Guards: Conditions required for transitions
- Actions: Side effects triggered during transitions
Asset Profiles for Field Work
Query Available Profiles
query GetAssetProfiles {
assetProfiles {
all(first: 20) {
edges {
node {
profile
title
displayNameProperty
descriptionProperty
properties {
id
name
dataType
required
}
relationships {
relationship
type
profile
referenceProperty
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
Get Specific Profile
query GetAssetProfile($profile: ID!) {
assetProfiles {
byId(profile: $profile) {
profile
title
displayNameProperty
views {
view
title
fields
}
filters {
filter
displayName
filterString
}
}
}
}
Variables:
{
"profile": "well-inspection"
}
query GetAssets($profile: ID!, $first: Int!) {
assets(profile: $profile) {
all(first: $first) {
edges {
node {
id
displayName
properties
geometry {
type
coordinates
}
relationships {
... on AssetRelationshipOne {
asset {
id
displayName
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
Variables:
{
"profile": "well-inspection",
"first": 50
}
mutation CreateAsset($input: CreateAssetInput!) {
createAsset(input: $input) {
asset {
id
displayName
properties
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"profile": "well-inspection",
"id": "well-abc-123",
"displayName": "Well ABC-123",
"properties": {
"operator": "ACME Energy",
"wellType": "gas",
"status": "active"
},
"geometry": {
"type": "Point",
"coordinates": [-114.0719, 51.0447]
}
}
}
Issue Tracking
List All Issues
query GetIssues($first: Int!, $filter: IssueFilter) {
issues {
all(first: $first, filter: $filter) {
edges {
node {
id
type
title
description
status
severity
openedAt
closedAt
dueAt
pastDue
assignee {
username
}
subject {
type
reference
}
trigger {
type
reference
}
}
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}
}
Variables (Filter Open Issues):
{
"first": 20,
"filter": {
"status": ["OPEN", "IN_PROGRESS"]
}
}
Get Specific Issue
query GetIssue($id: ID!) {
issues {
byId(id: $id) {
id
type
taskType
title
description
status
severity
openedAt
closedAt
dueAt
subject {
type
reference
}
assignee {
userId
username
}
annotations {
id
text
createdAt
createdBy {
username
}
attachments {
url
filename
contentType
}
}
xform {
formId
responseId
status
}
}
}
}
Create Issue
mutation CreateIssue($input: CreateIssueInput!) {
createIssue(input: $input) {
issue {
id
title
status
type
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"title": "Quarterly Well Inspection - ABC-123",
"description": "Routine quarterly inspection required",
"type": "TASK",
"severity": "MEDIUM",
"dueAt": "2025-12-31T23:59:59Z",
"subject": {
"type": "ASSET",
"reference": "well-abc-123"
},
"assignee": {
"userId": "user-123"
}
}
}
Update Issue Status
mutation UpdateIssue($input: UpdateIssueInput!) {
updateIssue(input: $input) {
issue {
id
status
closedAt
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"id": "issue-123",
"status": "RESOLVED"
}
}
Add Annotation to Issue
mutation AddIssueAnnotation($input: AddIssueAnnotationInput!) {
addIssueAnnotation(input: $input) {
annotation {
id
text
createdAt
createdBy {
username
}
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"issueId": "issue-123",
"text": "Inspection completed. No issues found. Equipment operating normally.",
"attachments": [
{
"url": "https://storage.example.com/photos/inspection-photo.jpg",
"filename": "inspection-photo.jpg",
"contentType": "image/jpeg"
}
]
}
}
Create or update a follow-up issue linked to an existing issue. The mutation finds an original issue using filters and creates a follow-up with the specified form data. Subsequent calls with the same triggerContextReference update the existing follow-up rather than creating duplicates.
mutation PerformIssueFollowUpAction($input: PerformIssueFollowUpActionInput!) {
performIssueFollowUpAction(input: $input) {
issue {
id
title
status
}
correlationId
}
}
Variables:
{
"input": {
"subjectFormReference": "leak",
"triggerContextReference": "external-id-123",
"filters": [
{
"field": "work_order_number",
"operator": "EQUAL",
"value": "external-id-123"
}
],
"followUpFormReference": "leak-repair",
"followUpTaskTitle": "Leak Repair Required",
"followUpTaskDescription": "Repair leak identified in work order external-id-123",
"followUpFormData": {
"technician_name": "John Smith",
"repairDate": "2025-01-01T00:00:00.000Z"
}
}
}
Delete Issue
Deletes an issue. Assets and statuses associated with the issue are not affected.
mutation DeleteIssue($input: DeleteIssueInput!) {
deleteIssue(input: $input) {
issue {
id
title
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"issueId": "issue-123"
}
}
Multi-Task Management
Multi-tasks enable coordinated campaigns across multiple locations or assets.
Create Multi-Task
mutation CreateMultiTask($input: CreateIssueInput!) {
createIssue(input: $input) {
issue {
id
title
type
status
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"title": "Q4 2025 Site-Wide Inspection",
"description": "Quarterly inspection of all wells at North Field",
"type": "MULTITASK",
"severity": "HIGH",
"dueAt": "2025-12-31T23:59:59Z"
}
}
Create Subtasks
mutation CreateSubtask($input: CreateIssueInput!) {
createIssue(input: $input) {
issue {
id
title
type
parentTask {
id
title
}
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"title": "Inspect Well ABC-123",
"type": "SUBTASK",
"parentTaskId": "multitask-456",
"subject": {
"type": "ASSET",
"reference": "well-abc-123"
},
"assignee": {
"userId": "field-tech-001"
}
}
}
Query Multi-Task with Subtasks
query GetMultiTaskWithSubtasks($id: ID!) {
issues {
byId(id: $id) {
id
title
type
status
subtasks {
id
title
status
assignee {
username
}
subject {
type
reference
}
}
}
}
}
query GetForms {
forms {
all(first: 100) {
edges {
node {
formId
title
version
description
xformDetails {
model
binds {
nodeset
type
required
readonly
}
body {
... on XFormBodyControlNode {
id
control
label
hint
ref
}
... on XFormBodyGroupNode {
id
label
childNodeIds
}
}
}
}
}
}
}
}
query GetForm($formId: ID!) {
forms {
byId(id: $formId) {
formId
title
version
description
xformDetails {
model
binds {
nodeset
type
required
relevant
constraint
calculate
}
body {
__typename
... on XFormBodyControlNode {
id
control
label
hint
items {
label
value
}
}
... on XFormBodyGroupNode {
id
label
childNodeIds
}
... on XFormBodyRepeatNode {
id
label
ref
childNodeIds
}
}
}
}
}
}
mutation SubmitFormResponse($input: CreateFormResponseInput!) {
createFormResponse(input: $input) {
formResponse {
responseId
formId
submittedAt
submittedBy {
username
}
data
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"formId": "well-inspection-form",
"issueId": "issue-123",
"data": {
"inspector": "John Smith",
"inspectionDate": "2025-11-05",
"wellCondition": "good",
"pressureReading": "850",
"temperatureReading": "72",
"notes": "No issues observed",
"photos": [
"https://storage.example.com/photo1.jpg",
"https://storage.example.com/photo2.jpg"
]
}
}
}
mutation LinkFormToTask($input: LinkFormToIssueInput!) {
linkFormToIssue(input: $input) {
issue {
id
xform {
formId
responseId
status
}
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"issueId": "issue-123",
"formId": "well-inspection-form"
}
}
Workflows
Query Workflow Definition
query GetWorkflow($workflowId: ID!) {
workflow(id: $workflowId) {
id
name
description
states {
id
name
isInitial
isFinal
}
transitions {
from
to
event
guards {
condition
message
}
}
}
}
Trigger Workflow Transition
mutation TransitionIssue($input: TransitionIssueInput!) {
transitionIssue(input: $input) {
issue {
id
status
workflow {
currentState
availableTransitions {
event
to
}
}
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"issueId": "issue-123",
"event": "complete_inspection"
}
}
Common Workflows
Mobile Inspection Workflow
- Create Task with asset subject and XForm
- Assign to field technician
- Technician opens task in mobile app
- Complete XForm with inspection data
- Submit form response
- Update task status to RESOLVED
- Supervisor reviews and CLOSES task
// Example mobile inspection flow
async function mobileInspectionWorkflow(assetId, formId, technicianId) {
// 1. Create task
const { data: taskData } = await createIssue({
title: `Inspect Asset ${assetId}`,
type: 'TASK',
subject: { type: 'ASSET', reference: assetId },
assignee: { userId: technicianId }
})
const taskId = taskData.createIssue.issue.id
// 2. Link form to task
await linkFormToIssue({
issueId: taskId,
formId: formId
})
// 3. Technician completes form
const formData = await collectDataInMobileApp()
// 4. Submit form response
await createFormResponse({
formId: formId,
issueId: taskId,
data: formData
})
// 5. Update task status
await updateIssue({
id: taskId,
status: 'RESOLVED'
})
return taskId
}
Multi-Task Campaign Workflow
- Create multi-task for campaign
- Generate subtasks for each location
- Assign subtasks to field teams
- Track completion of individual subtasks
- Multi-task auto-completes when all subtasks done
// Example multi-task campaign
async function createInspectionCampaign(assets, formId) {
// 1. Create multi-task
const { data: multiTaskData } = await createIssue({
title: 'Q4 Inspection Campaign',
type: 'MULTITASK',
dueAt: '2025-12-31T23:59:59Z'
})
const multiTaskId = multiTaskData.createIssue.issue.id
// 2. Create subtask for each asset
for (const asset of assets) {
await createIssue({
title: `Inspect ${asset.displayName}`,
type: 'SUBTASK',
parentTaskId: multiTaskId,
subject: { type: 'ASSET', reference: asset.id },
assignee: { userId: asset.assignedTech }
})
}
return multiTaskId
}
Issue Templates
Create Issue Template
mutation CreateIssueTemplate($input: CreateIssueTemplateInput!) {
createIssueTemplate(input: $input) {
template {
id
name
title
description
type
defaultSeverity
defaultFormId
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"name": "quarterly-well-inspection",
"title": "Quarterly Well Inspection",
"description": "Standard quarterly inspection procedure",
"type": "TASK",
"defaultSeverity": "MEDIUM",
"defaultFormId": "well-inspection-form"
}
}
Create Issue from Template
mutation CreateIssueFromTemplate($input: CreateIssueFromTemplateInput!) {
createIssueFromTemplate(input: $input) {
issue {
id
title
type
xform {
formId
}
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"templateId": "quarterly-well-inspection",
"subject": {
"type": "ASSET",
"reference": "well-abc-123"
},
"assignee": {
"userId": "tech-001"
},
"dueAt": "2025-12-15T23:59:59Z"
}
}
Asset Filters
Create reusable filters for finding form subjects:
Create Asset Filter
mutation CreateAssetFilter($input: CreateAssetFilterInput!) {
createAssetFilter(input: $input) {
assetFilter {
id
displayName
profile
filterString
}
correlationId
errors {
message
type
}
}
}
Variables:
{
"input": {
"id": "active-wells-north-field",
"displayName": "Active Wells - North Field",
"profile": "well-inspection",
"subProfiles": [],
"filterString": "properties.status = 'active' AND properties.field = 'north'"
}
}
Query Assets with Filter
query GetFilteredAssets($profile: ID!, $filterId: ID!) {
assets(profile: $profile) {
filtered(filter: $filterId, first: 100) {
edges {
node {
id
displayName
properties
}
}
}
}
}
Best Practices
Define asset profiles for entities that field teams will work on:
# Good - dedicated profile for inspection subjects
assetProfiles { byId(profile: "equipment-inspection") }
# Avoid - mixing operational assets with form subjects
# Use CAM assets for operational data, FSM asset profiles for forms
Always link XForms to tasks for proper tracking:
# Good - form linked to task
linkFormToIssue(input: { issueId: "task-123", formId: "inspection-form" })
# Incomplete - orphaned form response
createFormResponse(input: { formId: "inspection-form", data: {...} })
3. Use Multi-Tasks for Campaigns
For coordinated work across multiple locations, use multi-tasks:
# Good - multi-task with subtasks
createIssue(type: "MULTITASK") → createIssue(type: "SUBTASK", parentTaskId: "...")
# Less organized - individual unrelated tasks
createIssue(type: "TASK") + createIssue(type: "TASK") + ...
4. Leverage Templates
Create templates for recurring workflows:
# Good - consistent task creation
createIssueFromTemplate(templateId: "monthly-inspection")
# More work - manual task creation each time
createIssue(title: "...", description: "...", formId: "...")
5. Filter by Status for Work Queues
Query issues by status to create work queues:
# Active work queue
issues { all(filter: { status: ["OPEN", "IN_PROGRESS"] }) }
# Completed work
issues { all(filter: { status: ["RESOLVED", "CLOSED"] }) }
Error Handling
FSM mutations return errors in the standard format:
{
"data": {
"createIssue": {
"issue": null,
"correlationId": "abc-123",
"errors": [
{
"message": "Subject asset 'well-xyz' not found",
"type": "INVALID_SUBJECT"
}
]
}
}
}
Common error types:
INVALID_SUBJECT: Referenced subject does not exist
INVALID_ASSIGNEE: Assignee user not found
INVALID_FORM: Form ID does not exist
INVALID_STATUS_TRANSITION: Status change not allowed
MISSING_REQUIRED: Required field not provided
DUPLICATE_ID: ID already exists