# The manual for the New ION Experience has been released.

Check it out! [Link](https://docs.firstresonance.io/)

For the complete documentation index, see [llms.txt](https://manual.firstresonance.io/llms.txt). This page is also available as [Markdown](https://manual.firstresonance.io/api.md).

## Developers: How to create an API Key:
[Direct link to heading](https://manual.firstresonance.io/api#developers-how-to-create-an-api-key)

[API Keys](https://manual.firstresonance.io/api/api-keys)

## Getting started
[Direct link to heading](https://manual.firstresonance.io/api#getting-started)

To get started with the ION API, we recommend [**learning about GraphQL**](https://manual.firstresonance.io/api/about-graphql) and playing around with the [**interactive API explorer**](https://manual.firstresonance.io/api/interactive-api-explorer) in the ION web application.

Once you've played around a bit, you can request an API key and access token through this [**self-service process**](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
[Direct link to heading](https://manual.firstresonance.io/api#example-queries)

### Change Run Step status
[Direct link to heading](https://manual.firstresonance.io/api#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.

```json
{
  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

```json
{
  "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.

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

Query variables:

```json
{
  "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
[Direct link to heading](https://manual.firstresonance.io/api#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.

#### Creating a Redline
[Direct link to heading](https://manual.firstresonance.io/api#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.

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

Query variables:

```json
{
    "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.

#### Editing a Step in Redline
[Direct link to heading](https://manual.firstresonance.io/api#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:

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

#### Adding a new Step (Redlined step)
[Direct link to heading](https://manual.firstresonance.io/api#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.

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

Query variables:

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

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

#### Editing the Graph
[Direct link to heading](https://manual.firstresonance.io/api#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.

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

Query variables:

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

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

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

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

Query variables:

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

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

#### Finishing a Redline
[Direct link to heading](https://manual.firstresonance.io/api#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.

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

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

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.

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

#### Canceling a Redline
[Direct link to heading](https://manual.firstresonance.io/api#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.

```json
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)

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

### File Attachments and Assets
[Direct link to heading](https://manual.firstresonance.io/api#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
[Direct link to heading](https://manual.firstresonance.io/api#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.

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

### Assets
[Direct link to heading](https://manual.firstresonance.io/api#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
[Direct link to heading](https://manual.firstresonance.io/api#create-asset)

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

```json
{
    "entityId": 1
}
```

#### Delete Asset
[Direct link to heading](https://manual.firstresonance.io/api#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.

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

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

### File Attachments
[Direct link to heading](https://manual.firstresonance.io/api#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.

#### Create File Attachment
[Direct link to heading](https://manual.firstresonance.io/api#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.

```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:

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

```json
{
    "entityId": 2
}
```

#### Delete File Attachment
[Direct link to heading](https://manual.firstresonance.io/api#delete-file-attachment)

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

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

### Unattached Files
[Direct link to heading](https://manual.firstresonance.io/api#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
[Direct link to heading](https://manual.firstresonance.io/api#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.

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

```json
{
    "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.

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

```json
{
    "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.

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

```json
{
    "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.

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

```json
{
    "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.

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

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

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

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

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

### Issues
[Direct link to heading](https://manual.firstresonance.io/api#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.

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.

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

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

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

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

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

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

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

```json
{
    "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.
