> For the complete documentation index, see [llms.txt](https://docs.durohub.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.durohub.com/core-concepts/change-orders.md).

# Change Orders

Change Orders (COs) are the backbone of engineering change management in PLM systems. They provide a structured, auditable process for proposing, reviewing, and implementing modifications to your components. Whether you're updating a component specification, revising a BOM, or making design changes, change orders ensure proper review, approval, and traceability.

## Overview

### What Are Change Orders?

A Change Order is a formal request to modify one or more components in your library. It captures:

* **What's changing**: The affected components and proposed modifications
* **Why it's changing**: Description and justification for the change
* **Who approves it**: Reviewers assigned to evaluate the change
* **The outcome**: Whether the change was approved, rejected, or withdrawn

### Change Order Lifecycle

Change orders follow a defined lifecycle:

```
                    MANUAL_CLOSE
DRAFT ──────► OPEN ──────────────► RESOLVED ──────► CLOSED
  ▲             │                     │               ▲
  │             │      AUTO_CLOSE     │               │
  │             └─────────────────────┼───────────────┘
  │                                   │
  └───────────────────────────────────┘
           reset (from OPEN or RESOLVED)
```

| Status     | Description                                                                                  |
| ---------- | -------------------------------------------------------------------------------------------- |
| `DRAFT`    | Initial state. The CO can be modified freely—add items, assign reviewers, fill in details.   |
| `OPEN`     | Submitted for review. Reviewers evaluate and make decisions.                                 |
| `RESOLVED` | Review complete, waiting to be closed by hand. Check the `resolution` field for the outcome. |
| `CLOSED`   | Final state. The change order is complete.                                                   |

What happens when a change order is approved, rejected, or withdrawn is set by its **router options**, one per outcome:

| Field                    | Applies when                                                 | `AUTO_CLOSE`      | `MANUAL_CLOSE`                                                        |
| ------------------------ | ------------------------------------------------------------ | ----------------- | --------------------------------------------------------------------- |
| `approvalRouterOption`   | The last stage approves                                      | `OPEN` → `CLOSED` | `OPEN` → `RESOLVED`, then [`close`](#closing-a-resolved-change-order) |
| `rejectionRouterOption`  | A stage rejects                                              | `OPEN` → `CLOSED` | `OPEN` → `RESOLVED`, then `close`                                     |
| `withdrawalRouterOption` | The change order is [withdrawn](#withdrawing-a-change-order) | `OPEN` → `CLOSED` | `OPEN` → `RESOLVED`, then `close`                                     |

Under `AUTO_CLOSE` the change order skips `RESOLVED` entirely. The template sets the defaults and which options are allowed (`allowableApprovalRouterOptions`, `allowableRejectionRouterOptions`, `allowableWithdrawalRouterOptions`); while the change order is in `DRAFT` you can pick among the allowed options with `updateResolutionRouterOption`. Read the three router fields before you decide whether to call `close` or `reset`.

### Resolution Types

Once review ends, the `resolution` field records the outcome, whether the change order is `RESOLVED` or `CLOSED`:

| Resolution  | Description                                                                     |
| ----------- | ------------------------------------------------------------------------------- |
| `PENDING`   | Still awaiting reviewer decisions                                               |
| `APPROVED`  | All required approvals obtained                                                 |
| `REJECTED`  | Rejected by one or more reviewers                                               |
| `WITHDRAWN` | The change order was withdrawn while `OPEN` (requires `change_orders.withdraw`) |

### Change Order Types

Every change order carries a **type** (`coType`), a classification drawn from the industry-standard `CoType` enum. It is returned on every change order as the non-null `coType` field. The type is chosen at creation, but stays editable while the change order is in `DRAFT` — see [Switching Type and Template in Draft](#switching-type-and-template-in-draft). Once the change order is submitted for review, the type is fixed.

| Value | Meaning                      |
| ----- | ---------------------------- |
| `ECO` | Engineering Change Order     |
| `MCO` | Manufacturing Change Order   |
| `DCO` | Documentation Change Order   |
| `ECR` | Engineering Change Request   |
| `MCR` | Manufacturing Change Request |
| `DCR` | Documentation Change Request |
| `ECN` | Engineering Change Notice    |
| `MCN` | Manufacturing Change Notice  |
| `DCN` | Documentation Change Notice  |

For most types the value is **classification metadata only** — it records intent and drives reporting, but does not change how the change order behaves.

{% hint style="info" %}
**`DCO` is the exception — it changes behavior.** A Documentation Change Order is revision-frozen: approving and closing it never bumps the revision of any component in the change order, regardless of whether the component was modified. Consequently a DCO also **cannot change a component's `status` or `revision`** — attempting either is rejected ([`DCO_STATUS_CHANGE_NOT_ALLOWED`, `DCO_REVISION_CHANGE_NOT_ALLOWED`](/advanced-topics/error-handling.md#dco_status_change_not_allowed-dco_revision_change_not_allowed)). Use a DCO for documentation-only updates that need visibility across your team without triggering a revision bump.
{% endhint %}

{% hint style="warning" %}
**A DCO is frozen against the last-released baseline.** Duro compares every DCO item's *live* `status` and `revision` against its *last-released baseline* and blocks the change order if they have drifted apart — both when you submit it for review and again when it closes. Two cases are worth calling out:

* **Concurrent drift.** If another change order releases a new revision of one of your DCO's items (or changes its status) after you added it, the DCO can no longer be released. Re-add the item against the new baseline, or move the change to a non-`DCO` type.
* **No releasable baseline.** If a DCO item has never been released — so there is no baseline to freeze against — or its live component can't be found, the DCO is likewise blocked.

Drift caught at close time aborts the release with [`DCO_STATUS_CHANGE_NOT_ALLOWED` / `DCO_REVISION_CHANGE_NOT_ALLOWED`](/advanced-topics/error-handling.md#dco_status_change_not_allowed-dco_revision_change_not_allowed). The guard is the built-in [DCO baseline validation](/library-configuration/change-order-validations.md#dco-baseline-freeze).
{% endhint %}

**How the type is chosen at creation:**

1. If you pass an explicit `coType` in the create input, that value is used.
2. Otherwise, when creating from a template, the template's `defaultCoType` is used.
3. Otherwise (a change order created from an inline `config` with no `coType`), the type defaults to `ECO`.

When a template is used, the resolved type must be one of the template's allowed `coTypes` — otherwise the request fails with [`CO_TYPE_NOT_ALLOWED`](/advanced-topics/error-handling.md#co_type_not_allowed). Templates declare their allowed types in the workflow YAML; see [Change Order Workflows → Declaring Allowed Change Order Types](/library-configuration/change-order-workflows.md#declaring-allowed-change-order-types).

The same three rules and the same `coTypes` check apply when you switch the type or the template on a draft change order.

***

## Required Headers

All change order operations require these headers:

```bash
curl 'https://api.durohub.com/graphql' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'x-organization: @your-org' \
  -H 'x-library: @your-org/your-library' \
  -H 'Content-Type: application/json' \
  -d '{"query": "..."}'
```

{% hint style="info" %}
Using the EU or GovCloud region? Use your region's host — see [Endpoints](/readme.md#endpoints).
{% endhint %}

| Header           | Required | Description                                    |
| ---------------- | -------- | ---------------------------------------------- |
| `x-api-key`      | Yes      | Your API authentication token                  |
| `x-organization` | Yes      | Organization slug (e.g., `@acme-corp`)         |
| `x-library`      | Yes      | Library slug (e.g., `@acme-corp/main-library`) |
| `Content-Type`   | Yes      | Must be `application/json`                     |

***

## Querying Change Orders

### List Change Orders

Query change orders with optional filtering and pagination:

```graphql
query GetChangeOrders {
  changeOrders {
    get(
      filter: { status: [DRAFT, OPEN] }
      pagination: { first: 20 }
    ) {
      connection {
        edges {
          node {
            id
            sequentialId
            name
            description
            status
            resolution
            createdAt
            createdBy {
              id
              name
              username
              primaryEmail
            }
            currentStage {
              name
              decisionState
            }
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
        totalCount
      }
    }
  }
}
```

### Filter Options

| Field           | Type                       | Description                                                   |
| --------------- | -------------------------- | ------------------------------------------------------------- |
| `ids`           | `[ID!]`                    | Filter by specific UUIDs                                      |
| `sequentialIds` | `[Int!]`                   | Filter by sequential IDs, as plain integers (e.g., `[1, 2]`)  |
| `status`        | `[ChangeOrderStatus!]`     | Filter by status (DRAFT, OPEN, RESOLVED, CLOSED)              |
| `resolution`    | `[ChangeOrderResolution!]` | Filter by resolution (PENDING, APPROVED, REJECTED, WITHDRAWN) |
| `stageId`       | `ID`                       | Filter by current stage                                       |

To find change orders by reviewer, use `advancedFilter`. `reviewer` matches a user on any stage; `awaitingReviewer` matches only change orders whose current stage is still waiting on that user's decision:

```graphql
query ChangeOrdersAwaitingMe {
  changeOrders {
    get(
      advancedFilter: { and: [{ awaitingReviewer: { id: { eq: "user-uuid-1" } } }] }
      pagination: { first: 20 }
    ) {
      connection {
        edges {
          node {
            id
            sequentialId
            name
            status
          }
        }
        totalCount
      }
    }
  }
}
```

***

## Getting Available Templates

Before creating a change order, query the available templates for your library:

```graphql
query GetTemplates {
  changeOrders {
    getTemplates {
      id
      name
      config
      coTypes
      defaultCoType
      isEnabled
      createdAt
    }
  }
}
```

By default `getTemplates` returns only **enabled** templates — the ones a change order can be created from. Pass `includeDisabled: true` to also return templates that have been [disabled](#disabling-a-template); each template's `isEnabled` field tells you which is which:

```graphql
query GetAllTemplates {
  changeOrders {
    getTemplates(includeDisabled: true) {
      id
      name
      isEnabled
    }
  }
}
```

Templates define the workflow structure including:

* Custom fields to capture change information
* Approval stages and their decision methods
* Default reviewers and notification settings
* The [change order types](#change-order-types) the template allows (`coTypes`) and its default (`defaultCoType`)

{% hint style="info" %}
`coTypes` and `defaultCoType` are only populated for templates authored against schema version `1.1` or later. Legacy (`1.0`) templates return `null` for both fields and behave as if they allow only `ECO`. See the [Workflow YAML Reference](/library-configuration/change-order-workflow-reference.md#change-order-types).
{% endhint %}

The same query supplies the `configId` you pass to `submitDraft` when [switching a draft change order's template](#switching-type-and-template-in-draft).

See [Change Order Workflows](/library-configuration/change-order-workflows.md) for creating custom templates.

### Disabling a template

A template can be disabled without deleting it — useful for templates that are still being drafted, are only used at certain times of year, or are kept as a historical record. Toggle it with `setTemplateEnabled`:

```graphql
mutation DisableTemplate {
  changeOrders {
    setTemplateEnabled(templateId: "template-uuid-1", enabled: false) {
      id
      name
      isEnabled
    }
  }
}
```

Pass `enabled: true` to the same mutation to re-enable it.

A disabled template:

* **Cannot be used for new change orders**, and cannot be switched to with `submitDraft` (`configId`) — see [Switching Type and Template in Draft](#switching-type-and-template-in-draft).
* **Is omitted from `getTemplates`** unless you pass `includeDisabled: true`.
* **Stays visible and editable** in library settings, and keeps its history — it is not archived.
* **Does not affect existing change orders** that were created from it. They keep their stages, reviewers, and content fields and continue through their workflow as normal.

***

## Creating a Change Order

Create a new change order in `DRAFT` status. Every change order is built from a workflow: pass **exactly one** of `configId` (the `id` of a template from [`getTemplates`](#getting-available-templates)) or `config` (an inline workflow configuration). Passing neither fails with `TEMPLATE_OR_CONFIG_REQUIRED`.

```graphql
mutation CreateChangeOrder {
  changeOrders {
    create(input: {
      name: "ECO-2024-001: Update Motor Specifications"
      description: "Updating motor torque specifications to meet new performance requirements"
      configId: "template-uuid-1"
      coType: ECO
    }) {
      id
      sequentialId
      name
      description
      status
      resolution
      coType
      stages {
        id
        name
        order
        decisionMethod
      }
      contents {
        id
        name
        label
        isRequired
      }
      createdAt
    }
  }
}
```

The `coType` input is optional. When omitted, the server resolves the type from the template's `defaultCoType`, falling back to `ECO` for change orders created from an inline `config`. If you pass a `coType` that the template does not allow, creation fails with [`CO_TYPE_NOT_ALLOWED`](/advanced-topics/error-handling.md#co_type_not_allowed). See [Change Order Types](#change-order-types) for the full list of values and the behavior of `DCO`.

Neither choice is permanent: while the change order stays in `DRAFT` you can switch its type and its template with `submitDraft` — see [Switching Type and Template in Draft](#switching-type-and-template-in-draft).

### Example Response

```json
{
  "data": {
    "changeOrders": {
      "create": {
        "id": "123e4567-e89b-12d3-a456-426614174000",
        "sequentialId": 42,
        "name": "ECO-2024-001: Update Motor Specifications",
        "description": "Updating motor torque specifications...",
        "status": "DRAFT",
        "resolution": "PENDING",
        "coType": "ECO",
        "stages": [
          {
            "id": "stage-uuid-1",
            "name": "Engineering Review",
            "order": 1,
            "decisionMethod": "MAJORITY"
          },
          {
            "id": "stage-uuid-2",
            "name": "Quality Approval",
            "order": 2,
            "decisionMethod": "UNANIMOUS"
          }
        ],
        "contents": [
          {
            "id": "content-uuid-1",
            "name": "change_category",
            "label": "Change Category",
            "isRequired": true
          }
        ],
        "createdAt": "2024-01-15T10:30:00Z"
      }
    }
  }
}
```

***

## Adding Items to a Change Order

Add components to the change order to indicate what will be modified:

```graphql
mutation AddItems {
  changeOrders {
    addItems(
      changeOrderId: "123e4567-e89b-12d3-a456-426614174000"
      input: {
        items: [
          { id: "component-uuid-1", version: 3 }
          { id: "component-uuid-2", version: 1 }
        ]
      }
    ) {
      id
      itemId
      itemVersion
      userPickedRevisionChangeType
      noBump
      isAutoAdded
    }
  }
}
```

Each entry in `items` is a component `id` and the `version` you are changing. A single call accepts up to 1,000 items; larger batches fail with `TOO_MANY_ITEMS`, so split them across several calls.

Every item carries `isAutoAdded: Boolean!`. It is `false` for items you added explicitly, and `true` for documents Duro added for you — see [Auto-Added Documents](#auto-added-documents). Each item also carries `lastReleasedStatus`, the component's status at its most recent release (`null` if it has never been released), so a review can show the status change alongside the proposed one.

### Setting Proposed Changes

You can specify what each component will become after the change order is approved. Pass `componentUpdates` to `submitDraft` while the change order is in `DRAFT`; `id` is the component's ID:

```graphql
mutation UpdateProposals {
  changeOrders {
    submitDraft(
      id: "123e4567-e89b-12d3-a456-426614174000"
      input: {
        componentUpdates: [
          {
            id: "component-uuid-1"
            statusId: "released-status-uuid"
            revisionValue: "2.A"
          }
          {
            id: "component-uuid-2"
            changeType: MINOR
          }
        ]
      }
    ) {
      id
      items {
        itemId
        userPickedRevisionChangeType
        noBump
      }
    }
  }
}
```

| `ComponentUpdateItemInput` field | Type         | Description                                                                                  |
| -------------------------------- | ------------ | -------------------------------------------------------------------------------------------- |
| `id`                             | `ID!`        | The component to update. It must already be an item on the change order.                     |
| `statusId`                       | `ID`         | The status the component moves to.                                                           |
| `revisionValue`                  | `String`     | An explicit revision, such as `2.A`.                                                         |
| `changeType`                     | `ChangeType` | Let Duro bump the revision by kind of change: `MAJOR`, `MINOR`, `PATCH`, or `DOCUMENTATION`. |
| `noBump`                         | `Boolean`    | `true` keeps a released component's revision unchanged on approval.                          |

### Removing Items

Items can be removed only while the change order is in `DRAFT`. `itemIds` are component IDs; an ID that is not on the change order fails with `ITEMS_NOT_FOUND`.

```graphql
mutation RemoveItems {
  changeOrders {
    removeItems(
      changeOrderId: "123e4567-e89b-12d3-a456-426614174000"
      input: {
        itemIds: ["component-uuid-2"]
      }
    ) {
      id
      itemId
    }
  }
}
```

### Auto-Added Documents

When you add a component to a change order, the documents linked to that component are added to the change order as items too. These items are flagged with `isAutoAdded: true`, so you can tell them apart from the items you added yourself — select `isAutoAdded` wherever you read `ChangeOrderItem`.

While the change order is in `DRAFT`, its auto-added items track the component's document links live:

* **Linking** a document to a component that is already an item on a draft change order adds that document to the change order.
* **Unlinking** a document removes it again — unless another component still on the change order links the same document, in which case it stays.
* **Removing a component** item takes its auto-added documents with it, subject to the same rule: a document linked by another remaining item is kept.

{% hint style="info" %}
**Explicitly added documents are never swept.** A document you added yourself with `addItems` has `isAutoAdded: false` and stays on the change order even when nothing links it anymore. Only items with `isAutoAdded: true` are removed automatically. Documents attached as [change order documents](#attaching-documents-to-a-change-order) are a separate mechanism and are unaffected.
{% endhint %}

{% hint style="warning" %}
Live tracking applies only in `DRAFT`. Once the change order is `OPEN` (or `RESOLVED` and approved), it locks its items' document links, and linking or unlinking a document on those components is rejected. [Reset the change order to `DRAFT`](#resetting-to-draft) if you need to change them.
{% endhint %}

Auto-adding and auto-sweeping a document does **not** emit a [`components.updated` webhook](/advanced-topics/webhooks.md#component-events) for that document, because the document itself did not change. Every other path — editing a component's fields, linking a document from the component itself, status changes — still emits as before.

#### Checking Whether Auto-Add Is Enabled

Automatic addition of component-linked documents is enabled per library. Before staging components, check the library named in your `x-library` header with `documentAutoAddEnabled`:

```graphql
query DocumentAutoAddEnabled {
  changeOrders {
    documentAutoAddEnabled
  }
}
```

```json
{
  "data": {
    "changeOrders": {
      "documentAutoAddEnabled": true
    }
  }
}
```

`documentAutoAddEnabled` is a non-null `Boolean` on `ChangeOrdersQueryOperations` and takes no arguments. It reports the setting of the library in the request headers, so query it once per library rather than once per change order.

| Value   | What `addItems` does with a component's linked documents                                                                           |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `true`  | Adds them as items with `isAutoAdded: true`, and tracks link changes live while the change order is in `DRAFT` as described above. |
| `false` | Adds only the components you passed. No document items are created, and `isAutoAdded` is `false` on every item.                    |

{% hint style="info" %}
**Use it to preview before you add.** The server expands a component's documents only when auto-add is enabled, so a client that wants to show which documents a staged component will bring should read `documentAutoAddEnabled` first. When it is `false`, add the documents you want explicitly with `addItems` — they are then never swept.
{% endhint %}

### Reading a Component's Documents on a Change Order

A change order's item summaries report how many documents each component carries, without returning the documents themselves. Read them with `getChangeOrderItemSummaries`; `ChangeOrderItemSummary.documentCount` is a non-null `Int` — `0` when the component has no associated documents:

```graphql
query GetItemSummaries {
  component {
    getChangeOrderItemSummaries(
      componentIds: ["component-uuid-1", "component-uuid-2"]
      changeOrderId: "123e4567-e89b-12d3-a456-426614174000"
    ) {
      componentId
      snapshotCount
      documentCount
    }
  }
}
```

Fetch the documents behind that count one component at a time with `getChangeOrderItemDocuments`:

```graphql
query GetItemDocuments {
  component {
    getChangeOrderItemDocuments(
      changeOrderId: "123e4567-e89b-12d3-a456-426614174000"
      componentId: "component-uuid-1"
    ) {
      componentId
      component {
        id
        name
        eid
      }
      documentCount
    }
  }
}
```

Documents are items, so the query returns `ChangeOrderItemSummary` — the same type as the change order's other items. It returns an empty array, not an error, when the component has no documents on that change order.

Both the field and the query require `components.read`. Writing items remains guarded by `change_orders.update`.

{% hint style="info" %}
**Read the count first, the documents on demand.** `documentCount` is designed so a client can render a change order's full item list in one call and request documents only for the component it is displaying. Fetching every component's documents up front makes the payload grow with the size of the change order rather than with what is on screen.
{% endhint %}

{% hint style="warning" %}
The returned summaries are flat. Document summaries carry no `ancestors`, because document links are not assembly links — do not expect them to nest under the component you queried.
{% endhint %}

***

## Attaching Documents to a Change Order

Attach supporting documents to a change order in `DRAFT` status. Unlike items, which have dedicated mutations, documents are managed through `submitDraft`. Each document must be a component in a `DOCUMENT`-type category in the same library as the change order; otherwise the call fails with `DOCUMENTS_NOT_DOCUMENT_CATEGORY` or `DOCUMENTS_WRONG_LIBRARY`, and listing the same document twice fails with `DUPLICATE_DOCUMENTS`:

### Adding Documents

```graphql
mutation AttachDocuments {
  changeOrders {
    submitDraft(
      id: "123e4567-e89b-12d3-a456-426614174000"
      input: {
        documents: {
          add: [
            { componentId: "document-uuid-1", componentVersion: 1 }
            { componentId: "document-uuid-2", componentVersion: 3 }
          ]
        }
      }
    ) {
      id
      documents {
        id
        componentId
        componentVersion
      }
    }
  }
}
```

### Removing Documents

```graphql
mutation RemoveDocuments {
  changeOrders {
    submitDraft(
      id: "123e4567-e89b-12d3-a456-426614174000"
      input: {
        documents: {
          remove: ["document-uuid-1"]
        }
      }
    ) {
      id
      documents {
        id
        componentId
      }
    }
  }
}
```

{% hint style="info" %}
The `remove` field takes the **document's component ID** — the `componentId` of each `ChangeOrderDocumentLink` — not the link's own `id`. Passing link IDs fails with `INVALID_DOCUMENT_IDS`. Query the change order's `documents { componentId }` to get these IDs.
{% endhint %}

***

## Switching Type and Template in Draft

While a change order is in `DRAFT`, `submitDraft` can also change its [type](#change-order-types) and its template:

| `SubmitDraftInput` field | Type     | Description                                                                                                                                                                                                  |
| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `coType`                 | `CoType` | New change order type. Must be allowed by the change order's template.                                                                                                                                       |
| `configId`               | `ID`     | ID of the template (config) to switch to — the `id` from [`getTemplates`](#getting-available-templates). The template must be [enabled](#disabling-a-template); a disabled or archived template is rejected. |
| `documents`              |          | Document links to add or remove (see [above](#attaching-documents-to-a-change-order)).                                                                                                                       |

Both fields are optional, and both are only accepted while the status is `DRAFT`. After the change order has been submitted for review, its type and template are fixed — reset it back to `DRAFT` first if you need to change either.

### Changing the type

```graphql
mutation SwitchCoType {
  changeOrders {
    submitDraft(
      id: "123e4567-e89b-12d3-a456-426614174000"
      input: { coType: DCO }
    ) {
      id
      status
      coType
    }
  }
}
```

The new type must be a member of the template's `coTypes`, or the mutation fails with [`CO_TYPE_NOT_ALLOWED`](/advanced-topics/error-handling.md#co_type_not_allowed).

{% hint style="warning" %}
**Switching to or from `DCO` resets proposed item values.** A `DCO` is revision-frozen, so it cannot carry a proposed `status` or `revision` for its items. Switching **into** `DCO` reverts every item to its last-released status and revision, discarding the proposals you set with [`submitDraft` `componentUpdates`](#setting-proposed-changes). Switching **out of** `DCO` re-proposes a revision bump automatically for each item that has a change type (`userPickedRevisionChangeType`); any other proposal must be set again. The items themselves stay on the change order either way. Re-check the proposed values before submitting for review.

A type or template switch also clears the change order's validation result (`isValid` becomes `false`), so [run validations](#run-validations) again before submitting.

Components that a `DCO` cannot legally change are reported by the change order's validations rather than by the switch itself, so check `canSubmit` / `cannotSubmitReasons` after switching. See [`DCO_STATUS_CHANGE_NOT_ALLOWED` / `DCO_REVISION_CHANGE_NOT_ALLOWED`](/advanced-topics/error-handling.md#dco_status_change_not_allowed-dco_revision_change_not_allowed).
{% endhint %}

### Changing the template

```graphql
mutation SwitchTemplate {
  changeOrders {
    submitDraft(
      id: "123e4567-e89b-12d3-a456-426614174000"
      input: { configId: "template-uuid-2" }
    ) {
      id
      status
      coType
      stages {
        id
        name
        order
        decisionMethod
        reviewers { id }
      }
      contents {
        id
        name
        label
        isRequired
      }
    }
  }
}
```

A template owns the whole workflow shape, so switching it rebuilds the change order from the new template's configuration:

* **Reset** — stages, reviewers and notify lists, decision methods, resolution options, and custom content fields and their values are replaced by the new template's defaults (including any pre-assigned [participants](/library-configuration/change-order-workflows.md#participant-population-behavior)).
* **Kept** — the change order's items and its own metadata, such as `name` and `description`.
* **Re-resolved** — the `coType`. If you do not pass `coType` in the same call, the type falls back to the new template's `defaultCoType`. If you pass both `configId` and `coType`, the type you pass must be allowed by the **new** template.

Because stage and content IDs change, re-read `stages` and `contents` from the response (as above) before calling `addReviewersToStages` or `setContentValue` again — IDs captured before the switch are stale.

{% hint style="info" %}
Query [`getTemplates`](#getting-available-templates) to discover the `configId` values you can switch to, along with each template's `coTypes` and `defaultCoType`. Its default result set is exactly the set of templates you can switch to — [disabled templates](#disabling-a-template) are excluded and cannot be selected.
{% endhint %}

***

## Setting Custom Field Values

If your template defines custom fields (like "Impact Assessment" or "Change Category"), set their values:

```graphql
mutation SetContentValue {
  changeOrders {
    setContentValue(
      contentId: "content-uuid-1"
      input: {
        values: ["design"]
      }
    ) {
      id
      name
      label
      values
    }
  }
}
```

{% hint style="info" %}
The `contentId` comes from the `contents` array in the change order response. Each content field has a unique ID.
{% endhint %}

***

## Adding Reviewers to Stages

Assign users to review each approval stage:

```graphql
mutation AddReviewers {
  changeOrders {
    addReviewersToStages(
      changeOrderId: "123e4567-e89b-12d3-a456-426614174000"
      input: {
        stages: [
          {
            stageId: "stage-uuid-1"
            userIds: ["user-uuid-1", "user-uuid-2"]
          }
          {
            stageId: "stage-uuid-2"
            userIds: ["user-uuid-3"]
          }
        ]
      }
    ) {
      id
      stages {
        id
        name
        reviewers {
          id
          user {
            id
            name
            primaryEmail
          }
          isRequiredToApprove
          decisionState
        }
      }
    }
  }
}
```

### Removing Reviewers

Each stage has a reviewer floor (see [Minimum reviewers](#minimum-reviewers) below). How removal interacts with it depends on the change order status:

* **`DRAFT`**: removal may leave a stage below its floor. The change order simply cannot be submitted until enough reviewers are added back. Other guards still apply, for example [required approvers](/advanced-topics/error-handling.md#cannot_remove_required_reviewer) cannot be removed.
* **`OPEN`, future stage**: removal cannot reduce a stage below its floor. The request fails with `REMOVAL_VIOLATES_DECISION_METHOD` and no reviewers are removed.
* **`OPEN`, current or past stage**: existing restrictions are unchanged.

```graphql
mutation RemoveReviewers {
  changeOrders {
    removeReviewersFromStages(
      changeOrderId: "123e4567-e89b-12d3-a456-426614174000"
      input: {
        stages: [
          {
            stageId: "stage-uuid-1"
            userIds: ["user-uuid-2"]
          }
        ]
      }
    ) {
      id
      stages {
        id
        reviewers {
          id
        }
      }
    }
  }
}
```

***

## Configuring Stage Decision Methods

Each stage can use different approval logic. Decision methods can be changed only while the change order is in `DRAFT`, and only to a method listed in the stage's `allowableDecisionMethods` (set by the template's stage `types`; `null` allows every method):

| Method      | Description                                            |
| ----------- | ------------------------------------------------------ |
| `UNANIMOUS` | All reviewers must approve                             |
| `MAJORITY`  | More than 50% must approve                             |
| `MINIMUM`   | At least `decisionMinimumCount` reviewers must approve |

### Minimum reviewers

Every stage also carries `minReviewers: Int!`, a **read-only** floor on the number of named reviewers the stage must have before the change order can be submitted. It is copied from the template's `minReviewers` when the stage is created and defaults to `1`. It cannot be changed on the change order.

`minReviewers` and `decisionMinimumCount` are different things:

| Field                  | Meaning                                                            | Set by                           |
| ---------------------- | ------------------------------------------------------------------ | -------------------------------- |
| `minReviewers`         | How many reviewers must be assigned to the stage before submission | Template, at stage creation      |
| `decisionMinimumCount` | How many approvals pass the stage when the method is `MINIMUM`     | `updateDecisionMethodsForStages` |

The reviewer floor a stage must meet before submission is:

| Decision method | Reviewers required before submission      |
| --------------- | ----------------------------------------- |
| `UNANIMOUS`     | `minReviewers`                            |
| `MAJORITY`      | `minReviewers`                            |
| `MINIMUM`       | `max(minReviewers, decisionMinimumCount)` |

For `MAJORITY` and `UNANIMOUS`, the number of approvals needed to pass the stage is still derived from the actual reviewer count. `minReviewers` only guarantees how many reviewers there are to begin with.

{% hint style="info" %}
For `MINIMUM`, the server clamps `decisionMinimumCount` up to `minReviewers` when the supplied value is lower, `null`, or omitted. The mutation succeeds and the stored count is the clamped value, so read `decisionMinimumCount` back from the response rather than assuming it matches your input.
{% endhint %}

```graphql
mutation UpdateDecisionMethods {
  changeOrders {
    updateDecisionMethodsForStages(
      changeOrderId: "123e4567-e89b-12d3-a456-426614174000"
      input: {
        stages: [
          {
            stageId: "stage-uuid-1"
            decisionMethod: MAJORITY
          }
          {
            stageId: "stage-uuid-2"
            decisionMethod: MINIMUM
            decisionMinimumCount: 2
          }
        ]
      }
    ) {
      id
      stages {
        id
        name
        decisionMethod
        decisionMinimumCount
        minReviewers
      }
    }
  }
}
```

***

## Submitting for Review

Submitting moves a change order from `DRAFT` to `OPEN`. It is a two-step operation: run the change order's validations, then submit.

### Run Validations

Submission requires the change order's validations to have run and passed (`isValid: true`). Call `validate` once the draft is complete:

```graphql
mutation ValidateChangeOrder {
  changeOrders {
    validate(id: "123e4567-e89b-12d3-a456-426614174000") {
      id
      isValid
      validationRun {
        id
        state
      }
    }
  }
}
```

`validationRun.state` is `PASS`, `FAIL`, or `IN_PROGRESS`. In some libraries validation runs asynchronously: the mutation returns straight away with `state: IN_PROGRESS` and `isValid: false`, and the verdict lands on the change order when the run finishes. In that case poll the change order's `isValid` (or listen for the [`change_orders.validation_completed` webhook](/advanced-topics/webhooks.md#change-order-events)) until it settles. When the run fails, read `validationRun.results` to see which checks failed, fix them, and validate again. See [Change Order Validations](/library-configuration/change-order-validations.md) for the checks that run.

Switching the type or template, or resetting the change order, clears `isValid`, so validate again after either.

### Submit

```graphql
mutation SubmitForReview {
  changeOrders {
    submitForReview(id: "123e4567-e89b-12d3-a456-426614174000") {
      id
      status
      resolution
      currentStage {
        name
        decisionState
      }
    }
  }
}
```

{% hint style="warning" %}
**Before Submitting**: `submitForReview` succeeds only when all of these hold:

* the change order is in `DRAFT`;
* it has at least one item;
* its validations have run and passed (`isValid: true`) — see [Run Validations](#run-validations);
* it has at least one stage, and every stage has at least as many reviewers as its [reviewer floor](#minimum-reviewers).

Use the `canSubmit` and `cannotSubmitReasons` fields to check readiness; each unmet condition appears in `cannotSubmitReasons` as a readable message.
{% endhint %}

### Check Submission Readiness

Query `minReviewers` and `decisionMinimumCount` alongside each stage's reviewers so you can tell how many reviewers are still missing:

```graphql
query CheckReadiness {
  changeOrders {
    get(filter: { ids: ["123e4567-e89b-12d3-a456-426614174000"] }) {
      connection {
        edges {
          node {
            id
            isValid
            canSubmit
            cannotSubmitReasons
            stages {
              id
              name
              decisionMethod
              decisionMinimumCount
              minReviewers
              reviewers {
                id
              }
            }
          }
        }
      }
    }
  }
}
```

Examples of how the floor applies:

* A stage with `minReviewers: 2` and one reviewer blocks submission, whichever decision method is selected.
* A `MINIMUM` stage with `minReviewers: 1` and `decisionMinimumCount: 3` needs three named reviewers before submission, because the floor is `max(minReviewers, decisionMinimumCount)`.
* A `MAJORITY` stage with `minReviewers: 2` and three reviewers can be submitted; two of the three must then approve.

***

## Making Review Decisions

Reviewers approve or reject their assigned stages while the change order is `OPEN` (otherwise the call fails with `CANNOT_REVIEW_INVALID_STATUS`). `updateReviewDecision` records the decision of the **calling user**, so the API key's user must be a reviewer on that stage. A stage the decision method would approve stays pending until every required approver (`isRequiredToApprove: true`) has approved, and a rejection from a required approver rejects the stage at once:

### Approving

```graphql
mutation ApproveStage {
  changeOrders {
    updateReviewDecision(
      stageId: "stage-uuid-1"
      input: {
        decisionState: APPROVED
        note: "Reviewed and approved. All specifications meet requirements."
      }
    ) {
      id
      decisionState
      note
      decidedAt
      user {
        name
      }
    }
  }
}
```

### Rejecting

```graphql
mutation RejectStage {
  changeOrders {
    updateReviewDecision(
      stageId: "stage-uuid-1"
      input: {
        decisionState: REJECTED
        note: "Missing thermal analysis documentation. Please add before resubmission."
      }
    ) {
      id
      decisionState
      note
      decidedAt
    }
  }
}
```

{% hint style="info" %}
When a stage meets its decision criteria (based on the decision method), the change order automatically progresses to the next stage. After the last stage approves, or any stage rejects, the change order follows its [router option](#change-order-lifecycle): `AUTO_CLOSE` moves it straight to `CLOSED`, `MANUAL_CLOSE` leaves it `RESOLVED` until someone calls `close`.
{% endhint %}

***

## Completing Change Orders

### Closing a Resolved Change Order

A change order resolved under `MANUAL_CLOSE` — approved, rejected, or withdrawn — waits in `RESOLVED` until you close it. `close` requires `RESOLVED` status (otherwise `CANNOT_CLOSE_INVALID_STATUS`), and the caller must be the change order's author, an organization admin, or one of its reviewers (otherwise `UNAUTHORIZED_CLOSE_ACCESS`). Closing an **approved** change order releases its changes to the components; closing a rejected or withdrawn one releases nothing. Under `AUTO_CLOSE` this happens on resolution and there is nothing to close.

```graphql
mutation CloseChangeOrder {
  changeOrders {
    close(id: "123e4567-e89b-12d3-a456-426614174000") {
      id
      status
      resolution
    }
  }
}
```

### Withdrawing a Change Order

Any user with the `change_orders.withdraw` permission can withdraw an `OPEN` change order; withdrawing one in any other status fails with `CANNOT_WITHDRAW_INVALID_STATUS`. The resolution becomes `WITHDRAWN`, and the status becomes `CLOSED` or `RESOLVED` depending on `withdrawalRouterOption`:

```graphql
mutation WithdrawChangeOrder {
  changeOrders {
    withdraw(id: "123e4567-e89b-12d3-a456-426614174000") {
      id
      status
      resolution
    }
  }
}
```

### Resetting to Draft

Reset a change order back to `DRAFT` to make modifications and resubmit. Only an `OPEN` or `RESOLVED` change order can be reset (otherwise `CANNOT_RESET_INVALID_STATUS`), and only its author or an organization admin can reset it:

```graphql
mutation ResetChangeOrder {
  changeOrders {
    reset(id: "123e4567-e89b-12d3-a456-426614174000") {
      id
      status
      resolution
    }
  }
}
```

{% hint style="info" %}
**When to Reset**: Use reset to pull an `OPEN` change order back for changes, or to revive one that was rejected or withdrawn. A rejected or withdrawn change order can be revived only if its `rejectionRouterOption` / `withdrawalRouterOption` is `MANUAL_CLOSE`, which leaves it `RESOLVED`; under `AUTO_CLOSE` it is already `CLOSED` and cannot be reset. Resetting clears `isValid`, so validate again before resubmitting.
{% endhint %}

***

## Complete Workflow Example

Here's a complete example of the typical happy path: pick a template, build the draft, validate, submit, approve, and close if the template asks for a manual close.

```javascript
const { GraphQLClient } = require('graphql-request');

const endpoint = 'https://api.durohub.com/graphql';
const headers = {
  'x-organization': '@acme-corp',
  'x-library': '@acme-corp/main-library',
};

// The author's key builds and submits the change order.
const client = new GraphQLClient(endpoint, {
  headers: { ...headers, 'x-api-key': process.env.DURO_API_KEY },
});
// A review decision is recorded for the key's user, so approvals use the reviewer's key.
const reviewerClient = new GraphQLClient(endpoint, {
  headers: { ...headers, 'x-api-key': process.env.DURO_REVIEWER_API_KEY },
});

const REVIEWER_USER_ID = 'reviewer-uuid'; // the user behind DURO_REVIEWER_API_KEY

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function createAndSubmitChangeOrder() {
  // Step 1: Pick a template
  const templatesResult = await client.request(`
    query Templates {
      changeOrders {
        getTemplates { id name defaultCoType }
      }
    }
  `);
  const template = templatesResult.changeOrders.getTemplates[0];
  console.log(`Using template: ${template.name}`);

  // Step 2: Create the change order from the template
  const createResult = await client.request(`
    mutation CreateCO($input: CreateChangeOrderInput!) {
      changeOrders {
        create(input: $input) {
          id
          stages { id name order minReviewers }
          contents { id name isRequired }
        }
      }
    }
  `, {
    input: {
      name: "ECO-2024-001: Motor Specification Update",
      description: "Update torque specs for Model X motor",
      configId: template.id
    }
  });

  const co = createResult.changeOrders.create;
  const coId = co.id;
  const stages = [...co.stages].sort((a, b) => a.order - b.order);
  console.log(`Created CO: ${coId}`);

  // Step 3: Add items (components)
  await client.request(`
    mutation AddItems($coId: ID!, $input: AddItemsInput!) {
      changeOrders {
        addItems(changeOrderId: $coId, input: $input) {
          id
        }
      }
    }
  `, {
    coId,
    input: {
      items: [{ id: "component-uuid", version: 1 }]
    }
  });
  console.log("Items added");

  // Step 4: Attach reference documents (components in a DOCUMENT-type category)
  await client.request(`
    mutation AttachDocuments($id: ID!, $input: SubmitDraftInput!) {
      changeOrders {
        submitDraft(id: $id, input: $input) {
          id
          documents { id componentId componentVersion }
        }
      }
    }
  `, {
    id: coId,
    input: {
      documents: {
        add: [
          { componentId: "test-report-uuid", componentVersion: 1 },
          { componentId: "spec-sheet-uuid", componentVersion: 2 }
        ]
      }
    }
  });
  console.log("Reference documents attached");

  // Step 5: Add a reviewer to every stage
  // (a stage whose minReviewers is above 1 needs that many reviewers)
  await client.request(`
    mutation AddReviewers($coId: ID!, $input: AddReviewersToStagesInput!) {
      changeOrders {
        addReviewersToStages(changeOrderId: $coId, input: $input) {
          id
        }
      }
    }
  `, {
    coId,
    input: {
      stages: stages.map((stage) => ({ stageId: stage.id, userIds: [REVIEWER_USER_ID] }))
    }
  });
  console.log("Reviewers added");

  // Step 6: Run validations, waiting for the verdict if they run asynchronously
  const validateResult = await client.request(`
    mutation Validate($id: ID!) {
      changeOrders {
        validate(id: $id) {
          isValid
          validationRun { id state }
        }
      }
    }
  `, { id: coId });

  let { isValid, validationRun } = validateResult.changeOrders.validate;
  while (validationRun.state === 'IN_PROGRESS') {
    await sleep(2000);
    const poll = await client.request(`
      query ValidationState($id: ID!) {
        changeOrders {
          get(filter: { ids: [$id] }) {
            connection {
              edges { node { isValid latestValidationRun { id state } } }
            }
          }
        }
      }
    `, { id: coId });
    const node = poll.changeOrders.get.connection.edges[0].node;
    isValid = node.isValid;
    validationRun = node.latestValidationRun;
  }
  if (!isValid) {
    throw new Error(`Validation failed (run ${validationRun.id}); fix the reported issues and validate again`);
  }
  console.log("Validations passed");

  // Step 7: Submit for review
  const submitResult = await client.request(`
    mutation Submit($id: ID!) {
      changeOrders {
        submitForReview(id: $id) {
          status
          resolution
        }
      }
    }
  `, { id: coId });
  console.log(`Status: ${submitResult.changeOrders.submitForReview.status}`);

  // Step 8: The reviewer approves each stage in order
  for (const stage of stages) {
    await reviewerClient.request(`
      mutation Approve($stageId: ID!, $input: UpdateReviewDecisionInput!) {
        changeOrders {
          updateReviewDecision(stageId: $stageId, input: $input) {
            decisionState
          }
        }
      }
    `, {
      stageId: stage.id,
      input: {
        decisionState: "APPROVED",
        note: "Approved - specifications verified"
      }
    });
    console.log(`Approved stage: ${stage.name}`);
  }

  // Step 9: Close it if approval left it RESOLVED (MANUAL_CLOSE); it may
  // already be CLOSED (AUTO_CLOSE), or still OPEN if a stage needs more votes
  const statusResult = await client.request(`
    query Status($id: ID!) {
      changeOrders {
        get(filter: { ids: [$id] }) {
          connection {
            edges { node { status resolution approvalRouterOption } }
          }
        }
      }
    }
  `, { id: coId });
  const current = statusResult.changeOrders.get.connection.edges[0].node;

  if (current.status === 'RESOLVED') {
    // MANUAL_CLOSE: closing an approved change order releases its changes
    const closeResult = await client.request(`
      mutation Close($id: ID!) {
        changeOrders {
          close(id: $id) {
            status
            resolution
          }
        }
      }
    `, { id: coId });
    console.log(`Final status: ${closeResult.changeOrders.close.status}`);
  } else if (current.status === 'CLOSED') {
    // AUTO_CLOSE: approval already closed the change order and released its changes
    console.log(`Final status: CLOSED (${current.resolution})`);
  } else {
    // Still OPEN: a stage is waiting on another approver (a required approver,
    // or more votes under MAJORITY/UNANIMOUS). Nothing has been released yet.
    console.log(
      `Still ${current.status}: waiting on approvals ` +
        `(on approval this template will ${current.approvalRouterOption})`
    );
  }
}

createAndSubmitChangeOrder().catch(console.error);
```

***

## Handling Rejection

What a rejection leaves you with depends on the change order's `rejectionRouterOption`:

* **`MANUAL_CLOSE`** — the change order stays `RESOLVED` with resolution `REJECTED`, and you can revive it.
* **`AUTO_CLOSE`** — the change order is `CLOSED` with resolution `REJECTED`. It cannot be reset; create a new change order instead.

To revive a `RESOLVED` rejection:

1. **Review the rejection notes** to understand what needs to change
2. **Reset to DRAFT** using the `reset` mutation (the author or an organization admin only)
3. **Make necessary modifications** (update items, content, etc.)
4. **Run validations** again with `validate` — resetting clears `isValid`
5. **Resubmit** using `submitForReview`

```graphql
# After rejection, reset and fix
mutation ResetAndFix {
  changeOrders {
    reset(id: "co-uuid") {
      status  # Returns to DRAFT
    }
  }
}

# Make your changes and validate them
mutation Revalidate {
  changeOrders {
    validate(id: "co-uuid") {
      isValid
      validationRun {
        state
      }
    }
  }
}

# Then resubmit
mutation Resubmit {
  changeOrders {
    submitForReview(id: "co-uuid") {
      status
      resolution
    }
  }
}
```

***

## Best Practices

### Planning

* **Use descriptive names**: Include an identifier (e.g., "ECO-2024-001") and brief description
* **Choose the right template**: Select a template that matches your change type
* **Complete all required fields**: Fill out custom content fields before submission

### Items and Scope

* **Include all affected components**: Add every component that will be modified
* **Specify correct versions**: Reference the specific version being changed
* **Keep scope focused**: One change order should address one logical change

### Review Process

* **Add appropriate reviewers**: Ensure all required stakeholders are included
* **Provide context in notes**: When approving or rejecting, include detailed notes
* **Validate, then check `canSubmit` before submitting**: Avoid submission errors

### Workflow Management

* **Don't skip states**: Follow the proper lifecycle transitions
* **Close resolved COs promptly**: Closing an approved change order is what releases its changes to the components. Under `MANUAL_CLOSE` nothing is released until someone calls `close`; under `AUTO_CLOSE` approval releases them automatically
* **Document rejection reasons**: Provide actionable feedback when rejecting

***

## Next Steps

* [**Documents**](/core-concepts/documents.md): Learn about document management and how documents relate to change orders
* [**Change Order Workflows**](/library-configuration/change-order-workflows.md): Create custom templates with multi-stage approvals
* [**Change Order Workflow Reference**](/library-configuration/change-order-workflow-reference.md): Complete YAML specification
* [**Change Order Validations**](/library-configuration/change-order-validations.md): Implement validation rules
* [**Webhooks**](/advanced-topics/webhooks.md): Get notified when change orders are updated


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

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

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.durohub.com/core-concepts/change-orders.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

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