Scripting API

Introduction

Scripts in Major Tom are extremely powerful. They allow you to automate routine actions, react to Events, and generally add intelligence to your ground operations.

The Major Tom Scripting API uses GraphQL, an expressive typed query language. You may send GraphQL queries and mutations over raw HTTP POST requests or with a GraphQL client in your language of choice. All responses will be in JSON, in accordance with the GraphQL spec.

To use the API, first create a new Script in Major Tom, which will provide you with a Script Authentication Token. You will provide this token in the X-Script-Token HTTP header whenever you interact with the Scripting API.

Response Codes

If the provided Script Authentication Token is invalid, you will receive a 403 response.

If the Script is temporarily disabled, as is possible to do in the Major Tom UI, you will receive a 422 response.

If your instance of Major Tom requires HTTP Basic Access Authentication and you have provided an invalid one, you will receive a 401.

If your instance of Major Tom is undergoing maintenance, or is otherwise temporarily unavailable, you may receive a 404 or 503 error. You should wait a few seconds and try again.

RATE_LIMIT_MESSAGE
If your Script has reached its request rate limit, you will receive a 420 response. When receiving this error, look at
the X-RateLimit-RetryAfter header to see how many seconds you must wait before the next request will be accepted.
After any request, you can also look at the X-RateLimit-Rate and X-RateLimit-Remaining headers to see the average
allowed request rate per minute and number of requests remaining before a rate limit will be triggered.

API Documentation

For documentation and an interactive playground, click the "GraphQL Playground" button in the upper right of this documentation section. From there, click on "Docs" on the far right hand side of the interface.

Code Examples

For code examples and a Python API wrapper, please see our repo of Example Major Tom Scripts.

Executing Queries

As an example, let's request information about the current Script from the Major Tom Scripting API using a GraphQL query:

1 2 3 4 5 6 7 8 9 10 query CurrentAgent { agent { type script { id name mission { id } } } }

You can run this query with curl, a client of your choice, or with the GraphQL Playground button in the upper right of this documentation section. Here is how you would run it with curl:

1 2 3 4 curl '<https://app.majortom.cloud/script_api/v1/graphql'> \ -H 'X-Script-Token: 4f5453...' \ -H 'Content-Type: application/json' \ --data-binary '{"query":"query CurrentAgent { agent { type script { id name mission { id } } } }"}'

The response will look like:

1 {"data":{"agent":{"type":"Script","script":{"id":"1","name":"Script","mission":{"id":"2"}}}}}

Your response data will be in the data top-level JSON key.

System and Query Errors

If there are any system or query errors, such as invalid input, a top-level JSON errors key will be populated.
Here we request an unknown field on agent called name:

1 2 3 4 curl '<https://app.majortom.cloud/script_api/v1/graphql'> \ -H 'X-Script-Token: 4f5453...' \ -H 'Content-Type: application/json' \ --data-binary '{"query":"query CurrentAgent { agent { name } }"}'
1 {"errors":[{"message":"Field 'name' doesn't exist on type 'Agent'","locations":[{"line":1,"column":35}],"fields":["query CurrentAgent","agent","name"]}]}

Executing Mutations

In GraphQL, mutations are how you perform actions.

Let's use the executeCommand mutation to send a queued Command to its Gateway:

1 2 3 4 5 curl '<https://app.majortom.cloud/script_api/v1/graphql'> \ -H 'X-Script-Token: 4f5453' \ -H 'Content-Type: application/json' \ --data-binary '{"query":"mutation ExecuteCommand($id: ID!) { executeCommand(input: { id: $id }) { success notice errors command { id state } } }", "variables": { "id": 1 } }'

This is our first example using GraphQL variables, provided in the variables parameter.
GraphQL variables are typed. This one must be an ID, and must not be null. Refer to the API documentation and schema in the GraphQL Playground for details.

In this example, if Command 1 exists and is in the queued state, the response will be:

1 {"data":{"executeCommand":{"success":true,"notice":"Command execution sent","errors":[],"command":{"id":"1","state":"waiting_for_gateway"}}}}

However, if Command 1 cannot be found, an error will be returned.

Mutation Errors

If a data validation or integrity error is encountered when performing a mutation, the errors JSON key
inside of data and the mutation return field will be populated. For example, if Command 1 does not exist in the previous example:

1 {"data":{"executeCommand":{"success":false,"notice":"Unable to execute queued Command","errors":["No command with id 1 found."],"command":null}}}

Filters, Sorting, and Pagination

Many fields in the Scripting API return collections of data, paginated with the Relay cursor specification. This means that, to get the next page of data given the previous one, you provide the ending cursor from the previous page. For example, here's a
query to request the first page of 2 Commands that are either completed, failed, or cancelled, ordered by the time of their last update.

Query:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 query CommandsQuery($systemId: ID!, $states: [CommandState!], $first: Int!, $afterCursor: String) { system(id: $systemId) { commands(filters: { state: $states }, orderBy: { sort: UPDATED_AT, direction: DESC }, first: $first, after: $afterCursor) { nodes { id commandType state } pageInfo { hasNextPage endCursor } totalCount } } }

Variables:

1 { "systemId": 1, "states": ["completed", "failed", "cancelled"], "first": 2, "afterCursor": null },

The response will look like:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 { "data": { "system": { "commands": { "nodes": [ { "id": "5", "commandType": "calibrate_thermometer", "state": "completed" }, { "id": "4", "commandType": "jettison_payload", "state": "completed" } ], "pageInfo": { "hasNextPage": true, "endCursor": "Mg==" }, "totalCount": 5 } } } }

There are a total of 5 results, but we only received the first 2, so hasNextPage is true.
To get the next page, you'd provide the endCursor from this page:

1 { "systemId": 1, "states": ["completed", "failed", "cancelled"], "first": 2, "afterCursor": "Mg==" }

And so on.