# Customize the login page
Source: https://docs.firstresonance.io/administration/authentication-settings/customize-login-page
Add your organization's logo and brand colors to the ION sign-in page.
## Customize the login page
1. In ION, go to **Settings > Organization > Authentication**.
2. In the **Login Page Branding** card, enter:
* **Logo URL**: a publicly accessible URL to your logo image (for example, `https://example.com/logo.png`).
* **Primary Color**: a hex color for buttons and accents (for example, `#16A34A`).
* **Background Color**: a hex color for the page background (for example, `#FFFFFF`).
3. Click **Save Branding**.
Your branding applies to the login page.
# Enforce MFA
Source: https://docs.firstresonance.io/administration/authentication-settings/enforce-mfa
Require multi-factor authentication for everyone in your organization or just yourself.
For what MFA adds to either sign-in method, see the [Overview](/administration/authentication-settings).
## Enforce MFA for all users
1. In ION, go to **Settings > Organization > Authentication**.
2. In the **Multi-Factor Authentication** card, turn on **Require MFA for all users**.
Org-wide MFA applies to everyone the moment you turn it on. To avoid locking
users out, confirm your team can enroll an authenticator app, and validate the
change in a sandbox tenant first if you have one.
## Enforce MFA for yourself
You can enable MFA on your own profile without org-wide enforcement enabled. After enabling, log out and sign back in to start the MFA setup flow. If you lose access to your MFA device, see [Troubleshooting sign-in issues](/administration/authentication-settings/sign-in#troubleshooting).
## Related
* [Troubleshooting sign-in issues](/administration/authentication-settings/sign-in#troubleshooting)
# Overview
Source: https://docs.firstresonance.io/administration/authentication-settings/index
How authentication works in ION: email and password or SSO, and the domain verification that ties users to your organization.
ION identifies every user by their company email address. When someone [signs in](/administration/authentication-settings/sign-in), ION looks at the email's domain to decide what happens next: if the domain has an SSO connection, ION redirects to your identity provider to authenticate; otherwise the user enters an ION password. Everything you configure on the **Settings > Organization > Authentication** page shapes that flow: which domains belong to your organization, whether SSO is on, whether MFA is required, and what the sign-in page looks like.
## Two ways to sign in
* **Email and password**: ION manages the credential. New users get an invite email with a link to set their password, and a forgotten password is [reset from the sign-in page](/administration/authentication-settings/reset-your-password).
* **Single sign-on (SSO)**: your identity provider manages the credential. ION creates a user profile automatically on first SSO sign-in with the default **User** role, and removing someone from your IdP stops their SSO access to ION. Passwords, in this case, are reset in your IdP, not in ION. For connecting and maintaining an IdP, see the [SSO overview](/administration/authentication-settings/sso).
The two coexist per domain, not per user: once SSO is active for a verified domain, users on that domain are routed to the IdP.
## Domains
A claimed, verified domain tells ION that email addresses on that domain belong to your organization. Verification works by DNS: ION gives you a TXT record to publish, then confirms you own the domain. A verified domain acts as an allowlist that associates matching users with your organization at sign-up, and it's the prerequisite for SSO. See [Manage domains](/administration/authentication-settings/manage-domains).
# Manage domains
Source: https://docs.firstresonance.io/administration/authentication-settings/manage-domains
Claim and verify your company's email domain so ION can route users to your organization at login.
A verified domain is required before you can set up enterprise SSO or automatic user provisioning. For what domain verification does, see the [Overview](/administration/authentication-settings).
Coordinate a full domain change, such as migrating your organization to a new company email domain, with [First Resonance support](https://support.firstresonance.io) in advance. Claiming a new domain associates matching users with your organization going forward. It doesn't move your existing users, who keep signing in with their current email addresses until each account is updated individually.
## Add a domain
1. In ION, go to **Settings > Organization > Authentication**.
2. In the **Domain Management** card, enter your domain in the **Add Domain** field and click **Add Domain**.
The domain appears in your **Claimed Domains** list with a **Pending** status.
## Verify the domain
After you add a domain, ION displays the name and value of a DNS TXT record to publish.
1. Give the TXT record name and value to whoever manages your DNS, and have them add it to the domain's DNS settings.
2. Once the record is live, click **Check Verification** next to the domain.
ION checks for the record and updates the domain's status. DNS changes can take a few minutes to propagate. If verification is still **Pending**, wait and try again.
## Domain statuses
| Status | Meaning |
| ------------ | ---------------------------------------------------------------------------------------------------------- |
| **Verified** | Ownership confirmed. ION routes users on this domain to your organization. |
| **Pending** | Waiting on the DNS record, or DNS hasn't propagated yet. |
| **Failed** | ION couldn't find the expected TXT record. Double-check the record and click **Check Verification** again. |
## Remove a domain
1. In the **Domain Management** card, click the remove icon next to the domain.
2. In the **Remove domain** dialog, click **Remove domain** to confirm.
Removing a domain means users with email addresses on that domain are no longer automatically associated with your organization at login. Only remove a domain you no longer use. If you're transferring your organization to a different domain, work with [First Resonance support](https://support.firstresonance.io) to move accounts over before you remove the old domain.
## Related
* [Set up SSO](/administration/authentication-settings/sso/set-up-sso)
# Reset your password
Source: https://docs.firstresonance.io/administration/authentication-settings/reset-your-password
How to reset a forgotten ION password from the sign-in page.
1. On the ION sign-in page, click **Don't remember your password?**
2. Enter your email address and submit.
3. Check your inbox for a reset email from `invites@firstresonance.io` and follow the link to set a new password.
If your organization uses SSO, reset your password in your identity provider, not in ION.
If the reset email doesn't arrive, check your spam folder. IT teams: allowlist `invites@firstresonance.io` to ensure password reset and invite emails are delivered.
## Related
* [Sign in to ION](/administration/authentication-settings/sign-in)
# Sign in to ION
Source: https://docs.firstresonance.io/administration/authentication-settings/sign-in
How to sign in to ION with email and password or single sign-on.
Go to your organization's ION URL and enter your **company email address**, then click **Continue**. ION either prompts you for your ION password or, if your email domain uses SSO, redirects you to your identity provider and back. For how this routing works, see the [Overview](/administration/authentication-settings).
## Sign in for the first time
With SSO, just sign in through your identity provider. The very first person to sign in to a new ION organization becomes its administrator; every subsequent user gets the default **User** role, which an admin can extend.
With email and password, an administrator at your company sends you an invite email with a link to set your password.
## Troubleshooting
Verify that you're signing in with the correct URL: `app-v2.buildwithion.com`, `app-v2.gov.buildwithion.com`, or `app-v2.ap.buildwithion.com` (or the correct sandbox URL if applicable).
Your ION environment might still be setting up. Wait 15 minutes and try again.
Contact an administrator at your company to reactivate your ION account.
A required permission isn't enabled for your role. An admin can grant it. See [Create a role](/administration/users-and-permissions/manage-roles).
Contact [support](https://support.firstresonance.io/) to reset it.
## Related
* [Reset your password](/administration/authentication-settings/reset-your-password)
# Disable SSO
Source: https://docs.firstresonance.io/administration/authentication-settings/sso/disable-sso
Turn off your SSO connection so users sign in with email and password instead.
For what disabling SSO means for your team, see the [Overview](/administration/authentication-settings).
Disabling SSO removes the connection entirely. Turning SSO back on later
requires running the setup wizard again. Anyone already signed in stays signed
in until their session ends.
## Disable SSO
1. In ION, go to **Settings > Organization > Authentication**.
2. In the **Enterprise SSO** card, click **Disable SSO**.
## Related
* [Set up SSO](/administration/authentication-settings/sso/set-up-sso)
* [Rotate your SAML signing certificate](/administration/authentication-settings/sso/rotate-saml-certificate)
# Overview
Source: https://docs.firstresonance.io/administration/authentication-settings/sso/index
The life of an SSO connection in ION: set it up, keep its certificate current, and retire it.
An SSO connection hands authentication to your identity provider (Okta, Microsoft Entra ID, Google Workspace, ADFS, or another SAML or OIDC provider) and has a life beyond initial setup:
1. **Set up**: after verifying your domain, you connect your IdP through a self-service wizard that exchanges SAML or OIDC details and maps user attributes. See [Set up SSO](/administration/authentication-settings/sso/set-up-sso).
2. **Maintain**: SAML connections verify each login against your IdP's signing certificate. When that certificate expires or your security team rotates it, you update ION with the new one, coordinated with the IdP switch. OIDC keys rotate automatically. See [Rotate your SAML signing certificate](/administration/authentication-settings/sso/rotate-saml-certificate).
3. **Retire**: disabling SSO removes the connection and returns your team to email and password sign-in without deleting any accounts. See [Disable SSO](/administration/authentication-settings/sso/disable-sso).
A misconfigured SSO connection or an out-of-sync certificate rotation blocks fresh sign-ins to production. Validate authentication changes in a sandbox tenant when you have one, and keep the previous certificate on hand during a rotation.
# Rotate your SAML signing certificate
Source: https://docs.firstresonance.io/administration/authentication-settings/sso/rotate-saml-certificate
Replace an expiring or rotated SAML signing certificate on your SSO connection without opening a support ticket.
This page applies to SAML connections only. OIDC connections have no signing certificate to rotate. For how certificate rotation fits into the SSO lifecycle, see the [Overview](/administration/authentication-settings).
## When you'll need to do this
* **Certificate expiry.** Most IdP signing certificates are valid for one to three years. Your IdP or security team issues a replacement before the old one expires.
* **Scheduled key rotation.** Routine security hygiene, or a response to a suspected key compromise.
* **IdP changes.** Switching identity providers, or upgrading the signing algorithm (for example, moving from SHA-1 to SHA-256).
* **Auto-rolled keys.** Your IdP changed its signing key on its own (for example, Microsoft Entra auto-rolled its certificate).
## Before you start
The **Rotate certificate** button is only visible to org administrators with SSO management permission.
Rotating the certificate requires updating both your IdP and ION at roughly the same time. If they're out of sync, logins fail.
ION holds one signing certificate per connection at a time. There's no overlap
period where both the old and new certificates are valid. New sign-ins briefly
fail in the window between your IdP switching to the new key and ION being
updated to match. Anyone already signed in stays signed in; the certificate is
only checked on fresh logins.
Keep your previous certificate available until you've confirmed a successful login. Rolling back means pasting the old certificate back.
## What you'll need
From your IdP administrator, get one of the following for the new certificate:
* The new **X.509 certificate** in PEM format (a text block beginning with `-----BEGIN CERTIFICATE-----`), or
* Your IdP's **SAML 2.0 metadata XML** (beginning with ` Organization > Authentication**.
2. In the **Enterprise SSO** card, click **Rotate certificate**.
3. Paste the new PEM certificate or SAML metadata XML into the dialog, or use **Upload from file** to select it. ION confirms the format it detected below the input.
4. Have your IdP administrator activate the new signing key.
5. Click **Rotate**. ION immediately starts verifying logins with the new certificate and displays a SHA-256 fingerprint of the new certificate.
ION's fingerprint is SHA-256, but most IdP consoles (Okta, Microsoft Entra,
ADFS) display a SHA-1 thumbprint, so the two won't match by eye even when
everything is correct. To compare, generate the SHA-256 yourself: `openssl
x509 -noout -fingerprint -sha256 -in cert.pem`.
6. Ask someone (not yourself) to sign out and sign back in through SSO to verify the new certificate works.
## If sign-ins start failing
This is recoverable and does not affect anyone already signed in.
* **Most likely cause**: ION and your IdP are temporarily out of sync. One is using the new certificate and the other is still on the old one.
* **Fastest fix**: open **Rotate certificate** again and paste the previous certificate back. New sign-ins recover immediately. Then retry the rotation once both sides are ready to switch together.
* If logins still fail after both sides are confirmed on the new certificate, [contact support](https://support.firstresonance.io/) with the fingerprint ION displayed and the time of the change.
## Frequently asked questions
No. The signing certificate is only checked when someone signs in fresh. Everyone with an active session keeps working uninterrupted.
Not without a brief interruption. Because ION holds only one certificate at a
time, loading the new one before your IdP starts signing with it causes new
sign-ins to fail until the IdP catches up. Switch both sides together instead.
The action is restricted to administrators with SSO management permission. Ask
an admin on your team, or [contact
support](https://support.firstresonance.io/).
For a routine certificate swap, paste the PEM. It updates only the signing
certificate and leaves the rest of your connection untouched. Use metadata XML
only when you intend to refresh the whole connection (new endpoints or
attribute mappings).
Paste the certificate your IdP will sign with after the cutover, coordinated
with the switch.
This action updates only the IdP signing certificate ION uses to verify login tokens. It does not change ION's request-signing certificate or any assertion-encryption keys. If you need to rotate those, contact [support](https://support.firstresonance.io/).
## Provider-specific notes
Entra downloads its certificate as a binary `.cer` (DER) file. Convert it to PEM, or use the federation metadata. Entra auto-rolls its signing certificate and lists multiple certificates in its federation metadata during the overlap, so paste the one it will use after the switch. GovCloud tenants live under `login.microsoftonline.us`.
Generate the next certificate in Okta, then paste its signing certificate
(PEM) or Okta's metadata URL/file. Okta metadata lists both encryption and
signing certificates. ION automatically picks the signing one.
ADFS's AutoCertificateRollover publishes a primary and secondary token-signing
certificate, exported as binary `.cer` (DER). Convert to PEM and paste the one
that will be primary after the rollover.
Download the SAML app's signing certificate as PEM and paste it. If your provider only offers metadata, paste the metadata XML.
## Related
* [Set up SSO](/administration/authentication-settings/sso/set-up-sso)
* [Disable SSO](/administration/authentication-settings/sso/disable-sso)
# Set up SSO
Source: https://docs.firstresonance.io/administration/authentication-settings/sso/set-up-sso
Connect your identity provider to ION so your team can sign in with their existing company credentials.
For how SSO fits into ION authentication, including how accounts are provisioned on first sign-in, see the [Overview](/administration/authentication-settings).
## Before you start
[Claim and verify your email domain](/administration/authentication-settings/manage-domains) before setting up SSO.
Adding SSO after you onboard many users means reconciling existing accounts with your identity provider.
A misconfigured SSO connection can lock users out of production. If you have a sandbox tenant, validate the connection there before enabling it in production.
## Set up SSO
1. In ION, go to **Settings > Organization > Authentication**.
2. In the **Enterprise SSO** card, click **Configure SSO**, then click **Open SSO Setup Wizard** and follow the steps to connect your identity provider. The wizard walks you through selecting your provider, exchanging SAML or OIDC details, mapping user attributes, and enabling the connection.
3. When the wizard is complete, return to ION and click **Check Status**. Once your IdP connection is live, the card shows **SSO Active** with your provider and connection name.
You can hand the setup wizard link to whoever manages your IdP if you don't have access to configure it yourself.
The wizard shows you the values to enter into your identity provider and collects your provider's metadata or signing certificate directly. You don't need to send anything to First Resonance to complete setup. For more information, see Auth0's [self-service SSO documentation](https://auth0.com/docs/authenticate/enterprise-connections/self-service-SSO).
## Troubleshooting
The wizard must fully complete and the connection must be enabled before ION can verify it. Return to the wizard and confirm the final step shows the connection as enabled, then try **Check Status** again.
Confirm your email domain is claimed and verified in ION before enabling SSO. See [Manage domains](/administration/authentication-settings/manage-domains). Unverified domains won't route users to your IdP.
Check the attribute mapping step in the wizard. ION expects the email attribute to be mapped correctly from your IdP. For SAML providers, the email attribute is typically `email` or `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`. Confirm with your IdP's documentation.
If your IdP uses SAML and rotated its signing certificate, ION needs to be updated with the new certificate. See [Rotate your SAML signing certificate](/administration/authentication-settings/sso/rotate-saml-certificate).
## Provider-specific notes
The wizard is the same for every provider and shows you the exact service-provider values to enter into your IdP. These notes cover what's specific to the most common providers.
Create a **SAML 2.0** app integration in Okta and paste in the **Single sign-on URL** and **Audience URI (SP Entity ID)** the wizard displays. They take the form `https://firstresonance.auth0.com/login/callback?connection=` and `urn:auth0:firstresonance:`, with the connection name filled in by the wizard. Okta sends the user's `email` in the SAML assertion by default. When you provide Okta's metadata, ION automatically picks the **signing** certificate if both signing and encryption certificates are listed. Background: [Auth0 community guide to Okta as the SAML IdP](https://community.auth0.com/t/saml-setup-okta-as-idp-and-auth0-as-sp/91164).
Register the application in the Microsoft Entra admin center using the **Reply URL (ACS)** the wizard displays (based on `https://firstresonance.auth0.com/login/callback`), then provide your application's **federation metadata** (URL or XML) back in the wizard. Confirm Entra releases the user's **email** claim. **GovCloud** tenants sign in under `login.microsoftonline.us`. Entra **auto-rolls** its signing certificate and lists multiple certificates during the overlap. This matters when you later [rotate the signing certificate](/administration/authentication-settings/sso/rotate-saml-certificate).
Configure the relying party in ADFS following [Auth0's ADFS connection documentation](https://auth0.com/docs/connections/enterprise/adfs), using **Realm Identifier** `urn:auth0:firstresonance` and **Endpoint** `https://firstresonance.auth0.com/login/callback`. Then provide your ADFS **federation metadata URL** (for example, `https://adfs.yourcompany.com/FederationMetadata/2007-06/FederationMetadata.xml`) in the wizard.
## Related
* [Rotate your SAML signing certificate](/administration/authentication-settings/sso/rotate-saml-certificate)
* [Disable SSO](/administration/authentication-settings/sso/disable-sso)
* [Manage domains](/administration/authentication-settings/manage-domains)
# Custom attributes
Source: https://docs.firstresonance.io/administration/custom-attributes
Add org-defined metadata fields to ION records (procedures, runs, parts, issues, and more) from Settings.
Custom attributes let you capture additional metadata on ION records beyond the default fields. They are available for the following entity types:
| Entity | Where in Settings |
| --------------- | --------------------------------------------- |
| Procedures | **Settings > Production > Procedures** |
| Standard steps | **Settings > Production > Standard Steps** |
| Runs | **Settings > Production > Runs** |
| Issues | **Settings > Quality > Issues** |
| Parts | **Settings > Supply Chain > Parts** |
| Purchases | **Settings > Supply Chain > Purchases** |
| Receipts | **Settings > Supply Chain > Receipts** |
| Suppliers | **Settings > Supply Chain > Suppliers** |
| Part kits | **Settings > Supply Chain > Part Kits** |
| Locations | **Settings > Supply Chain > Locations** |
| Parts Inventory | **Settings > Supply Chain > Parts Inventory** |
| Plans | **Settings > Supply Chain > Plans** |
| Further Actions | **Settings > Quality > Further Actions** |
The **Purchases** page defines two separate attribute sets: **Purchase Order Attributes** apply to the order, and **Purchase Order Line Attributes** apply to each line on the order.
## Add a custom attribute
To add a custom attribute:
1. Go to the Settings page for the entity you want to extend (see table above).
2. In the **Attributes** section, enter a **Name** for the attribute.
3. Select a **Type** from the dropdown.
4. Click **Add**. The attribute is saved immediately.
### Attribute types
The **Type** dropdown lists these options. After you save the attribute, ION shows the type as a badge, which can use a different label than the dropdown option.
| Dropdown option | Badge label | Description |
| ------------------ | ------------ | ---------------------------------------------------------------------- |
| **String** | String | Free-text input. |
| **Number** | Number | Numeric value. |
| **Boolean** | Boolean | On or off toggle. |
| **Datetime** | Date & Time | Date and time picker. |
| **FileAttachment** | File | File attachment. |
| **Select** | Select | Single-choice dropdown. You define the options after creation. |
| **Multiselect** | Multi-Select | Multiple-choice dropdown. You define the options after creation. |
| **ION: Parts** | ION: Parts | Reference to a part record. |
| **ION: Users** | ION: Users | Reference to a user. |
| **Rich Text** | Rich Text | Formatted text. Available only on entity types that support rich text. |
## Manage options for select and multi-select attributes
Select and multi-select attributes require you to define their options separately after creation. Options follow an archive-then-delete lifecycle: archiving takes an option out of circulation while leaving the records that already use it intact, and permanent deletion is only available once an option is archived.
To manage an attribute's options:
1. In the **Attributes** section, find the attribute and click the expand arrow next to it.
2. To add an option, type the option name in the input field and press **Enter** or click **Add**.
3. To archive an option, click the archive icon on the option.
4. To work with an option you already archived, expand **Archived**, which shows the archived count, and use one of the icons on the option:
* The restore icon returns the option to the active list.
* The delete icon removes the option from the attribute permanently.
Archived options stay out of the pick list on new entries, so nobody can select one again until you unarchive it. A record that already holds an archived option keeps that value.
Deleting an archived option can't be undone. ION blocks the deletion if any
record still holds the option, so archive it instead when you need to retire an
option that's in use.
## Edit a custom attribute
The name and type of a custom attribute can't be changed after creation. To rename an attribute or change its type, delete it and create a new one.
For **Select** and **Multi-Select** attributes, you can add and archive options at any time. For more information, see [Manage options for select and multi-select attributes](#manage-options-for-select-and-multi-select-attributes).
## Archive a custom attribute
Archiving an attribute hides it from data entry across ION: it stops appearing on create forms, table column pickers, and the panels that add an attribute to a record. Records that already carry a value for the attribute keep it, and you can still edit that value.
1. In the **Attributes** section, find the attribute you want to archive.
2. Click the archive icon at the end of the attribute row.
To bring an attribute back, expand **Archived** and click the restore icon on the attribute row.
## Delete a custom attribute
Delete is only available on an archived attribute, so archive it first.
To delete a custom attribute:
1. In the **Attributes** section, expand **Archived**.
2. Click the delete button on the attribute row.
3. In the **Delete Attribute** dialog, click **Delete**.
If you accidentally deleted a custom attribute that other records still
used, create a new attribute with the exact same name and type. ION will
restore the attribute and its values on each record.
## Archived values on records
When a record holds a value whose option has since been archived, the value pickers mark it with an **Archived** badge so you can tell it apart from an active option. Because an archived option can't be selected again, ION asks you to confirm in the **Deselect archived option?** dialog before it removes the value.
## How attribute values copy between records
When you duplicate a record, ION carries its custom attribute values to the new record in these cases:
* Splitting an inventory line copies the parent's attribute values to the new line.
* Copying a purchase order copies attribute values on both the order and its lines.
* Creating a new part revision copies the source revision's attribute values.
Creating a brand-new part does not copy attribute values, since there is no source record to copy from.
# Customer S3 Delivery Setup
Source: https://docs.firstresonance.io/administration/customer-s3-delivery-setup
Set up an S3 bucket in your AWS account to receive automated data snapshots from ION.
## Overview
ION delivers automated data snapshots directly to an S3 bucket in your AWS account as `.tar` archives, once you set up the bucket and grant First Resonance access to write to it.
## What you need
* An S3 bucket in your AWS account
* An IAM role that First Resonance assumes to write to your bucket
* An external ID (provided by First Resonance) for secure role assumption
Once configured, snapshots are delivered to:
```
s3://///
```
## Set up delivery
Create a bucket in your preferred AWS region, either through the AWS console or using the CLI:
```bash theme={null}
aws s3 mb s3:// --region
```
We recommend enabling:
* **Versioning**: protects against accidental overwrites.
* **Server-side encryption** (SSE-S3 or SSE-KMS): encrypts data at rest.
Create an IAM role that First Resonance assumes to deliver snapshots to your bucket.
### Trust policy
The trust policy allows First Resonance to assume the role using an external ID. Replace `` with the First Resonance AWS account ID for your environment (provided by your account team), and `` with the external ID we provide.
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam:::root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": ""
}
}
}
]
}
```
The external ID prevents the [confused deputy problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html) and ensures only First Resonance can assume this role.
### Permission policy
Attach the following policy to the role. Replace `` with your bucket name.
```json theme={null}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SnapshotDeliveryBucketAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:AbortMultipartUpload",
"s3:ListBucketMultipartUploads",
"s3:ListMultipartUploadParts",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::",
"arn:aws:s3:::/*"
]
}
]
}
```
`s3:PutObject` handles the core upload. The additional permissions allow First Resonance to clean up incomplete uploads and list bucket contents for verification.
Provide the following details to your account team:
| Field | Description |
| ------------- | -------------------------------------------- |
| Bucket name | Your S3 bucket name. |
| Bucket region | The AWS region where your bucket is located. |
| Role ARN | The full ARN of the IAM role you created. |
First Resonance provides the external ID and configures your snapshot schedule.
After we receive your configuration, our system validates access by:
1. Assuming the IAM role with the external ID.
2. Verifying the bucket exists and is accessible.
3. Writing and deleting a small test object.
If validation fails, we reach out with the specific error so you can adjust permissions.
## What gets delivered
Each snapshot creates files under your bucket with this structure:
```
s3:///
└── /
└── /
├── snapshot_tables_.tar
└── snapshot_attachments_.tar
```
For large snapshots that are split into multiple files, each file includes a part number (for example, `snapshot_tables_part1_.tar` and `snapshot_tables_part2_.tar`).
* **snapshot\_tables**: all database tables as compressed CSV files, bundled into a tar archive.
* **snapshot\_attachments**: file attachments bundled into a tar archive.
## Troubleshooting
**Cause:** Trust policy does not allow the First Resonance account.
**Fix:** Verify the `Principal` in the trust policy matches the account ID provided by your account team.
**Cause:** External ID mismatch.
**Fix:** Verify the `sts:ExternalId` condition matches the value provided by First Resonance.
**Cause:** Missing or incorrect permission policy.
**Fix:** Verify the permission policy is attached to the role and the bucket name matches.
**Cause:** Role lacks `s3:PutObject` permission.
**Fix:** Check the permission policy includes `PutObject` on the bucket resource.
**Cause:** Wrong bucket name or region.
**Fix:** Verify the bucket name and that it exists in the expected region.
## Security notes
* First Resonance uses **STS AssumeRole** with short-lived credentials that are automatically refreshed during long-running snapshots. No credentials are stored.
* The **external ID** ensures only First Resonance can assume the role.
* First Resonance only writes to your tenant's prefix and does not read or modify other data in your bucket.
* All data is transmitted over HTTPS (TLS).
## Related pages
For an overview of data snapshots, see [Data Snapshots](/administration/data-snapshots).
# Data Snapshots
Source: https://docs.firstresonance.io/administration/data-snapshots
Create a full snapshot of your organization's data and attachments from ION for backup, compliance, or analytics purposes.
## Overview
Data snapshots let you create a complete copy of your organization's data from ION. Snapshots are useful for backups, compliance requirements, data warehousing, or feeding external analytics tools. By default, a snapshot includes both:
* **Tables**: all of your organization's structured data, such as parts, inventory, BOMs, runs, and issues.
* **Attachments**: all documents, images, and other files uploaded across ION, including those attached to runs, procedures, and issues.
If needed, you can choose to snapshot only tables or only attachments.
Data snapshots are available depending on your organization's plan and settings. To find out whether snapshots are enabled for your organization, contact your account team.
## How data snapshots work
Data snapshots run automatically on a scheduled cadence configured for your organization. To get started, reach out to your account team to set up your snapshot schedule and delivery destination.
Once a snapshot completes, you can download the resulting files from the **data snapshots page**. You can also track the status of all snapshots from the snapshot history table.
The data snapshots page is only accessible to organization admins for security purposes. All snapshot data is encrypted at rest and in transit using TLS.
## What you receive
Snapshots are delivered as `.tar` archives. A tables snapshot contains one compressed file per table (`.csv.gz` or `.jsonl.gz`), and an attachments snapshot contains all uploaded files.
For large snapshots, ION automatically splits the output into multiple `.tar` files labeled with a part number, such as `snapshot_tables_part1_2026-03-31-143022.tar` and `snapshot_tables_part2_2026-03-31-143022.tar`.
## Delivery destinations
By default, snapshots are stored in ION and made available for download through the UI. Snapshots stored in ION are retained for **30 days**, after which they are automatically deleted.
You can set up [Customer S3 delivery](/administration/customer-s3-delivery-setup) to have snapshots delivered directly to your own AWS S3 bucket. Delivering to your own bucket gives you ownership of the snapshot files and lets you apply your own retention and access policies.
## Scheduling
Snapshots can be scheduled to run weekly or monthly. Your account team configures the cadence during onboarding.
## Onboarding
To get started with data snapshots:
1. **Contact your account team.** Let them know you want to set up data snapshots and whether you need tables, attachments, or both.
2. **Choose a delivery destination.** Snapshots can be downloaded from ION directly, or delivered to your own S3 bucket.
3. **Set up customer-managed S3 delivery, if applicable.** Follow the [Customer S3 delivery setup](/administration/customer-s3-delivery-setup) guide to create your bucket, configure an IAM role, and share your configuration for validation.
4. **Go live.** Once configured, snapshots run on your chosen schedule and are delivered automatically.
## Snapshot history
The data snapshots page shows the status of all snapshots for your organization:
| Status | Description |
| ----------- | -------------------------------------------------------------------------------- |
| Pending | The snapshot is waiting to be processed. |
| In Progress | The snapshot is currently running. |
| Completed | The snapshot finished successfully and files are available for download. |
| Failed | The snapshot encountered an error. Check the error details for more information. |
# Export data
Source: https://docs.firstresonance.io/administration/export-data
Export ION data to CSV or Excel on demand or on a schedule, track each export job, and download the finished file.
The Exports page delivers a data view from ION as a CSV or Excel file, either once on demand or on a recurring schedule. Each run of an export is a job whose status and output you can check from the export's detail page. ION can email the file to the recipients you set, or download it straight to your browser.
## Request an export
1. In ION, go to **OS > Exports**.
2. Click **New Export**.
3. In the **Create export** dialog, fill in:
* **Name**: what to call this export. This field is required.
* **View name**: the data view to export. Start typing to search your available views, listed under **Available views**, or enter any view name you have access to. This field is required.
* **Format**: **CSV** or **Excel**.
* **Delivery**: how you get the file, **Email** or **Download**. This choice is available on the **Run once** tab; scheduled exports always deliver by email.
* **Delivery recipients**: the email addresses that receive the file. At least one is required. This field appears only for **Email** delivery.
4. Choose when it runs:
* On the **Run once** tab, click **Run now** to run the export immediately. With **Download** delivery, ION prepares the file and saves it to your browser once it's ready. With **Email** delivery, ION emails the recipients when the file is ready.
* On the **Schedule** tab, enter a **Cron expression** and pick a **Timezone**, then click **Save schedule** to run it on a recurring cadence. ION emails the recipients each time the file is ready.
## Manage exports
The Exports table lists each export with its **Name**, **View**, **Schedule**, and **Last updated**. Use the **Enabled** toggle to pause or resume a scheduled export. The actions menu on each row offers **Run now**, **View details**, **Enable** or **Disable**, and **Delete**.
## Check job status and download the file
1. On the **Exports** page, open the actions menu on an export and click **View details**, or click the export's name.
2. The detail page shows the export's **View**, **Format**, **Schedule**, **Recipients**, and whether it is **Enabled**, with a **Run now** button to trigger it again.
3. Under **Recent jobs**, each row is one run:
* **Status**: **Pending**, **In progress**, **Completed**, or **Failed**. A failed job shows its error message under **Details**.
* **Trigger**, **Started**, and **Completed**: how and when the job ran.
* **Rows** and **Size**: what the job produced.
4. On a completed job, click **Download** to save the file.
## Related
* [Data snapshots](/administration/data-snapshots)
* [Export run data](/build-hardware/runs-and-execution/export-runs-data)
# Overview
Source: https://docs.firstresonance.io/administration/ion-importers/index
Use CSV imports to bulk-create or update records in ION.
ION's import feature lets you upload a CSV file to create or update records in bulk. Instead of entering data one row at a time through the UI, you can prepare a spreadsheet offline and import it all at once.
## How imports work
To start an import, navigate to **Imports** in the sidebar, then pick an import type from the **New Import** tab. The importer cards are grouped by category (**Production**, **Supply Chain**, **Quality**, and **OS**) to match where each record type lives in ION. Imports are processed **asynchronously**, so you can navigate away and continue working while the import runs. You receive a notification when the import completes or fails.
Clicking **Start Import** on any card opens the import wizard, which walks you through **Upload**, **Match**, and **Review** steps.
On the **Upload** step you can download the CSV template, toggle the importer's options (the option set varies by importer), or choose **Enter data manually** to type rows into the grid instead of uploading a file.
The editable preview covers files up to 5,000 rows. Upload a larger file and ION says so, skips the **Match** and **Review** steps, and offers **Import file as-is**, which sends the file unedited. A file above 1,000,000 rows is rejected outright.
The **Match** step maps your file's columns to ION fields. ION auto-maps each column to the field whose name it matches, and you can adjust any mapping in that column's **Maps To** dropdown: pick a different field, keep the column as a custom attribute, or skip the column so it isn't imported. Required fields must stay mapped. When you enter data manually instead of uploading, this step becomes a column picker where you choose which fields to include.
On the **Review** step, ION shows your rows in an editable grid. Edit any cell inline, then use the validate control to check the data. ION flags each failing cell with its error and highlights the affected rows, so you can fix them and revalidate before importing. The control reads **Validate** before the first check and **Revalidate** after, and editing a validated file prompts you to revalidate before you can import.
Imports are **all-or-nothing**. If any row fails validation, no rows are written and you receive an error report. You never end up with a partially imported file.
You can track the status of all imports from the **Import History** tab. Expand any failed job to see its per-row error messages, or click **Download** to pull the full error CSV.
## Video walkthrough
The end-to-end import flow is the same for every importer: pick a type, download the template, upload your CSV, run validation, then commit. The walkthrough below uses the mBOM importer as an example.
## Required and optional columns
Each import type has its own set of columns. Some columns are required and must be present in every row, while others are optional. Optional columns that you omit from your CSV are skipped entirely, so ION does not touch those fields on the imported records. Only when an optional column is included in the CSV but left blank for a given row does ION apply a default value based on the field type (see below). If a required column is missing or empty, the import fails with a validation error.
## How empty CSV cells are handled
When an optional column is present in your CSV but a row leaves it blank, the cell defaults to a falsy or zero-equivalent value (not null). This only applies to columns that are actually in the file. Omitting the column entirely leaves the field untouched.
### Lookup and control columns are exempt
A handful of columns are exempt from the falsy-default rule, either because they're used to look up an existing record or control importer behavior, or because `0` isn't a falsy value for that field's type. For these, an empty cell means "not provided":
| Column | Importer | Empty cell means |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
| `id` | [Part Inventory](/administration/ion-importers/part-inventory), [Tool Inventory](/administration/ion-importers/tool-inventory), [Issues](/administration/ion-importers/issues) | Create a new record (no update lookup) |
| `original_name` | [Locations](/administration/ion-importers/location-imports) | No rename |
| `original_po_number` | [Purchase Orders](/administration/ion-importers/purchase-orders) | No rename |
| `latitude`, `longitude` | [Locations](/administration/ion-importers/location-imports) | Unset. `0` is not a falsy value for a coordinate |
| `can_hold_inventory`, `pickable` | [Locations](/administration/ion-importers/location-imports) | Keep the field's default of `true`, rather than flipping to `false` like other booleans |
| `last_maintained_date`, `uri` | [Tool Inventory](/administration/ion-importers/tool-inventory) | Unset. No date is written and no URI is stored |
| `due_date`, `quantity`, `assigned_to` | [Runs](/administration/ion-importers/runs) | Unset. No due date, quantity, or assignee is written |
Each importer's column table calls out which fields behave this way.
| Field Type | Empty Cell Value | Examples |
| ---------- | ---------------- | ------------------------------------------- |
| Text | `""` | `description`, `address` |
| Numeric | `0` | `quantity` |
| Boolean | `false` | `made_on_assembly`, `available`, `archived` |
If you leave a boolean column blank, ION treats it as **false** (unchecked). To set a field to true, you must explicitly write a truthy value.
### Accepted boolean values
Boolean fields accept any of the following (case-insensitive):
| Truthy | Falsy |
| ------ | ------- |
| `true` | `false` |
| `yes` | `no` |
| `1` | `0` |
## Errors
When validation fails, ION reports up to **5,000 errors** with the specific row number and a description of the problem. Row numbers correspond to the CSV file where row 1 is the header and row 2 is the first data row.
If your file contains more than 5,000 errors, ION stops scanning at that point. Fix the reported batch and re-upload to see the rest.
Review your column headers and values before uploading, and pay special attention to boolean columns: blank cells default to `false`, so any field you intend to be `true` must be set explicitly. Because imports are all-or-nothing, resolve every error the **Review** step flags and revalidate until the file passes before you re-upload. Run your import in a staging environment and verify the results before importing into production.
# Issue imports
Source: https://docs.firstresonance.io/administration/ion-importers/issues
Create or update issues from a CSV file, including dispositions, assignees, and custom attributes.
## Overview
The Issues importer bulk-creates or updates issues. The distinction is row-by-row:
* Rows **without** an `id` value **create** a new issue. `title` is required, and the issue starts at status `pending` unless the row sets an explicit `status`.
* Rows **with** an `id` value **update** the matching issue.
## Columns
| Column | Required | Description |
| ------------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | No | Issue ID. Provide to update an existing issue; omit to create a new one. **Lookup column**: an empty cell is treated as "not provided," not `0`. |
| `title` | Yes (for create) | Issue title. |
| `status` | No | One of `pending`, `in_progress`, `in_review`, `resolved`. Matched case-insensitively. New issues default to `pending`. |
| `cause_condition` | No | Plain-text cause description. See [Rich-text fields](#rich-text-fields). |
| `disposition` | No | Plain-text disposition description. See [Rich-text fields](#rich-text-fields). |
| `expected_condition` | No | Plain-text expected-condition description. See [Rich-text fields](#rich-text-fields). |
| `issue_disposition_type` | No | Title of a disposition type configured in your organization. Matched case-insensitively. |
| `assigned_to` | No | Email address of the user to assign. Matched case-insensitively against existing users. |
## Rich-text fields
`cause_condition`, `disposition`, and `expected_condition` are rich-text fields in the ION UI. The importer accepts plain text and converts it into a single paragraph, which you can elaborate on later in the UI. On update rows, an empty cell leaves the existing content untouched.
## Custom attributes
Any column not in the standard list above is treated as a custom attribute on the issue. Custom attribute columns must match a configured attribute key for your organization; unknown columns cause a validation error.
## Related
For general import behavior, empty cell handling, and error reporting, see [Importers](/administration/ion-importers).
# Location imports
Source: https://docs.firstresonance.io/administration/ion-importers/location-imports
Import factory locations from a CSV file, including nested hierarchies, supervisors, and custom attributes.
## Overview
Location imports let you bulk-create or update factory locations. Column headers are matched case-insensitively and extra whitespace is trimmed.
## Columns
| Column | Required | Description |
| ----------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | Yes | Location name. Must be unique within the CSV and cannot exceed 128 characters. |
| `original_name` | No | If renaming an existing location, this is the current name in ION. Max 128 characters. **Lookup column**: an empty cell means "no rename," not `""`. |
| `parent_name` | No | Name of the parent location. Used to build nested hierarchies. |
| `description` | No | Description of the location. Max 512 characters. |
| `address` | No | Physical address. Max 128 characters. |
| `available` | No | Whether the location is available. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `archived` | No | Whether the location is archived. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `exclude_from_merge_part_inventory` | No | Exclude this location from merged part inventory views. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `can_hold_inventory` | No | Whether the location is a valid destination for inventory. Boolean field. **Default-true column**: an empty cell keeps the current value rather than setting `false`. |
| `pickable` | No | Whether kitting can pull from the location. Boolean field. **Default-true column**: an empty cell keeps the current value rather than setting `false`. |
| `latitude` | No | Latitude coordinate. Must be between -90 and 90. Empty cells are treated as unset (not `0`). |
| `longitude` | No | Longitude coordinate. Must be between -180 and 180. Empty cells are treated as unset (not `0`). |
| `type` | No | Location type. One of `workcenter`, `tote`, `warehouse`, `rack`, or `shelf`. |
| `supervisor` | No | Email address of the supervisor. Must match an existing user in ION. |
## Custom attributes
Any columns beyond the standard set are matched against your organization's custom location attributes. If a column header does not match a standard column or a configured custom attribute, the import will fail with a validation error.
## Parent-child hierarchies
You can define nested locations by specifying a `parent_name` for each row. ION resolves parents automatically, including parents that are created within the same import. Parents are always processed before their children regardless of row order.
Circular parent-child relationships are detected and rejected. An example is Location A being the parent of Location B, which is in turn the parent of Location A.
**Can Hold Inventory** and **Pickable** are advisory until an admin turns on the matching enforcement setting in **Settings > Supply Chain > Locations**. With **Enforce can hold inventory** on, a row that turns off `can_hold_inventory` for a location that still holds inventory is rejected.
## Related
For general import behavior, empty cell handling, and error reporting, see [Importers](/administration/ion-importers).
For managing locations through the UI, see [Locations and work centers](/build-hardware/locations-and-work-centers).
# mBOM imports
Source: https://docs.firstresonance.io/administration/ion-importers/mbom-imports
Import multi-level bills of materials from a CSV file, using Depth or Level notation to define part hierarchies.
## Overview
mBOM imports let you define multi-level part structures in a single CSV, from top-level assemblies down to individual components. ION supports two ways to describe how parts are nested: **Depth** notation and **Level** notation. ION automatically detects which one you are using based on the first column header in your CSV.
## Import options
| Option | Default | Description |
| ------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Auto-create missing parts | On | When enabled, any parts in the import that do not already exist in the Parts Library are created automatically. When disabled, referencing a part that does not exist causes a validation error. |
| Always create new version | On | When enabled, a new mBOM version is always created. When disabled, the latest version is updated in place if it is a draft; otherwise a new version is created. |
## Columns
| Column | Required | Description |
| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `depth` or `level` | Yes | The first column in your CSV. Determines how the part hierarchy is structured. See sections below. |
| `part_number` | Yes | The part number for this row. |
| `revision` | Yes | The part revision. |
| `quantity` | Yes | How many of this part are needed in the parent assembly. |
| `substitutes` | No | Alternative parts, formatted as `part_number[revision];part_number[revision]`. Example: `BOLT-300-ALT[A];BOLT-300-METRIC[B]`. |
| `made_on_assembly` | No | Whether this part is fabricated as part of the parent assembly. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `reference_designators` | No | Reference designator strings for the component. For multiple designators, separate each with a semicolon. Example: `J1;J2;J3`. |
Ensure part numbers and revisions match existing records exactly. Part matching is case-insensitive.
## Depth notation
Depth notation describes your BOM structure by indenting parts one level at a time, similar to how you might indent items in an outline. The top-level assembly is depth **1**, its direct components are depth **2**, their sub-components are depth **3**, and so on.
To move back up the tree and start a new branch, return to a lower depth number. Rows must be listed in the order they appear in the assembly. Each component must come after the assembly it belongs to.
**Example:** An assembly (`ASSEMBLY-100`) contains a bracket and a panel. The bracket itself requires four bolts.
| depth | part\_number | revision | quantity |
| ----- | ------------ | -------- | -------- |
| 1 | ASSEMBLY-100 | A | 1 |
| 2 | BRACKET-200 | B | 2 |
| 3 | BOLT-300 | A | 4 |
| 2 | PANEL-400 | A | 1 |
You cannot skip depth levels. For example, jumping from depth 1 directly to depth 3 is not allowed. Each component must be listed directly under its parent assembly.
## Level notation
Level notation uses a numbering scheme, such as `1`, `1.1`, and `1.1.1`, to explicitly identify where each part sits in the assembly tree. This is similar to how work breakdown structures or section numbering works. The number itself tells you the full path from the top-level assembly down to the component.
Unlike Depth notation, **rows can appear in any order** because the level value fully defines each part's position in the hierarchy. ION sorts and organizes the structure for you.
**Example:** The same assembly as above, with rows intentionally listed out of order to show that ordering does not matter.
| level | part\_number | revision | quantity |
| ----- | ------------ | -------- | -------- |
| 1 | ASSEMBLY-100 | A | 1 |
| 1.2 | PANEL-400 | A | 1 |
| 1.1 | BRACKET-200 | B | 2 |
| 1.1.1 | BOLT-300 | A | 4 |
Here, `1.1` (BRACKET-200) and `1.2` (PANEL-400) are both direct components of `1` (ASSEMBLY-100), and `1.1.1` (BOLT-300) is a sub-component of the bracket, regardless of the row order in the CSV.
Every parent level must exist somewhere in the file. For example, you cannot define `1.1.1` without also having a `1.1` row. However, the rows do not need to be in any particular order.
## Cycle detection
ION automatically detects circular references in your mBOM. If assembly A contains part B and part B also contains assembly A (directly or through any chain of intermediate parts), the import fails with a validation error identifying the cycle. This check also applies to substitute parts.
## Related
For general import behavior, empty cell handling, and error reporting, see [Importers](/administration/ion-importers).
# Part inventory imports
Source: https://docs.firstresonance.io/administration/ion-importers/part-inventory
Create or update part inventory records from a CSV file.
## Overview
The Part Inventory importer creates new inventory or updates existing inventory from a single CSV. The distinction is row-by-row:
* Rows **without** an `id` value **create** new inventory. `part_number` is required.
* Rows **with** an `id` value **update** the matching inventory record. `part_number` and `revision` are ignored on update.
Creates run through the same path used by the UI, so aBOM creation, serial autogeneration, and tracking-type validation all apply. Updates run through the matching update path, so cascade behavior such as location propagation and quantity validation is preserved.
## Columns
| Column | Required | Description |
| --------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | No | Inventory ID. Provide to update an existing record; omit (or leave blank for the row) to create new inventory. **Lookup column**: an empty cell is treated as "not provided," not `0`. |
| `part_number` | Yes (for create) | Part number for new inventory. Ignored on update. |
| `revision` | No | Part revision. Combined with `part_number` to look up the part; if omitted, the latest revision is used. |
| `serial_number` | No | Serial number. |
| `lot_number` | No | Lot number. |
| `quantity` | No | Quantity. Must be non-negative. Empty cells become `0`. |
| `quantity_scrapped` | No | Scrapped quantity. Must be non-negative. Empty cells become `0`. |
| `location` | No | Location name. Must match an existing location in ION. |
| `supplier` | No | Supplier name. Must match an existing supplier in ION. |
| `unit_of_measure` | No | Unit of measure name. Must match an existing unit in ION. |
| `intent_option` | No | Intent option value. Must match a configured intent option. |
| `autogenerate_serial` | No | When `true` on a create row, ION generates a serial number. Cannot be set on update rows. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `autogenerate_lot` | No | When `true` on a create row, ION generates a lot number. Cannot be set on update rows. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
Name-valued fields (`location`, `supplier`, `unit_of_measure`, `intent_option`) are resolved case-insensitively against existing records. An unrecognized name produces a row-level error.
`autogenerate_serial` and `autogenerate_lot` only apply on create rows. Setting either to `true` on a row that includes an `id` is rejected. Remove the `id` or the flag to fix the row.
## Custom attributes
Any column not listed above is treated as a custom attribute on the part inventory. Custom attribute columns must match a configured attribute key for your organization; unknown columns cause a validation error.
## Related
For general import behavior, empty cell handling, and error reporting, see [Importers](/administration/ion-importers).
# Part-procedure relation imports
Source: https://docs.firstresonance.io/administration/ion-importers/part-procedure-relations
Link parts to procedures in bulk from a CSV file.
## Overview
The Part-Procedure Relations importer links parts to procedures, one CSV row per `(part, procedure)` pair. The part is identified by `part_number` + `revision`, the procedure by `procedure_title` + `procedure_version`, and the only data field is the `required` flag.
Re-imports are idempotent: if the link already exists, its `required` flag is updated to match the CSV; otherwise a new link is created.
## Columns
| Column | Required | Description |
| ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `part_number` | Yes | Part number. Matched case-insensitively. |
| `revision` | No | Part revision. If omitted and the part has exactly one revision, that revision is used; multiple revisions produce an ambiguity error asking you to specify one. |
| `procedure_title` | Yes | Title of the procedure. |
| `procedure_version` | Yes | Procedure version. Must be a positive integer. |
| `required` | No | Whether the procedure is required for the part. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). Defaults to `false`. |
Each `(part, procedure)` pair can appear only once per file. Duplicate rows are rejected.
Procedure titles aren't globally unique, so a `(procedure_title, procedure_version)` pair that matches more than one procedure produces an ambiguity error rather than guessing.
This importer does not support custom attributes. Any column outside the list above causes a validation error.
## Related
For general import behavior, empty cell handling, and error reporting, see [Importers](/administration/ion-importers).
# Parts imports
Source: https://docs.firstresonance.io/administration/ion-importers/parts
Import parts from a CSV file, including revisions, supplier parts, quality clauses, and custom attributes.
## Overview
The Parts importer lets you bulk-create or revise parts in a single CSV. It supports the same revision behavior as the UI: rows with an explicit revision create or update that exact `(part_number, revision)`, while rows that omit revision either initialize a new part at the org's default revision scheme or auto-revise an existing part using the latest-revision flow (carrying over the source part's mBOM and extensible attributes).
## Columns
| Column | Required | Description |
| -------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `part_number` | Yes | The part number. |
| `revision` | No | The part revision. Omit to let ION assign the next revision automatically (or the scheme's initial value if the part is new). |
| `revision_scheme` | No | Name of the revision scheme to use when creating a brand-new part. Defaults to the org's default scheme. |
| `description` | No | Free-text description. |
| `status` | No | Part status. One of `released` or `archived`. Matched case-insensitively. |
| `purchase_type` | No | Purchase type. One of `receivable_inventory`, `receivable_non_inventory`, or `non_receivable_non_inventory`. Matched case-insensitively. |
| `tracking_type` | No | Tracking type. One of `serial` or `lot`, matched case-insensitively. Leave the cell blank for an untracked part; `none` is rejected. |
| `sourcing_strategy` | No | Sourcing strategy. One of `make`, `buy`, or `dual_source`. Matched case-insensitively. |
| `cost` | No | Default unit cost. Empty cells become `0`. |
| `lead_time` | No | Lead time as a duration. See [Duration format](#duration-format). |
| `reorder_minimum_quantity` | No | Reorder minimum. Must be non-negative. Empty cells become `0`. |
| `reorder_maximum_quantity` | No | Reorder maximum. Must be non-negative. Empty cells become `0`. |
| `export_controlled` | No | Whether the part is export controlled. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `unit_of_measure` | No | Unit of measure name, such as `each` or `kg`. Must match an existing unit in ION. |
| `quality_clauses` | No | Semicolon-delimited list of quality clause reference names to attach to the part. |
| `supplier_parts` | No | Semicolon-delimited list of supplier-part records. See [Supplier parts](#supplier-parts) below. |
Part matching is case-insensitive. Enum fields (`status`, `purchase_type`, `tracking_type`, `sourcing_strategy`) only accept the values listed above. An invalid value produces a row-level error listing the allowed options.
A blank enum cell is not the same as leaving the column out. Omit the column and the field is untouched. Include the column and leave a cell blank, and ION clears the field, which succeeds for `tracking_type` and `sourcing_strategy` (making the part untracked, or clearing its strategy) and fails for `status` and `purchase_type`, which can't be empty.
## Revisions
The Parts importer mirrors the UI's revision flow:
* **Row with an explicit revision**: looks up `(part_number, revision)`. If it exists, the row updates that part; otherwise it creates a new part at that exact revision.
* **Row without a revision, no existing part with that `part_number`**: ION assigns the initial revision from the configured `revision_scheme` (or the org's default scheme, typically `"A"` or `"1"`).
* **Row without a revision, existing part with that `part_number`**: ION computes the next revision from the latest existing revision and creates a revised part. The new part inherits the mBOM and extensible attributes of the source part.
You cannot have two rows that both omit `revision` for the same `part_number`. ION cannot disambiguate which one should auto-bump. Specify revisions explicitly to fix the conflict.
Likewise, an explicit-revision row that collides with an auto-bumped revision from another row is rejected as a duplicate. This happens when one row sets `revision = B` and another auto-bumps to `B`.
## Duration format
The `lead_time` column accepts a duration written in days, hours, and minutes. Use the units `d`, `h`, and `m`, in that order, with optional spaces between terms. Each unit is optional. The value is rounded down to the nearest minute. A plain number with no unit is read as a number of seconds.
Valid values include:
| Value | Meaning |
| --------- | ---------------------- |
| `11d` | 11 days |
| `1d 2h` | 1 day, 2 hours |
| `10d 21m` | 10 days, 21 minutes |
| `45m` | 45 minutes |
| `3600` | 3,600 seconds (1 hour) |
The **Review** step flags a value that uses any other unit (such as weeks or seconds), lists the units out of order, or is otherwise unparseable.
## Supplier parts
The `supplier_parts` column attaches one or more SupplierParts to the part. Use semicolons (`;`) to separate records and pipes (`|`) to separate fields within a record:
```
supplier_name|supplier_part_number|cost|conversion_factor;supplier_name|...
```
| Field | Required | Notes |
| ---------------------- | -------- | -------------------------------------------- |
| `supplier_name` | Yes | Must match an existing supplier in ION. |
| `supplier_part_number` | Yes | The supplier's part number for this part. |
| `cost` | No | Falls back to the Part's `cost` if omitted. |
| `conversion_factor` | No | Defaults to `1.0`. Must be greater than `0`. |
Example: `Acme|ACME-WID-001|4.25|1;Globex|GLX-W1`
If a SupplierParts row already exists for the `(part, supplier, supplier_part_number)` combination, the importer updates its `cost` and `conversion_factor` in place. Otherwise it inserts a new row.
## Quality clauses
The `quality_clauses` column accepts a semicolon-delimited list of quality clause `reference_name` values. Each must match an existing Requirement with `requirement_type = QUALITY_CLAUSE`. The importer skips associations that already exist, so re-imports are idempotent.
Example: `QC-001;QC-002`
## Custom attributes
Any column not in the standard list above is treated as a custom attribute on the part. Custom attribute columns must match a configured attribute key for your organization; unknown columns cause a validation error.
## Related
For general import behavior, empty cell handling, and error reporting, see [Importers](/administration/ion-importers).
# Plan input imports
Source: https://docs.firstresonance.io/administration/ion-importers/plan-inputs
Bulk-add inputs to existing draft plans from a CSV file.
## Overview
The Plan Inputs importer adds inputs to **existing draft plans**, one CSV row per input, with the parent plan identified by `plan_id`. A single file can target multiple plans.
This importer only creates plan inputs. It never modifies the parent plan itself, and because a plan can legitimately hold multiple inputs for the same part, per-row updates are not supported. To rebuild a plan's inputs from scratch, enable the **Replace existing inputs** option in the import dialog. ION then removes the plan's current inputs and recreates them from the CSV.
```
plan_id | part_number | revision | quantity | due_date | serial_number
42 | WIDGET-A | A | 10 | 2026-05-01 |
42 | WIDGET-B | A | 5 | 2026-05-01 |
42 | WIDGET-C | B | 1 | 2026-05-01 | SN-12345
```
## Import options
| Option | Default | Description |
| ----------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Replace existing inputs | Off | When enabled, the targeted plans' existing inputs are removed and recreated from the CSV. When disabled, imported inputs are added to the existing ones. |
## Columns
| Column | Required | Description |
| ------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `plan_id` | Yes | ID of the parent plan. The plan must exist and be in **draft** status. |
| `part_number` | Yes | Part number of the input. Matched case-insensitively. |
| `revision` | No | Part revision. If omitted and the part has exactly one revision, that revision is used; multiple revisions produce an ambiguity error asking you to specify one. |
| `quantity` | Yes | Input quantity. Must be a non-negative integer. |
| `due_date` | Yes | Due date for the input, such as `2026-05-01`. |
| `serial_number` | No | Serial number for the input. |
| `name` | No | Display name for the input. |
| `is_generate_new_demand` | No | Whether the input generates new demand. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `is_independent` | No | Whether the input is independent. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
Plan inputs can only be added to **draft** plans. Rows referencing a plan in any other status fail with a row-level error, as do rows referencing a plan ID that doesn't exist.
This importer does not support custom attributes. Any column outside the list above causes a validation error.
## Related
For general import behavior, empty cell handling, and error reporting, see [Importers](/administration/ion-importers).
# Purchase order imports
Source: https://docs.firstresonance.io/administration/ion-importers/purchase-orders
Import purchase orders and their lines from a single denormalized CSV.
## Overview
The Purchase Orders importer creates or updates Purchase Orders **and** their Purchase Order Lines from a single CSV. The CSV is denormalized: one row per PO line, with the PO header columns repeated across every row that belongs to the same PO.
Example: two POs, three lines.
| po\_number | supplier | currency | part\_number | quantity | cost |
| ---------- | -------- | -------- | ------------ | -------- | ------ |
| PO-001 | Acme | USD | WIDGET-A | 10 | 5.00 |
| PO-001 | Acme | USD | WIDGET-B | 5 | 12.00 |
| PO-002 | Globex | EUR | GADGET-X | 1 | 100.00 |
Rows sharing the same `po_number` are merged into a single PO header. If a header column is included in your CSV, it must be populated on **every row of that PO** and all values must match. Any blank cell or any value that disagrees with another row of the same PO fails the import with a per-conflict error before any data is written.
## Header columns
These describe the PO itself. If you include a header column in your CSV, it must be present and identical on every row that shares a `po_number`. Otherwise the import fails.
| Column | Required | Description |
| ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `po_number` | Yes | PO identifier. The grouping key. |
| `original_po_number` | No | Existing PO number when renaming. **Lookup column**: an empty cell means "no rename," not `""`. |
| `po_description` | No | Free-text PO description. |
| `supplier` | No | Supplier name. Must match an existing supplier. |
| `ship_to_location` | No | Ship-to location name. Must match an existing location. |
| `bill_to_location` | No | Bill-to location name. Must match an existing location. |
| `currency` | No | Currency type, such as `USD` or `EUR`. Must match an existing currency. |
| `ordered_at` | No | Order date. Parsed flexibly (any pandas-recognized date format). |
| `assigned_to` | No | Email of the user the PO is assigned to. Must match an existing user. |
| `po_intent_option` | No | Intent option value at the PO level. Prefixed with `po_` to disambiguate from the line-level `intent_option`. |
| `po_labels` | No | Semicolon-delimited list of label values to attach to the PO. |
| `terms_and_conditions` | No | Title of a Requirement with type `TERMS_AND_CONDITIONS`. Overrides the org's default. |
| `fees` | No | Semicolon-delimited list of fee records. See [Fees](#fees) below. |
## Line columns
These describe a single PO line. Every row contributes one line if any line column is populated.
| Column | Required | Description |
| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `part_number` | No | Part number for the line. Combined with `revision` to look up the part. |
| `revision` | No | Part revision. If omitted, the latest revision of `part_number` is used. |
| `description` | No | Line description. |
| `quantity` | No | Line quantity. Must be non-negative. |
| `cost` | No | Line unit cost. Must be non-negative. |
| `need_date` | No | Date needed. Parsed flexibly. |
| `estimated_arrival_date` | No | Estimated arrival date. Parsed flexibly. |
| `paid` | No | Marks the line as paid. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `percentage_fee_exempt` | No | Excludes this line from percentage-based PO fees. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `labels` | No | Semicolon-delimited list of label values to attach to the line. |
| `quality_clauses` | No | Semicolon-delimited list of quality clause titles. Must match Requirements with `requirement_type = QUALITY_CLAUSE`. |
| `intent_option` | No | Intent option value at the line level. |
A row that populates only header columns (no `part_number` or other line column) contributes to the PO header but does not create a line. Use this to create or update a PO without lines.
## Fees
The `fees` column accepts a semicolon-delimited list of fee records. Each record uses two bracketed fields after the fee name:
```
Name[value][type];Name[value][type]
```
| Field | Notes |
| ------- | -------------------------------------------- |
| `Name` | Required. Must be unique within the PO. |
| `value` | Required. Must parse as a number. |
| `type` | Required. One of `currency` or `percentage`. |
Example: `Shipping[50.00][currency];VAT[8.5][percentage]`
Fee names must be unique within a PO. Duplicate fee names in the same row's `fees` cell are rejected at validation.
## Custom attributes
Any column not in the standard set above is treated as a custom attribute. The importer routes each column to either PO-level or PO-line-level attributes based on which table defines it in your organization's attribute configuration:
* A column matching a PO custom attribute is attached to the PO.
* A column matching a PO-line custom attribute is attached to the line.
* A column matching **both** is **ambiguous** and rejected. Rename one side to disambiguate.
* A column matching neither is rejected as unrecognized.
Matching is case- and format-sensitive, so a CSV header `BatchID` matches an attribute keyed `BatchID` only.
## Header conflicts
Header-column conflicts are collected across all POs and reported together, so you can fix everything in one pass.
## Related
For general import behavior, empty cell handling, and error reporting, see [Importers](/administration/ion-importers).
# Run imports
Source: https://docs.firstresonance.io/administration/ion-importers/runs
Bulk-create runs from a CSV file, including procedures, part inventory, assignees, and run batches.
## Overview
The Runs importer bulk-creates runs, one CSV row per run. It drives the same path as creating runs in the UI, so step copying, batching, and every downstream side effect behave identically.
This importer is **create-only**: it does not update existing runs.
Each run can:
* be created from a **procedure**, identified by `procedure_id` *or* `procedure_title` + `procedure_version` (a run with no procedure is also allowed),
* be tied to **part inventory**, either linking an existing record via `part_inventory_id` or creating a new one via `part_number`,
* join a **run batch** via `run_batch_title`,
* carry an assignee, intent option, due date, and custom attributes.
## Columns
| Column | Required | Description |
| ---------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | Conditional | Run title. Each row needs either a `title` or `autogenerate_title = true`, but not both. |
| `autogenerate_title` | Conditional | When `true`, ION generates the run title. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `procedure_id` | No | ID of the procedure to create the run from. Cannot be combined with `procedure_title` / `procedure_version`. |
| `procedure_title` | No | Procedure title. Must be paired with `procedure_version`. |
| `procedure_version` | No | Procedure version (integer). Must be paired with `procedure_title`. |
| `description` | No | Free-text description. |
| `due_date` | No | Due date, such as `2026-06-15`. An empty cell leaves the field unset. |
| `quantity` | No | Run quantity. Must be a non-negative integer. When the row creates a new inventory via `part_number`, the quantity applies to that inventory. An empty cell leaves the field unset. |
| `part_number` | No | Part number. Creates a **new** inventory record for the run. Cannot be combined with `part_inventory_id`. |
| `revision` | No | Part revision for the new inventory. If omitted and the part has exactly one revision, that revision is used. |
| `serial_number` | No | Serial number for the new inventory. |
| `lot_number` | No | Lot number for the new inventory. |
| `autogenerate_serial_number` | No | When `true`, ION generates a serial number for the new inventory. Boolean field. |
| `autogenerate_lot_number` | No | When `true`, ION generates a lot number for the new inventory. Boolean field. |
| `part_inventory_id` | No | ID of an **existing** inventory record to link to the run. Cannot be combined with `part_number` or any of the new-inventory columns above. |
| `assigned_to` | No | Email address of the user to assign. An empty cell leaves the run unassigned. |
| `intent_option` | No | Intent option name. Must match a configured intent option. |
| `export_controlled` | No | Whether the run is export controlled. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `run_batch_title` | No | Title of the run batch to add the run to. See [Run batches](#run-batches). |
Three column groups are mutually exclusive per row:
* `title` vs. `autogenerate_title`: provide exactly one.
* `procedure_id` vs. `procedure_title` + `procedure_version`: provide at most one form.
* `part_inventory_id` vs. `part_number`: when linking an existing inventory, the new-inventory columns (`revision`, `serial_number`, `lot_number`, `autogenerate_serial_number`, `autogenerate_lot_number`) must be empty.
Procedure titles aren't globally unique, so a `(procedure_title, procedure_version)` pair that matches more than one procedure produces an ambiguity error. Use `procedure_id` to disambiguate.
## Run batches
Rows sharing a `run_batch_title` are grouped into a single run batch. Batch titles are unique in ION, so this is a find-or-create: if a batch with that title already exists, the runs join it; otherwise ION creates the batch.
## Custom attributes
Any column not in the standard list above is treated as a custom attribute on the run. Custom attribute columns must match a configured attribute key for your organization; unknown columns cause a validation error.
## Related
For general import behavior, empty cell handling, and error reporting, see [Importers](/administration/ion-importers).
# Supplier imports
Source: https://docs.firstresonance.io/administration/ion-importers/suppliers
Create or update suppliers from a CSV file, including contact details and custom attributes.
## Overview
The Suppliers importer bulk-creates or updates suppliers from a single CSV. Matching is **case-insensitive on `name`**. Rows whose name matches an existing supplier update that supplier, and rows with a new name create one.
## Columns
| Column | Required | Description |
| -------------- | -------- | ------------------------------------------------------------------------- |
| `name` | Yes | The supplier name. Matched case-insensitively against existing suppliers. |
| `description` | No | Free-text description. |
| `phone_number` | No | Contact phone number. |
| `address` | No | Supplier address. |
| `email` | No | Contact email address. |
| `contact_name` | No | Name of the primary contact. |
Each supplier name may appear only once per file. Two rows with the same name (compared case-insensitively) produce a duplicate-name validation error.
## Custom attributes
Any column not in the standard list above is treated as a custom attribute on the supplier. Custom attribute columns must match a configured attribute key for your organization; unknown columns cause a validation error.
## Related
For general import behavior, empty cell handling, and error reporting, see [Importers](/administration/ion-importers).
# Tool inventory imports
Source: https://docs.firstresonance.io/administration/ion-importers/tool-inventory
Create or update tool inventory records from a CSV file, including maintenance dates and asset URIs.
## Overview
The Tool Inventory importer creates or updates inventory records for tools (parts with the **tool** part type). It works like the [Part Inventory importer](/administration/ion-importers/part-inventory), where rows without an `id` create new inventory and rows with an `id` update the matching record, with two tool-specific additions: `last_maintained_date` and `uri`.
Because tools are always serial-tracked, **every create row must provide a `serial_number`** matching the physical asset's tag or manufacturer serial. There is no serial autogeneration and no lot tracking for tool inventory.
## Columns
| Column | Required | Description |
| ---------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | No | Inventory ID. Provide to update an existing record; omit to create new inventory. **Lookup column**: an empty cell is treated as "not provided," not `0`. |
| `part_number` | Yes (for create) | Part number of the tool. Ignored on update. |
| `revision` | No | Tool revision. Combined with `part_number` to look up the tool; if omitted, the latest revision is used. |
| `serial_number` | Yes (for create) | Serial number identifying the physical tool. Update rows inherit the existing serial. |
| `location` | No | Location name. Must match an existing location in ION. |
| `last_maintained_date` | No | Date the tool was last maintained, in ISO 8601 format, such as `2026-05-01` or `2026-05-01T12:30:00`. An empty cell leaves the field unset. |
| `uri` | No | Link to the physical asset, such as an asset-management URL. Must be unique across tool inventory; an empty cell stores no value. |
`last_maintained_date` only accepts ISO 8601. Ambiguous formats like `1/2/3` are rejected rather than guessed.
## Custom attributes
Any column not in the standard list above is treated as a custom attribute on the inventory record. Custom attribute columns must match a configured attribute key for your organization; unknown columns cause a validation error.
## Related
* [Tools imports](/administration/ion-importers/tools): import the tool definitions first.
* [Part inventory imports](/administration/ion-importers/part-inventory): shared create/update behavior.
* [Importers](/administration/ion-importers): general import behavior, empty cell handling, and error reporting.
# Tools imports
Source: https://docs.firstresonance.io/administration/ion-importers/tools
Create or revise tools from a CSV file, including subtypes and maintenance intervals.
## Overview
The Tools importer bulk-creates or revises tools. In ION, a tool is a part with the **tool** part type, so this importer follows the same revision flow as the [Parts importer](/administration/ion-importers/parts) but exposes a narrower, tool-relevant column set.
Two things are set automatically on every row:
* **Part type** is always `tool`.
* **Tracking type** is always `serial`, because tools are serial-tracked. Including a `tracking_type` column in the CSV is rejected.
## Columns
| Column | Required | Description |
| ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `part_number` | Yes | The tool's part number. |
| `revision` | No | The revision. Omit to let ION assign the next revision automatically (or the scheme's initial value if the tool is new). |
| `revision_scheme` | No | Name of the revision scheme to use when creating a brand-new tool. Defaults to the org's default scheme. |
| `description` | No | Free-text description. |
| `export_controlled` | No | Whether the tool is export controlled. Boolean field (see [accepted values](/administration/ion-importers#accepted-boolean-values)). |
| `maintenance_interval` | No | Maintenance interval as a duration. See [Duration format](#duration-format). |
| `subtypes` | No | Semicolon-delimited list of tool subtype names, such as `Torque Wrench;Calibrated`. |
## Duration format
The `maintenance_interval` column accepts a duration written in days, hours, and minutes. Use the units `d`, `h`, and `m`, in that order, with optional spaces between terms. Each unit is optional. The value is rounded down to the nearest minute. A plain number with no unit is read as a number of seconds.
Valid values include:
| Value | Meaning |
| ----------- | -------------------------- |
| `30d` | 30 days |
| `1d 2h` | 1 day, 2 hours |
| `1d 2h 20m` | 1 day, 2 hours, 20 minutes |
| `45m` | 45 minutes |
| `3600` | 3,600 seconds (1 hour) |
The **Review** step flags a value that uses any other unit (such as weeks or seconds), lists the units out of order, or is otherwise unparseable.
## Revisions
Revision behavior mirrors the [Parts importer](/administration/ion-importers/parts#revisions): rows with an explicit revision create or update that exact `(part_number, revision)`, while rows that omit revision either initialize a new tool at the scheme's first value or auto-revise an existing tool from its latest revision.
## Subtypes
The `subtypes` column attaches one or more subtypes to the tool. Subtype names are matched case-insensitively; **names that don't exist yet are created automatically**, so you can bootstrap your subtype list from the same CSV. Associations that already exist are skipped, keeping re-imports idempotent.
## Custom attributes
Any column not in the standard list above is treated as a custom attribute on the tool. Custom attribute columns must match a configured attribute key for your organization; unknown columns cause a validation error.
## Related
* [Tool inventory imports](/administration/ion-importers/tool-inventory): import the physical tool instances.
* [Parts imports](/administration/ion-importers/parts): full revision-flow details.
* [Importers](/administration/ion-importers): general import behavior, empty cell handling, and error reporting.
# Labels
Source: https://docs.firstresonance.io/administration/labels
Create and delete the shared labels used to tag and group procedures, runs, purchases, issues, and other records across ION.
**Labels** are shared tags you apply across ION to group related records. The same label library is shared by every object type, so a single label can tie together procedures, runs, purchases, and issues. Use them to slice work by program, customer, priority, or any cross-cutting theme your team tracks.
## Create a label
You create labels inline from any record that supports them, such as a procedure, run, purchase, or issue:
1. On the record, click **Labels** (or **Add labels**).
2. In the picker, type the label name in the **Search or create labels...** box.
3. If no matching label exists, click **Create ""**.
ION creates the label, adds it to the shared library for every object type, and applies it to the current record. To reuse it elsewhere, open the labels picker on another record and search for it.
## Delete a label
Deleting a label removes it from all records across ION. Label deletion is only available through the API; there is no UI option yet.
1. Query for the label to get its `id` and `_etag`. Replace `` with the label value.
```graphql theme={null}
{
labels(filters: {value: {eq: }}) {
edges {
node {
id
_etag
value
}
}
}
}
```
2. Delete the label using the `id` and `_etag` from the previous query.
```graphql theme={null}
mutation DeleteLabel($id: ID!, $etag: String!) {
deleteLabel(id: $id, etag: $etag) {
id
}
}
```
Variables:
```json theme={null}
{
"id": "",
"etag": ""
}
```
# Configure barcode labels
Source: https://docs.firstresonance.io/administration/organization-settings/configure-barcode-labels
Customize barcode label templates for inventory, kits, and locations in ION.
ION can print barcode labels for inventory items, kits, locations, and runs. Each entity type can have multiple named label templates, and you can optionally connect a dedicated print server.
## Create a label template
1. In ION, go to **Settings > Organization > Barcode Labels**.
2. In the **Label templates** section, select the tab for the entity type you want to configure: **Inventory**, **Kits**, **Locations**, or **Runs**.
3. Click **New Template**.
4. Fill in the template fields:
| Field | Description |
| ------------------------- | ---------------------------------------------------------------------- |
| **Template Name** | A name to identify this template. |
| **Print density (DPI)** | Printer DPI setting. Default is 203. Match this to your printer's DPI. |
| **Label height (inches)** | Height of the label in inches. Default is 1. |
| **Label width (inches)** | Width of the label in inches. Default is 3. |
| **ZPL Template** | The label layout written in ZPL (Zebra Programming Language). |
5. Click **Create Template**.
## Edit a label template
1. In ION, go to **Settings > Organization > Barcode Labels**.
2. Select the tab for the entity type, then select the template you want to edit from the **Templates** list.
3. Update the template fields.
4. Click **Save Template**.
To remove a template, select it, then click **Deactivate**.
### Available template variables
Insert these variables into your ZPL template and ION replaces them with live data when printing:
| Variable | Description |
| -------------------------- | ---------------------------------------------- |
| `${partNumber}` | Part number |
| `${partDescription}` | Part description |
| `${partRevision}` | Part revision |
| `${serialNumber}` | Serial number |
| `${lotNumber}` | Lot number |
| `${quantity}` | Quantity |
| `${location}` | Location |
| `${dateCreated}` | Date the record was created |
| `${barcode}` | Barcode value |
| `${qrCode}` | QR code value |
| `${activeKitId}` | Active kit ID |
| `${abomInstallationCount}` | Number of aBOM installations |
| `${canceledPoLineCount}` | Number of canceled purchase order lines |
| `${createdBy.email}` | Email of the user who created the record |
| `${createdBy.id}` | ID of the user who created the record |
| `${createdBy.lastLogin}` | Last login of the user who created the record |
| `${createdBy.locationId}` | Location ID of the user who created the record |
## Connect a print server
A print server lets ION send barcode labels directly to a printer without requiring a browser print dialog.
1. In ION, go to **Settings > Organization > Barcode Labels**.
2. Turn on **Use print server**.
3. In the **Print server API key** field, enter the API key for your print server.
To view the existing API key, click **Show**. You need admin access to reveal the key.
# Configure export control
Source: https://docs.firstresonance.io/administration/organization-settings/configure-export-control
Configure identity groups and understand how ION restricts export-controlled parts, procedures, and runs.
Export control adds a group-based access decision on top of ION's standard roles and permissions. A person needs an allowed identity-provider group to reach export-controlled parts, procedures, and runs. Their normal ION permissions still determine which actions they can perform on the records they can reach.
```mermaid theme={null}
flowchart LR
subgraph config["1. Configure in your identity provider"]
G["Add people to the Export UnRestricted group"]
end
subgraph signin["2. At each sign-in"]
S["SSO sends the person's group membership to ION"]
end
subgraph records["3. Records in ION"]
R["Parts, procedures, and runs marked export controlled"]
end
subgraph access["4. What each person can access"]
U["Export-unrestricted Every record, including controlled parts, procedures, and runs"]
X["Export-restricted Controlled records are hidden from lists; a direct link returns not found"]
end
G --> S --> D{"In an allowed export group?"}
R --> D
D -- "Yes" --> U
D -- "No or missing" --> X
```
## How ION decides access
During sign-in, ION reads the group memberships your identity provider sends through its standard group data. For each request, ION checks that membership for an exact match with either of these group names:
* `Employee Export UnRestricted`
* `Export UnRestricted`
If either name is present, the person is export-unrestricted for that session. A missing group, an empty group list, or any other group name keeps the person export-restricted. Everyone is export-restricted by default until they belong to one of the allowed groups.
The export-control decision reads your identity provider's group membership, not an arbitrary SSO attribute. If your identity provider stores the entitlement in another attribute, map that value into the group data your SSO connection sends before ION can act on it.
## What export control protects
Export control applies to three record types. The effect on an export-restricted person is the same for each: the record is filtered out.
| Flagged record | Access effect for an export-restricted person |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Part | The part is omitted from query results, and a direct link returns not found. A run associated with the part is also treated as export-controlled. |
| Procedure | The procedure is omitted from query results, and a direct link returns not found. A run created from the procedure is also treated as export-controlled. |
| Run | The run is omitted from query results, and a direct link returns not found. A run is treated as export-controlled when its own flag, its associated part, or its source procedure is export-controlled. |
Because ION filters these records before an update can load them, an export-restricted person can't edit an existing export-controlled part, procedure, or run. They also can't mark a part, procedure, or run as export-controlled.
Export-control filtering covers parts, procedures, and runs. Related records keep their own access behavior. Part inventory records aren't filtered, but an export-restricted person can't update inventory tied to an export-controlled part.
## Configure export-control access
1. Contact [First Resonance Support](https://support.firstresonance.io/) to enable export control and confirm that your SSO connection sends identity-provider group membership to ION.
2. In your identity provider, create or identify a group named exactly `Employee Export UnRestricted` or `Export UnRestricted`.
3. Add each person who can access export-controlled records to one of the allowed groups. Keep everyone else out of both groups.
4. After you change group membership, have the affected person start a new sign-in session so ION receives their current groups.
ION doesn't manage export-control group membership. You manage it in your identity provider.
## Mark a record as export-controlled
An export-unrestricted person sets the flag on the record itself:
* On a procedure, turn on the **Export control** toggle.
* While creating a run, turn on **Export controlled**.
* On a part, set the `export_controlled` column to `true` when you [import parts](/administration/ion-importers/parts).
## Verify the restriction
1. Prepare a controlled test record:
* On a test procedure, turn on **Export control**, then create a run from that procedure.
* To test a part, [import a test part](/administration/ion-importers/parts) with `export_controlled` set to `true`, then associate it with a run.
* To test a run on its own, turn on **Export controlled** while creating the run.
2. Sign in with an account in one of the allowed groups that also has the ION permissions the records need. Confirm that the part or procedure and its associated run are available.
3. Sign in with an account outside both allowed groups. Confirm that list results omit the controlled records and that a direct link returns not found.
4. If the part has inventory, confirm that the inventory record stays visible but an update to it is blocked.
## Related
* [Set up SSO](/administration/authentication-settings/sso/set-up-sso)
* [Import parts](/administration/ion-importers/parts)
* [Import runs](/administration/ion-importers/runs)
# Configure general settings
Source: https://docs.firstresonance.io/administration/organization-settings/configure-general-settings
Update your organization's name and logo in ION.
General settings control how your organization appears: the name shown across the app, and the logo displayed in the interface and on a printed [purchase order](/manage-supply-chain/purchasing/create-a-purchase-order).
## Set the organization name
1. In ION, go to **Settings > Organization > General Settings**.
2. In the **Organization Name** field, type the new name.
Every organization starts without a name, and clearing the field returns it to that state rather than being rejected. While the field is empty, ION shows your organization's domain in place of the name, which is what the field's placeholder names.
## Update the organization logo
1. In ION, go to **Settings > Organization > General Settings**.
2. In the **Branding** section, click **Change Image**.
3. In the **Upload Photo URL** dialog, enter a publicly accessible URL in the **Image URL** field.
4. Click **Save**.
To remove the current logo, click **Change Image**, then click **Remove**.
The logo is a URL you host, so it can sit on any host. Until you set one, a printed purchase order carries your organization's name alone and its **Company Logo** option is unavailable.
## Related
* [Create a purchase order](/manage-supply-chain/purchasing/create-a-purchase-order)
# Turn on ION Intelligence
Source: https://docs.firstresonance.io/administration/organization-settings/enable-ion-intelligence
Opt your organization in to ION Intelligence and every other AI surface in ION.
AI is off until an admin turns it on. The setting is org-wide, so while it is off nobody in the
organization can reach an AI surface, admins included.
## Turn on ION Intelligence
1. In ION, go to **Settings > Organization > AI**.
2. Under **ION Intelligence**, turn on **Enable ION Intelligence**.
Only an admin can change the setting. Everyone else sees it as read-only.
While it is off, nobody sees an AI entry point anywhere in ION, such as the ION pill and its sidebar
entry, the **ION** tab in the mobile navigation, the suggested-action cards on **Kits**,
**Purchases**, and **Inventory**, and the suggestion pills on a procedure step. Turning it back on
restores them.
Turning it off disables AI everywhere it is reachable, not only on ION's own screens.
If ION Intelligence is not available to your organization yet, the section says so in place of the
setting. Contact your First Resonance representative to have it enabled.
## Related
* [ION Intelligence](/automate-with-ion/ion-intelligence)
# Manage OAuth apps
Source: https://docs.firstresonance.io/administration/organization-settings/manage-oauth-apps
Register a custom application to authenticate with the ION API using OAuth 2.0.
OAuth applications let your custom tools and integrations authenticate with the ION API. ION supports two app types: web and desktop apps using the PKCE flow, and IoT or limited-input devices using the Device Authorization Grant.
## Register an app
1. In ION, go to **Settings > Organization > OAuth Apps**.
2. Click **Register App**.
3. Enter an **Application Name**.
4. Select the application type:
* **Web / Desktop App (PKCE)**: For web or desktop applications using the Authorization Code + PKCE flow.
* **Device / Wearable**: For IoT devices or limited-input devices using the Device Authorization Grant.
5. Click **Register**.
Copy the **Client ID** from the app's entry for use in your application.
## Add redirect URIs (web / desktop apps only)
After registering a web or desktop app, add the URIs your application uses:
1. In the app's entry, click **Add Callback URL** and enter your callback URL (for example, `https://your-app.com/callback`).
2. Optional: Add any of the following:
* Click **Add Origin** to add an allowed CORS origin.
* Click **Add Logout URL** to add a post-logout redirect URL.
An app's URLs are grouped under **Callback URLs**, **Allowed Origins (CORS)**, and **Logout URLs**. To remove one, click the **X** next to the URL you want to remove.
An app's name and type are fixed after registration. To change them, delete the app and register a new one.
## Delete an app
1. In ION, go to **Settings > Organization > OAuth Apps**.
2. In the app's entry, click the delete icon.
3. Confirm the deletion.
Deleting an OAuth app revokes access for all applications using that client ID. This cannot be undone.
## Related
* [API reference](/api-reference)
# Set the date format
Source: https://docs.firstresonance.io/administration/organization-settings/set-the-date-format
Choose how dates are displayed across your ION organization.
The date format setting controls how dates appear throughout ION for all users in your org. Choose from a predefined format or enter a custom one.
## Set the date format
1. In ION, go to **Settings > Organization > Date Time**.
2. In the **Date Format** field, either:
* Type a custom format string (for example, `YYYY-MM-DD`).
* Open the dropdown to select a predefined format.
3. Check the **Preview** section to confirm today's date appears in the selected format.
## Predefined formats
| Format | Example |
| ------------- | ----------- |
| `YYYY-MM-DD` | 2026-06-11 |
| `MM/DD/YYYY` | 06/11/2026 |
| `DD/MM/YYYY` | 11/06/2026 |
| `DD/MMM/YYYY` | 11/Jun/2026 |
| `DD-MMM-YYYY` | 11-Jun-2026 |
# Auto-assign runs to their creator
Source: https://docs.firstresonance.io/administration/production-settings/auto-assign-runs-to-their-creator
Assign every new run to the person who created it, unless they pick a different assignee.
By default a new run is unassigned until someone picks it up. When this setting is on, a run is assigned to whoever created it unless an assignee is chosen at creation.
## Auto-assign runs to their creator
1. In ION, go to **Settings > Production > Runs**.
2. Turn on **Auto-assign runs to their creator**.
## What changes for the person creating a run
While the setting is on, the run creation form pre-fills **Assigned to** with the current user. The field stays optional, so you can pick someone else, and clearing it still lands the run on the creator.
## Related
* [Create a run](/build-hardware/runs-and-execution/starting-a-run)
# Configure procedure reviewer roles
Source: https://docs.firstresonance.io/administration/production-settings/configure-procedure-reviewer-roles
Set which roles must approve a procedure before it can be released.
Reviewer roles define which roles must sign off on a procedure before it reaches the Released state. Each role has a minimum number of approvals required.
**Settings > Production > Procedures** shows the controls for the approval model your org is on. An org on role-based approvals gets the **Reviewer roles** editor described here. An org on the reviewer-count model gets a single **Reviewers required** field instead. See [Set the number of reviewers required](#set-the-number-of-reviewers-required).
Which model your org uses isn't something you set on this page. To change it, contact [support.firstresonance.io](https://support.firstresonance.io).
## Add a reviewer role
1. In ION, go to **Settings > Production > Procedures**.
2. In the **Reviewer roles** section, click **Add role**.
3. Select the role from the dropdown.
4. Set the number of approvals required for that role.
5. Click **Save Changes**.
## Update the approver count
1. In the **Reviewer roles** section, find the role you want to update.
2. In the count field beside the role, enter the number of approvals required.
3. Click **Save Changes**.
## Remove a reviewer role
1. In the **Reviewer roles** section, click **X** next to the role you want to remove.
2. Click **Save Changes**.
## Set the number of reviewers required
On the reviewer-count model, a procedure needs a set number of approvals to be released, from any reviewer, with no per-role breakdown.
1. In ION, go to **Settings > Production > Procedures**.
2. In **Reviewers required**, enter the number of approvals a procedure needs. The value saves on its own a moment after you stop typing.
Enter 0 to release a procedure without approvals. Clearing the field doesn't save an empty value, so the previous number stays until you type a new one.
## Related
* [Configure standard step reviewer roles](/administration/production-settings/configure-standard-step-reviewer-roles)
# Configure run enforcement
Source: https://docs.firstresonance.io/administration/production-settings/configure-run-enforcement
Set which rules ION enforces when runs are in progress or completing.
Run enforcement settings control what you can and can't do during run execution.
## Configure run enforcement
1. In ION, go to **Settings > Production > Runs**.
2. Turn on or off the settings you need:
| Setting | What it does |
| -------------------------------------------------------- | --------------------------------------------------------------------------- |
| **Require part-procedure relationship** | Parts must be linked to procedures before they can be used in runs. |
| **Allow run steps to complete without required install** | Steps can be marked complete even if required parts haven't been installed. |
| **Enforce peer signoff** | A different user must sign off on each step completion. |
| **Enforce run title template** | Run titles must follow the configured naming template. |
| **Enforce all aBOM parts installed for completion** | Runs can't be completed if any aBOM parts remain uninstalled. |
When **Enforce all aBOM parts installed for completion** is on, you can also turn on **Allow completion with justification** to complete runs with uninstalled parts by providing a written justification.
The **Number of redline approvers required** field on the same page sets how many approvals a redline needs. See [Set the redline approver count](/administration/production-settings/set-redline-approver-count).
## Set the run title template
The run title template names new runs automatically. Set it before turning on **Enforce run title template**, which stays disabled until a valid template is saved.
1. In ION, go to **Settings > Production > Runs**.
2. In **Run title template**, enter the pattern to apply to new run titles, for example `${id}-${partNumber}-${partRevision}`.
3. Include `${id}` (required), plus any of these tokens: `${partNumber}`, `${partRevision}`, `${partDescription}`, `${procedureTitle}`, `${procedureVersion}`, `${serialNumber}`, `${lotNumber}`, `${quantity}`, and `${dueDate}`. Text outside the tokens is kept as written. ION flags a template that omits `${id}` or uses an unknown token.
## Related
* [Require barcode scanning for installations](/administration/production-settings/require-barcode-scanning-for-installations)
# Configure standard step reviewer roles
Source: https://docs.firstresonance.io/administration/production-settings/configure-standard-step-reviewer-roles
Set which roles must approve a standard step before it can be released.
Reviewer roles define which roles must sign off on a standard step before it reaches the Released state.
## Add a reviewer role
1. In ION, go to **Settings > Production > Standard Steps**.
2. In the **Reviewer roles** section, click **Add role**.
3. Select the role from the dropdown.
4. Set the number of approvals required for that role.
5. Click **Save Changes**.
## Update the approver count
1. In the **Reviewer roles** section, find the role you want to update.
2. In the count field beside the role, enter the number of approvals required.
3. Click **Save Changes**.
## Remove a reviewer role
1. In the **Reviewer roles** section, click **X** next to the role you want to remove.
2. Click **Save Changes**.
## Related
* [Configure procedure reviewer roles](/administration/production-settings/configure-procedure-reviewer-roles)
# Enforce mBOM parts assigned to steps
Source: https://docs.firstresonance.io/administration/production-settings/enforce-mbom-parts-assigned-to-steps
Require every mBOM part to be pinned to a version and assigned to a step before a procedure can be sent for review or released.
This setting makes step assignment a condition of moving a procedure forward, and it changes how runs read the mBOM.
## Enforce mBOM parts assigned to steps
1. In ION, go to **Settings > Production > Procedures**.
2. Turn on **Enforce mBOM parts assigned to steps**.
## What the setting changes
With the setting on:
* Every part must be pinned to a specific mBOM version and assigned to a step before a procedure can be sent for review or released.
* New runs follow the pinned version instead of the latest released one.
* Once all of an assembly's parts are assigned to steps, part quantities stay in sync with the assignments: editing a step's assignment updates the run quantity automatically.
The gate applies to both forward transitions: **Draft** to **In Review**, and **In Review** to **Released**. It covers the procedure's own mBOM lines and the components inside its sub-assemblies, at any depth. When something is unassigned, the transition control is disabled and names the count and the first few part numbers.
When ION can't check the whole sub-assembly tree, it says that some components were not checked and asks you to check them before releasing, without blocking the transition. That covers a sub-assembly level holding more items than can be checked at once, and a tree nested deep enough that the check stops early. A failed data load is different: it does block, because a reload clears it.
Assigning a sub-assembly's components still needs that sub-assembly's own setup first: its mBOM version has to be pinned, which happens when you assign the sub-assembly in the procedure's right rail.
## Related
* [Create and release a procedure](/build-hardware/procedures/create-and-release-a-procedure)
* [Manufacturing BOM (mBOM)](/build-hardware/bills-of-materials/manufacturing-bom)
# Require barcode scanning for installations
Source: https://docs.firstresonance.io/administration/production-settings/require-barcode-scanning-for-installations
Require barcode scanning when you install inventory on an aBOM.
When this setting is on, you can only install inventory by scanning a barcode. Manual inventory selection is disabled. You can designate one role that can bypass the restriction.
## Require barcode scanning for installations
1. In ION, go to **Settings > Production > aBOMs**.
2. Turn on **Disable manual inventory installations**.
3. Optional: In the **Role allowed to bypass install restrictions** field, select the role that can still install inventory manually.
# Set the redline approver count
Source: https://docs.firstresonance.io/administration/production-settings/set-redline-approver-count
Set how many approvals are required before a redline is applied in ION.
The redline approver count controls how many users must approve a redline before it is applied to a procedure.
## Set the redline approver count
1. In ION, go to **Settings > Production > Runs**.
2. In the **Number of redline approvers required** field, enter the number of approvals required.
## Related
* [Enable auto-submit redlines](/administration/quality-settings/enable-auto-submit-redlines)
# Configure intent control policies
Source: https://docs.firstresonance.io/administration/quality-settings/configure-intent-control-policies
Set how intent affects run execution, inventory availability, installs, and part quality in ION.
Intent control policies determine what ION does when an item's intent is set to a particular value. The policies are grouped into four sections: **Run Controls**, **Inventory Controls**, **Install Controls**, and **Part Quality**.
Intent control policies appear only when intent features are enabled for your organization.
## Configure a policy
1. In ION, go to **Settings > Quality > Intent**.
2. Expand the section that holds the policy you want.
3. Turn on the policy, and choose a mode where one is offered.
Where a policy offers **Hierarchical** or **Exact** matching, the policy compares items by intent rank (set in [Manage intent options](/administration/quality-settings/manage-intent-options)). **Hierarchical** accepts any intent at or above the required tier; **Exact** requires the same tier.
## Run Controls
Govern intent on runs.
| Policy | What it does |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Require Intent on Run Create** | Makes intent mandatory when a run is created. |
| **Require Intent on Run Update** | Prevents clearing a run's intent once it has been set. |
| **Require Intent on Run Step Start** | Makes intent mandatory when a run step starts. |
| **Run Intent Sync** | Copies a run's intent to its attached part inventory when the inventory has no intent set. When the run's intent is a lower tier than the inventory's, the **Downgrade inventory on mismatch** option controls whether the inventory is downgraded to match. Assigning a run intent higher than the inventory's current intent is not permitted. |
**Require Intent on Run Create**, **Require Intent on Run Update**, and **Require Intent on Run Step Start** can each be limited to specific procedure types, so only runs of those types require intent.
## Inventory Controls
Govern intent on inventory.
| Policy | What it does |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Require Kit Intent Match** | Blocks kitting when kitted inventory intent doesn't meet the run's part inventory intent. Checked both when an item is added to a kit and when the kit is delivered to an assembly. Offers **Hierarchical** or **Exact** matching. |
| **PO Line Intent Sync** | Propagates intent from a purchase order line to the on-order inventory created from it. |
| **Issue Intent Update** | When an issue is resolved, applies an intent to the affected inventory based on the issue's disposition type. You set the disposition-type-to-intent mapping in this policy with **Map disposition types to intent options** and **Add mapping**. A disposition type with no mapping leaves the inventory's intent unchanged. |
| **Required at Manual Inventory Create** | Makes intent mandatory when you create inventory by hand. |
## Install Controls
Govern intent during installs.
| Policy | What it does |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Require Install Intent Match** | Blocks an install when the child part's intent doesn't meet the parent assembly's intent. Offers **Hierarchical** or **Exact** matching. |
| **Downgrade on Install** | Lowers a child part's intent to match the parent when it's installed in an aBOM, but only when the parent assembly's intent is lower than the child's. If the parent's intent is equal or higher, the child is unchanged. |
## Part Quality
Governs part inventory intent.
| Policy | What it does |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Part Quality Check** | Keeps part inventory intent from exceeding the part's intent. Applies wherever intent is assigned to inventory: manual assignment, receipt from a purchase order line, run intent sync, and issue resolution. In **Block** mode, an exceeding value raises an error; in **Downgrade** mode, the inventory intent is lowered to match the part. |
## Related
* [Manage intent options](/administration/quality-settings/manage-intent-options)
# Configure issue creation and approvals
Source: https://docs.firstresonance.io/administration/quality-settings/configure-issue-creation-and-approvals
Auto-create issues on run step failure and set how many approvals are required to close an issue.
## Auto-create issues on run step failure
When this setting is on, ION automatically creates an issue ticket whenever a run step is marked as failed.
1. In ION, go to **Settings > Quality > Issues**.
2. Turn on **Create issues on run step failure**.
## Set the number of approvals required
1. In ION, go to **Settings > Quality > Issues**.
2. In the **Number of approvals required** field, enter the number of approvals needed before an issue can be closed.
## Related
* [Rename fields](/administration/quality-settings/rename-fields)
* [Set the default availability behavior for issues](/administration/quality-settings/set-default-availability-behavior)
# Enable auto-submit redlines
Source: https://docs.firstresonance.io/administration/quality-settings/enable-auto-submit-redlines
Automatically apply a redline's changes once all required reviewers have approved.
When auto-submit is on, ION applies a redline's changes as soon as all required reviewers have approved. When off, you must manually submit the redline after all approvals are collected.
## Enable auto-submit redlines
1. In ION, go to **Settings > Quality > Redlines**.
2. Turn on **Auto Submit Redlines**.
## Related
* [Set the redline approver count](/administration/production-settings/set-redline-approver-count)
# Manage disposition types
Source: https://docs.firstresonance.io/administration/quality-settings/manage-disposition-types
Create, edit, and delete issue disposition types, along with their availability behavior and approval roles.
A disposition type defines a disposition decision for your organization: its name, how it affects the availability of the affected inventory, and which roles must approve it before the issue can progress.
## Add a disposition type
1. In ION, go to **Settings > Quality > Issues**.
2. In the **Disposition types** section, type a name in **New disposition type name** and click **Add**.
## Configure a disposition type
Expand a disposition type to set:
| Setting | What it does |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Active** | Makes the type available to apply to issues. Turn it off to retire a type without deleting it. |
| **Disposition type name** | The label shown when choosing a disposition. |
| **Description** | Optional explanation of when to use the type. |
| **Availability** | How the affected inventory behaves once the disposition is applied. Changing it prompts a confirmation that shows how much inventory is affected. |
| **Auto-resolve** | When on, issues with this disposition resolve automatically once approved, skipping **In Review**. |
The **Availability** options are:
| Option | Effect |
| --------------------------- | --------------------------------------------------------------------- |
| **Available Immediately** | Inventory is available as soon as the disposition is applied. |
| **Release on Resolve** | Inventory becomes available only when the issue reaches **Resolved**. |
| **Permanently Unavailable** | Inventory stays locked out. |
### Approval roles
Each disposition type defines who must approve at each stage of the issue. On an expanded disposition type in **Settings > Quality > Issues**:
1. In **Approver Roles**, click **Add role** and set the number of approvers required from that role. These roles must approve before an issue can leave **Pending**.
2. In **Closure Roles**, click **Add role** and set the number of approvers required. These roles must approve before an issue can reach **Resolved**.
To remove a role from either list, click the remove control next to it.
## Edit a disposition type
Expand the type and change its fields in place. The name and description save when you click away; **Active** and **Auto-resolve** save as soon as you toggle them. Changing **Availability** prompts a confirmation before it takes effect.
## Delete a disposition type
Click the trash icon on the disposition type.
To stop offering a type without removing it, turn off **Active** instead.
## Related
* [Set the default availability behavior](/administration/quality-settings/set-default-availability-behavior)
* [Configure issue creation and approvals](/administration/quality-settings/configure-issue-creation-and-approvals)
# Manage intent options
Source: https://docs.firstresonance.io/administration/quality-settings/manage-intent-options
Add, edit, and remove the intent values available in your organization.
Intent options are the quality tiers you assign to items, for example **Flight**, **Production**, and **Development**. Their order sets their rank: the option at the top is the strictest, highest-quality tier, and rank decreases as you move down. Intent control policies that compare intent, such as kit and install matching, use this ranking.
## Add an intent option
1. In ION, go to **Settings > Quality > Intent**.
2. In the **Options** section, click **Add Option**.
3. Type a name in the new option's value field.
## Edit an intent option
1. In ION, go to **Settings > Quality > Intent**.
2. In the **Options** section, click the value field for the option you want to edit and update the text.
To configure the **Inspection Run Required** setting for an option, expand the option row and turn **Inspection Run Required** on or off. When it's on, receiving inventory that carries this intent automatically creates the part's inspection runs. When it's off, those inspection runs aren't created automatically on receipt.
## Reorder intent options
In the **Options** section, drag an option row by its grip handle to reorder it.
## Remove an intent option
1. In ION, go to **Settings > Quality > Intent**.
2. In the **Options** section, click **X** next to the option you want to remove.
## Where intent appears
Once your organization uses intent, an intent column shows on the inventory tables across ION. It's editable on the **Inventory** page and in a part's **Inventory** section, and read-only everywhere else:
* The Factory Floor location inventory table.
* A kit's fulfillment table and its kitted inventory rows.
* The inventory tables in the create-issue dialog and the clone or split issue dialog.
The column takes whatever name your organization gave the intent field, so it may not read "Intent". See [Rename fields](/administration/quality-settings/rename-fields).
## Related
* [Rename fields](/administration/quality-settings/rename-fields)
* [Configure intent control policies](/administration/quality-settings/configure-intent-control-policies)
# Rename fields
Source: https://docs.firstresonance.io/administration/quality-settings/rename-fields
Replace default ION field labels with your organization's terminology, from Settings.
ION lets you rename certain built-in fields to match your terminology. The label you enter replaces the default wherever that field appears in ION. The renameable fields are grouped below by the Settings page where you rename them.
## Issues
Rename these on **Settings > Quality > Issues**, in the **Issue ticket label configuration** section.
| Default field | Alias input |
| ------------------ | ---------------------------- |
| Disposition | **Disposition alias** |
| Cause condition | **Cause condition alias** |
| Expected condition | **Expected condition alias** |
## Further actions
Rename these on **Settings > Quality > Further Actions**.
| Default field | Alias input |
| ----------------- | --------------------------- |
| Problem statement | **Problem statement alias** |
| Analysis notes | **Analysis notes alias** |
| Resolution | **Resolution alias** |
## Intent
Rename this on **Settings > Quality > Intent**.
| Default field | Alias input |
| ------------- | ---------------- |
| Intent | **Intent Alias** |
## Rename a field
1. Go to the Settings page listed for the field you want to rename.
2. Enter your preferred label in that field's alias input.
## Related
* [Custom attributes](/administration/custom-attributes)
# Set the default availability behavior for issues
Source: https://docs.firstresonance.io/administration/quality-settings/set-default-availability-behavior
Control whether inventory tied to an open issue remains available or is put on hold by default.
The default availability behavior applies to inventory tied to issues that don't yet have a disposition type assigned. Once a disposition type is assigned, that type's own availability behavior takes over.
## Set the default availability behavior
1. In ION, go to **Settings > Quality > Issues**.
2. In the **Default availability behavior** field, select the behavior you want.
3. Click **Confirm**.
## Related
* [Configure issue creation and approvals](/administration/quality-settings/configure-issue-creation-and-approvals)
# Security and compliance
Source: https://docs.firstresonance.io/administration/security-and-compliance
How First Resonance handles backups, encryption, and data access for ION.
## Backups
First Resonance performs daily backups of your ION environment. Backups are retained for 25 days and are reserved for emergency database recovery. Backups let First Resonance restore your environment to an earlier point if needed.
## Encryption
ION uses AWS Key Management Service (KMS) to manage encryption keys. AWS KMS is designed so that no one (including First Resonance) can retrieve your plaintext keys.
## GovCloud access
If you're on the GovCloud version of ION and traveling internationally, you can still access ION from outside the United States by connecting through a VPN.
# SIEM integration
Source: https://docs.firstresonance.io/administration/siem-integration
How to pull ION data into your SIEM using the GraphQL API.
ION doesn't have a native SIEM connector, but you can pull any ION data into your SIEM by querying the GraphQL API on a schedule.
For setup instructions, see [API reference](/api-reference/getting-started).
## Polling pattern
A typical polling loop:
1. Read the `last_polled_at` timestamp from your pipeline's state store.
2. Query ION for records where `updatedAt > last_polled_at`.
3. Transform and forward the results to your SIEM ingest endpoint.
4. On success, write the current timestamp as the new `last_polled_at`.
The data most commonly relevant to SIEM ingestion includes:
* **Runs**: production order lifecycle events (created, started, completed, paused).
* **Issues**: quality events, nonconformances, and dispositions.
* **Inventory**: adjustments, transfers, and lot/serial number changes.
* **Users and roles**: membership changes, permission grants, and deactivations.
## Alternatives to polling
If your architecture can accept inbound HTTPS traffic, [webhooks](/api-reference/guides/webhooks) let ION push change events to your endpoint in near-real time, which reduces polling overhead and latency. Webhooks cover the same resources and include the before/after state of the changed record.
Use polling when your SIEM sits behind a firewall that blocks inbound connections, or when your compliance posture requires a strict pull-only model.
## Related
* [API reference](/api-reference/getting-started)
* [Webhooks](/api-reference/guides/webhooks)
* [Security and compliance](/administration/security-and-compliance)
# Allow kitting of WIP inventory for outside processing
Source: https://docs.firstresonance.io/administration/supply-chain-settings/allow-wip-osp-kitting
Let inventory that is out at a supplier on an outside-processing purchase order be kitted while it is still in WIP.
Inventory in **WIP** is not kittable. When this setting is on, one exception applies: inventory linked to a purchase order line through an [outside-processing](/build-hardware/runs-and-execution/outside-processing) run step can be kitted while it is still in **WIP**. All other WIP inventory stays un-kittable.
The exception applies everywhere a kit draws on inventory: the kitting picker, the kit's inventory viewer, and barcode scanning.
## Allow kitting of WIP inventory for OSP
1. In ION, go to **Settings > Supply Chain > Parts Inventory**.
2. Turn on **Allow kitting of WIP inventory for OSP**.
## Related
* [Create and fulfill kits](/manage-supply-chain/kitting/create-and-fulfill-kits)
* [Send a step to outside processing](/build-hardware/runs-and-execution/outside-processing)
# Configure demand settings
Source: https://docs.firstresonance.io/administration/supply-chain-settings/configure-demand-settings
Control how ION calculates material demand and what counts as supply when running Autoplan.
Demand settings control which parts of the BOM drive demand calculations and how supply is counted during Autoplan runs.
## Configure demand settings
1. In ION, go to **Settings > Supply Chain > Plans**.
2. Turn on or off the settings you need:
| Setting | What it does |
| --------------------------------------- | ----------------------------------------------------------------------------------- |
| **Drive demand below purchased parts** | Extends demand calculations to sub-components beneath purchased parts in the BOM. |
| **Drive demand from kits** | Includes kit requirements when calculating material demand. |
| **Group plan items with same date** | Consolidates plan line items with the same target date into a single group. |
| **Exclude uncommitted purchase orders** | Counts only ordered POs as supply; draft, requested, and approved POs are excluded. |
## Related
* [Set working time](/administration/supply-chain-settings/set-working-time)
# Configure inventory merge behavior
Source: https://docs.firstresonance.io/administration/supply-chain-settings/configure-inventory-merge-behavior
Control whether merging inventories creates a single new inventory record in ION.
When inventories are merged, this setting controls whether they are combined into a single new inventory record.
## Configure inventory merge behavior
1. In ION, go to **Settings > Supply Chain > Parts Inventory**.
2. In the **Merge Inventories** section, turn **Merge into single new inventory** on or off.
# Configure part revision schemes
Source: https://docs.firstresonance.io/administration/supply-chain-settings/configure-part-revision-schemes
Define the naming formats used to track part revisions in ION.
Revision schemes define how part revisions are named (for example, alphabetic A, B, C or numeric 1, 2, 3). You can configure multiple schemes and set one as the default.
## Revision scheme format
A revision scheme's format defines the sequence of characters a revision can use. You build it from segments and separators:
* Click **A–Z** to add an alphabetic segment (A, B, C, and so on).
* Click **0–9** to add a numeric segment (1, 2, 3, and so on).
* To place a separator between segments, type a single character in the **Separator** field and click **Add**.
A format must include at least one numbering segment (**A–Z** or **0–9**).
## Add a revision scheme
1. In ION, go to **Settings > Supply Chain > Parts**.
2. In the **Part Revision Schemes** section, fill in the new scheme form:
* Enter a **name** for the scheme.
* Build the **format**, as described in [Revision scheme format](#revision-scheme-format).
* Optional: Turn on **Overflow** to continue the sequence past the last defined value.
* Optional: Turn on **Default** to make this the default scheme.
3. Click **Create Scheme**.
## Edit a revision scheme
1. In ION, go to **Settings > Supply Chain > Parts**.
2. In the **Part Revision Schemes** section, click a scheme to expand it.
3. Update the name, **Allow overflow** setting, or **Default** flag as needed.
## Delete a revision scheme
1. In ION, go to **Settings > Supply Chain > Parts**.
2. In the **Part Revision Schemes** section, click the trash icon on the scheme's row.
## Related
* [Enable part interchangeability](/administration/supply-chain-settings/enable-part-interchangeability)
# Configure purchase order approval workflow
Source: https://docs.firstresonance.io/administration/supply-chain-settings/configure-purchase-order-approval
Define the approval levels required before a purchase order can proceed in ION.
The purchase order approval workflow defines how many levels of approval a PO requires, who is responsible at each level, and the cost threshold that triggers each level.
## How approval levels work
Each approval level activates when a PO's total cost meets or exceeds that level's **Cost Threshold**.
You can add as many levels as you need and reorder them by dragging.
## Configure the approval workflow
1. In ION, go to **Settings > Supply Chain > Purchases**.
2. In the **Approval Policy** section, click **Add Level**.
3. For each level, fill in the fields:
* **Role**: the role you must have to approve at this level.
* **Team**: the team responsible for approvals at this level. Can be left for you to select at review time.
* **Cost Threshold**: the minimum PO total (in your org's currency) that triggers this level.
4. Drag levels into the order in which you want approvals to happen.
## Configure approval reset thresholds
If a PO is edited after approval, ION can automatically reset approvals and require the workflow to run again. Set this in the **Approval Reset Cost Threshold** section:
| Field | Description |
| ----------------- | -------------------------------------------------------------------------------------------------- |
| **By Percentage** | Reset approvals if the PO total changes by this percentage. |
| **By Amount** | Reset approvals if the PO total changes by this amount in your org's currency. |
| **Condition** | Whether both thresholds must be met (**and**) or either one is enough (**or**) to trigger a reset. |
## Related
* [Set purchase order defaults](/administration/supply-chain-settings/set-purchase-order-defaults)
# Disable automatic location update on run start
Source: https://docs.firstresonance.io/administration/supply-chain-settings/disable-auto-location-update
Keep inventory at its current location instead of moving it to the step location when a run starts.
By default, ION moves inventory to the step's location when a run starts. When this setting is on, inventory stays at its current location instead.
## Disable automatic location update on run start
1. In ION, go to **Settings > Supply Chain > Parts Inventory**.
2. Turn on **Disable auto location update on run start**.
# Enable part interchangeability
Source: https://docs.firstresonance.io/administration/supply-chain-settings/enable-part-interchangeability
Allow any revision of a part to be used in place of the specific revision specified in the aBOM.
When part interchangeability is on, any revision of a part can be installed wherever the aBOM specifies a particular revision. This is useful when revision-level differences are not functionally significant.
Use this functionality when your organization enforces that part numbers have the same form, fit, and function, and uses different part numbers when they do not.
## Enable part interchangeability
1. In ION, go to **Settings > Supply Chain > Parts**.
2. In the **Part Interchangeability** section, turn on **Allow any part revision to be used**.
## How it behaves
Once enabled, while kitting or installing you can consume inventory that has the same part number as the required part even if it's a different revision.
## Related
* [Configure part revision schemes](/administration/supply-chain-settings/configure-part-revision-schemes)
# Manage requirements
Source: https://docs.firstresonance.io/administration/supply-chain-settings/manage-requirements
Create and manage terms and conditions and quality clauses used on purchase orders in ION.
Requirements are reusable terms and conditions or quality clauses that can be attached to purchase orders. Define them in Settings rather than on individual POs so your team always works from a consistent set of approved language. Terms cover payment terms and conditions; quality clauses set requirements for a supplier to deliver against on each PO line, such as a certificate of conformance or a dimensional inspection report.
## Create a requirement
1. In ION, go to **Settings > Supply Chain > Requirements**.
2. Select the tab for the type you want to create: **Terms** or **Quality Clauses**.
3. Click **Create Term** or **Create Quality Clause**.
4. Fill in the fields:
* For a term:
* **Title** (required): name of the term as it appears on the PO.
* **Requirement** (optional): full text of the term or condition (rich text).
* For a quality clause:
* **Reference** (required): a short identifier code for the clause, for example `QC-101`.
* **Title** (required): name of the clause as it appears on the PO.
* **Requirement** (optional): full text of the quality clause (rich text).
5. Click **Create**.
## Edit a requirement
1. In ION, go to **Settings > Supply Chain > Requirements**.
2. Select the tab for the requirement type: **Terms** or **Quality Clauses**.
3. In the list, click the requirement's **Title**.
4. Make your changes and click **Save**.
## Delete a requirement
1. In ION, go to **Settings > Supply Chain > Requirements**.
2. Select the tab for the requirement type: **Terms** or **Quality Clauses**.
3. In the actions column of the requirement's row, click the trash icon.
4. Confirm the deletion in the dialog, then click **Delete**.
Deleting a requirement removes it permanently. This cannot be undone.
## Related
* [Set purchase order defaults](/administration/supply-chain-settings/set-purchase-order-defaults)
# Manage units of measure
Source: https://docs.firstresonance.io/administration/supply-chain-settings/manage-units-of-measure
Add and remove the units of measure available for inventory quantities in ION.
Units of measure are the quantity labels available when tracking inventory (for example, `each`, `kg`, `m`).
## Add a unit of measure
1. In ION, go to **Settings > Supply Chain > Inventory**.
2. In the **Units of measure** field, type the unit you want to add.
3. Press **Enter** or click **Add**.
## Remove a unit of measure
1. In ION, go to **Settings > Supply Chain > Inventory**.
2. In the units list, click **X** next to the unit you want to remove.
# Set purchase order defaults
Source: https://docs.firstresonance.io/administration/supply-chain-settings/set-purchase-order-defaults
Pre-populate default locations, currency, and payment terms on new purchase orders in ION.
Purchase order defaults are pre-filled on new POs, so your team doesn't have to set them manually every time.
## Set purchase order defaults
1. In ION, go to **Settings > Supply Chain > Purchases**.
2. Configure the default values:
* **Default ship to location**: The location pre-populated in the ship-to field.
* **Default bill to location**: The location pre-populated in the bill-to field.
* **Default currency**: The currency pre-selected on new POs.
* **Default terms**: The payment terms pre-selected on new POs.
## Related
* [Configure purchase order approval workflow](/administration/supply-chain-settings/configure-purchase-order-approval)
# Set working time
Source: https://docs.firstresonance.io/administration/supply-chain-settings/set-working-time
Define which days of the week count as working days when Autoplan schedules the parts you make. Purchased-part lead times always use calendar days.
Working time controls which days ION counts as working days when calculating lead times in plans. The value is a standard cron expression. ION reads the day-of-week field (the fifth field) to determine which days count as working days.
For example, `* * * * 1-5` means Monday through Friday.
For the full cron specification, see the [crontab manpage](https://man7.org/linux/man-pages/man5/crontab.5.html).
## What working time applies to
Working time applies when Autoplan schedules parts your organization makes: parts with a make or dual-source sourcing strategy. When Autoplan counts back from a need date to a start date for a made part, it counts only working days, and the start date always lands on a working day.
Purchased-part lead times always use calendar days. Suppliers quote lead times in calendar time, so a supplier lead time of 35 days means 35 calendar days regardless of the working time setting.
If you record purchased-part lead times in business days, convert them to calendar days when entering them in ION. For a Monday through Friday schedule, multiply by 7/5. For example, 35 business days is about 49 calendar days.
Working time affects only Autoplan scheduling. Purchase order dates, run schedules, and due dates elsewhere in ION use calendar days.
## Set working time
1. In ION, go to **Settings > Supply Chain > Plans**.
2. In the **Working time** field, enter a cron expression.
## Related
* [Configure demand settings](/administration/supply-chain-settings/configure-demand-settings)
# Deactivate a user
Source: https://docs.firstresonance.io/administration/users-and-permissions/deactivate-a-user
Remove a user's access to ION without deleting their history or records.
Deactivating a user revokes their ability to log in to ION. Their account, activity history, and any records they created are preserved.
## Deactivate a user
1. In ION, go to **Settings > Users**.
2. On the user's row, click the actions menu (three dots).
3. Click **Deactivate User**.
The user can no longer log in. They remain visible in the **Users** list with a deactivated status.
## Reactivate a user
1. In ION, go to **Settings > Users**.
2. On the user's row, click the actions menu (three dots).
3. Click **Activate User**. The user can log in again and their status changes to active in the Users list.
## Related
* [Invite a user](/administration/users-and-permissions/invite-a-user)
* [Manage user roles](/administration/users-and-permissions/manage-user-roles)
# Overview
Source: https://docs.firstresonance.io/administration/users-and-permissions/index
ION's access model: users, roles, and teams, how permissions flow, and the full permissions reference.
ION's access model has three building blocks:
* A **user** is an individual with sign-in credentials.
* A **role** is a named, reusable set of permissions.
* A **team** is a group of users you assign, notify, and route sign-offs to together.
## How permissions flow
Permissions attach to roles, never to users directly. A user's access is the union of every role they hold, whether assigned to them directly or through a team they belong to. Roles are **additive**: adding one only grants access, so to narrow what someone can do, give them a role with fewer permissions rather than trying to subtract. Teams hold no permissions of their own, but a role assigned to a team grants its permissions to every current and future member.
Permissions are organized into groups by feature area. Every create, update, and delete action has a permission; read actions generally don't, and read access is managed separately. Changes take effect immediately, and removing a permission from a role removes it from every user and team that holds it. To see a user's effective access, combine the roles on their profile with the roles from their teams.
## Provisioning
| Approach | How it works |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Manual | Invite users and assign roles directly in ION. |
| SSO with role mapping | Roles map from your identity provider's groups (Okta, Entra ID) when users sign in. See [Set up SSO](/administration/authentication-settings/sso/set-up-sso). |
## Common roles
Most manufacturing organizations converge on a handful of roles:
| Role | What they can do |
| -------------------------- | --------------------------------------------------------------------------- |
| **Operator** | Run procedures, file issues. Can't edit procedures or close their own runs. |
| **Lead Operator** | Everything an Operator can do, plus run review and close. |
| **Manufacturing Engineer** | Author procedures, manage parts, schedule runs. |
| **Quality Engineer** | Manage issues, dispositions, CAPAs; read across the org. |
| **Buyer / Planner** | Manage POs, receipts, inventory. |
| **Admin** | Org settings, users, integrations. |
| **Read-only** | View everything, change nothing. |
**Admin** and **Read-only** are system roles: grant them directly to a user, not to a team.
Pick role names that age well ("Operator," not "FactoryFloorTier1"): renaming a role in use across many users is painful. Keep admin and org-settings access to a small circle, and review role permissions periodically as people change jobs.
## Permissions reference
Permissions control which actions a role can perform, and you assign them to roles, not to individual users. Read permissions (what a role can view) are managed separately and aren't listed here. To add permissions to a role, see [Manage role permissions](/administration/users-and-permissions/manage-role-permissions).
| Permission | Description |
| -------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `addAssetToItem` | Add an existing file attachment as an asset to an item. |
| `addFileAttachmentToItem` | Add an existing file attachment to an item. |
| `addHeaderToWebhookReceiver` | Add headers to a webhook receiver configuration. |
| `addInputToPlan` | Add inputs to a plan. |
| `addInventoriesToReceipt` | Add multiple inventories to a receipt. |
| `addInventoryToPurchaseOrderLine` | Add inventory to a purchase order line. |
| `addInventoryToReceipt` | Add a quantity of inventory to a receipt. |
| `addItemsToReceipt` | Add items to a receipt from a purchase order, or without a PO. |
| `addLabelToItem` | Add labels to an item (run, procedure, kit, etc.). |
| `addLabelToProcedureFamily` | Add labels to a procedure family. |
| `addOauthRedirectUri` | Add a callback URL or allowed origin to an OAuth app. |
| `addPartInventoryToRunStep` | Add part inventory to a run step. |
| `addPartsToKitFromMbom` | Add parts to a kit from an mBOM using the button on the kits page. |
| `addPlanItemToPlan` | Add plan items to a plan in the results table, outside of plan inputs. |
| `addRequirementToItem` | Add requirements to an item (for example, adding terms to a PO). |
| `addResultToPlanItem` | Add results to a plan item. |
| `addSubtypeToPart` | Add subtypes to a part (for example, Tool). |
| `addUserToTeam` | Add users to a team. |
| `archiveRun` | Archive a run. |
| `attachPermissionGroupToRole` | Attach permission groups to a role. |
| `attachRoleToTeam` | Attach roles to a team. |
| `attachRoleToUser` | Attach roles to a user. |
| `batchRunSteps` | Group multiple run steps into a batch for simultaneous execution. |
| `cancelRun` | Cancel a run. |
| `cancelRunStepRedline` | Cancel a redline. |
| `checkIn` | Check in to a run. |
| `checkOut` | Check out of a run. |
| `checkSsoConnectionStatus` | Check whether SSO setup is complete. |
| `cloneProcedure` | Clone a procedure. |
| `convertResultsFromPlanItem` | Convert results from a plan item. |
| `convertResultsFromPlanItems` | Convert results from multiple plan items. |
| `copyField` | Copy a field in a step. |
| `copyPurchaseOrder` | Copy a purchase order. |
| `copyPurchaseOrderLine` | Copy a line item from a purchase order. |
| `copyStandardStep` | Copy a standard step. |
| `copyStep` | Copy a step. |
| `copyStepToRun` | Copy a step to a run. |
| `createAbomForPartInventory` | Create an aBOM for part inventory. |
| `createAbomInstallation` | Create an aBOM installation. |
| `createApiKey` | Create an API key. |
| `createApprovalLevel` | Create an approval level in an approval workflow. |
| `createAsset` | Add an asset to a run or procedure (for example, a file attachment). |
| `createBarcodeLabel` | Create a barcode label from inventory, kits, or locations. |
| `createBarcodePattern` | Create a new barcode pattern in organization settings. |
| `createBarcodePrintRequest` | Generate a print request for barcodes. |
| `createBarcodeTemplate` | Create a barcode template in organization settings. |
| `createBuildRequirement` | Create a build requirement for an aBOM. |
| `createBuildRequirementReferenceDesignator` | Create a build requirement reference designator for an aBOM. |
| `createBuildRequirementSubstitute` | Create a build requirement substitute for an aBOM. |
| `createComment` | Write comments in ION. |
| `createContact` | Create a contact. |
| `createCurrency` | Create a currency (API only). |
| `createDatagridColumn` | Create a datagrid column in a datagrid step. |
| `createDatagridRow` | Create a datagrid row in a datagrid step. |
| `createDiffRequest` | Create a request to compare two objects, for example two versions of a procedure. |
| `createFileAttachment` | Upload a file attachment. |
| `createFurtherAction` | Create a further action (a corrective or preventive action). |
| `createFurtherActionApproval` | Approve a further action. |
| `createFurtherActionApprovalRequest` | Request approval of a further action. |
| `createFurtherActionApprovalRole` | Add a role that can approve a further action. |
| `createFurtherActionIssue` | Link a further action to an issue. |
| `createFurtherActionPart` | Link a further action to a part. |
| `createFurtherActionRun` | Link a further action to a run. |
| `createImportJob` | Create an import job. |
| `createIntentOption` | Create an intent option. |
| `createInvite` | Invite another person to ION. |
| `createIssue` | Create an issue ticket. |
| `createIssueApproval` | Approve an issue. |
| `createIssueApprovalRequest` | Ask someone to approve an issue. |
| `createIssueDispositionType` | Create an issue disposition type in organization settings. |
| `createIssueDispositionTypeRole` | Select the role that can approve a disposition type on an issue. |
| `createIssuePartInventory` | Link part inventory to an issue. |
| `createIssueRelation` | Add related issues. |
| `createIssues` | Bulk create issues. |
| `createKitForRun` | Create a kit from a run. |
| `createLabel` | Create a label to tag a kit, procedure, run, etc. |
| `createLocation` | Create a new location. |
| `createLocationSubtype` | Create a location subtype. |
| `createMbom` | Create an mBOM. |
| `createMbomApproval` | Approve an mBOM. |
| `createMbomApprovalRequest` | Request approval of an mBOM. |
| `createMbomApprovalRole` | Add roles to approve an mBOM. |
| `createMbomItem` | Add parts to an mBOM. |
| `createMbomItemReferenceDesignator` | Add reference designators to mBOM items. |
| `createMbomSubstitute` | Add a substitute to an mBOM item. |
| `createMbomSubstitutes` | Add more than one substitute to an mBOM item. |
| `createMrpJob` | Create a material requirements planning (MRP) job in Autoplan. |
| `createMultipleMbomItems` | Import an mBOM. |
| `createOrganizationDomain` | Add a domain to claim for the organization. |
| `createOrganizationGlobalUniqueSerialNumberScheme` | Create a serial number scheme for the organization. |
| `createOrganizationPartRevisionScheme` | Create a part revision scheme for the organization. |
| `createOrUpdateMultipleMboms` | Create or update multiple mBOMs. |
| `createOrUpdateMultipleMbomsCsv` | Create or update multiple mBOMs from a CSV file. |
| `createOrUpdateMultipleParts` | Create or update multiple parts. |
| `createPart` | Create a new part library item. |
| `createPartInventories` | Bulk create part inventories. |
| `createPartInventory` | Create a part inventory. |
| `createPartKit` | Create a part kit. |
| `createPartKitItem` | Add parts to a kit. |
| `createPartKitItems` | Add multiple parts to a kit. |
| `createPartProcedure` | Create a part-procedure relationship. |
| `createPartRevision` | Create a part revision. |
| `createPartSubtype` | Create a part subtype (used for tools). |
| `createPlan` | Create a plan. |
| `createPlanConstraint` | Create a plan constraint. |
| `createPlanItem` | Create a plan item. |
| `createPlanItemAllocation` | Create a plan item allocation. |
| `createPlanReservation` | Create a plan reservation. |
| `createPrintJob` | Create a print job, for example to print barcode labels. |
| `createProcedure` | Create a procedure. |
| `createProcedureApproval` | Approve a procedure. |
| `createProcedureApprovalRequest` | Request approval of a procedure. |
| `createProcedureApprovalRole` | Add a role that can approve a procedure. |
| `createProcedureVersion` | Create a version of a procedure. |
| `createPurchaseOrder` | Create a purchase order. |
| `createPurchaseOrderApproval` | Approve a purchase order. |
| `createPurchaseOrderApprovalLevel` | Create an approval level for a purchase order. |
| `createPurchaseOrderApprovalRequest` | Ask another user to approve a purchase order. |
| `createPurchaseOrderFee` | Add a fee to a purchase order. |
| `createPurchaseOrderLine` | Create a purchase order line. |
| `createReceipt` | Create a receipt. |
| `createRedlineApproval` | Approve a redline on a run. |
| `createRedlineApprovalRequest` | Ask another user to approve redlines. |
| `createRedlineApprovalRequests` | Request approval of multiple redlines. |
| `createRedlineApprovalRole` | Add a role that can approve a redline. |
| `createRedlineApprovals` | Approve multiple redlines. |
| `createRedlineRoleBasedApproval` | Approve a redline as part of a role-based approval. |
| `createRedlineRoleBasedApprovalRequest` | Request role-based approval of a redline. |
| `createRedlineRoleBasedApprovalRequests` | Request role-based approval of multiple redlines. |
| `createRedlineRoleBasedApprovals` | Approve multiple redlines as part of role-based approvals. |
| `createRequirement` | Create a requirement for purchasing. |
| `createReview` | Create a review (via mBOMs, issues, procedures, etc.). |
| `createReviewRequest` | Create a review request. |
| `createRole` | Create a new role. |
| `createRule` | Create a new rule. |
| `createRun` | Create a new run. |
| `createRunBatch` | Create a run batch. |
| `createRuns` | Bulk create runs. |
| `createRunStep` | Create a run step. |
| `createRunStepEdge` | Create a run step dependency. |
| `createRunStepField` | Create a run step field. |
| `createRunStepFieldValidation` | Add a run step field validation. |
| `createSnapshotExportJob` | Create a snapshot export job. |
| `createSsoSetupTicket` | Generate a self-service SSO setup link for an organization admin. |
| `createStandardStepVersion` | Create a new version of a standard step. |
| `createStep` | Create a new step in a procedure. |
| `createStepApproval` | Create a step approval. |
| `createStepApprovalRequest` | Create a step approval request. |
| `createStepApprovalRole` | Add a role that can approve a step. |
| `createStepEdge` | Create a step dependency. |
| `createStepField` | Create a step field. |
| `createStepFieldValidation` | Create a step field validation. |
| `createStepPartRequirement` | Create an association between a step and an mBOM item. |
| `createStepRoleBasedApproval` | Approve a step as part of a role-based approval. |
| `createStepRoleBasedApprovalRequest` | Request role-based approval of a step. |
| `createSupplier` | Create a new supplier in the purchasing module. |
| `createSupplierPart` | Create a part supplier. |
| `createTeam` | Create a new team. |
| `createUnitOfMeasurement` | Create a new unit of measurement. |
| `createUserSubscription` | Subscribe yourself or another user to something in ION. |
| `createWebhookHeader` | Create a webhook header. |
| `createWebhookReceiver` | Create a webhook receiver. |
| `createWebhookSubscription` | Create a webhook subscription. |
| `deleteAbomInstallation` | Delete an aBOM installation. |
| `deleteApiKey` | Delete an API key. |
| `deleteApprovalLevel` | Delete an approval level from an approval workflow. |
| `deleteAsset` | Delete an asset such as a file attachment. |
| `deleteBarcodePattern` | Delete a barcode pattern. |
| `deleteBuildRequirement` | Delete a build requirement. |
| `deleteBuildRequirementReferenceDesignator` | Delete a build requirement reference designator. |
| `deleteBuildRequirementSubstitute` | Delete a build requirement substitute. |
| `deleteComment` | Delete a comment. |
| `deleteContact` | Delete a contact. |
| `deleteCurrency` | Delete a currency. |
| `deleteDatagridColumn` | Delete a datagrid column. |
| `deleteDatagridColumns` | Delete multiple columns from a datagrid. |
| `deleteDatagridRow` | Delete a datagrid row. |
| `deleteDatagridRows` | Delete multiple rows from a datagrid. |
| `deleteFileAttachment` | Delete a file attachment. |
| `deleteFurtherActionApprovalRequest` | Delete a further action approval request. |
| `deleteFurtherActionApprovalRole` | Remove a role from a further action approval. |
| `deleteFurtherActionIssue` | Remove the link between a further action and an issue. |
| `deleteFurtherActionPart` | Remove the link between a further action and a part. |
| `deleteFurtherActionRun` | Remove the link between a further action and a run. |
| `deleteIntentOption` | Delete an intent option. |
| `deleteIssueApprovalRequest` | Delete an issue approval request. |
| `deleteIssueDispositionType` | Delete an issue disposition type. |
| `deleteIssueDispositionTypeRole` | Delete an issue disposition type role. |
| `deleteIssuePartInventory` | Delete an issue part inventory. |
| `deleteIssueRelation` | Delete an issue relation. |
| `deleteLabel` | Delete a label. |
| `deleteLocation` | Delete a location. |
| `deleteLocationSubtype` | Delete a location subtype. |
| `deleteMbom` | Delete an mBOM. |
| `deleteMbomApprovalRequest` | Delete an mBOM approval request. |
| `deleteMbomApprovalRole` | Delete an mBOM approval role. |
| `deleteMbomItem` | Delete an mBOM item. |
| `deleteMbomItemReferenceDesignator` | Delete an mBOM item reference designator. |
| `deleteMbomSubstitute` | Delete an mBOM substitute. |
| `deleteOauthApp` | Delete an OAuth application registration. |
| `deleteOrganizationDomain` | Remove a claimed domain from the organization. |
| `deleteOrganizationFurtherActionAttributes` | Delete further action custom attributes for the organization. |
| `deleteOrganizationGlobalUniqueSerialNumberScheme` | Delete a global unique serial number scheme for the organization. |
| `deleteOrganizationIssueAttributes` | Delete issue custom attributes for the organization. |
| `deleteOrganizationLocationAttributes` | Delete location custom attributes for the organization. |
| `deleteOrganizationMbomAttributes` | Delete mBOM custom attributes for the organization. |
| `deleteOrganizationPartAttributes` | Delete part custom attributes for the organization. |
| `deleteOrganizationPartInventoryAttributes` | Delete part inventory custom attributes for the organization. |
| `deleteOrganizationPartKitAttributes` | Delete part kit custom attributes for the organization. |
| `deleteOrganizationPartKitItemAttributes` | Delete part kit item custom attributes for the organization. |
| `deleteOrganizationPartRevisionScheme` | Delete a part revision scheme for the organization. |
| `deleteOrganizationPlanAttributes` | Delete plan custom attributes for the organization. |
| `deleteOrganizationProcedureAttributes` | Delete procedure custom attributes for the organization. |
| `deleteOrganizationPurchaseOrderAttributes` | Delete purchase order custom attributes for the organization. |
| `deleteOrganizationPurchaseOrderLineAttributes` | Delete purchase order line custom attributes for the organization. |
| `deleteOrganizationReceiptAttributes` | Delete receipt custom attributes for the organization. |
| `deleteOrganizationRunAttributes` | Delete run custom attributes for the organization. |
| `deleteOrganizationRunStepAttributes` | Delete run step custom attributes for the organization. |
| `deleteOrganizationStepAttributes` | Delete step custom attributes for the organization. |
| `deleteOrganizationSupplierAttributes` | Delete supplier custom attributes for the organization. |
| `deletePart` | Delete a part. |
| `deletePartInventory` | Delete part inventory. |
| `deletePartInventoryBuildRequirement` | Delete a build requirement from part inventory. |
| `deletePartKit` | Delete a part kit. |
| `deletePartKitItem` | Delete a part kit item. |
| `deletePartProcedure` | Delete a part-procedure relationship. |
| `deletePartSubtype` | Delete a part subtype. |
| `deletePlan` | Delete a plan. |
| `deletePlanConstraint` | Delete a plan constraint. |
| `deletePlanItem` | Delete a plan item. |
| `deletePlanItemAllocation` | Delete a plan item allocation. |
| `deletePlanReservation` | Delete a plan reservation. |
| `deleteProcedure` | Delete a procedure. |
| `deleteProcedureApprovalRequest` | Delete a procedure approval request. |
| `deleteProcedureApprovalRole` | Remove a role that can approve a procedure. |
| `deleteProcedureMbom` | Delete the mBOM associated with a procedure. |
| `deletePurchaseOrder` | Delete a purchase order. |
| `deletePurchaseOrderApprovalLevel` | Delete an approval level for a purchase order. |
| `deletePurchaseOrderApprovalRequest` | Delete a purchase order approval request. |
| `deletePurchaseOrderFee` | Delete a purchase order fee. |
| `deletePurchaseOrderLine` | Delete a purchase order line. |
| `deleteReceipt` | Delete a receipt. |
| `deleteRedlineApprovalRequest` | Delete a redline approval request. |
| `deleteRedlineApprovalRole` | Remove a role from a redline approval. |
| `deleteRedlineRoleBasedApprovalRequest` | Delete a redline role-based approval request. |
| `deleteRequirement` | Delete a requirement. |
| `deleteReviewRequest` | Delete a review request. |
| `deleteRole` | Delete a role. |
| `deleteRule` | Delete a rule. |
| `deleteRunStep` | Delete a run step. |
| `deleteRunStepEdge` | Delete a run step dependency. |
| `deleteRunStepField` | Delete a run step field. |
| `deleteRunStepFieldValidation` | Delete a run step field validation. |
| `deleteStep` | Delete a step. |
| `deleteStepApprovalRequest` | Delete a step approval request. |
| `deleteStepApprovalRole` | Remove a role from a step approval. |
| `deleteStepEdge` | Delete a step dependency. |
| `deleteStepField` | Delete a step field. |
| `deleteStepFieldValidation` | Delete a step field validation. |
| `deleteStepPartRequirement` | Delete an association between a step and an mBOM item. |
| `deleteStepRoleBasedApprovalRequest` | Delete a step role-based approval request. |
| `deleteSupplier` | Delete a supplier. |
| `deleteSupplierPart` | Delete a part supplier. |
| `deleteTeam` | Delete a team. |
| `deleteUnitOfMeasurement` | Delete a unit of measurement. |
| `deleteUserSubscription` | Delete a user subscription. |
| `deleteWebhookHeader` | Delete a webhook header. |
| `deleteWebhookReceiver` | Delete a webhook receiver. |
| `deleteWebhookSubscription` | Delete a webhook subscription. |
| `detachPermissionGroupFromRole` | Detach a permission group from a role. |
| `detachRoleFromTeam` | Detach a role from a team. |
| `detachRoleFromUser` | Detach a role from a user. |
| `disableSsoConnection` | Disable the enterprise SSO connection for the organization. |
| `dispatchNotification` | Dispatch notifications. |
| `generateReadEmbeddedAnalytics` | Generate read operations for embedded analytics. |
| `generateRunSummary` | Generate a summary of a run. |
| `generateWriteEmbeddedAnalytics` | Generate write operations for embedded analytics. |
| `importKitItems` | Import kit items. |
| `importKitItemsCsv` | Import kit items from a CSV file. |
| `importStepsFromPdf` | Import steps from a PDF document. |
| `installKitOnAbom` | Install a kit on an aBOM. |
| `inviteOrganizationMember` | Send an invitation email to a new organization member. |
| `issueItemToKit` | Issue an item to a kit. |
| `linkOrganizationToAuth0` | Link an existing organization to its identity provider. |
| `linkRunStepToPurchaseOrder` | Link an outside-processing run step to a purchase order. |
| `mergePartInventory` | Merge part inventory. |
| `mergePartInventoryV2` | Merge multiple part inventory records into one. |
| `mergeRunStep` | Merge a run step. |
| `mergeRunStepToProcedure` | Merge a run step into a procedure. |
| `mergeRunStepToRuns` | Merge a run step into runs. |
| `moveItemToInventory` | Move an item to inventory. |
| `moveKitInventoryToLocation` | Move kit inventory to a location. |
| `provisionCustomer` | Provision a new customer organization. |
| `registerOauthApp` | Register a new OAuth application for the organization. |
| `removeAllInventoryFromKit` | Remove all inventory allocated to a kit. |
| `removeAssetFromItem` | Remove an asset from an item without deleting the file attachment. |
| `removeHeaderFromWebhookReceiver` | Remove a header from a webhook receiver. |
| `removeInputFromPlan` | Remove an input from a plan. |
| `removeInventoryFromPurchaseOrderLine` | Remove inventory from a purchase order line. |
| `removeInventoryFromReceipt` | Remove inventory from a receipt. |
| `removeItemFromReceipt` | Remove an item from a receipt. |
| `removeLabelFromItem` | Remove a label from an item. |
| `removeLabelFromProcedureFamily` | Remove a label from a procedure family. |
| `removeOauthRedirectUri` | Remove a callback URL or allowed origin from an OAuth app. |
| `removePartInventoryFromRunStep` | Remove part inventory from a run step. |
| `removePlanItemFromPlan` | Remove a plan item from a plan. |
| `removeRequirementFromItem` | Remove a requirement from an item. |
| `removeResultFromPlanItem` | Remove a result from a plan item. |
| `removeSubtypeFromPart` | Remove a subtype from a part. |
| `removeUserFromTeam` | Remove a user from a team. |
| `reorderApprovalLevel` | Reorder an approval level in an approval workflow. |
| `reorderDatagridColumn` | Reorder datagrid columns. |
| `reorderDatagridRow` | Reorder datagrid rows. |
| `reorderIntentOptions` | Reorder intent options. |
| `reorderPurchaseOrderLine` | Reorder purchase order lines. |
| `reorderRunStepFields` | Reorder run step fields. |
| `reorderRunSteps` | Reorder run steps. |
| `reorderStepFields` | Reorder step fields. |
| `reorderSteps` | Reorder steps. |
| `resendInvite` | Resend an invitation. |
| `resetIssueApprovals` | Reset issue approvals. |
| `resetPurchaseOrderApprovalLevels` | Reset the approval levels on a purchase order. |
| `revokeInvite` | Revoke an invitation. |
| `rotateSsoSigningCredentials` | Replace the identity provider signing certificate or metadata on the SSO connection. |
| `saveReceipt` | Save a receipt after adding or removing items. |
| `setDatagridValue` | Set the value of a datagrid cell. |
| `splitManyPartInventory` | Split multiple part inventories. |
| `splitPartInventory` | Split part inventory. |
| `splitUnfulfilledPartKit` | Split an unfulfilled part kit. |
| `submitRunAbomJustification` | Submit the aBOM completion justification on a run. |
| `unbatchRunSteps` | Remove run steps from a batch. |
| `unlinkRunStepFromPurchaseOrder` | Unlink an outside-processing run step from its purchase order. |
| `updateAbomInstallation` | Update an aBOM installation. |
| `updateApiKey` | Update an API key. |
| `updateApprovalLevel` | Update an approval level in an approval workflow. |
| `updateBarcodeLabel` | Update a barcode label. |
| `updateBarcodePattern` | Update a barcode pattern. |
| `updateBarcodeTemplate` | Update a barcode template. |
| `updateBuildRequirement` | Update a build requirement. |
| `updateBuildRequirementReferenceDesignator` | Update a build requirement reference designator. |
| `updateBuildRequirementSubstitute` | Update a build requirement substitute. |
| `updateComment` | Update a comment. |
| `updateContact` | Update a contact. |
| `updateCurrency` | Update a currency. |
| `updateDatagridColumn` | Update a datagrid column. |
| `updateDatagridRow` | Update a datagrid row. |
| `updateFurtherAction` | Update a further action. |
| `updateFurtherActionApproval` | Update a further action approval. |
| `updateFurtherActionApprovalRequest` | Update a further action approval request. |
| `updateFurtherActionApprovalRole` | Update a further action approval role. |
| `updateImportJob` | Update an import job. |
| `updateInputToPlan` | Update a plan input. |
| `updateIntentOption` | Update an intent option. |
| `updateIssue` | Update an issue. |
| `updateIssueApproval` | Update an issue approval. |
| `updateIssueApprovalRequest` | Update an issue approval request. |
| `updateIssueAttribute` | Update an issue custom attribute. |
| `UpdateIssueAttributes` | Update the custom attribute values on an issue. |
| `updateIssueDispositionType` | Update an issue disposition type. |
| `updateIssueDispositionTypeRole` | Update an issue disposition type role. |
| `updateLabel` | Update a label. |
| `updateLocation` | Update a location. |
| `updateLocationAttribute` | Update a location custom attribute. |
| `UpdateLocationAttributes` | Update the custom attribute values on a location. |
| `updateLocationSubtype` | Update a location subtype. |
| `updateMbom` | Update an mBOM. |
| `updateMbomApproval` | Update an mBOM approval. |
| `updateMbomApprovalRequest` | Update an mBOM approval request. |
| `updateMbomApprovalRole` | Update an mBOM approval role. |
| `updateMbomAttribute` | Update an mBOM custom attribute. |
| `UpdateMbomAttributes` | Update the custom attribute values on an mBOM. |
| `updateMbomItem` | Update an mBOM item. |
| `updateMbomItemReferenceDesignator` | Update an mBOM item reference designator. |
| `updateMrpJob` | Update an MRP job in Autoplan. |
| `updateMultiplePartInventory` | Update multiple part inventory records. |
| `updateOauthApp` | Update an OAuth application registration. |
| `updateOrganization` | Update organization details. |
| `updateOrganizationBranding` | Update the branding on the organization login page. |
| `updateOrganizationFurtherActionAttributes` | Update further action custom attributes for the organization. |
| `updateOrganizationGlobalUniqueSerialNumberScheme` | Update a global unique serial number scheme for the organization. |
| `updateOrganizationIssueAttributes` | Update issue custom attributes for the organization. |
| `updateOrganizationLocationAttributes` | Update location custom attributes for the organization. |
| `updateOrganizationMbomAttributes` | Update mBOM custom attributes for the organization. |
| `updateOrganizationMfaPolicy` | Enable or disable the multi-factor authentication requirement for the organization. |
| `updateOrganizationPartAttributes` | Update part custom attributes for the organization. |
| `updateOrganizationPartInventoryAttributes` | Update part inventory custom attributes for the organization. |
| `updateOrganizationPartKitAttributes` | Update part kit custom attributes for the organization. |
| `updateOrganizationPartKitItemAttributes` | Update part kit item custom attributes for the organization. |
| `updateOrganizationPartRevisionScheme` | Update a part revision scheme for the organization. |
| `updateOrganizationPlanAttributes` | Update plan custom attributes for the organization. |
| `updateOrganizationProcedureAttributes` | Update procedure custom attributes for the organization. |
| `updateOrganizationPurchaseOrderAttributes` | Update purchase order custom attributes for the organization. |
| `updateOrganizationPurchaseOrderLineAttributes` | Update purchase order line custom attributes for the organization. |
| `updateOrganizationReceiptAttributes` | Update receipt custom attributes for the organization. |
| `updateOrganizationRunAttributes` | Update run custom attributes for the organization. |
| `updateOrganizationRunStepAttributes` | Update run step custom attributes for the organization. |
| `updateOrganizationStepAttributes` | Update step custom attributes for the organization. |
| `updateOrganizationSupplierAttributes` | Update supplier custom attributes for the organization. |
| `updatePart` | Update a part. |
| `updatePartAttribute` | Update a part custom attribute. |
| `UpdatePartAttributes` | Update the custom attribute values on a part. |
| `updatePartInventory` | Update part inventory. |
| `updatePartInventoryAttribute` | Update a part inventory custom attribute. |
| `UpdatePartInventoryAttributes` | Update the custom attribute values on part inventory. |
| `updatePartKit` | Update a part kit. |
| `updatePartKitAttribute` | Update a part kit custom attribute. |
| `UpdatePartKitAttributes` | Update the custom attribute values on a part kit. |
| `updatePartKitItem` | Update a part kit item. |
| `updatePartKitItemAttribute` | Update a part kit item custom attribute. |
| `UpdatePartKitItemAttributes` | Update the custom attribute values on a part kit item. |
| `updatePartProcedure` | Update a part-procedure relationship. |
| `updatePartSubtype` | Update a part subtype. |
| `updatePlan` | Update a plan. |
| `updatePlanAttribute` | Update a plan custom attribute. |
| `UpdatePlanAttributes` | Update the custom attribute values on a plan. |
| `updatePlanConstraint` | Update a plan constraint. |
| `updatePlanItem` | Update a plan item. |
| `updatePlanItemAllocation` | Update a plan item allocation. |
| `updatePlanReservation` | Update a plan reservation. |
| `updateProcedure` | Update a procedure. |
| `updateProcedureApproval` | Update a procedure approval. |
| `updateProcedureApprovalRequest` | Update a procedure approval request. |
| `updateProcedureApprovalRole` | Update a procedure approval role. |
| `updateProcedureAttribute` | Update a procedure custom attribute. |
| `UpdateProcedureAttributes` | Update the custom attribute values on a procedure. |
| `updatePurchaseOrder` | Update a purchase order. |
| `updatePurchaseOrderApproval` | Update a purchase order approval. |
| `updatePurchaseOrderApprovalLevel` | Update an approval level for a purchase order. |
| `updatePurchaseOrderApprovalRequest` | Update a purchase order approval request. |
| `updatePurchaseOrderAttribute` | Update a purchase order custom attribute. |
| `UpdatePurchaseOrderAttributes` | Update the custom attribute values on a purchase order. |
| `updatePurchaseOrderFee` | Update a purchase order fee. |
| `updatePurchaseOrderLine` | Update a purchase order line. |
| `updatePurchaseOrderLineAttribute` | Update a purchase order line custom attribute. |
| `UpdatePurchaseOrderLineAttributes` | Update the custom attribute values on a purchase order line. |
| `updatePurchaseOrderLines` | Update multiple purchase order lines. |
| `updateReceipt` | Update a receipt. |
| `updateReceiptAttribute` | Update a receipt custom attribute. |
| `UpdateReceiptAttributes` | Update the custom attribute values on a receipt. |
| `updateReceiptItem` | Update a receipt item. |
| `updateRedline` | Update a redline. |
| `updateRedlineApproval` | Update a redline approval. |
| `updateRedlineApprovalRequest` | Update a redline approval request. |
| `updateRedlineApprovalRequests` | Update multiple redline approval requests. |
| `updateRedlineApprovalRole` | Update a redline approval role. |
| `updateRedlineRoleBasedApproval` | Update a redline role-based approval. |
| `updateRedlineRoleBasedApprovalRequest` | Update a redline role-based approval request. |
| `updateRedlineRoleBasedApprovalRequests` | Update multiple redline role-based approval requests. |
| `updateRequirement` | Update a requirement. |
| `updateReview` | Update a review. |
| `updateReviewRequest` | Update a review request. |
| `updateRole` | Update a role's name. |
| `updateRule` | Update a rule. |
| `updateRun` | Update a run's information. |
| `updateRunAbomJustification` | Update the aBOM completion justification on a run. |
| `updateRunAttribute` | Update a run custom attribute. |
| `UpdateRunAttributes` | Update the custom attribute values on a run. |
| `updateRunBatch` | Update a run batch. |
| `updateRunInventoryLocation` | Update the location of inventory associated with a run. |
| `updateRuns` | Update multiple runs. |
| `updateRunStep` | Update a run step, including changing its status. |
| `updateRunStepAttribute` | Update a run step custom attribute. |
| `UpdateRunStepAttributes` | Update the custom attribute values on a run step. |
| `updateRunStepField` | Update a run step field. |
| `updateRunStepFieldValidation` | Update a run step field validation. |
| `updateRunStepFieldValue` | Update the value of a run step field. |
| `updateSession` | Update a check-in or check-out event. |
| `updateSnapshotExportJob` | Update a snapshot export job. |
| `updateStep` | Update step content in a procedure. |
| `updateStepApproval` | Update a step approval. |
| `updateStepApprovalRequest` | Update a step approval request. |
| `updateStepApprovalRole` | Update a step approval role. |
| `updateStepAttribute` | Update a step custom attribute in a procedure. |
| `UpdateStepAttributes` | Update the custom attribute values on a step. |
| `updateStepField` | Update a step field in a procedure. |
| `updateStepFieldValidation` | Update a step field validation in a procedure. |
| `updateStepPartRequirement` | Update an association between a step and an mBOM item. |
| `updateStepRoleBasedApproval` | Update a step role-based approval. |
| `updateStepRoleBasedApprovalRequest` | Update a step role-based approval request. |
| `updateSupplier` | Update a supplier and their contact information. |
| `updateSupplierAttribute` | Update a supplier's custom attributes. |
| `UpdateSupplierAttributes` | Update the custom attribute values on a supplier. |
| `updateSupplierPart` | Update a part supplier. |
| `updateTeam` | Update a team's name and settings. |
| `updateUnitOfMeasurement` | Update a unit of measurement in organization settings. |
| `updateUser` | Update a user's profile and settings. |
| `updateUserNotification` | Update user notifications. |
| `updateWebhookHeader` | Update a webhook header. |
| `updateWebhookReceiver` | Update a webhook receiver. |
| `updateWebhookSubscription` | Update a webhook subscription. |
| `verifyOrganizationDomain` | Verify ownership of a claimed domain. |
# Invite a user
Source: https://docs.firstresonance.io/administration/users-and-permissions/invite-a-user
Send an email invitation to add a new person to your ION organization.
## Invite a user
1. In ION, go to **Settings > Users**.
2. Click **Invite User**.
3. In the **Email Address** field, enter the person's email address. This field is required.
4. Optional: Add more details:
* In the **Name (optional)** field, enter the person's name.
* In the **Message (optional)** field, enter a message to include in the invitation email.
* To invite more than one person at once, click **Add More** and fill in the **Email Address** field for each additional invitee. The message applies to everyone in the invitation.
5. Click **Invite**.
ION sends an invitation email to each address you entered.
## Manage pending invites
The **Pending invites** section on **Settings > Users** lists invitations that haven't been accepted yet, with each invite's email, name, status, when it was sent, and how many times it's been resent. Use the status filter to switch between **Pending**, **Accepted**, and **Revoked** invites.
1. In ION, go to **Settings > Users**.
2. Expand the **Pending invites** section.
3. For an invite that hasn't been accepted, choose an action:
* Click **Resend invite** to send the invitation email again.
* Click **Revoke invite**, then confirm, to invalidate the invite. The recipient can no longer use that invite link.
## Related
* [Manage user roles](/administration/users-and-permissions/manage-user-roles)
* [Manage team members](/administration/users-and-permissions/manage-team-members)
* [Deactivate a user](/administration/users-and-permissions/deactivate-a-user)
# Manage role permissions
Source: https://docs.firstresonance.io/administration/users-and-permissions/manage-role-permissions
Configure which actions a role can perform by enabling or disabling permissions on the role's detail page.
Add or remove a role's permissions on its detail page. For how permissions work, see [Users and permissions](/administration/users-and-permissions).
## Add permissions to a role
1. In ION, go to **Settings > Roles & Permissions**.
2. Click the role you want to configure.
3. On the role detail page, find the permission group you want to enable.
4. Select the checkbox next to a permission to enable it, or select the group's checkbox to enable every permission in that group at once.
To enable all permissions currently shown, click **Enable All**. The **Enable All** and **Disable All** controls apply to the permissions that match your current search and filter.
## Remove permissions from a role
On the role detail page, clear the checkbox next to a permission to disable it. To disable all permissions currently shown, click **Disable All**. Removing a permission from a role removes it from every user and team that holds that role.
## Related
* [Permissions reference](/administration/users-and-permissions#permissions-reference)
* [Create a role](/administration/users-and-permissions/manage-roles)
* [Manage user roles](/administration/users-and-permissions/manage-user-roles)
* [Manage team roles](/administration/users-and-permissions/manage-team-roles)
# Manage roles
Source: https://docs.firstresonance.io/administration/users-and-permissions/manage-roles
Create and delete the roles that group the permissions you assign to users and teams.
A role is a named set of permissions you assign to users and teams. For the access model, common role patterns, and provisioning, see [Users and permissions](/administration/users-and-permissions).
## Create a role
1. In ION, go to **Settings > Roles & Permissions**.
2. Click **New Role**.
3. Enter a name for the role and click **Create Role**.
ION creates the role and opens its detail page. The role has no permissions yet.
## Delete a role
1. In ION, go to **Settings > Roles & Permissions**.
2. In the role's actions menu, click **Delete Role**.
3. In the confirmation dialog, click **Delete**.
Deleting a role also removes it from every user and team it's assigned to, which can remove those users' access. Deletion can't be undone.
## Related
* [Manage role permissions](/administration/users-and-permissions/manage-role-permissions)
* [Manage team roles](/administration/users-and-permissions/manage-team-roles)
* [Manage user roles](/administration/users-and-permissions/manage-user-roles)
# Manage team members
Source: https://docs.firstresonance.io/administration/users-and-permissions/manage-team-members
Add users to a team so they inherit the team's roles and can be assigned and notified as a group.
## Add users to a team
1. In ION, go to **Settings > Teams**.
2. Click the team you want to add users to.
3. In the **Users** card, click **Add Users**.
4. Select the users you want to add and click **Add Selected (N)**, where N is the number of users you selected.
The users appear in the **Users** card and immediately inherit the team's roles.
## Remove a user from a team
1. In ION, go to **Settings > Teams**.
2. Click the team the user belongs to.
3. In the **Users** card, find the user you want to remove.
4. Click the delete icon next to their name.
The user loses any permissions they held through this team. Permissions from other roles or teams are not affected.
## Related
* [Create a team](/administration/users-and-permissions/manage-teams)
* [Manage team roles](/administration/users-and-permissions/manage-team-roles)
* [Invite a user](/administration/users-and-permissions/invite-a-user)
# Manage team roles
Source: https://docs.firstresonance.io/administration/users-and-permissions/manage-team-roles
Assign roles to a team so all team members inherit those permissions.
## Add roles to a team
1. In ION, go to **Settings > Teams**.
2. Click the team you want to configure.
3. In the **Roles** card, click **Add Roles**.
4. Select the roles you want to assign and click **Add Selected (N)**, where N is the number of roles you selected.
The roles appear in the **Roles** card and take effect immediately for all team members.
System roles such as **Admin** and **Read-only** can't be assigned to teams. Grant those directly to a user instead.
## Remove a role from a team
1. In ION, go to **Settings > Teams**.
2. Click the team you want to configure.
3. In the **Roles** card, find the role you want to remove.
4. Click the delete icon next to the role name.
All team members lose the permissions from that role. Permissions held through other roles or teams are not affected.
## Related
* [Create a team](/administration/users-and-permissions/manage-teams)
* [Manage team members](/administration/users-and-permissions/manage-team-members)
* [Create a role](/administration/users-and-permissions/manage-roles)
# Manage teams
Source: https://docs.firstresonance.io/administration/users-and-permissions/manage-teams
Create and delete the teams that group users so they can be assigned and notified collectively.
## Create a team
1. In ION, go to **Settings > Teams**.
2. Click **New Team**.
3. Enter a name for the team and click **Create Team**.
ION creates the team and opens its detail page.
## Delete a team
1. In ION, go to **Settings > Teams**.
2. In the team's actions menu, click **Delete Team**.
3. Confirm the deletion.
Deleting a team can't be undone.
## Related
* [Manage team roles](/administration/users-and-permissions/manage-team-roles)
* [Manage team members](/administration/users-and-permissions/manage-team-members)
# Manage user roles
Source: https://docs.firstresonance.io/administration/users-and-permissions/manage-user-roles
Add or remove roles directly on a user's profile to control what they can do in ION.
You can assign roles to a user directly from their profile page. This is the right approach when a user needs a role that isn't shared with a full team, or when you're setting up a single user's access without creating a team.
For assigning roles to many users at once, consider using [teams](/administration/users-and-permissions/manage-team-members) instead.
## Assign a role
1. In ION, go to **Settings > Users**.
2. Click the user's name to open their profile.
3. In the **Roles** section, click **Add**.
4. Select the roles you want to assign.
The roles appear in the user's **Roles** section and take effect immediately.
## Remove a role
1. In ION, go to **Settings > Users**.
2. Click the user's name to open their profile.
3. In the **Roles** section, find the role you want to remove.
4. Click the delete icon next to the role name.
The role is removed and the user loses any permissions they held through it. Permissions from other roles or teams are not affected.
Removing the last role from a user does not deactivate their account. They can still log in but cannot perform any create, update, or delete actions.
## Related
* [Create a role](/administration/users-and-permissions/manage-roles)
* [Manage team members](/administration/users-and-permissions/manage-team-members)
* [Invite a user](/administration/users-and-permissions/invite-a-user)
# Migrate to the new aBOM API
Source: https://docs.firstresonance.io/api-reference/abom-actions-for-developers
Update API, ION Actions, webhooks, and SQL from the legacy AbomItems model to BuildRequirements and AbomInstallations.
## Actions needed
This project will introduce breaking changes.
1. **API**: If you are using the API to call any of the below mutations or a query that uses any of the below fields, you will need to update your mutations/queries.
2. **Actions**: If you have any ION actions (rules) using any of the objects below, those will be disabled when this project is released and you will need to update those after release.
3. **Webhooks**: If you have any webhooks listening to any of the objects below, you will need to update your webhook listeners after release.
4. **Analytics**: If you have any SQL queries referencing `abom_items` or `abom_edges` those will need to be updated.
If the above does not apply to you then no changes necessary!
## Changes overview
Below shows an example of a car assembly that is partially installed and the differences between the old structure and the new structure.
Old structure:
```mermaid fullWidth="false" theme={null}
graph TB
i1([
inventory 1
car assembly
SN 45
])
i2([
inventory 2
SN 12
])
i3([
inventory 3
SN 13
])
ab1[
abom item 1
car assembly
qty 1
]
ab2[
abom item 2
wheel assembly
qty 1
]
ab3[
abom item 3
wheel assembly
qty 1
]
ab4[
abom item 4
wheel assembly
qty 2
]
ab5[
abom item 5
fastener
qty 50
]
i1 --- ab1
ab1 ---> ab2
ab1 ---> ab3
ab1 ---> ab4
ab1 ---> ab5
ab2 --- i2
ab3 --- i3
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
class i1,i2,i3 green
```
New structure:
```mermaid fullWidth="false" theme={null}
graph TB
i1([
inventory 1
car assembly
SN 45
])
i2([
inventory 2
SN 12
])
i3([
inventory 3
SN 13
])
br1[
build requirement 1
wheel assembly
qty 4
]
br2[
build requirement 2
fastener
qty 50
]
i1 ---> br1
i1 ---> br2
br1 --abom installation--- i2
br1 --abom installation--- i3
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
class i1,i2,i3 green
```
Old structure:
* aBOM items represent both the requirement and fulfillment of the installation
* aBOM items are used to link to inventories
* aBOM edges connect aBOM items
* aBOM items change as items are partially installed/removed. New ones get created or deleted, or quantities change
New structure:
* Build requirements belong to parent inventories and represent what needs to be installed
* Build requirements do not change as items are installed into them
* aBOM installations link inventories to a build requirement. The addition of an aBOM installation is an install. The removal of an aBOM installation is an uninstall
## Mutation/query changes
* AbomItems will be replaced by `BuildRequirements` and `AbomInstallations`, where the requirement and fulfillment (install) are clearly delineated
* AbomEdges will be replaced by `PartInventoryBuildRequirements`
Below shows the full list of items that are going away.
### Going away
#### Mutations
* `InstallKitOnAbomItemChildren`
* `CreateAbomItem`
* `DeleteAbomItem`
* `UpdateAbomItem`
#### GQL Objects/Queries
* `AbomItems`
* `ABomEdges`
* `ABomItemReferenceDesignators`
#### Fields
* `PartInventory.abomItems`
* `PartInventory.abomChildren`
* `PartInventory.abomParents`
* `PartInventory.allChildren`
* `PartInventory.allParents`
* `Part.abomItems`
* `MBomItem.abomItems`
* `MBomItemReferenceDesignator.abomItems`
* `AbomItemReferenceDesignator.abomItem`
(and any count fields generated from these relationships)
### Examples
#### API: Installing a part
Old structure:
Previously, you would have to update an existing `abom_item` with an inventory to install that inventory. The `abom_item` at that point may get swapped if the inventory being installed is already linked ot an `abom_item`
```graphql theme={null}
mutation UpdateABomItem($input: UpdateABomItemInput!) {
updateAbomItem(input: $input) {
abomItem {
id updatedById partInventoryId
}
}
}
```
```graphql theme={null}
{
"input": {
"id": 1,
"partInventoryId": 1,
"etag": "etag"
}
}
```
New structure:
Now installing a part is done via creating an aBOM installation. aBOM installations link inventories to `buildRequirements`. Conversely, uninstalling a part is done by deleting aBOM installations.
```graphql theme={null}
mutation CreateABomInstallation($input: CreateABomInstallationInput!) {
createAbomInstallation(input: $input) {
abomInstallation {
buildRequirementId
buildRequirementReferenceDesignatorId
partInventoryId
quantity
}
}
}
```
```graphql theme={null}
{
"input": {
"buildRequirementId": 1,
"partInventoryId": 3,
"quantity": 1
}
}
```
#### SQL: Query for an inventory's aBOM
Old structure:
```sql theme={null}
select
parent_part.part_number,
parent_part.description,
parent_inventory.serial_number,
parent_inventory.lot_number,
parent_inventory.quantity,
child_part.part_number,
child_part.description,
child_inventory.serial_number,
child_inventory.lot_number,
child_abom_item.quantity as 'installed quantity'
from parts_inventory parent_inventory
inner join parts parent_part on parent_part.id = parent_inventory.part_id
inner join abom_items parent_abom_item on parent_inventory.id = ai.part_inventory_id
inner join abom_edges ae on ae.parent_abom_item_id = parent_abom_item.id
inner join abom_items child_abom_item on child_abom_item.id = ae.child_abom_item_id
inner join parts_inventory child_inventory on child_abom_item.part_inventory_id = child_inventory.id
inner join parts child_part on child_part.id = child_abom_item.part_id
where parent_inventory.id = 105
```
New structure:
```sql theme={null}
select
parent_part.part_number,
parent_part.description,
parent_inventory.serial_number,
parent_inventory.lot_number,
parent_inventory.quantity,
child_part.part_number,
child_part.description,
child_inventory.serial_number,
child_inventory.lot_number,
ai.quantity as 'installed quantity'
from parts_inventory parent_inventory
inner join parts parent_part on parent_part.id = parent_inventory.part_id
inner join part_inventory_build_requirements pibr on pibr.part_inventory_id = parent_inventory.id
inner join abom_installations ai on pibr.build_requirement_id = ai.build_requirement_id
inner join parts_inventory child_inventory on ai.part_inventory_id = child_inventory.id
inner join parts child_part on child_part.id = child_abom_item.part_id
where parent_inventory.id = 105
```
# API release notes
Source: https://docs.firstresonance.io/api-reference/api-release-notes
Record of ION API changes between releases, including breaking changes and deprecation warnings.
# Manage API keys
Source: https://docs.firstresonance.io/api-reference/authentication/api-keys
Create, use, list, rotate, disable, and delete ION API keys for machine-to-machine integrations.
API keys authenticate machine-to-machine integrations. These include scripts, automations, ETL jobs, MES bridges, IoT devices, and other systems that call ION without a person signing in. You create, manage, and delete them through the API or the UI. For user-facing applications where ION enforces a specific user's permissions, use [OAuth 2.0](/api-reference/authentication/oauth) instead.
## How API keys behave
* Each API key is **specific to the environment** it was generated in. A key created in production will not work in sandbox.
* A key holds the **same permissions the creating user had at the moment it was generated**. Later changes to that user's permissions do not affect the key.
* Generating a new key does **not** invalidate previously generated keys. They keep working until disabled.
* Any automation using a key **acts on behalf of the user who created it**.
* A key is **automatically disabled when its user is deactivated**.
You need the `APIKeyObject` family of permissions to work with API keys. See
the [permissions
reference](/administration/users-and-permissions#permissions-reference) for
details.
For automations and integrations, back the key with a [service
account](#service-accounts) rather than an individual's user.
## Create a key
An org admin provisions API keys. Each key is bound to a user identity in your organization. Audit logs then reflect which integration made each call.
```graphql theme={null}
mutation CreateAPIKey {
createApiKey {
apikey {
clientId
clientSecret
}
}
}
```
The `clientSecret` is shown **once**. Capture it immediately and store it in a secrets manager such as AWS Secrets Manager, HashiCorp Vault, 1Password, or your CI/CD's encrypted secrets store. ION cannot retrieve a secret after creation.
## Get an access token
A key's `clientId` and `clientSecret` exchange for a short-lived access token through the client-credentials grant. ION then accepts that token on the `Authorization` header. Most integrations use a small client library that handles the exchange and caching for you. See the [Build an API client](/api-reference/guides/python-quickstart) for a runnable example.
To exchange the credentials directly, POST a `client_credentials` grant to the auth server for your environment:
```bash theme={null}
curl -X POST \
--data-urlencode "grant_type=client_credentials" \
-d "client_id=CLIENT_ID" \
-d "client_secret=CLIENT_SECRET" \
https:///realms/api-keys/protocol/openid-connect/token
```
The auth server and API endpoint differ per environment:
| App URL | Auth Server | API Endpoint |
| ------------------------------------- | ------------------------------- | -------------------------------------- |
| `app-v2.staging.buildwithion.com` | `staging-auth.buildwithion.com` | `https://staging-api.buildwithion.com` |
| `app-v2.buildwithion.com` | `auth.buildwithion.com` | `https://api.buildwithion.com` |
| `app-v2.staging.gov.buildwithion.com` | `staging-auth.ion-gov.com` | `https://staging-api.ion-gov.com` |
| `app-v2.gov.buildwithion.com` | `auth.ion-gov.com` | `https://api.ion-gov.com` |
| `app-v2.staging.ap.buildwithion.com` | `staging-auth.ion-aus.com` | `https://staging-api.ion-aus.com` |
| `app-v2.ap.buildwithion.com` | `auth.ion-aus.com` | `https://api.ion-aus.com` |
Append `/graphql` to the API endpoint for query requests, for example `https://staging-api.buildwithion.com/graphql`. For more runnable scripts, see the [ion-examples repository](https://github.com/FirstResonance/ion-examples).
## List keys
List your organization's API keys:
```graphql theme={null}
query APIKeys {
apiKeys {
edges {
node {
clientId
clientSecret
id
_etag
}
}
}
}
```
## Enable, disable, or regenerate a secret
Update a key with this mutation:
```graphql theme={null}
mutation UpdateAPIKey($input: APIKeyInput!) {
updateApiKey(input: $input) {
apikey {
clientId
clientSecret
enabled
}
}
}
```
Set the variables:
```json theme={null}
{
"input": {
"clientId": "",
"_etag": "",
"enabled": true,
"regenerateSecret": false
}
}
```
Set `enabled` to `false` to disable a key. Set `regenerateSecret` to `true` to rotate its secret. Disabling takes effect immediately. The next request using that key returns `401 Unauthorized`.
## Rotate a key
Rotate an API key when:
* The key might have been exposed, such as being committed to a public repo, leaked in logs, or held by an employee who has left.
* Your org's security policy requires periodic rotation.
* An integration is being decommissioned.
Rotate with zero downtime:
1. Provision a new key alongside the existing one.
2. Deploy the new key to the integration and verify traffic is succeeding with it.
3. Delete the old key.
The old and new keys are both valid during the cutover window. Consumers don't see auth failures.
## Delete a key
Delete a key with this mutation:
```graphql theme={null}
mutation DeleteAPIKey($input: APIKeyInput!) {
deleteApiKey(input: $input) {
apikey {
clientId
}
}
}
```
Set the variables:
```json theme={null}
{
"input": {
"clientId": "",
"_etag": ""
}
}
```
Deletion is immediate. The next request using that key returns `401 Unauthorized`.
## Service accounts
Service accounts decouple API keys from individual users who might get deactivated. They also make clear that the actions in ION come from a shared service rather than from one person. A service account is an email your company creates and maintains outside of any employee's email. Multiple people can access it as needed.
## Related
* [Authentication](/api-reference/authentication)
* [Authenticate with OAuth 2.0](/api-reference/authentication/oauth)
* [Build an API client](/api-reference/guides/python-quickstart)
# Authentication
Source: https://docs.firstresonance.io/api-reference/authentication/index
How to authenticate against the ION GraphQL API: API keys for machine-to-machine integrations and OAuth 2.0 for user-facing applications.
## Authentication methods
The ION GraphQL API supports two authentication methods: **API keys** for machine-to-machine integrations and **OAuth 2.0** for user-facing applications. Every request uses the same endpoint and `Authorization` header. Only the way you obtain the token differs.
## Endpoint and headers
Every authenticated request targets a single GraphQL endpoint. Include the access token on every request:
```http theme={null}
POST /graphql HTTP/1.1
Host: api.buildwithion.com
Authorization: Bearer
Content-Type: application/json
```
ION also accepts `Authorization: Token ` for backward compatibility. Prefer `Bearer`. For multipart file uploads, set `Content-Type: multipart/form-data`. Follow the flow in [File Upload](/api-reference/guides/file-upload).
## Troubleshooting
When ION rejects a request during authentication, the response carries an `errors[].message` payload:
```json theme={null}
{
"errors": [{ "message": "Token is expired." }]
}
```
For the full list of authentication failures and their fixes (`401 Unauthorized`, `403 Forbidden`, rate limits, and `5xx` errors), see [Error codes](/api-reference/error-codes).
## Related
* [Manage API keys](/api-reference/authentication/api-keys)
* [Authenticate with OAuth 2.0](/api-reference/authentication/oauth)
* [Getting started](/api-reference/getting-started)
* [Error codes](/api-reference/error-codes)
# Authenticate with OAuth 2.0
Source: https://docs.firstresonance.io/api-reference/authentication/oauth
Register an OAuth application, run the authorization code flow, and handle token refresh and scopes for user-facing apps on ION.
Use OAuth 2.0 when the caller is a user-facing application, such as a web app, desktop tool, or mobile client. OAuth lets ION enforce that user's permissions rather than a service account's. For system-to-system integrations, use an [API key](/api-reference/authentication/api-keys) instead.
## Register an OAuth application
Before users can sign in to your application, an org admin must register the application with ION. Run this mutation to register it:
```graphql theme={null}
mutation RegisterApp {
registerOauthApp(name: "Acme Production Dashboard", appType: "regular_web") {
oauthApp {
id
clientId
}
}
}
```
After registration, configure callback URLs, allowed origins, and logout URLs:
```graphql theme={null}
mutation AddCallback {
addOauthRedirectUri(
oauthAppId: 42
uri: "https://app.example.com/auth/callback"
uriType: "callback"
) {
redirectUri {
id
uri
uriType
}
}
}
```
Three URI types are supported:
| URI type | Purpose |
| ---------- | ------------------------------------------------------------------------ |
| `callback` | Where ION redirects users with the authorization code after they sign in |
| `origin` | Browser origins allowed to make authenticated requests |
| `logout` | Where ION redirects users after sign-out |
Register every URL your application uses (production, staging, local development) to avoid `redirect_uri` mismatches.
## Run the authorization code flow
Follow the standard OAuth 2.0 authorization code flow:
1. **Redirect the user to the ION authorization endpoint.** Include `client_id`, `redirect_uri`, `response_type=code`, `scope`, and `state`.
2. The user authenticates with their ION credentials or SSO.
3. ION redirects to your `redirect_uri` with an authorization `code` and the `state` you sent.
4. **Exchange the code** at the token endpoint for an `access_token`, and optionally a `refresh_token`.
5. **Use the access token** on subsequent API calls.
The authorization and token endpoint URLs depend on your org's auth provider configuration. ION surfaces them in the OAuth app registration response. If you're integrating against ION for the first time, ask your CSM for the endpoint values that match your environment.
## Handle token expiration and refresh
Access tokens have a short lifetime, typically one hour. Two patterns handle expiration:
* **Short-lived integrations.** Let the token expire. Re-run the auth flow the next time the user opens the app.
* **Long-lived integrations.** Request the `offline_access` scope at authorization. Then exchange refresh tokens for new access tokens transparently.
Always re-validate tokens before relying on them. Clock skew, server-side revocation, or org membership changes can invalidate a token mid-flight.
## Scopes
Scopes constrain what an OAuth-issued token can do. Standard scopes include the following:
| Scope | Allows |
| ---------------- | ---------------------------------------------- |
| `openid` | Receive an ID token alongside the access token |
| `profile` | Read the user's profile |
| `email` | Read the user's email |
| `offline_access` | Receive a refresh token |
ION applies the user's existing role and permission grants on top of the scope. A scope cannot grant a user more access than their role allows.
## Related
* [Authentication](/api-reference/authentication)
* [Manage API keys](/api-reference/authentication/api-keys)
* [Error codes](/api-reference/error-codes)
# Data model
Source: https://docs.firstresonance.io/api-reference/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.
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.
## 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).
**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)
# Error codes
Source: https://docs.firstresonance.io/api-reference/error-codes
HTTP status codes and GraphQL error payloads ION returns: what each one means, what causes it, and how to fix it.
## Overview
When an ION API request fails, two things tell you what went wrong. The HTTP status code gives you a coarse category and the `errors[]` payload in the response body gives you the specific message. That message often points directly at the fix.
A typical error response looks like this:
```json theme={null}
{
"errors": [
{
"message": "User does not have permission to update this resource."
}
]
}
```
GraphQL operations always return HTTP 200 even when the operation itself fails. The failure shows up in `errors[]`. Authentication and transport failures are the exception. The auth layer can return a 4xx or 5xx status code before GraphQL runs.
## HTTP status reference
| Status | Meaning | When you'll see it |
| ------ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `200` | Success or GraphQL error | Always for `/graphql`. Check `errors[]` |
| `400` | Malformed request | Invalid JSON body, a request body with no `query` key, missing required field on the wire |
| `401` | Unauthenticated | Missing, expired, or invalid token. See [the 401 table](#401-unauthorized) |
| `403` | Authenticated but not allowed | Permission, scope, or org-isolation failure. See [403 Forbidden](#403-forbidden) |
| `404` | Resource not found | Wrong ID, soft-deleted entity, or not visible to your org |
| `409` | Concurrency conflict | The `_etag` mismatch. Your update raced another writer. See [409 Concurrency conflict](#409-concurrency-conflict) |
| `422` | Validation error | Field value violates a domain rule, such as quantity ≤ 0 |
| `429` | Rate-limited | Back off and retry |
| `5xx` | Server error | ION-side failure. Safe to retry with exponential backoff |
One exception to the `400` row: a request whose body is entirely empty returns `200` with the error in `errors[]`, like any other GraphQL error.
## 401 Unauthorized
ION rejects the request at the authentication layer. Here are the common messages and their fixes:
| Message | Cause | Fix |
| ------------------------------------------------------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `Please provide proper credentials` | No `Authorization` header on the request | Add `Authorization: Bearer `. |
| `Unable to parse authentication token` | Malformed token, such as one that is truncated or in the wrong format | Verify the full token is being sent. A common cause is an env var that lost a trailing character. |
| `Unable to find appropriate RSA key` | The token was signed by a key ION doesn't recognize | The token came from the wrong auth provider. Check that you're hitting the right `client_id` and audience. |
| `Token is expired` | The access token's `exp` claim has passed | Refresh the token for OAuth, or re-issue it for an API key. |
| `Unable to validate authentication token` | Generic verification failure | Re-issue the token. If it persists, check that the token audience and issuer match your environment. |
| `Token organization does not match user's organization` | The token was issued for one org, but the user belongs to another | Re-authenticate the user against their actual organization. |
| `User is deactivated` | The user has been deactivated in ION | Reactivate the user or use a different account. |
| `Sorry, this organization is blocked from accessing ION` | The org is in a blocked state | Contact your CSM. |
| `Login domain '' is not valid for this organization` | The user's email domain isn't allowed on the org | Add the domain to org settings or use an allowed email. |
## 403 Forbidden
The request authenticated successfully, but the principal isn't authorized for the operation. The distinct causes each have a different fix.
| Cause | What happened | How to fix |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **Missing role permission** | The user or service account's role doesn't include the action being performed. For example, the role lacks the "create purchase order" permission. | An admin grants the role the missing permission, or assigns the user a role that has it. |
| **Resource not visible to org** | The entity ID exists in another tenant's data. ION treats it as nonexistent for this caller. | Verify the ID belongs to your org. Cross-org references are never allowed. |
| **Read-only organization** | The org is in read-only mode, such as a billing freeze or compliance hold, and any mutation is rejected. | Contact your CSM to release the read-only flag. |
| **Read-only schema** | A specific schema, your tenant, is flagged read-only on the ION side. | Contact support. |
| **Deactivated user** | The user account is deactivated. Auth still passes if the token is valid, but every operation returns a 403. | Reactivate the user in admin settings. |
| **Approval gate not satisfied** | The operation requires an approval that hasn't been granted. For example, publishing a procedure that requires QE sign-off. | Complete the approval flow first. |
| **OAuth scope insufficient** | The token was issued with limited scopes that don't cover the requested operation. | Re-authorize the OAuth app with broader scopes. |
| **Feature flag disabled** | The operation is gated behind a feature flag not enabled for your org. | Contact your CSM to enable the feature for your tier. |
| **Cross-org token** | A token issued for org A is trying to access org B's data. | Re-authenticate against the correct org. |
If a 403 says "permission", check the user's role. If it says "not found" or
refers to an ID, check the entity's tenant. If it says "read-only", check the
org's billing and compliance state.
### Sample 403 payload
```json theme={null}
{
"errors": [
{
"message": "User does not have permission to perform UPDATE on Purchase Order."
}
]
}
```
The action verb (`UPDATE`) and the resource type (`Purchase Order`) are the two pieces an admin needs to find the missing role permission.
## 404 Not Found
ION returns a 404, and a GraphQL `null` for the field, when the entity doesn't exist or doesn't belong to your tenant. The two cases are intentionally indistinguishable to prevent enumeration. A 404 doesn't leak that an ID exists in another org.
Common causes:
* An ID typo.
* The entity was soft-deleted.
* A cross-tenant ID. See 403 above. Sometimes this surfaces as a 404 instead, depending on the resolver.
If the entity should exist, query a filtered list such as `parts(filters: { id: { in: [42] } }) { edges { node { id } } }` instead of `part(id: "42")`. The filtered query returns an empty `edges` array. That confirms a tenant or scope issue.
## 409 Concurrency conflict
ION uses optimistic concurrency control via the `_etag` field. Every mutable entity returns an `_etag` on read. Mutations require you to pass back the `_etag` you received. If another writer modified the entity in the meantime, the etag won't match and ION returns a 409.
```json theme={null}
{
"errors": [{ "message": "Concurrent modification detected — etag mismatch." }]
}
```
The fix is always the same:
1. Re-read the entity to get the latest `_etag` and the latest field values.
2. Re-apply your changes on top of the new state.
3. Retry the mutation with the new `_etag`.
This is intentional. Re-reading surfaces the other writer's changes before you overwrite them. Always re-read and re-apply, even when you intend to discard the other writer's changes.
## 422 Validation error
The request was structurally valid and authorized, but a field value violated a domain rule:
* A quantity is zero or negative.
* A required field is missing.
* A date is out of range.
* A string exceeds the maximum length.
* An enum value is not allowed.
The error message names the field and the constraint:
```json theme={null}
{
"errors": [{ "message": "PartInventory quantity must be greater than 0." }]
}
```
These errors are deterministic. Fix the input and retry.
## 429 Rate limit
ION applies fair-use rate limiting on the `/graphql` endpoint. Production traffic patterns rarely hit it. If you do, the response is HTTP 429.
Address it in this order:
1. Implement exponential backoff in your client. Start at 1 second, double up to about 30 seconds, and add jitter.
2. Batch related queries. One query selecting many fragments beats several queries that each select one fragment.
3. Cache locally. For data that doesn't change often, such as parts and procedures, don't re-fetch every time.
## 5xx Server errors
A 5xx response indicates a failure on ION's side. These responses are safe to retry with exponential backoff.
```http theme={null}
HTTP/1.1 502 Bad Gateway
```
Standard recovery pattern:
1. Retry with exponential backoff. Use 1, 2, 4, and 8 seconds.
2. After three consecutive failures across about 30 seconds, surface the error to the user or on-call.
3. Check ION's status page if available, or contact support if the error persists.
## GraphQL errors\[] shape
Inside the GraphQL response body, an HTTP 200, the `errors` array is the source of truth:
```json theme={null}
{
"data": null,
"errors": [
{
"message": "User does not have permission to perform UPDATE on Purchase Order.",
"path": ["updatePurchaseOrder"],
"locations": [{ "line": 2, "column": 3 }]
}
]
}
```
| Field | Meaning |
| ----------- | ----------------------------------------------------------------- |
| `message` | Human-readable error. |
| `path` | Which field in your query failed. |
| `locations` | Position in the query string for IDE and playground integrations. |
A partial `data` payload with a non-empty `errors[]` means the returned fields are valid and one or more other fields failed. Most clients merge these. Yours should too.
## Troubleshooting flow
1. Check the HTTP status.
2. If it's a 200, parse `errors[]` from the response body.
3. Match the message against the tables on this page.
4. If the message is unfamiliar, its wording usually points at the offending field, permission, or resource. Search for it in [Authentication](/api-reference/authentication) and the tables on this page.
5. If you're still stuck, reproduce the call in the [API Playground](/api-reference/playground), which surfaces errors more readably. Paste the request and error into a support ticket.
Most 403s are fixed by granting the user's role the missing permission. Check
permissions before you change code. For a 409, show the conflict to a human so
they can re-merge.
## Related
* [Authentication](/api-reference/authentication)
* [API playground](/api-reference/playground)
# Example requests
Source: https://docs.firstresonance.io/api-reference/examples
The most common queries and mutations you can run on the ION API
This page collects the most common queries and mutations you can run on the ION API. The quickest way to run them is through the [API Playground](/api-reference/playground).
Find parts by part number or description substring:
```graphql theme={null}
query SearchParts($q: String!, $first: Int) {
parts(filters: { partNumber: { ilike: $q } }, first: $first) {
edges {
node {
id
partNumber
description
revision
partType
status
partSubtypes { id name }
}
}
}
}
```
```json theme={null}
{ "q": "%BRKT%", "first": 25 }
```
Fetch a procedure template plus every step and its fields:
```graphql theme={null}
query GetProcedure($id: Int!) {
procedure(id: $id) {
id
title
steps {
id
title
fields { id }
}
}
}
```
```json theme={null}
{ "id": 12 }
```
Walk an as-built BOM tree from the parent down to all installations:
```graphql theme={null}
query AbomTree($id: Int!) {
partInventory(id: $id) {
id
serialNumber
part { partNumber }
buildRequirements {
id
part { partNumber }
quantity
}
}
}
```
```json theme={null}
{ "id": 9876 }
```
Pull two as-built BOMs and diff them client-side:
```graphql theme={null}
query CompareAboms($idA: Int!, $idB: Int!) {
a: partInventory(id: $idA) {
id
serialNumber
buildRequirements { id part { partNumber } quantity }
}
b: partInventory(id: $idB) {
id
serialNumber
buildRequirements { id part { partNumber } quantity }
}
}
```
```json theme={null}
{ "idA": 9876, "idB": 9877 }
```
Get every run that's in progress, with the current step the operator is on:
```graphql theme={null}
query OpenRuns {
runs(filters: { status: { in: ["TODO", "IN_PROGRESS"] } }, first: 100) {
edges {
node {
id
title
status
procedure { id title }
partInventory { id serialNumber part { partNumber } }
}
}
}
}
```
Every run a specific part inventory has gone through, newest first:
```graphql theme={null}
query RunHistory($partInventoryId: Int!) {
runs(
filters: { partInventoryId: { eq: $partInventoryId } }
first: 50
) {
edges {
node {
id
title
status
_created
procedure { id title }
}
}
}
}
```
```json theme={null}
{ "partInventoryId": 9876 }
```
List inventory units of a given part, grouped by physical location:
```graphql theme={null}
query InventoryByLocation($partId: Int!) {
partInventories(filters: { partId: { eq: $partId } }, first: 250) {
edges {
node {
id
serialNumber
lotNumber
quantity
status
location { id name type }
}
}
}
}
```
```json theme={null}
{ "partId": 12 }
```
Get every serialized instance of a part with its current state:
```graphql theme={null}
query SerializedUnits($partId: Int!) {
partInventories(filters: { partId: { eq: $partId } }, first: 250) {
edges {
node {
id
serialNumber
status
location { id name }
}
}
}
}
```
```json theme={null}
{ "partId": 12 }
```
All open issues grouped by disposition type for triage:
```graphql theme={null}
query OpenIssuesByDisposition {
issues(
filters: { status: { in: ["PENDING", "IN_PROGRESS", "IN_REVIEW"] } }
first: 250
) {
edges {
node {
id
title
status
issueDispositionType { id title }
assignedTo { id name }
_created
}
}
}
}
```
Verify your token and find which org you're in:
```graphql theme={null}
query Me {
me {
id
name
email
organization { id domain }
}
}
```
Create a new part in the library:
```graphql theme={null}
mutation CreatePart($input: CreatePartInput!) {
createPart(input: $input) {
part { id partNumber description revision partType status }
}
}
```
```json theme={null}
{
"input": {
"partNumber": "TEST-001",
"description": "Integration test part",
"revision": "A",
"partType": "part",
"trackingType": "lot"
}
}
```
Start a new run by binding a procedure to a part inventory:
```graphql theme={null}
mutation CreateRun($input: CreateRunInput!) {
createRun(input: $input) {
run {
id
title
status
procedure { id title }
partInventory { id serialNumber }
}
}
}
```
```json theme={null}
{
"input": {
"procedureId": 12,
"partInventoryId": 9876,
"title": "Build #2026-04-26 Unit 1"
}
}
```
Sign off a step with measurements. Requires the step's `_etag`:
```graphql theme={null}
mutation SubmitStep($input: UpdateRunStepInput!) {
updateRunStep(input: $input) {
runStep {
id
status
fields { id name value }
}
}
}
```
```json theme={null}
{
"input": {
"id": 4567,
"_etag": "abc123",
"status": "complete",
"fieldValues": [
{ "runStepFieldId": 88, "value": "PASS" },
{ "runStepFieldId": 89, "value": "12.45" }
]
}
}
```
Move a run between lifecycle states:
```graphql theme={null}
mutation UpdateRunStatus($input: UpdateRunInput!) {
updateRun(input: $input) {
run { id status startTime endTime }
}
}
```
```json theme={null}
{
"input": {
"id": 1234,
"_etag": "xyz789",
"status": "complete"
}
}
```
Issue a new PO with line items:
```graphql theme={null}
mutation CreatePO($input: CreatePurchaseOrderInput!) {
createPurchaseOrder(input: $input) {
purchaseOrder {
id
status
supplier { id name }
purchaseOrderLines { id part { partNumber } quantity }
}
}
}
```
```json theme={null}
{
"input": {
"vendorId": 42,
"lineItems": [
{ "partId": 12, "quantity": 100 },
{ "partId": 13, "quantity": 50 }
]
}
}
```
Open a quality issue tied to a specific run step:
```graphql theme={null}
mutation CreateIssue($input: CreateIssueInput!) {
createIssue(input: $input) {
issue {
id
title
status
runStep { id }
}
}
}
```
```json theme={null}
{
"input": {
"title": "Surface scratches on RX-22 lot 4",
"runStepId": 4567,
"description": "Visible scratches across 4 of 12 units"
}
}
```
Move an issue to resolved once reviews are signed off:
```graphql theme={null}
mutation ResolveIssue($input: UpdateIssueInput!) {
updateIssue(input: $input) {
issue { id status }
}
}
```
```json theme={null}
{
"input": {
"id": 7777,
"_etag": "etag-value",
"status": "resolved"
}
}
```
# Getting started
Source: https://docs.firstresonance.io/api-reference/getting-started
Make your first authenticated GraphQL request to the ION API.
## Prerequisites
* An ION organization.
* An **API key** for machine-to-machine integrations, or **OAuth credentials** for user-facing applications. For how to obtain each, see [Authentication](/api-reference/authentication).
* A tool that can make HTTPS POST requests with JSON bodies, such as `curl`, `httpie`, or Postman.
* Familiarity with GraphQL.
New to GraphQL? See the [official GraphQL
documentation](https://graphql.org/learn/).
## The endpoint
All API requests go to one endpoint:
```
POST https://api.buildwithion.com/graphql
```
Authenticate each request with these headers:
```
Authorization: Bearer
Content-Type: application/json
```
## Get an access token
If you have an **API key**, exchange it for a short-lived access token through your auth provider's client-credentials grant. For a reusable pattern, see the [Build an API client](/api-reference/guides/python-quickstart).
If you use **OAuth 2.0**, complete the authorization code flow in [Authenticate with OAuth 2.0](/api-reference/authentication/oauth). After the user signs in, your callback receives an `access_token`.
Both paths give you a JWT string. Treat it like a password.
## Make your first request
A minimal "who am I?" query confirms the token works:
```bash theme={null}
curl -X POST https://api.buildwithion.com/graphql \
-H "Authorization: Bearer $ION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "query { me { id name email organization { id domain } } }"
}'
```
Install `httpx` (`pip install httpx`), then run:
```python theme={null}
import os
import httpx
resp = httpx.post(
"https://api.buildwithion.com/graphql",
headers={"Authorization": f"Bearer {os.environ['ION_TOKEN']}"},
json={"query": "query { me { id name email organization { id domain } } }"},
)
resp.raise_for_status()
print(resp.json()["data"]["me"])
```
For a reusable client with retries and error handling, see the [Build an API client](/api-reference/guides/python-quickstart).
A successful response looks like this:
```json theme={null}
{
"data": {
"me": {
"id": 42,
"name": "Ada Lovelace",
"email": "ada@acme.com",
"organization": {
"id": 7,
"domain": "acme.com"
}
}
}
}
```
If the response is an `errors` payload instead, see [Error codes](/api-reference/error-codes) and its [401 table](/api-reference/error-codes#401-unauthorized).
## Query data
Once auth works, try a domain query. Listing parts is a good first test because it touches inventory, the most common integration target:
```bash theme={null}
curl -X POST https://api.buildwithion.com/graphql \
-H "Authorization: Bearer $ION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "query { parts(filters: {}, first: 5) { edges { node { id partNumber description } } } }"
}'
```
```python theme={null}
import os
import httpx
resp = httpx.post(
"https://api.buildwithion.com/graphql",
headers={"Authorization": f"Bearer {os.environ['ION_TOKEN']}"},
json={"query": "query { parts(filters: {}, first: 5) { edges { node { id partNumber description } } } }"},
)
resp.raise_for_status()
print(resp.json()["data"]["parts"]["edges"])
```
The response should contain up to five parts from your org. If the response comes back empty, your org might not have parts yet. Query `me` again to confirm the token is valid. Then check the admin UI for data.
## Pick your next page
You now have a working integration. Where to go next depends on what you're building:
| If you're… | Go to |
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| Building a reusable API client | [Build an API client](/api-reference/guides/python-quickstart) |
| Working through common queries and mutations | [Example requests](/api-reference/examples) |
| Building real-time event-driven integrations | [Set up webhooks](/api-reference/guides/webhooks) |
| Uploading files, such as procedure attachments and run artifacts | [Upload a file](/api-reference/guides/file-upload) |
| Mapping ION's data model into your system | [Data model](/api-reference/data-model) |
| Hitting a 403 or unfamiliar error | [Error codes](/api-reference/error-codes) |
| Testing without affecting production | [Sandbox](/api-reference/sandbox) |
| Taking an integration to production | [Build a production integration](/api-reference/guides/build-a-production-integration) |
## Related
* [Authentication](/api-reference/authentication)
* [Example requests](/api-reference/examples)
# Build an aBOM
Source: https://docs.firstresonance.io/api-reference/guides/abom-as-built-bill-of-materials-api
Build an as-built bill of materials (aBOM) with the ION API: install parts and manage build requirements.
An as-built bill of materials (aBOM) tracks the build process of parts in a hierarchical tree. The aBOM records which part instances and lots are used to create parts, subsystems, and systems. The aBOM is a more detailed version of the mBOM, with relations to physical inventory rather than to parts.
## Set up part inventory
To create an aBOM, you need inventory. For details on creating inventory, see [Manage part inventory and kitting](/api-reference/guides/part-inventory-and-kitting). A part with inventory has a part number, and that part should have an mBOM. ION uses this mBOM to create the aBOM.
When you first create the part inventory, it has only empty build requirements. Build requirements define the parameters for what parts you can install.
## Install parts
You install a part by creating an aBOM installation. An aBOM installation links inventory to `buildRequirements`. To uninstall a part, delete its aBOM installation.
Create the aBOM installation with this mutation:
```graphql theme={null}
mutation CreateABomInstallation($input: CreateABomInstallationInput!) {
createAbomInstallation(input: $input) {
abomInstallation {
buildRequirementId
buildRequirementReferenceDesignatorId
partInventoryId
quantity
}
}
}
```
Set the variables:
```json theme={null}
{
"input": {
"buildRequirementId": 1,
"partInventoryId": 3,
"quantity": 1
}
}
```
## Edit build requirements
You can add build requirements to an inventory that don't originate from the mBOM.
Create the build requirement with this mutation:
```graphql theme={null}
mutation CreateBuildRequirement($input: CreateBuildRequirementInput!) {
createBuildRequirement(input: $input) {
buildRequirement {
fixedQuantityPerBuildRequirement
id
madeOnAssembly
originMbomItemId
partId
partInventories {
id
}
quantityPerParentPartInventory
}
}
}
```
Set the variables:
```json theme={null}
{
"input": {
"partId": 1,
"partInventoryId": 484,
"quantityPerParentPartInventory": 5,
"madeOnAssembly": false
}
}
```
Update the build requirement with this mutation:
```graphql theme={null}
mutation UpdateBuildRequirement($input: UpdateBuildRequirementInput!) {
updateBuildRequirement(input: $input) {
buildRequirement {
fixedQuantityPerBuildRequirement
id
}
}
}
```
Set the variables:
```json theme={null}
{
"input": {
"id": 1031,
"etag": "dca4e886ecce4a499d15411006a6d82a",
"quantityPerParentPartInventory": 8
}
}
```
This example changes the `quantityPerParentPartInventory` of the `buildRequirement`. You can also change the fixed quantity, made-on-assembly designation, substitutes, and reference designators.
## Related
* [Manage part inventory and kitting](/api-reference/guides/part-inventory-and-kitting)
* [Manage mBOM items](/api-reference/guides/mboms)
# Update run step fields
Source: https://docs.firstresonance.io/api-reference/guides/automatically-updating-fields-in-runs
Query the fields on a run step and update a field value with the GraphQL API.
To update a field on a run step, first query the run to find the field you want, then run a mutation to set its value.
## Get the fields
Query the run to get the fields that exist on the run steps you want to update:
```graphql theme={null}
{
run(id: 419) {
id steps {
id fields {
id name value type _etag
}
}
}
}
```
The response looks like this:
```json theme={null}
{
"data": {
"run": {
"id": 419,
"steps": [
{
"id": 1429,
"fields": [
{
"id": 1461,
"name": "required_field",
"value": null,
"type": "STRING",
"_etag": "066b1eb08dab44d6925d7bbd899e92cf"
},
{
"id": 1462,
"name": "pass_fail",
"value": null,
"type": "BOOLEAN",
"_etag": "066b1eb08dab44d6925d7bbd899e92cf"
},
{
"id": 1463,
"name": "quality",
"value": null,
"type": "SIGNOFF",
"_etag": "066b1eb08dab44d6925d7bbd899e92cf"
}
]
}
]
}
}
}
```
The query returns a list of run steps within that run, and the fields within those run steps. Find the field that you want to update.
## Update a field value
From the example response above, suppose you want to update field `1462` because your test equipment can determine pass or fail. Use this mutation:
```graphql theme={null}
mutation {
updateRunStepFieldValue(input: {
id: 1462,
value: true,
etag: "066b1eb08dab44d6925d7bbd899e92cf"
}) {
runStepField {
id value
}
}
}
```
This mutation does the following:
* Because it updates data, it's a `mutation`.
* The mutation is `updateRunStepFieldValue`.
* The field `id` is `1462` and the `value` you're setting is `true`.
* It includes the `etag` of the original field. The `etag` prevents accidental data overwrites by enforcing concurrency control.
The response returns the `id` and the updated `value` of the field.
# Build a production integration
Source: https://docs.firstresonance.io/api-reference/guides/build-a-production-integration
Take an integration from your first API call to production.
This is the GraphQL API path to an integration. For other approaches, such as the Workflow Builder, see [Create an integration](/automate-with-ion/integrations/create-an-integration). Once your first calls work (see [Getting started](/api-reference/getting-started)), follow this path to take an integration to production.
## Get access
For your own org, ask an admin for access to your [sandbox](/api-reference/sandbox). For an external org, that org grants you access to their environment.
Gov Cloud is restricted to US persons only. If you're a non-US person building for a Gov Cloud organization, use your own ION sandbox and grant access to other domains instead.
If your organization doesn't have a sandbox yet, reach out to First Resonance to request one.
## Set permissions
An admin grants the permissions your integration needs. An API key holds the permissions its creating user had **at the moment it was generated**. Set those permissions first. See the [permissions reference](/administration/users-and-permissions#permissions-reference), [Manage API keys](/api-reference/authentication/api-keys), and [Webhooks](/api-reference/guides/webhooks).
## Connect
Obtain an access token ([Authentication](/api-reference/authentication)) and subscribe to the events you care about ([Webhooks](/api-reference/guides/webhooks)). You can build the integration in a traditional environment such as Python or Node.js, or in a low-code iPaaS tool.
## Map the flow
List the triggers, steps, and API calls your integration needs. Configure any new [custom attributes](/administration/custom-attributes) to represent data that doesn't have a home in ION yet.
Every unique mutation in ION has an associated permission. Use the [API playground](/api-reference/playground) to confirm which permissions a call requires before you build.
## Test and promote
Validate your edge cases against the sandbox, then roll out to production. The production environment needs its own set of API keys and webhook receivers.
## Related
* [Getting started](/api-reference/getting-started)
* [Authentication](/api-reference/authentication)
* [Set up webhooks](/api-reference/guides/webhooks)
# Edit time-tracking session data
Source: https://docs.firstresonance.io/api-reference/guides/edit-time-tracking-session-data
Update the check-in and check-out times for a time-tracking session through the ION GraphQL API.
You can edit a session through the API or from a run step. For the in-app workflow, see [Time tracking](/build-hardware/runs-and-execution/time-tracking).
When you need to correct the check-in or check-out time for a session, update the session data through the ION API. The steps below walk through querying for a session and updating its times.
## Get the session ID and information
Query for the sessions you want to edit. This query returns the last five sessions. To narrow the results, query by user or another parameter:
```graphql theme={null}
{
sessions (last: 5){
edges {
node {
id
_etag
runStep {
runId
position
}
checkIn
checkOut
createdBy {
email
}
}
}
}
}
```
## Update the session data
Once you have the `id` and `_etag`, use this mutation and input to modify the session data:
```graphql theme={null}
mutation UpdateSession($input: UpdateSessionInput!) {
updateSession(input: $input) {
session {
checkIn
checkOut
id
}
}
}
```
Provide these query variables:
```json theme={null}
{
"input": {
"id": "",
"etag": "",
"checkIn": "