Jobs
Jobs are the fundamental structure for organizing content and data within Vision. Each Job is associated with a single customer, customer location, and work order number. One or more workflows can be assigned to a Job to define the steps to be taken during the process of working that Job. Read about getJobSummary for more information about how multiple workflows are represented when summarizing jobs.
NOTE: There are some technical details about the relationship between jobs and workflows noted below.
API URLs
- Testing:
https://gql-jobs-external.staging.xoeye.com/graphql
- Production:
https://gql-jobs-external.xoi.io/graphql
Creating a Job
Example Request
The following mutation will create a Job and return specific fields about the newly created Job. You'll want to make sure to request the ID of the job as part of your request so that you can store it for later use.
If you cannot persist the Vision Job ID in your system upon creation of the job, you can include an integrationEntityId
in your createJob
input. Vision Jobs can be retrieved via this external ID (see this query for details), but the external ID must be resolved to a Vision Job ID for the other queries and mutations described here. Details about the fields required are provided here.
If you know that you're going to need a public share immediately upon job creation, you can request to have that share created as part of the createJob
process. See additionalActions
and additionalActionsResults
in the example below.
Note that assigneeIds
takes a list of Vision User IDs, but is currently enforced as a single item list. We plan to soon have the ability to have multiple Assignees per Job, so we are ensuring we do not need to change the API when that time comes.
See the CreateJobInput schema for the details of what is required for this request.
mutation CreateJob {
createJob(
input: {
newJob: {
assigneeIds: ["a-tech@your-org.com"]
customerName: "Some Customer"
jobLocation: "Some Location"
workOrderNumber: "Some Workorder"
label: "A human-friendly identifier"
tags: ["tag1", "tag2"]
tagSuggestions: ["tag3", "tag4"]
integrationEntityId: {
namespace: "AtLeast10Characters"
id: "ShouldBeUniqueInYourSystem"
}
internalNote: { text: "A note about the job." }
}
additionalActions: { createPublicShare: { enabled: true } }
}
) {
job {
id
createdAt
createdBy
assigneeIds
customerName
jobLocation
workOrderNumber
label
tags
tagSuggestions
deepLinks {
visionMobile {
editJob {
url
}
jobLocationActivitySearch {
url
}
}
integrationEntityId {
namespace
id
}
internalNote {
text
}
}
}
additionalActionsResults {
createPublicShare {
shareLink
}
}
}
}
Example Response
The id
included in this response is the unique ID created by XOi Cloud, and should be stored in your system for retrieving the job later.
See the JobResult schema for the details of what to expect in your response.
{
"data": {
"createJob": {
"job": {
"id": "458c-d887fee6358a4daf9ed7c38c14b2c444",
"createdAt": "2020-02-13T18:23:33.323616Z",
"createdBy": "some-vision-user-id",
"assigneeIds": ["a-tech@your-org.com"],
"customerName": "Some Customer",
"jobLocation": "Some Location",
"workOrderNumber": "Some Workorder",
"label": "A human-friendly identifier",
"tags": ["tag1", "tag2"],
"tagSuggestions": ["tag3", "tag4"],
"deepLinks": {
"visionMobile": {
"editJob": {
"url": "xoi-vision://my-work/edit?payload=%7B%22jobId%22%3A%20%22458c-d887fee6358a4daf9ed7c38c14b2c444%22%2C%20%22jobLocation%22%3A%20%22Some%20Location%22%2C%20%22workOrderNumber%22%3A%20%22Some%20Workorder%22%2C%20%22owner%22%3A%20%22a-tech%40your-org.com%22%2C%20%22customerName%22%3A%20%22Some%20Customer%22%7D"
},
"jobLocationActivitySearch": {
"url": "xoi-vision://activity/search?payload=%7B%22exactMatchAllTags%22%3A%20%5B%22some%20location%22%5D%7D"
}
}
},
"integrationEntityId": {
"namespace": "AtLeast10Characters",
"id": "ShouldBeUniqueInYourSystem"
},
"internalNote": {
"text": "A note about the job."
}
},
"additionalActionsResults": {
"createPublicShare": {
"id": "is307a3eb",
"shareLink": "https://visionshare.xoi.io/?id=is307a3eb"
}
}
}
}
}
Retrieving a Job
Example Request
A Vision Job id
can be used to directly retrieve a job.
See the GetJobInput schema for the details of what is required for this request.
query GetJob {
getJob(input: { id: "a-vision-job-id" }) {
job {
id
createdAt
createdBy
assigneeIds
customerName
jobLocation
workOrderNumber
label
tags
tagSuggestions
deepLinks {
visionWeb {
viewJob {
url
}
}
visionMobile {
viewJob {
url
}
editJob {
url
}
jobLocationActivitySearch {
url
}
}
}
}
}
}
Special note about retrieving a workflow job
Using a Workflow Job id
in the getJob query will return information about a Workflow Job. All returned information is specific to the requested Workflow Job. The customerName, jobLocation, and workOrderNumber fields will be empty because they are set at the Container Job level. We do not support deep links into a Workflow Job, therefore deepLinks
will return null
. Please see the technical details about Workflow Jobs below for more information.
Example Response
The response here is the same response you get from the createJob
mutation.
See the JobResult schema for the details of what to expect in your response.
Note: Although viewJob deepLinks are returned in this example, they will only be present for a completed job.
{
"data": {
"getJob": {
"job": {
"id": "458c-d887fee6358a4daf9ed7c38c14b2c444",
"createdAt": "2020-02-13T18:23:33.323616Z",
"createdBy": "some-vision-user-id",
"assigneeIds": ["a-tech@your-org.com"],
"customerName": "Some Customer",
"jobLocation": "Somewhere, USA",
"workOrderNumber": "Workorder #123",
"label": "A human-friendly identifier",
"tags": ["tag1", "tag2"],
"tagSuggestions": ["tag3", "tag4"],
"deepLinks": {
"visionWeb": {
"viewJob": {
"url": "https://visionweb.xoi.io/jobactivity/458c-d887fee6358a4daf9ed7c38c14b2c444"
}
},
"visionMobile": {
"viewJob": {
"url": "xoi-vision://activity/view?payload=%7B%22jobId%22%3A%20%22458c-d887fee6358a4daf9ed7c38c14b2c444%22%7D"
},
"editJob": {
"url": "xoi-vision://my-work/edit?payload=%7B%22jobId%22%3A%20%22458c-d887fee6358a4daf9ed7c38c14b2c444%22%2C%20%22jobLocation%22%3A%20%22Some%20Location%22%2C%20%22workOrderNumber%22%3A%20%22Some%20Workorder%22%2C%20%22owner%22%3A%20%22a-tech%40your-org.com%22%2C%20%22customerName%22%3A%20%22Some%20Customer%22%7D"
},
"jobLocationActivitySearch": {
"url": "xoi-vision://activity/search?payload=%7B%22exactMatchAllTags%22%3A%20%5B%22some%20location%22%5D%7D"
}
}
}
}
}
}
}
Updating a Job
Example Request
You can update an existing job using the job's id
.
Note that, just as with createJob, assigneeIds
takes a list of Vision User IDs, but is currently enforced as a single item list.
See the UpdateJobInput schema for the details of which fields can be updated.
mutation UpdateJob {
updateJob(
input: {
id: "458c-d887fee6358a4daf9ed7c38c14b2c444"
fieldUpdates: {
customerName: "Some Customer"
jobLocation: "Some Location"
workOrderNumber: "Some Workorder"
label: "A human-friendly identifier"
tags: ["tag1", "tag2"]
tagSuggestions: ["tag3", "tag4"]
internalNote: { text: "A note about the job." }
assigneeIds: ["a-different-tech@your-org.com"]
}
}
) {
job {
id
createdAt
createdBy
assigneeIds
customerName
jobLocation
workOrderNumber
label
tags
tagSuggestions
internalNote {
text
}
deepLinks {
visionWeb {
viewJob {
url
}
}
visionMobile {
viewJob {
url
}
editJob {
url
}
jobLocationActivitySearch {
url
}
}
}
}
}
}
Example Response
See the UpdateJobResult schema for the details of what to expect in your response.
Note: Although viewJob deepLinks are returned in this example, they will only be present for a completed job.
{
"data": {
"updateJob": {
"job": {
"id": "458c-d887fee6358a4daf9ed7c38c14b2c444",
"createdAt": "2020-02-13T18:23:33.323616Z",
"createdBy": "some-vision-user-id",
"assigneeIds": ["a-different-tech@your-org.com"],
"customerName": "Some Customer",
"jobLocation": "Some Location",
"workOrderNumber": "Some Workorder",
"label": "A human-friendly identifier",
"tags": ["tag1", "tag2"],
"tagSuggestions": ["tag3", "tag4"],
"internalNote": {
"text": "A note about the jobs."
},
"deepLinks": {
"visionWeb": {
"viewJob": {
"url": "https://visionweb.xoi.io/jobactivity/458c-d887fee6358a4daf9ed7c38c14b2c444"
}
},
"visionMobile": {
"viewJob": {
"url": "xoi-vision://activity/view?payload=%7B%22jobId%22%3A%20%22458c-d887fee6358a4daf9ed7c38c14b2c444%22%7D"
},
"editJob": {
"url": "xoi-vision://my-work/edit?payload=%7B%22jobId%22%3A%20%22458c-d887fee6358a4daf9ed7c38c14b2c444%22%2C%20%22jobLocation%22%3A%20%22Some%20Location%22%2C%20%22workOrderNumber%22%3A%20%22Some%20Workorder%22%2C%20%22owner%22%3A%20%22a-tech%40your-org.com%22%2C%20%22customerName%22%3A%20%22Some%20Customer%22%7D"
},
"jobLocationActivitySearch": {
"url": "xoi-vision://activity/search?payload=%7B%22exactMatchAllTags%22%3A%20%5B%22some%20location%22%5D%7D"
}
}
}
}
}
}
}
Getting a List of Jobs
A list of jobs can be retrieved using the listJobs query. This list can be filtered using the dateQuery
parameters to limit responses to those jobs either created or completed within the specified timespan. Additionally, by specifying a workOrderNumber
in the filter
input, jobs can be limited to those associated with a given work order.
For backward compatibility, until the multiple workflows per job feature is enabled for a given API user account, Container Jobs will be excluded from the listing. Since the createJob
mutation is similarly restricted from creating Container Jobs until that setting is changed, this should never result in any jobs created via the API being excluded.
The limit
parameter determines the maximum number of results to be returned in a single request. If additional results are present, a nextToken
will be returned and can be passed into a subsequent request to retrieve the next page of results. Due to how filtering is implemented (especially Container Job filtering), it is possible that fewer results may be returned in a given page than the specified limit
value. Rely on the presence of a nextToken
to determine if additional pages are present.
NOTE: There are some additional technical details about Container Jobs at the end of this document.
See the ListJobsInput schema for the details of what is required for this request.
Listing Jobs Example (without a dateQuery filter)
Without submitting any dateQuery
input, you're going to receive Jobs in descending order by the createdAt datetime field.
query ListJobs {
listJobs(input: { limit: 2 }) {
jobConnection {
nextToken
items {
id
}
}
}
}
Listing Completed Jobs Example
If you need to get only completed jobs, you can add dateQuery
to your input
, and set the type
as shown below. You can also then choose to return the results in either ascending or descending order.
query ListJobs {
listJobs(
input: {
dateQuery: { type: COMPLETED_AT, ascending: true }
limit: 2
nextToken: ""
}
) {
jobConnection {
nextToken
items {
id
}
}
}
}
Filtering Jobs returned Example
You can use the filter
input either by itself, or in conjunction with dateQuery
. The following example uses filter
by itself.
The available filter values can be found in the FilterInput documentation.
Note the current available filters are workOrderNumber
, jobLocation
, customerName
.
query ListJobs {
listJobs(
input: {
filter: { workOrderNumber: "workorder number 1" }
limit: 10
nextToken: ""
}
) {
jobConnection {
nextToken
items {
id
}
}
}
}
Example Response
Since the above query only requested the id
for each job, the items
below only contain the id. Feel free to add more fields to your query. Each entry in the items
list is a Job, so all Job fields are available.
See the ListJobsResult schema for the details of what to expect in your response.
{
"data": {
"listJobs": {
"jobConnection": {
"nextToken": "eyJGcm9tIjogMjB9",
"items": [
{ "id": "job-383fa6f1-4529-an26-bf0643febebe-ua78-d2b63615af99" },
{ "id": "job-66390d23-4529-an26-27d949279aa1-ua78-5f5648207a75" }
]
}
}
}
}
Getting Summary Information About a Job
Due to the complex nature of a Job within XOi Cloud, we provide the ability to retrieve aggregated summary data about a Job. The information available through this query will be augmented as needs arise from integration partners.
The getJobSummary's documentation
list contains content uploaded to any step within the workflows associated with the request job. Additionally, responses to any step types that collection information (such as short text entry, numeric entry, or date entry) are included.
For these step responses, a trait is added to the documentation list item to indicate the type of the step and can be used to determine which sub-structures on the entry contain relevant data. For example, if a note is present, a note
trait will be included and the note can be retrieved via the note
property. The various other step types will contain specific properties and a matching trait. See the schema for more information on these properties and traits:
note
-- a textual notechoice
-- chosen option for a Yes/No or multiple choice steptext_entry
-- a short textual answernumber_entry
-- a numeric entry and an associated unitdate_entry
-- a calendar date value
By default, only items that include a trait will be returned. By setting includeAllSteps
to true
when retrieving the job summary, even empty steps will be included in the results.
The documentation list returned will include documentation from all workflows when multiple workflows are assigned to the requested job. Each documentation item will contain information about the workflow with which it is associated. Any documentation items that have both a content summary and transcript will utilize the content summary as the transcript. If no content summary is found then the transcribed transcript will be returned.
The XOi mobile app allows a technician to either use AI to generate a work summary based on questions and answers from the workflow, or to write up a work summary on their own if they'd rather not use AI. Either way the work summary is generated, it will be available through getJobSummary
as a documentation item. We will use traits to distinguish work summaries that are AI generated from ones the technician generates on their own. If the technician generated the summary, there will be a trait of user_generated
present. If the summary was generated by AI, then the trait will be ai_generated
. If the summary was generated by AI, but the technician edited the summary, then user_edited
will be present along with ai_generated
. Since a work summary is provided at the workflow level as opposed to the step, these documentation items will not have values for stepIndex
or stepName
. Similarly, they are not related to a content record, so they will not have a contentId
value either.
It is possible to filter the results to a single workflow within a job. To do so, pass both the job's ID (as id
) and the Workflow's ID (as workflowJobId
) when making the request. See the schema for more details.
The supportStatuses
field provides visibility into the current support status of any workflows associated with the job being viewed. Support Status must be enabled in a given workflow to enable this particular field. supportStatus
contains a list of such statuses, identified using a workflowJobId
. Each entry in the list contains the status
along with information about how the status was last set. If an agent was responsible for setting the status, an agentInfo
entry with information about that agent (currently just their email
) will be present. In some situations, such as when the system sets the default status of NEW
, no agent is responsible for the change and therefore none is supplied. See the schema for more details on how this list is structured.
If more documentation items are present than can be returned in a single page of results, nextToken
will be returned along with the results. Passing this nextToken
value into an additional request will return the next page of results. To retrieve all values, continue passing in the returned nextToken
until no nextToken
is returned, which will occur on the final page.
NOTE: There are some technical details about the relationship between jobs and workflows noted below.
Example Requests
Retrieving a Job Summary
The following query will return all of the dataplates and transcripts available for a given Job as well as traits that help describe the state of data. If multiple workflows are assigned to the job, data from all of them will be summarized.
In addition to information about the content associated with a given job, information about the assignee can also be returned. See the schema for details.
See the GetJobSummaryInput schema for the details of what is required for this request.
query GetJobSummary {
getJobSummary(input: { jobId: "a-vision-job-id" }) {
nextToken
jobSummary {
jobId
documentation {
workflowName
traits
tags
note {
text
}
choice {
chosen
}
derivedData {
make
model
serial
transcript
manufacture_date
}
workSummary
}
assignees {
id
email
given_name
family_name
}
}
}
}
Filtering a Job Summary by Workorder
The following query will return the same data as the prior example, but will only do so for information assocated with the specified workflow.
See the GetJobSummaryInput schema for the details of what is required for this request.
query GetJobSummary {
getJobSummary(
input: {
jobId: "a-vision-job-id"
workflowJobId: "a-vision-workflow-job-id"
}
) {
nextToken
jobSummary {
jobId
documentation {
workflowName
traits
tags
derivedData {
make
model
serial
transcript
manufacture_date
}
}
}
}
}
Example Responses
See the GetJobSummaryResult schema for the details of what to expect in your response.
The dataplate and all transcripts are currently in-process. Notes were requested and were present on one step.
{
"data": {
"getJobSummary": {
"nextToken": "",
"jobSummary": {
"jobId": "a-vision-job-id",
"documentation": [
{
"workflowName": "Gather Information",
"traits": ["dataplate", "pending"],
"tags": ["tag1", "tag2"],
"derivedData": null,
"note": null
},
{
"workflowName": "Gather Information",
"traits": ["transcript", "pending"],
"derivedData": null,
"note": null
},
{
"workflowName": "Install Equipment",
"traits": ["note"],
"derivedData": null,
"note": { "text": "Some comments related to the installation." }
},
{
"workflowName": "Install Equipment",
"traits": ["transcript", "pending"],
"derivedData": null,
"note": null
}
]
}
}
}
}
There are partial results for the dataplate.
{
"data": {
"getJobSummary": {
"nextToken": "",
"jobSummary": {
"jobId": "a-vision-job-id",
"documentation": [
{
"workflowName": "Gather Information",
"traits": ["dataplate", "pending", "partial_dataplate"],
"tags": ["tag1", "tag2"],
"derivedData": {
"make": "Carrier",
"model": "Some Model",
"serial": "",
"transcript": "",
"manufacture_date": ""
}
},
{
"workflowName": "Gather Information",
"traits": ["transcript", "pending"],
"derivedData": null
},
{
"workflowName": "Install Equipment",
"traits": ["transcript", "pending"],
"derivedData": null
}
]
}
}
}
}
The dataplate has been processed, but not all transcripts have been.
{
"data": {
"getJobSummary": {
"nextToken": "",
"jobSummary": {
"jobId": "a-vision-job-id",
"documentation": [
{
"workflowName": "Gather Information",
"traits": ["dataplate", "processed"],
"tags": ["tag1", "tag2"],
"derivedData": {
"make": "Carrier",
"model": "Some Model",
"serial": "ABC12345",
"transcript": "",
"manufacture_date": "2020-01-01"
}
},
{
"workflowName": "Gather Information",
"traits": ["transcript", "processed"],
"derivedData": {
"make": "",
"model": "",
"serial": "",
"transcript": "The text extracted from a video.",
"manufacture_date": ""
}
},
{
"workflowName": "Install Equipment",
"traits": ["transcript", "pending"],
"derivedData": null
}
]
}
}
}
}
All dataplates and transcripts are fully processed.
{
"data": {
"getJobSummary": {
"nextToken": "",
"jobSummary": {
"jobId": "a-vision-job-id",
"documentation": [
{
"workflowName": "Gather Information",
"traits": ["dataplate", "processed"],
"tags": ["tag1", "tag2"],
"derivedData": {
"make": "Carrier",
"model": "Some Model",
"serial": "ABC12345",
"transcript": "",
"manufacture_date": "2020-01-01"
}
},
{
"workflowName": "Gather Information",
"traits": ["transcript", "processed"],
"derivedData": {
"make": "",
"model": "",
"serial": "",
"transcript": "The text extracted from a video.",
"manufacture_date": ""
}
},
{
"workflowName": "Install Equipment",
"traits": ["transcript", "processed"],
"derivedData": {
"make": "",
"model": "",
"serial": "",
"transcript": "The text extracted from a different video.",
"manufacture_date": ""
}
}
]
}
}
}
}
An image was uploaded that was expected to be a dataplate, but was not identified as such. No transcripts are expected.
{
"data": {
"getJobSummary": {
"nextToken": "",
"jobSummary": {
"jobId": "a-vision-job-id",
"documentation": [
{
"workflowName": "Gather Information",
"traits": ["dataplate", "processed", "not_a_dataplate"],
"tags": ["tag1", "tag2"],
"derivedData": {
"make": "",
"model": "",
"serial": "",
"transcript": "",
"manufacture_date": ""
}
}
]
}
}
}
}
Getting Documentation from Multiple Jobs
Similar to a combination of getJobSummary
and listJobs
, this query returns a list of documentation items based on filter criteria that are applied to the parent jobs. See the notes for those queries to learn more about supplying filters to the request (see listJobs
) or the traits and properties of the response (see getJobSummary
).
Note: includeAllSteps
functions here as it does for getJobSummary
.
query ListDocumentation {
listDocumentation(
input: { nextToken: "", filter: { jobLocation: "wisconsin" } }
) {
documentationConnection {
items {
jobId
workflowJobId
derivedData {
make
model
}
}
nextToken
}
}
}
Example Response
{
"data": {
"listDocumentation": {
"documentationConnection": {
"items": [
{
"jobId": "cjob-68b7bfda-cb57-44136fa3-32e0485abf42-C095-943ccb2e2061",
"workflowJobId": "wjob-3dc06f0c-cb57-44136fa3-20a5454e88cc-C095-3bb88d3b0eb5",
"derivedData": null
},
{
"jobId": "cjob-68b7bfda-cb57-44136fa3-32e0485abf42-C095-943ccb2e2061",
"workflowJobId": "wjob-3dc06f0c-cb57-44136fa3-20a5454e88cc-C095-3bb88d3b0eb5",
"derivedData": null
},
{
"jobId": "cjob-7031ff0b-cb57-44136fa3-a8894fb0953f-C095-6a96f7ebe2cc",
"workflowJobId": "wjob-5804b376-cb57-44136fa3-e506465ea0b9-C095-9163a7251dd2",
"derivedData": {
"make": "trane",
"model": "test"
}
}
],
"nextToken": ""
}
}
}
}
Deleting a Job
Example Request
The following mutation will delete the job with the given ID, if that job exists. There are no extra input parameters for this mutation. An id
is all that is required.
mutation {
deleteJob(input: { id: "a-vision-job-id" }) {
id
}
}
Example Response
This is the response shape you will get, even if you attempt to delete a job that doesn't exist. You will not get any NotFound
error with a non-existent job.
{
"data": {
"deleteJob": {
"id": "a-vision-job-id"
}
}
}
Getting a List of Job IDs for an Integration Entity ID
If an integrationEntityId
was set when a job was created (such a when a job is created via a Deep Link) , then that job can be retrieved using the getJobIdsByIntegrationEntityId
query.
Since we do not enforce uniqueness on that field, this query returns a list to account for more than one job having been created with the same integrationEntityId
.
We recommend that you store our Job IDs in your system so that you can do a simple getJob
in the future whenever you need to access a job from our API.
Example Request
See the GetJobIdsInput schema for the full details.
query GetJobIdsByIntegrationEntityId {
getJobIdsByIntegrationEntityId(
input: {
integrationEntityId: {
namespace: "AtLeast10Characters"
id: "UniqueWithinYourSystem"
}
}
) {
jobIds
}
}
Example Response
Note that the response here, as described above, is an array of job IDs.
Although the most common scenario should be that you get a single-item array, your code should account for cases where more than one job ID is return.
{
"data": {
"getJobIdsByIntegrationEntityId": {
"jobIds": ["job-id9406b85d48c45b4808c8566197f905f"]
}
}
}
Technical Details About Workflows
Internally, workflows are referred to as Workflow Jobs while the outer, organizing job is referred to as a Container Job. The workflowJobId
field returned by getJobSummary
reveals this naming convention.
All of the actions described in this document operate on Container Jobs. Some details about Workflow Jobs can can be retrieved via getJobSummary and getJob, but they cannot be created or modified via this API. As a result, only Container Job IDs are valid to be used in mutations. Any attempt to modify a Workflow Job will result in a "Not Found" error -- use the Container Job ID instead.
Prior to the introduction of the multiple workflow feature, a single job filled both the container and workflow roles. As a result, on records that predate the introduction of this feature the top level id
returned by getJobSummary may be identical to the workflowJobId
values returned for any documentation it contains. This represents the dual-role nature of those older records. These older records can only ever have a single workflow.