# API

## Developers: How to create an API Key:

{% content-ref url="/pages/-MNQBbma7O8G6PHKpYF7" %}
[API Keys](/api/api-keys.md)
{% endcontent-ref %}

## Getting started

To get started with the ION API, we recommend [<mark style="color:purple;">**learning about GraphQL**</mark>](/api/about-graphql.md) and playing around with the [<mark style="color:purple;">**interactive API explorer**</mark>](/api/interactive-api-explorer.md) in the ION web application.

Once you've played around a bit, you can request an API key and access token through this [<mark style="color:purple;">**self-service process**</mark>](https://manual.firstresonance.io/api/access-tokens).

Remember, this API key gives access to your private ION data and allows automations to "act as you", so treat it like a password and never share it with third parties.

If you plan to deploy a large number of automations, we recommend setting up an Ion service account— such as <ion@yourdomain.com>— so you can differentiate automation actions from user actions.

## Example queries

### Change Run Step status

Find the id of the runStep you want to change, replacing the run id below with the run ID you are changing.

```graphql
{
  run(id: 372) {
    steps {id status _etag}
  }
}
```

You will see a response like the following. Notice the run step that is failed. That's the one we want to change in the subsequent mutation

```javascript
{
  "data": {
    "run": {
      "steps": [
        {
          "id": 1133,
          "status": "todo",
          "_etag": "b0fe83a65319455685e760614f585099"
        },
        {
          "id": 1134,
          "status": "todo",
          "_etag": "9e1aa0e8909345cdb63e25289b7ce916"
        },
        {
          "id": 1135,
          "status": "failed",
          "_etag": "e3bb9a721745409cb4c20ca3aa1d591a"
        }
      ]
    }
  }
}
```

Once you've found the `id` and `_etag` for the runStep you want to move back to "todo", use the following mutation to affect the change. You will also need to define the variables in the "Query Variables" section below the query explorer.

```graphql
mutation UpdateRunStep($input: UpdateRunStepInput!) {
  updateRunStep(input: $input) {
    runStep {
        id status
    }
  }
}
```

Query variables:

```javascript
{
  "input": {
    "id": 1135,
    "status": "todo",
    "etag": "e3bb9a721745409cb4c20ca3aa1d591a"
  }
}
```

After you submit the query, you will get a response containing the run step's ID and the newly set status.

### Redlines

Redlines are used to demarcate any deviation a particular run has from the procedure that created the run. Redlining a step allows an engineer to edit a step or its fields. Redlining also allows for the creation of additional steps and modifying the dependency graph of the steps downstream from the step being redlined. This powerful feature allows an engineer to alter branches of a run, while not affecting other branches which are running concurrent to the step or steps being redlined.  To show how redlines work in ION we will imagine a run with the execution graph below.

![](/files/-M-qWMLbnUvEamPEXFvg)

#### Creating a Redline

A redline is created by updating a step to the status of redline. You must have the "engineer" role to do this.

```graphql
mutation UpdateRunStep($input: UpdateRunStepInput!) {
    updateRunStep(input: $input) {
        runStep {
            id status title _etag content redlines { diff id }
        }
    }
}
```

Query variables:

```graphql
{
    "input": {
        "id": 4,
        "status": "redline",
        "etag": "etag"
    }
}
```

Now the run execution graph has entered a redline state, steps 2 and 3 are still able to be executed since they are on a different branch than the redlined step. All steps downstream from the step in `redline` cannot be executed and have entered a special `dag_modifiable` (previously, `content_locked`) state which will be described in further detail below.

![](/files/-M-qW1mPYNQXfJXg5Wp9)

#### Editing a Step in Redline

Now that step 4 is in redline, we can edit the step by altering its content, title, or fields. Using the above defined UpateRunStep mutation with the below variables we edit the title of the step.

Query variables:

```graphql
{
    "input": {
        "id": 4,
        "title": "redline",
        "etag": "new_etag"
    }
}
```

#### Adding a new Step (Redlined step)

We can also create a new step within the run. Below we will discuss how to add this new step to run execution graph. The new step will be created with the status of redline, so that it can be altered after creation.

```graphql
mutation CreateRunStep($input: CreateRunStepInput!) {
    createRunStep(input: $input) {
        step {
            id title content _etag
        }
    }
}
```

Query variables:

```graphql
{
    "input": {
        "title": "step created in redline",
        "content": "new step content",
        "runId": 1
    }
}
```

The run execution graph would now look like the diagram below.

![](/files/-M-qWB1mp--ClIBafJzm)

#### Editing the Graph

When a step is put in redline, all of its downstream steps which have the status of `todo` or `in_progress` are put into a special status known as content\_locked. This `dag_modifiable` status prevents a user from altering any of the content within a run step, but does allow the user to edit its graph edges. We will use the below mutations to add the newly created step into our run execution graph.&#x20;

```graphql
mutation CreateRunStepEdge($input: CreateStepEdgeInput!) {
    createRunStepEdge(input: $input) {
        stepEdge {
            id stepId upstreamStepId  
        }
    }
}
```

Query variables:

```graphql
{
    "input": {
        "stepId": 8,
        "upstreamStepId": 6
    }
}
```

```graphql
{
    "input": {
        "stepId": 7,
        "upstreamStepId": 8
    }
}
```

After creating the new edges, we will remove the existing edge between steps 6 and 7.

```graphql
mutation DeleteRunStepDAG($id: ID!, $etag: String!) {
    deleteRunStepEdge(id: $id, etag: $etag) {
        id
    }
}
```

Query variables:

```graphql
{
    "id": 6,
    "etag": "edge_etag"
}
```

Now step 8 is within the run execution graph and the current state is reflected below.

![](/files/-M-qW6udcEOvcIGZCWFE)

#### Finishing a Redline

A redline is completed by updating the status of a run step being redlined to todo. Completing the redline will allow the user to see a diff of what was altered during the redline. Ending a redline also converts all downstream steps which have the status `dag_modifiable`back into the status of `todo`. Using the `UpdateRunStep` mutation defined above with the below query variables will finish a redline.

```graphql
{
    "input": {
        "id": 4,
        "status": "todo",
        "etag": "current_step_etag"
    }
}
```

Steps 4, 5, and 6 have moved back to the status of todo.

![](/files/-M-qWF2xVK4seVTB8t36)

Because there are two steps in redline, we also have to update step 8 to a status of todo so that the run no longer has any steps in redline.

```graphql
{
    "input": {
        "id": 8,
        "status": "todo",
        "etag": "current_step_etag"
    }
}
```

![](/files/-M-qWJC8l_v1WU8zqEM_)

#### Canceling a Redline

If no edges were added or removed within a redline, then that redline can be canceled and all changes will be reverted to their original state. If the execution graph edges have been altered than the user must revert the edges to their original state before a redline can be canceled. Below is the mutations to cancel a redline.

```graphql
mutation CancelRunStepRedline($id: ID!, $etag: String!) {
    cancelRunStepRedline(id: $id, etag: $etag) {
        runStep { id title status content }
    }
}
```

Query variables (`id`  and `etag` of the run step) &#x20;

```graphql
{
    "id": 4, 
    "etag": "new_etag" 
}
```

### File Attachments and Assets

The ION API differentiates between files into two different types: assets and file attachments. The driving force behind this separation is to provide greater access control and validation to file handling. For instance when a procedure is instantiated as a run, all the procedure steps' assets that were created by an engineer are copied over to that run, and any files attached by an operator during the run execution are saved as file attachments. Another way to think about the difference is that assets are *defined* while file attachments are *collected*.

#### Getting a File

Getting a file attachment or asset can be done with the same query which returns the filename and other meta data about the file. It does not return the file itself, but along with the metadata it returns a signed URL which provides temporary and secure access to the file within the ION file storage system.&#x20;

```graphql
query GetFile {
    fileAttachment(id: 1) {
        id url s3Bucket s3Key filename
    }
}
```

### Assets

All asset mutations require the user to have the "engineer" role. The two places assets can be added and deleted are on procedure steps and run steps when the run step is in redline. To create an asset you can make a request with the `entityId` of the object to attach the asset to. Below is an example of creating an asset for a step with entity id 1. The response will contain a secure link to upload your file to using the HTTP `PUT` method.

#### Create Asset

```graphql
mutation CreateAsset($input: CreateFileAttachmentInput!) {
    createAsset(input: $input) {
        fileAttachment { id s3Key entityId }
    }
}
```

```graphql
{
    "entityId": 1 
}
```

#### Delete Asset

Creating an asset creates a file object with an ID. To delete an asset, use the below mutation with the new file object id and the steps entity id.

```graphql
mutation DeleteAsset($input: DeleteFileAttachmentInput!) {
    deleteAsset(input: $input) {
        fileAttachmentId entityId
    }
}
```

```graphql
{
    "fileAttachmentId": 2, 
    "entityId": 1 
}
```

### File Attachments

File attachments are used to attach files to a run or run step during a run's execution, as well as to parts.&#x20;

#### Create File Attachment

Uploading files directly using our API has been deprecated and will be removed in the future. The preferred way is to use a signed s3 URL which allows a user to upload a file directly to s3. This is done be creating a file attachment in our system and then using the returned URL to post the file to s3.&#x20;

```python
import requests
from urllib.parse import urljoin
import os
from dataclasses import dataclass


CREATE_FILE_ATTACHMENT = """
mutation CreateFileAttachment($input: CreateFileAttachmentInput!) {
    createFileAttachment(input: $input) {
        fileAttachment {
            id
            entityId
            filename
            contentType
            s3Bucket
        }
        uploadUrl
    }
}
"""

ION_API_URI = os.getenv("ION_API_URI")
HEADERS = {
    "Authorization": f"Bearer {os.getenv('ION_API_TOKEN')}",
    "Content-Type": "application/json",
}


@dataclass
class FileAttachmentInfo:
    """File Attachment dataclass."""

    name: str
    id: int
    upload_url: str
    content_type: str


def create_ion_file_attachment(
    file_name: str,
) -> FileAttachmentInfo:
    """Create file attachment object in ion."""
    resp = make_ion_request(
        request_json={
            "query": CREATE_FILE_ATTACHMENT,
            "variables": {
                "input": {
                    "filename": file_name,
                    "entityId": entity_Id,
                }
            },
        },
    )
    file_info_dict = resp["data"]["createFileAttachment"]
    return FileAttachmentInfo(
        id=file_info_dict["fileAttachment"]["id"],
        name=file_info_dict["fileAttachment"]["filename"],
        content_type=file_info_dict["fileAttachment"]["contentType"],
        upload_url=file_info_dict["uploadUrl"],
    )


def make_ion_request(request_json: dict) -> dict:
    """Make a request to ion graphql api and return response data."""
    with requests.post(
        urljoin(ION_API_URI, "graphql"),
        json=request_json,
        headers=HEADERS,
    ) as response:
        response.raise_for_status()
        response_json = response.json()
        return response_json


def upload_file_to_s3(file_attachment: FileAttachmentInfo, file_object: bytes):
    """Upload file to s3 with signed URL."""
    with requests.put(
        url=file_attachment.upload_url,
        headers={"Content-Type": file_attachment.content_type},
        data=file_object,
    ) as response:
        response.raise_for_status()


if __name__ == "__main__":
    file_name = 'file_document.txt'
    file_attachment = create_ion_file_attachment(file_name)
    with open(file_name, 'rb') as f:
        upload_file_to_s3(file_attachment, f.read())
```

Deprecated:

```graphql
mutation CreateFileAttachment($input: CreateFileAttachmentInput!) {
    createFileAttachment(input: $input) {
        fileAttachment { id s3Key entityId }
    }
}
```

```graphql
{
    "entityId": 2 
}
```

#### Delete File Attachment

```graphql
mutation DeleteFileAttachment($input: DeleteFileAttachmentInput!) {
    deleteFileAttachment(input: $input) {
        fileAttachmentId entityId
    }
}
```

```graphql
{
    "fileAttachmentId": 3, 
    "entityId": 2 
}
```

### Unattached Files

There are some situations, such as for the thumbnail of a part or a file for a run step field which require a file to be uploaded without attaching it to an object. To do this use the same create file attachment mutation above, but do not include an entity id. As is the case with other file uploads, you will receive a secure link to upload the file to using the HTTP `PUT` method.

### Reviews Requests and Reviews

Before a procedure is released it must go through a review process. The number of reviewers required to release a procedure can be set in an organization's settings. A procedure is put into review by changing its status to in\_review via an update procedure mutation.

```graphql
mutation UpdateProcedure($input: UpdateProcedureInput!) {
    updateProcedure(input: $input) {
        procedure {
            id title description status createdById
        }
    }
}
```

```graphql
{
    "id": 4, 
    "status": "in_review",
    "etag": "etag" 
}
```

Once a procedure is in review it cannot be edited. Review requests can be sent out with the following mutation. Each review request will also send out notification to the user whom you requested a review from.

```graphql
mutation CreateReviewRequest($input: ReviewRequestInput!) {
    createReviewRequest(input: $input) {
        reviewRequest { id reviewer { id } status entityId }
    }
}
```

```graphql
{
    "procedureId": 4, 
    "reviewerId": 1
}
```

Once a review has been requested that user can leave a review of pending, approved or rejected. To leave a review that is just a comment, use the status pending as it will neither reject nor approve the procedure.

```graphql
mutation CreateReview($input: CreateReviewInput!) {
    createReview(input: $input) {
        review { id organizationId status entityId }
    }
}
```

```graphql
{
    "procedureId": 4, 
    "status": "pending",
    "comment": "Just leaving a comment"
}
```

You cannot delete reviews, but if someone should not review a procedure you can delete their review request using the following mutation.

```graphql
mutation DeleteReviewRequest($id: ID!, $etag: String!) {
    deleteReviewRequest(id: $id, etag: $etag) { id }
}
```

```graphql
{
    "id": 1, 
    "etag": "etag"
}
```

Once all requested reviewers have approved, and the minimum number of reviewers required has been met than a procedure is automatically released.

You can list all the existing review requests with a few filters, the query below lists all requested reviews for a specific procedure.

```graphql
query GetReviewRequests($filters: ReviewsInputFilters) {
    reviewRequests(filters: $filters) {
        edges {
            node { id procedureId reviewerId status}
        }
    }
}
```

```graphql
{
    "procedureId": {
        "eq": 4
    }
}
```

Along with listing review requests, you can also list all the reviews.

```graphql
query GetReviews($filters: ReviewsInputFilters) {
    reviews(filters: $filters) {
        edges {
            node { id procedureId reviewerId comment status}
        }
    }
}
```

```graphql
{
    "procedureId": {
        "eq": 4
    }
}
```

### Issues

ION's issue system is rather flexible and has several controls within an organization's settings. There are settings to automatically create issues when a step fails, to prevent a step from being moved back to todo while open issues exist, and to set the number of approvals required till an issue can be marked as resolved. Below is an diagram that shows the non-conformance workflow of issues and each disposition type.&#x20;

![](/files/-M2ZiyJcrlU35K6vciRv)

You can use a series of filters to list current issues by status, disposition type, and a few other filters. The query below lists all issues related to a specific run step.

```graphql
query GetIssues($filters: IssueInputFilters, $sort: [IssueSortEnum]) {
    issues(sort: $sort, filters: $filters) {
        edges{node {
            id dispositionType causeCondition expectedCondition
            _etag title status disposition
            runStep { id status title }
        }}
    }
}
```

```graphql
{
    "runStepId": {
        "eq": 1
    }
}
```

To create an issue, you can use the following mutation. An issue is required to be linked to a run step.

```graphql
mutation CreateIssue($input: CreateIssueInput!) {
    createIssue(input: $input) {
        issue {
            id dispositionType causeCondition 
            expectedCondition disposition status
            _etag title runStep { id status title }
        }
    }
}
```

```graphql
{
    "runStepId": 4, 
    "dispositionType": "repair",
    "title": "Part instance needs fixing"
}
```

Once an issue is created you can update it's values using the following mutation.

```graphql
mutation UpdateIssue($input: UpdateIssueInput!) {
    updateIssue(input: $input) {
        issue {
            id dispositionType causeCondition expectedCondition
            disposition status _etag title
            runStep { id status title }
        }
    }
}
```

```graphql
{
    "disposition": "Rewire circuit",
    "causeCondition": "5V across the resistor",
    "expectedCondition": "3V across the resistor"
}
```

When an issue is created, its status defaults to "pending". An issue can have its status moved to in\_progress, in\_review  or resolved using the above update mutation. An issue has an approval process very similar to that of procedures. And an issue cannot be moved into resolved until all the requested reviews have been approved and the minimum number of approvals has been met. Lastly an issue cannot be resolved without setting its disposition type.

## Errors

Any errors happening on the API will be returned in the `errors` array following the GraphQL convention.

### Known errors

Known errors are extended with an error `code` and their payload will look as follows :

```javascript
{ message: 'Validation error', extensions: { code: 'VALIDATION_ERROR'}}
```

Clients using the API can use the error code to respond accordingly. We currently support the following error codes:

* `VALIDATION_ERROR` - Request to server is not valid (e.g. out of range numbers, parameters omitted, etc)
* `AUTHORIZATION_ERROR` - API caller is not authorized to perform action
* `NOT_FOUND_ERROR` - Resource is not found in the database
* `EXTERNAL_SERVICE_ERROR` - An external service is not responding as expected (e.g. email service)
* `CONCURRENCY_ERROR` - Multiple clients modifying the same resource against outdated data is prevented in the API

### Unexpected errors

Unexpected errors might not have an `extensions` attribute and therefore might no include an error  `code`.  Client's using the API should guard against this and handle unexpected errors accordingly.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://manual.firstresonance.io/api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
