Skip to main content

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 (Form Subjects)

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

XForms

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 Assets (Form Subjects)

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
}

Create Asset (Form Subject)

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"
      }
    ]
  }
}

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
        }
      }
    }
  }
}

XForms for Data Collection

Query Available Forms

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
              }
            }
          }
        }
      }
    }
  }
}

Get Specific Form

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
          }
        }
      }
    }
  }
}

Submit Form Response

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

  1. Create Task with asset subject and XForm
  2. Assign to field technician
  3. Technician opens task in mobile app
  4. Complete XForm with inspection data
  5. Submit form response
  6. Update task status to RESOLVED
  7. 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

  1. Create multi-task for campaign
  2. Generate subtasks for each location
  3. Assign subtasks to field teams
  4. Track completion of individual subtasks
  5. 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

1. Use Asset Profiles for Form Subjects

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