> ## 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.

# Data model

> How ION's core entities relate to each other and how they map to terminology in adjacent systems (ERP, MES, PLM, QMS).

## Overview

When you integrate against ION, eight or nine entities cover about 95% of what you touch. Each entity below shows what it represents, how it connects to the others, and what it maps to in adjacent systems such as ERP, MES, PLM, and QMS.

The full schema lives in [`schema.graphql`](/schema.graphql) and the [API Playground](/api-reference/playground).

## Entity map

The core entities connect through these relationships:

```mermaid theme={null}
flowchart TD
  PartSubtype["PartSubtype (categorization, many-to-many)"]
  Part["Part (library item, design intent)"]
  PartInventory["PartInventory (stockable or serialized unit)"]
  Procedure["Procedure (template)"]
  PurchaseOrder["PurchaseOrder line items"]
  Run["Run (execution)"]
  RunBatch["RunBatch (batched runs)"]
  RunStep["RunStep"]
  BuildRequirement["BuildRequirement (aBOM line item)"]
  Issue["Issue (quality event)"]

  PartSubtype -->|tags| Part
  Part -->|instances of| PartInventory
  PartInventory --> Procedure
  PartInventory --> PurchaseOrder
  Procedure -->|template for| Run
  RunBatch --> Run
  Run --> RunStep
  RunStep --> BuildRequirement
  RunStep --> Issue
```

## Parts

A **Part** is the library entry. It holds a part number, description, revision, and the design intent. A Part is not the physical thing on the shelf. That physical thing is a `PartInventory`.

| Field              | Notes                                                                                  |
| ------------------ | -------------------------------------------------------------------------------------- |
| `id`, `partNumber` | Org-unique within `revision`. `partNumber` is your SKU.                                |
| `description`      | Free text.                                                                             |
| `revision`         | Engineering revision letter or string.                                                 |
| `partType`         | `PART` or `TOOL`.                                                                      |
| `trackingType`     | `SERIAL`, `LOT`, or unset (untracked).                                                 |
| `status`           | `RELEASED` or `ARCHIVED`.                                                              |
| `sourcingStrategy` | `MAKE`, `BUY`, or `DUAL_SOURCE`.                                                       |
| `purchaseType`     | `RECEIVABLE_INVENTORY`, `RECEIVABLE_NON_INVENTORY`, or `NON_RECEIVABLE_NON_INVENTORY`. |
| `partSubtypes`     | Many-to-many with `PartSubtype`. These are categorization tags.                        |
| `cost`, `leadTime` | Optional planning fields.                                                              |
| `revisedFromId`    | Links this revision to its predecessor.                                                |

Query a part:

```graphql theme={null}
query GetPart {
  part(id: "42") {
    id
    partNumber
    description
    revision
    partType
    trackingType
    status
    partSubtypes { id name }
  }
}
```

**Maps to:** "SKU" or "Item Master" in ERP. "PartNumber" in PLM.

## PartSubtype

A **PartSubtype** is a categorization tag attached to a Part. Subtypes group parts that share routing, inspection, or planning behavior. Examples include "Mounting hardware", "Critical-to-quality fasteners", and "Long-lead-time stock". A part can have multiple subtypes.

Query the subtypes:

```graphql theme={null}
query Subtypes {
  partSubtypes(first: 50) {
    edges {
      node {
        id
        name
        description
      }
    }
  }
}
```

**Maps to:** "Item Family", "Commodity Code", or "Product Line" in ERP. Don't confuse a subtype with revision lineage. Revision lineage is `revisedFromId` on Part.

## PartInventory

A **PartInventory** is a physical instance of a Part. It can be a serialized unit, a lot, or a quantity at a location. This is the entity that gets installed onto assemblies, consumed in runs, or shipped.

| Field                               | Notes                                                                                        |
| ----------------------------------- | -------------------------------------------------------------------------------------------- |
| `serialNumber`                      | If the part is serialized.                                                                   |
| `lotNumber`                         | If the part is lot-tracked.                                                                  |
| `quantity`                          | For non-serialized inventory (count).                                                        |
| `status`                            | Such as `AVAILABLE`, `WIP`, `KITTED`, `INSTALLED`, `ON_ORDER`, `SCRAPPED`, or `UNAVAILABLE`. |
| `part`                              | Reference to its `Part`.                                                                     |
| `location`                          | Where it physically lives.                                                                   |
| `madeOnAssemblyParentPartInventory` | If installed onto an assembly, points up the build tree.                                     |
| `entity`                            | Polymorphic id used for file attachments and similar.                                        |

Query a part inventory record:

```graphql theme={null}
query Inventory {
  partInventory(id: "9876") {
    id
    serialNumber
    lotNumber
    quantity
    status
    part { id partNumber description }
    location { id name }
    madeOnAssemblyParentPartInventory { id serialNumber }
  }
}
```

**Maps to:** "Serial" or "Lot" in MES. "Inventory unit" or "Stockable item" in WMS. "Asset" in PLM.

## Assemblies (as-built BOM)

An **as-built BOM** is the parent and child tree of `PartInventory` units that compose a finished assembly. ION represents this tree two ways. The `madeOnAssemblyParentPartInventory` reference on each PartInventory points up the tree. The `BuildRequirement` records define which parts to install where to complete the assembly.

To traverse the tree downward:

```graphql theme={null}
query AbomTree {
  partInventory(id: "9876") {
    id
    serialNumber
    part { partNumber }
    buildRequirements {
      id
      part { partNumber }
      quantity
      abomInstallations {
        id
        partInventory {
          id
          serialNumber
          part { partNumber }
        }
      }
    }
  }
}
```

To walk up the tree, follow `madeOnAssemblyParentPartInventory` recursively.

**Maps to:** "Bill of Materials" in ERP. "Assembly tree" in PLM. "Bill of Build" in MES.

<Note>
  The `mBOM` (manufacturing BOM) is the planned structure. The `aBOM` (as-built) is what was actually installed at run time. Both have GraphQL types in ION.
</Note>

## Procedures and run steps

A **Procedure** is the manufacturing routing template. It defines what should happen on the floor. A Procedure is composed of steps.

Query a procedure with its steps:

```graphql theme={null}
query Proc {
  procedure(id: "12") {
    id
    title
    version
    steps {
      id
      title
      position
      type
      fields {
        id
        name
        type
      }
    }
  }
}
```

**Maps to:** "Routing" in ERP. "Recipe" or "Standard Operating Procedure" in MES. "Work Instruction" in QMS.

## Runs and run steps

A **Run** is one execution of a Procedure against a specific PartInventory. **Run steps** are the per-execution instances of the procedure's steps. A run step is where operators record measurements, sign off, and report quality issues.

| Field           | Notes                                                                                                  |
| --------------- | ------------------------------------------------------------------------------------------------------ |
| `id`, `title`   |                                                                                                        |
| `status`        | Such as `TODO`, `IN_PROGRESS`, or `COMPLETE`.                                                          |
| `procedure`     | Template.                                                                                              |
| `partInventory` | What's being built.                                                                                    |
| `steps`         | Ordered execution instances (`RunStep`).                                                               |
| `runBatch`      | If the run participates in a batch, see [Run batches](/build-hardware/runs-and-execution/run-batches). |

Query a run with its steps:

```graphql theme={null}
query GetRun {
  run(id: "1234") {
    id
    title
    status
    procedure { id title version }
    partInventory { id serialNumber part { partNumber } }
    steps {
      id
      position
      status
      endTime
      fields { id name value }
    }
  }
}
```

**Maps to:** "Work Order" in ERP. "Production Order" in MES. "Build" or "Job" colloquially.

## Build requirements

A **BuildRequirement** is a line item on an aBOM. It defines how many of a given part to install to complete the assembly. Operators install against build requirements during a run. Each install is a row of `abomInstallations` (an `ABomInstallation`) that links the `PartInventory` consumed to the build requirement satisfied.

For the user-facing flow, see [Editing build requirements](/build-hardware/bills-of-materials/editing-build-requirements).

## Issues

An **Issue** is a quality event. It can be a nonconformance, deviation, supplier problem, or observation. Issues attach to runs, run steps, or part inventories. Issues follow a defined lifecycle. The states are Pending, In Progress, In Review, and Resolved. Disposition types determine what happens to the affected inventory.

Query open issues by disposition:

```graphql theme={null}
query OpenIssues {
  issues(filters: { status: { eq: IN_PROGRESS } }) {
    edges {
      node {
        id
        title
        status
        issueDispositionType { id title }
        assignedTo { id name }
        runStep { id }
      }
    }
  }
}
```

For more information, see [Issue states, dispositions, and resolutions](/track-quality/issues/overview).

**Maps to:** "Nonconformance Report" (NCR) in QMS. "CAPA" precursor in QMS. "Defect" in PLM. "Incident" in ServiceNow-style systems.

## Purchase orders

For supply chain integrations:

```graphql theme={null}
query OpenPOs {
  purchaseOrders(filters: { isOpen: { eq: true } }) {
    edges {
      node {
        id
        prettyStr
        status
        supplier { id name }
        purchaseOrderLines {
          id
          part { partNumber }
          quantity
          receivedQuantity
        }
      }
    }
  }
}
```

**Maps to:** "PO" in ERP. Receiving against a PO updates `PartInventory` records.

## File attachments

Files attach to entities polymorphically through the `entity` ID. For the upload flow and the `entity { id }` lookup pattern, see [File upload](/api-reference/guides/file-upload).

## The `entity` polymorphic ID

ION gives every attachable object a uniform ID through an `entities` reference. Most major entities expose an `entity { id }` field. This field is the target for several features:

* File attachments
* Subscriptions (notifications)
* Comments
* Cross-entity links, such as an issue to a run step

Don't confuse this field with the entity's own `id`. When an integration calls for an `entityId`, fetch the parent's `entity.id` first.

## Field-mapping cheat sheet

When you bridge from another system, use this rough translation:

| Your system says…                      | ION calls it…                                               |
| -------------------------------------- | ----------------------------------------------------------- |
| SKU, Item Master, or Part Number       | `Part.partNumber`                                           |
| Item Family, Commodity, or Group       | `PartSubtype.name` (a part can belong to multiple subtypes) |
| Lot or Batch number                    | `PartInventory.lotNumber`                                   |
| Serial or Asset Tag                    | `PartInventory.serialNumber`                                |
| On-hand quantity                       | `PartInventory.quantity`                                    |
| BOM line item                          | `BuildRequirement`                                          |
| Work Order or Production Order         | `Run`                                                       |
| Routing, Recipe, or Process            | `Procedure`                                                 |
| Operation, Step, or Op                 | `Step` (template) or `RunStep` (execution)                  |
| NCR or Nonconformance                  | `Issue`                                                     |
| Disposition (Use As Is, Rework, Scrap) | `IssueDispositionType`                                      |
| PO                                     | `PurchaseOrder`                                             |
| PO line                                | `PurchaseOrderLine`                                         |
| Vendor or Supplier                     | `PurchaseOrder.supplier` (a `Supplier`)                     |
| Workcenter or Cell                     | `Location` (typed `WORKCENTER`)                             |
| Tote, Bin, or Rack                     | `Location` (other types)                                    |
| User group or Role                     | `Role`                                                      |
| User                                   | `User`                                                      |
| Tenant or Org                          | `Organization`                                              |

## Etag-based concurrency

Every mutable entity returns an `_etag` on read. Pass it back on `update` and `delete` mutations. ION rejects writes whose etag doesn't match the current value. A mismatch returns a 409. For more information, see [Error codes 409](/api-reference/error-codes#409-concurrency-conflict).

When you integrate, read then write inside a short window. Propagate the etag through your service.

## Discovering the rest of the schema

You can find anything not on this page three ways:

1. **API Playground**: an interactive schema explorer at [/api-reference/playground](/api-reference/playground).
2. [`schema.graphql`](/schema.graphql): committed to this repository and kept in sync with the production schema.
3. **`__schema` introspection**: programmatic access.

## Related

* [Example requests](/api-reference/examples)
* [Upload a file](/api-reference/guides/file-upload)
* [Set up webhooks](/api-reference/guides/webhooks)
