> 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/documents.md).

# Documents

Learn how to manage technical documentation and files through the Duro API.

### Document Types

Duro supports various document types:

* CAD Files
* Technical Drawings
* Specifications
* Test Reports
* Quality Documentation

### Querying Documents

Here's how to fetch documents:

```graphql
query {
  documents(first: 10) {
    edges {
      node {
        id
        title
        fileType
        version
        createdAt
        createdBy {
          name
        }
      }
    }
  }
}
```

### Document Operations

```graphql
mutation {
  createDocument(input: {
    title: "Assembly Instructions"
    componentId: "comp_123"
    fileUrl: "https://example.com/file.pdf"
  }) {
    document {
      id
      title
    }
  }
}
```

### File Management

* Direct upload URLs
* Version control
* Access permissions
* File metadata

***

## Organization Context for Uploads

Every file Duro stores records the organization that owns it. Duro derives that organization **server-side** from the request context — it is no longer taken from the uploader's list of organization memberships.

This matters when a user belongs to more than one organization. Previously the file was tagged with the first membership returned for the uploader, and that list has no defined order, so a file uploaded into one organization's library could be recorded against an unrelated organization the uploader also belonged to. Change order export packaging reads the organization on the file record, so a mis-tagged file surfaces there as an export that fails to build.

### Selecting the organization

Send the organization you are uploading into with the `x-organization` header, the same way you do for every other API request. Uploads accept either form of the value:

* Organization UUID: `550e8400-e29b-41d4-a716-446655440000`
* Organization slug: `@acme-corp`

```bash
curl -X POST https://api.durohub.com/graphql \
  -H 'Content-Type: application/json' \
  -H 'x-api-key: YOUR_API_TOKEN' \
  -H 'x-organization: @acme-corp' \
  -H 'x-library: @acme-corp/hardware-library' \
  -d '{"query": "..."}'
```

The header is validated against the caller's memberships — you cannot tag a file with an organization you are not a member of. See [Selecting an Organization](/getting-started/authentication.md#selecting-an-organization) for how organization selection is resolved and validated.

### `UploadRequest` fields

| Field                | Status         | Notes                                                                                                                                                                                              |
| -------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `orgId`              | **Deprecated** | Retained for backwards compatibility. Stop sending it and use the `x-organization` header instead; the organization on the resulting file record is resolved server-side.                          |
| `omitOrgId: Boolean` | New            | Set to `true` for an upload that should not be associated with any organization. Leave it unset (or `false`) for normal, organization-scoped uploads such as component and change order documents. |

{% hint style="warning" %}
Only set `omitOrgId: true` when you genuinely want an unscoped file. An organization-scoped feature that later reads the file — change order export packaging, for example — has no organization to work with and will not be able to include it.
{% endhint %}

{% hint style="info" %}
Files uploaded before this change may still carry an organization that does not match the library they live in. The document itself is in the right library; only the stored organization is wrong. If a change order export package fails to generate for a document that otherwise looks correct, contact Duro support so the file record can be corrected.
{% endhint %}

***

## What a Document Depicts

A document can record the component it is *of* — a drawing of a part, rather than a file merely attached to it. That relationship lives on the document component itself, in `depictsComponentId`:

```graphql
type Component {
  "The component this document depicts, if any."
  depictsComponentId: ID
}

input CreateComponentInput {
  depictsComponentId: ID
}

input UpdateComponentInput {
  depictsComponentId: ID
}
```

It is nullable and optional on both inputs. A document with `depictsComponentId: null` behaves exactly as documents always have — attached to whatever links to it, depicting nothing. Nothing changes for integrations that never set it.

A document depicts at most one component, while any number of components can attach it. Set it when you create the document, or later with the `component.update` mutation:

```graphql
mutation SetDepicts {
  component {
    update(inputs: [{
      id: "123e4567-e89b-12d3-a456-426614174000"
      depictsComponentId: "987fcdeb-51a2-43d1-9f12-426614174999"
      expectedVersion: 1
    }]) {
      id
      depictsComponentId
    }
  }
}
```

{% hint style="info" %}
`depictsComponentId` answers "which component is this document about, *now*". It is a live pointer, so it follows the component through a renumber. If you need the CPN and revision that were stamped into the artifact when it was generated, read those from the document's own recorded values — after a renumber the two legitimately differ, and the stamped value is what is true of the paper.
{% endhint %}

## Per-Link Staleness

`DocumentLink` reports whether the document is out of date on that particular link:

```graphql
type DocumentLink {
  "The link pins a document version older than the document's current version."
  pinnedDocumentOutdated: Boolean

  "The depicted component has moved on since the document was generated."
  generatedFromOlderRevision: Boolean
}
```

| Field                        | Meaning                                                                                                                                    | `null` when                                                         |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
| `pinnedDocumentOutdated`     | The link pins a specific document version and the document has since advanced past it — the link points at v1 while the document is at v3. | The link does not pin a version, so there is nothing to compare.    |
| `generatedFromOlderRevision` | The document depicts a component whose revision or status has moved on since the document was generated from it.                           | The document depicts nothing, or the link is not the depicting one. |

Both values are **computed on read, never stored**. There is no flag to set and none to clear: re-uploading a file or advancing a revision changes the answer on the next query, because the answer is always a comparison against current state.

Staleness is **per link**. The same document can be current on one parent and out of date on another, and only the depicting link reports `generatedFromOlderRevision` — attachments of the same file elsewhere return `null` for it. Read both fields on each link you render rather than deriving a single verdict for the document.

{% hint style="warning" %}
Treat `null` as "not applicable", not as "up to date". A `null` means the comparison does not apply to that link — a link with no pinned version and no depicts relationship returns `null` for both fields.
{% endhint %}

## Synced and Pinned Links

A document link is either **synced** — it follows the document's latest version — or **pinned** to one specific version of that document. New links start synced; a link only becomes pinned when something explicitly pins it.

Change order snapshots report which of the two a link was in when the snapshot was taken:

```graphql
type DocumentLinkSnapshotItem {
  "The link follows the document's latest version rather than pinning one."
  synced: Boolean
}
```

`synced: true` and a pinned version are mutually exclusive, so a synced link never reports `pinnedDocumentOutdated` — see [Per-Link Staleness](#per-link-staleness).

### Pinning and unpinning

`documentHistoryId` on `UpdateDocumentLinkInput` carries the pin. It is nullable and optional, and the three cases are distinct:

```graphql
input UpdateDocumentLinkInput {
  "UUID pins that document version, null unpins, omitted leaves the pin as it is."
  documentHistoryId: ID
}
```

| You send                | Result                                                                         |
| ----------------------- | ------------------------------------------------------------------------------ |
| A document history UUID | The link is pinned to that version of the document.                            |
| `null`                  | The link is unpinned and goes back to following the document's latest version. |
| Nothing (field omitted) | The current pin is preserved, whether the link is pinned or synced.            |

This is the same contract as assembly links: omitting a nullable field is not the same as sending `null` for it. Build the input so an untouched pin is left out rather than serialized as `null`, or an unrelated update will silently unpin the link.

```graphql
mutation UnpinDocumentLink {
  documentLink {
    update(inputs: [{
      id: "123e4567-e89b-12d3-a456-426614174000"
      documentHistoryId: null
    }]) {
      id
    }
  }
}
```

{% hint style="info" %}
Links created through the web app before this change were pinned to the document's latest version at creation time, and those pins were not backfilled away. If a link you expected to be synced reports a pin, unpin it by sending `documentHistoryId: null`.
{% endhint %}

***

## Creating and Linking Documents with Item Mutations

The `item.create` and `item.update` mutations can create documents and link them to a component in the same transaction that writes the component itself — see [Item Mutations](/core-concepts/components.md#item-mutations-bom-and-documents-in-one-call). Three inputs cover it:

| Field                   | Type                        | On             | Description                                                                                 |
| ----------------------- | --------------------------- | -------------- | ------------------------------------------------------------------------------------------- |
| `documents`             | `[ComponentDocumentInput!]` | create, update | Creates a DOCUMENT-category component from an uploaded file and links it to this component. |
| `documentComponentIds`  | `[ID!]`                     | create, update | Links document components that already exist.                                               |
| `removeDocumentLinkIds` | `[ID!]`                     | update only    | Removes the named document links.                                                           |

```graphql
input ComponentDocumentInput {
  "The file, already uploaded, that becomes the document."
  fileId: ID!
  name: String!
  categoryId: ID
}
```

Upload the file first through the normal [upload flow](#organization-context-for-uploads) and pass only its `fileId` here.

```graphql
mutation AddDocuments {
  item {
    update(inputs: [{
      id: "123e4567-e89b-12d3-a456-426614174000"
      expectedVersion: 3
      documents: [
        { fileId: "uploaded-file-uuid", name: "Housing Drawing" }
      ]
      documentComponentIds: ["existing-document-component-uuid"]
      removeDocumentLinkIds: ["stale-link-uuid"]
    }]) {
      id
      version
    }
  }
}
```

### Why `documents` takes a file rather than a document id

A document component inherits its CPN from the parent it is created under, so it cannot exist before that parent. `documents` therefore describes a document to *create* — Duro creates the document component and links it inside the same transaction, after the parent component is written. Use `documentComponentIds` for documents that already exist.

### Additive, not desired-state

Unlike [`children`](/core-concepts/components.md#bom-children-semantics), the document fields are **additive**. `documents` and `documentComponentIds` only add links; omitting a document never unlinks it. Removal on update is explicit, through `removeDocumentLinkIds`.

This is deliberate: documents are routinely attached outside the calling tool — in the Duro web app, or by another integration — and a desired-state list would silently discard them.

{% hint style="info" %}
`removeDocumentLinkIds` is tolerant of ids that no longer exist. An already-removed link is skipped rather than failing the call, so a double-click or a concurrent removal does not roll back the rest of your save. It takes **document link** ids, not document component ids.
{% endhint %}

Each document component created this way gets exactly one history row, and the parent component still gets only one — the links do not write separate entries.

***

## Documents on Change Orders

Documents can be attached to change orders as supporting reference material. See [Attaching Documents to a Change Order](/core-concepts/change-orders.md#attaching-documents-to-a-change-order) for usage.

### Querying Documents on a Change Order

```graphql
query GetCODocuments {
  changeOrders {
    get(filter: { ids: ["123e4567-e89b-12d3-a456-426614174000"] }) {
      connection {
        edges {
          node {
            id
            name
            documents {
              id
              componentId
              componentVersion
            }
          }
        }
      }
    }
  }
}
```

### Next Steps

Learn about managing product changes with [Change Orders](/core-concepts/change-orders.md).


---

# 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
