> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firstresonance.io/llms.txt
> Use this file to discover all available pages before exploring further.

# ion-api actions reference

> Every action the ion-api component provides for reading, writing, and searching ION data from a Workflow Builder flow.

The **ion-api** component is how a Workflow Builder flow reads or writes ION data. See [Create a flow](/automate-with-ion/workflow-builder/create-a-flow) for how to add it to a flow and configure its connection. This page covers every action it provides, grouped by what they do. For anything not covered by a dedicated action, use **Run GraphQL Query** to run any query or mutation directly.

## Run a query or mutation directly

| Action            | What it does                                                                                                               |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Run GraphQL Query | Runs any GraphQL query or mutation against ION, with a **Connection**, **GraphQL Query**, and **GraphQL Variables** field. |

Reach for a dedicated action below when one exists: it gives you a real form to fill in instead of a hand-written query, and it already accounts for the behaviors described on this page. Run GraphQL Query is the fallback for anything a dedicated action doesn't cover.

## Set a custom attribute's value

Nine actions set one custom attribute's value on one record. Each takes a **Connection**, the record's numeric ID, the attribute's **Key**, a **Value**, and an **Etag**, and returns the updated attribute.

| Action                               |
| ------------------------------------ |
| Update Run Attribute                 |
| Update Purchase Order Attribute      |
| Update Purchase Order Line Attribute |
| Update Issue Attribute               |
| Update Part Attribute                |
| Update Part Inventory Attribute      |
| Update Supplier Attribute            |
| Update Run Step Attribute            |
| Update Part Kit Attribute            |

Leave **Etag** blank to create an attribute that doesn't exist yet.

These actions set an attribute's **value**. Which attributes exist on an entity type at all is separate, org-level configuration. Read it with **Get Organization** (under `settings.<entity>.attributes`) and change it with the two actions below.

<Note>
  **Update Run Step Attribute** only works while the run step is in **REDLINE** status; every other status rejects the write. Put the step into redline, set the attribute, then submit and approve the redline to make the step workable again.
</Note>

## Configure which attributes exist

Two actions change an entity type's attribute **schema**, which attributes exist and what type they are, not any one record's value.

| Action                                             | What it configures                                       |
| -------------------------------------------------- | -------------------------------------------------------- |
| Update Organization Purchase Order Attributes      | The custom attributes available on purchase orders.      |
| Update Organization Purchase Order Line Attributes | The custom attributes available on purchase order lines. |

<Warning>
  Run **Get Organization** first and pass its `id` and `_etag` into these actions, not the purchase order's. The etag these actions check is the **organization's**, and a stale one fails the step.
</Warning>

Leave **Options** blank to leave an attribute's existing choices alone. These actions send nothing for that field when it's blank, rather than an empty list, so an untouched field can never wipe out a live attribute's options.

## Get, create, update, and delete records

| Action                               | Type   | Notes                                                                                                                                                 |
| ------------------------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Get Run                              | Get    | By ID. Returns nothing if no such run exists.                                                                                                         |
| Create Run                           | Create | From a procedure, against existing inventory or with new inventory.                                                                                   |
| Update Run                           | Update | Requires the run's current **Etag**.                                                                                                                  |
| Get Run Step                         | Get    | By ID. Includes the statuses it can move to next.                                                                                                     |
| Update Run Step                      | Update | Changes the step's status. Requires the step's current **Etag**, and confirms the status actually changed.                                            |
| Get Issue                            | Get    | By ID.                                                                                                                                                |
| Create Issue                         | Create | Optionally linked to a run step, part inventory record, or purchase order line.                                                                       |
| Get Purchase Order                   | Get    | By ID. Includes its lines and attributes.                                                                                                             |
| Create Purchase Order                | Create | Add lines afterward with Create Purchase Order Line.                                                                                                  |
| Update Purchase Order                | Update | Requires the order's current **Etag**, and confirms a status change actually took effect.                                                             |
| Get Purchase Order Line              | Get    | By ID.                                                                                                                                                |
| Create Purchase Order Line           | Create | Also returns any sibling lines ION renumbered as a result.                                                                                            |
| Delete Purchase Order Line           | Delete | Requires the line's current **Etag**.                                                                                                                 |
| Get Part                             | Get    | By ID. The way to read a part's current **Etag** before using Update Part.                                                                            |
| Update Part                          | Update | Requires the part's current **Etag**.                                                                                                                 |
| Get Part Inventory                   | Get    | By ID.                                                                                                                                                |
| Get Receipt                          | Get    | By ID. Includes its receipt items.                                                                                                                    |
| Get Supplier                         | Get    | By ID. The way to read a supplier's current **Etag** before using Update Supplier.                                                                    |
| Create Supplier                      | Create | Only a name is required.                                                                                                                              |
| Update Supplier                      | Update | Requires the supplier's current **Etag**.                                                                                                             |
| Get MBOM                             | Get    | By ID. The way to read an mBOM's current **Etag** before using Update MBOM.                                                                           |
| Create MBOM                          | Create | Copies the part's previous mBOM by default.                                                                                                           |
| Update MBOM                          | Update | Changes the mBOM's status. Requires its current **Etag**, and confirms the status actually changed.                                                   |
| Create Multiple MBOM Items           | Create | Adds a batch of mBOM lines from a JSON array.                                                                                                         |
| Delete MBOM Item                     | Delete | Requires the item's current **Etag**. Deletes one mBOM line, not the whole mBOM.                                                                      |
| Get Rule                             | Get    | By ID.                                                                                                                                                |
| Create Rule                          | Create | Creates a validation rule.                                                                                                                            |
| Update Rule                          | Update | Requires the rule's current **Etag**.                                                                                                                 |
| Delete Rule                          | Delete | Requires the rule's current **Etag**. Consider Update Rule with **Enabled** set to No instead, to switch a rule off without losing it.                |
| List Rules                           | List   | No inputs required. Returns every rule as a flat array.                                                                                               |
| Get Organization                     | Get    | Your organization's settings, including its custom-attribute schema per entity. Leave the ID blank for your own organization.                         |
| Create File Attachment               | Create | Attaches a file to an ION record.                                                                                                                     |
| Delete File Attachment               | Delete | Removes a file attachment.                                                                                                                            |
| Dispatch Notification                | Create | Sends an in-app notification to one user.                                                                                                             |
| Get Part Kit                         | Get    | By ID. Includes its kit items.                                                                                                                        |
| Create Part Kit                      | Create | Creates an empty kit. Add contents with Create Part Kit Item or Create Part Kit Items.                                                                |
| Update Part Kit                      | Update | Requires the kit's current **Etag** (not one of its items' etags).                                                                                    |
| Delete Part Kit                      | Delete | Requires the kit's current **Etag**. Deletes the kit and its lines, not the parts in it.                                                              |
| Create Part Kit Item                 | Create | Adds one kit line: which part, and how many.                                                                                                          |
| Create Part Kit Items                | Create | Adds a batch of kit lines from a JSON array. Prefer this over repeating Create Part Kit Item when filling a new kit.                                  |
| Update Part Kit Item                 | Update | Requires the item's current **Etag**. Can't move an item to a different kit, see below.                                                               |
| Delete Part Kit Item                 | Delete | Requires the item's current **Etag**. Deletes the kit line, not the part.                                                                             |
| Create MRP Job                       | Create | Starts a material-requirements-planning run. This is asynchronous, so poll Get MRP Job for the result.                                                |
| Get MRP Job                          | Get    | By ID. Read **Status** to see whether it's still running, and **Failed Reason** if it ended in failure.                                               |
| Get Plan                             | Get    | By ID. Includes the plan's own fields, not its demand lines, which are unbounded.                                                                     |
| Create Plan                          | Create | Creates a demand plan. Add lines with Add Input To Plan.                                                                                              |
| Add Input To Plan                    | Create | Adds one demand line to a plan.                                                                                                                       |
| Get Redline                          | Get    | By ID.                                                                                                                                                |
| Get Purchase Order Approval Level    | Get    | By ID. Also returns the organization-level approval template it came from.                                                                            |
| Delete Purchase Order Approval Level | Delete | Requires the level's current **Etag**. Deletes the per-order approval level, not the organization template.                                           |
| Get ABOM Installation                | Get    | By **two** IDs: the build requirement and the part inventory record. See [Get ABOM Installation needs two IDs](#get-abom-installation-needs-two-ids). |
| Get User                             | Get    | By ID. Falls back to the user's email if they have no display name set.                                                                               |
| Get User Notifications               | Get    | A user's in-app notifications.                                                                                                                        |

## Search and list records

| Action                    | Returns                                       |
| ------------------------- | --------------------------------------------- |
| Search Parts              | Parts matching your filters.                  |
| Search Purchase Orders    | Purchase orders matching your filters.        |
| Search Suppliers          | Suppliers matching your filters.              |
| Search Part Inventories   | Part inventory records matching your filters. |
| Search Users              | Users matching your filters.                  |
| Get User Notifications    | One user's in-app notifications.              |
| List Locations            | Every location.                               |
| List Teams                | Every team, not including its members.        |
| List Roles                | Every role, not including who holds it.       |
| List Units Of Measurement | Every configured unit of measure.             |
| List Procedures           | Every procedure, not including its steps.     |

These twelve actions share the same conventions:

* **Every typed filter is an exact match.** Fill in a field and it becomes an exact-match filter; leave it blank and it's left out, so it never narrows the search. Date fields come as **After**/**Before** pairs.
* **Additional Filters** is a JSON field for anything the typed filters don't cover, such as partial matches or filtering on a related record. A key you set there overrides the matching typed filter.
* **Max Results** (default 100) caps how many records come back. Without a cap, a broad search could return an entire catalog in one step's output. List Rules has no such cap, since the number of rules an organization has is naturally bounded.
* **Sort** (blank means oldest-first by ID) sets the row order, picked from a live list of that record type's sortable fields. See [Sort your results safely](#sort-your-results-safely) for what it does and doesn't guarantee.

Every one of these actions also logs the exact filter it sent, in the step's execution log, useful for confirming what actually reached ION when a search returns fewer results than you expect.

<Note>
  **Max Results keeps the OLDEST N by default, not the newest.** Set **Sort** to `_CREATED_DESC` (or `ID_DESC`) to cap the newest N instead. A `_DESC` on any other field only reverses that field's own order. For "everything that changed since my last run," filter on **Created After** or **Updated After** instead of sorting; it needs no multi-page read at all, which matters most on Get User Notifications, since that feed only ever grows.
</Note>

A few relations are deliberately left out of these actions, since they'd make the response unbounded: List Teams and List Roles don't include membership (use Search Users with a team or role filter instead), List Procedures doesn't include step content, and List Locations doesn't include its parent, all descendants, or its inventory.

<Info>
  **Locations, Teams, and Units Of Measurement** each also back a dropdown elsewhere in this component, separate from their List action here: the dropdown is for picking one value on another action's field, while the List action returns full records for your flow to use. You'll likely use both, for different purposes.
</Info>

## How inputs behave

A few conventions apply across most actions on this page:

* **Blank means "leave alone."** An optional field you don't fill in is left out of the update entirely, so it never clears an existing value. The one exception is **Attribute Value** on the attribute actions above: there, blank clears the attribute, since setting its value is the entire point of those actions.
* **Yes/No fields have a third option: "leave unchanged."** These render as a dropdown rather than a toggle, specifically so an untouched field doesn't get sent as an explicit "No" and overwrite an existing value.
* **Additional Fields** is a JSON field on most create and update actions, for any field this page's typed inputs don't expose. A key you set there overrides the matching typed field. A handful of actions don't have this field at all: those are the ones whose typed inputs already cover every field ION accepts.
* **Custom attributes can only be set on create, not on update.** Create actions take an **Attributes** field. To change an attribute afterward, use that entity's own Update Attribute action (in [Set a custom attribute's value](#set-a-custom-attributes-value) above) instead of its Update action.
* **Etags.** Every update and delete action requires the record's current etag. Read it from the matching Get or Search action immediately beforehand; an out-of-date etag fails the step with a concurrency error.
* **Values are checked, not guessed at.** An ID must be a whole number, a date must be a real date, and a Yes/No field must be an actual yes or no. Each fails the step with a clear message rather than silently being reinterpreted into something ION happens to accept.

## Things to know before you build

### File attachments are rejected on a run that's already complete

Create File Attachment fails if the run it's attaching to has already reached **Complete** status. There's no override. Attach the file while the run is still open, or reopen the run first if you need to attach one afterward.

### A status change that's accepted isn't always a status change that happened

Update Run Step, Update Purchase Order, and Update MBOM can each report success while the record's status doesn't actually move: an approval rule or validator can silently refuse the transition. All three actions check the record's status after the call and fail the step by name if it didn't change as asked. Update Run Step and Update Purchase Order also return the statuses each record can move to next, so you can check what's valid before attempting a transition.

### Some deletes are deliberately not exposed

ION's API can delete more record types than this component exposes. Deleting a supplier, a purchase order, or an entire mBOM outright is left out on purpose: each is destructive to master data, a financial record, or an entire bill of materials, and the judgment was that this class of delete should happen in ION's own UI, where the consequences are visible, rather than from an unattended flow. Use Run GraphQL Query if you deliberately need one of these.

### A part kit's items can't move between kits

There's no action that reparents a kit item to a different kit. To move one, delete it from its current kit with Delete Part Kit Item and re-create it on the other with Create Part Kit Item.

### Get ABOM Installation needs two IDs

Unlike every other Get action on this page, Get ABOM Installation is identified by a pair of IDs, the build requirement and the part inventory record, rather than one. An as-built installation doesn't have a single-column identity of its own: it's defined by which inventory went into which build requirement.

### Sort your results safely

Every List and Search action's **Sort** input appends the record's ID as a tiebreaker automatically, in the same direction you picked. This makes tied rows come back in a consistent order, but it does **not** make every sort safe across a multi-page read: only the default (blank, oldest-first by ID) guarantees that a record created while your flow is still reading won't be skipped or duplicated. A non-default sort is safe on a single page (Max Results at 100 or below) or on data nothing is actively changing. For "everything that changed since my last run" over an actively-changing set of records, filter on **Created After** or **Updated After** instead of changing Sort.

## Reading Run GraphQL Query's output

Run GraphQL Query doesn't fail the step when ION returns a GraphQL error. It always succeeds, so your flow can inspect the error and decide what to do next rather than the step aborting outright.

The response places the query's own data at the top level of the output, alongside two extra fields: `errors` and `extensions`.

A successful query has `errors` set to nothing:

```json theme={null}
{
  "purchaseOrders": [{ "id": "1" }],
  "errors": null,
  "extensions": null
}
```

A failed one has entries under `errors`, each with a `code` you can branch on, for example, refetching a record's latest etag and retrying on a concurrency error, or surfacing the message to whoever's watching the flow on a validation error:

```json theme={null}
{
  "updatePurchaseOrderLine": null,
  "errors": [
    {
      "message": "Etag does not match the server's.",
      "path": ["updatePurchaseOrderLine"],
      "extensions": { "code": "CONCURRENCY_ERROR" }
    }
  ],
  "extensions": null
}
```

## Related

* [Create a flow](/automate-with-ion/workflow-builder/create-a-flow)
* [Test a flow](/automate-with-ion/workflow-builder/test-a-flow)
* [Workflow Builder overview](/automate-with-ion/workflow-builder)
