---
title: Parts & revisions
description: The Parts API manages parts and their revisions.
---

# Parts & revisions

The Parts API manages parts and their revisions: create, list, fetch, update, and delete parts, and manage each part's revision history.

> **Auto-generated** from the public OpenAPI spec — this page never
> drifts from the running API. Base URL `https://api.arcnm.io`. Authenticate with
> the `X-API-Key` header (see [Authentication](../authentication.md)).

## List Parts

`GET /api/v1/parts/`

> **Paginated.** Pass `limit` and `offset` to page through results.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `q` | query | string | no | Substring match |
| `classification_id` | query | string | no | Identifier of the classification. |
| `limit` | query | integer | no | Maximum number of results to return. |
| `offset` | query | integer | no | Number of results to skip before the returned page. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/parts/ \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/parts/",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `count` | integer | Total number of parts matching the query. |
| `data` | PartPublic[] | The page of parts matching the query. |

**Example response**

```json
{
  "count": 0,
  "data": [
    {
      "attributes": {},
      "base_uom": "EA",
      "classification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "created_at": "2026-06-01T12:00:00Z",
      "default_currency": "EUR",
      "description": "string",
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "lead_time_days": 0,
      "make_or_buy": "make",
      "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "part_number": "BRACKET-001",
      "procurement_type": "in_house",
      "updated_at": "2026-06-01T12:00:00Z"
    }
  ]
}
```

## Create Part

`POST /api/v1/parts/`

**Request body** (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `attributes` | object | no | Free-form key/value metadata for the part. |
| `base_uom` | string | no | Base unit of measure for the part (ISO unit code, e.g. EA, KG, M). |
| `classification_id` | string | no | ID of the classification category this part belongs to, if any. |
| `default_currency` | string | no | Default currency for the part's pricing (ISO 4217 code, e.g. EUR, USD). |
| `description` | string | no | Human-readable description of the part. |
| `lead_time_days` | integer | no | Expected lead time to obtain the part, in days. |
| `make_or_buy` | string | no | Whether the part is manufactured in-house or purchased. |
| `part_number` | string | yes | Stable identifier (≈ SAP MATNR). Unique per org. |
| `procurement_type` | string | no | How the part is sourced (e.g. in-house production or external supplier). |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/parts/ \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "part_number": "BRACKET-001"
  }'
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/parts/",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "part_number": "BRACKET-001"
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "part_number": "BRACKET-001"
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `201` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `201`

| Field | Type | Description |
| --- | --- | --- |
| `attributes` | object | Free-form key/value metadata for the part. |
| `base_uom` | string | Base unit of measure for the part (ISO unit code, e.g. EA, KG, M). |
| `classification_id` | string | ID of the classification category this part belongs to, if any. |
| `created_at` | string | Timestamp when the part was created (UTC, ISO 8601). |
| `default_currency` | string | Default currency for the part's pricing (ISO 4217 code, e.g. EUR, USD). |
| `description` | string | Human-readable description of the part. |
| `id` | string | Unique identifier of the part. |
| `lead_time_days` | integer | Expected lead time to obtain the part, in days. |
| `make_or_buy` | string | Whether the part is manufactured in-house or purchased. |
| `org_id` | string | ID of the organization that owns the part. |
| `part_number` | string | Stable identifier (≈ SAP MATNR). Unique per org. |
| `procurement_type` | string | How the part is sourced (e.g. in-house production or external supplier). |
| `updated_at` | string | Timestamp when the part was last updated (UTC, ISO 8601). |

**Example response**

```json
{
  "attributes": {},
  "base_uom": "EA",
  "classification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-06-01T12:00:00Z",
  "default_currency": "EUR",
  "description": "string",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "lead_time_days": 0,
  "make_or_buy": "make",
  "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "part_number": "BRACKET-001",
  "procurement_type": "in_house",
  "updated_at": "2026-06-01T12:00:00Z"
}
```

## Delete Part

`DELETE /api/v1/parts/{part_id}`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X DELETE https://api.arcnm.io/api/v1/parts/{part_id} \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.delete(
    "https://api.arcnm.io/api/v1/parts/{part_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}", {
  method: "DELETE",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `message` | string | Human-readable confirmation that the resource was deleted. |

**Example response**

```json
{
  "message": "string"
}
```

## Get Part

`GET /api/v1/parts/{part_id}`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/parts/{part_id} \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/parts/{part_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `attributes` | object | Free-form key/value metadata for the part. |
| `base_uom` | string | Base unit of measure for the part (ISO unit code, e.g. EA, KG, M). |
| `classification_id` | string | ID of the classification category this part belongs to, if any. |
| `created_at` | string | Timestamp when the part was created (UTC, ISO 8601). |
| `default_currency` | string | Default currency for the part's pricing (ISO 4217 code, e.g. EUR, USD). |
| `description` | string | Human-readable description of the part. |
| `id` | string | Unique identifier of the part. |
| `lead_time_days` | integer | Expected lead time to obtain the part, in days. |
| `make_or_buy` | string | Whether the part is manufactured in-house or purchased. |
| `org_id` | string | ID of the organization that owns the part. |
| `part_number` | string | Stable identifier (≈ SAP MATNR). Unique per org. |
| `procurement_type` | string | How the part is sourced (e.g. in-house production or external supplier). |
| `updated_at` | string | Timestamp when the part was last updated (UTC, ISO 8601). |

**Example response**

```json
{
  "attributes": {},
  "base_uom": "EA",
  "classification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-06-01T12:00:00Z",
  "default_currency": "EUR",
  "description": "string",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "lead_time_days": 0,
  "make_or_buy": "make",
  "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "part_number": "BRACKET-001",
  "procurement_type": "in_house",
  "updated_at": "2026-06-01T12:00:00Z"
}
```

## Update Part

`PATCH /api/v1/parts/{part_id}`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |

**Request body** (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `attributes` | object | no | Free-form key/value metadata for the part. |
| `base_uom` | string | no | Base unit of measure for the part (ISO unit code, e.g. EA, KG, M). |
| `classification_id` | string | no | ID of the classification category this part belongs to, if any. |
| `default_currency` | string | no | Default currency for the part's pricing (ISO 4217 code, e.g. EUR, USD). |
| `description` | string | no | Human-readable description of the part. |
| `lead_time_days` | integer | no | Expected lead time to obtain the part, in days. |
| `make_or_buy` | string | no | Whether the part is manufactured in-house or purchased. |
| `procurement_type` | string | no | How the part is sourced (e.g. in-house production or external supplier). |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X PATCH https://api.arcnm.io/api/v1/parts/{part_id} \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "attributes": {},
    "base_uom": "EA",
    "classification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "default_currency": "string",
    "description": "string",
    "lead_time_days": 0,
    "make_or_buy": "string",
    "procurement_type": "string"
  }'
```

```python title="Python"
import requests

resp = requests.patch(
    "https://api.arcnm.io/api/v1/parts/{part_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "attributes": {},
        "base_uom": "EA",
        "classification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "default_currency": "string",
        "description": "string",
        "lead_time_days": 0,
        "make_or_buy": "string",
        "procurement_type": "string"
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}", {
  method: "PATCH",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "attributes": {},
    "base_uom": "EA",
    "classification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "default_currency": "string",
    "description": "string",
    "lead_time_days": 0,
    "make_or_buy": "string",
    "procurement_type": "string"
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `attributes` | object | Free-form key/value metadata for the part. |
| `base_uom` | string | Base unit of measure for the part (ISO unit code, e.g. EA, KG, M). |
| `classification_id` | string | ID of the classification category this part belongs to, if any. |
| `created_at` | string | Timestamp when the part was created (UTC, ISO 8601). |
| `default_currency` | string | Default currency for the part's pricing (ISO 4217 code, e.g. EUR, USD). |
| `description` | string | Human-readable description of the part. |
| `id` | string | Unique identifier of the part. |
| `lead_time_days` | integer | Expected lead time to obtain the part, in days. |
| `make_or_buy` | string | Whether the part is manufactured in-house or purchased. |
| `org_id` | string | ID of the organization that owns the part. |
| `part_number` | string | Stable identifier (≈ SAP MATNR). Unique per org. |
| `procurement_type` | string | How the part is sourced (e.g. in-house production or external supplier). |
| `updated_at` | string | Timestamp when the part was last updated (UTC, ISO 8601). |

**Example response**

```json
{
  "attributes": {},
  "base_uom": "EA",
  "classification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "created_at": "2026-06-01T12:00:00Z",
  "default_currency": "EUR",
  "description": "string",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "lead_time_days": 0,
  "make_or_buy": "make",
  "org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "part_number": "BRACKET-001",
  "procurement_type": "in_house",
  "updated_at": "2026-06-01T12:00:00Z"
}
```

## Part Preview Url

`GET /api/v1/parts/{part_id}/preview-url`

Engine-agnostic GLB preview for a Part.

Resolves the part's most-recent revision's primary CAD dataset and
hands back a presigned URL for its glTF/GLB. The frontend uses this
on the parts overview and part detail pages so the 3D model is
surfaced before any calculation has run.

Returns ``{url, status}`` (status semantics match
``/parts/datasets/{ds_id}/preview-url``). The frontend treats the
same payload identically across all surfaces — that is the
"globally unified" 3D preview contract.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/parts/{part_id}/preview-url \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/parts/{part_id}/preview-url",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/preview-url", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `data_source_id` | string | Identifier of the resolved CAD file; null when no dataset was found. |
| `status` | string | Preview readiness state (e.g. ok, pending, failed, no_dataset). |
| `url` | string | Presigned URL to the GLB preview; null unless status is 'ok'. |

**Example response**

```json
{
  "data_source_id": "string",
  "status": "string",
  "url": "string"
}
```

## List Revisions

`GET /api/v1/parts/{part_id}/revisions`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/parts/{part_id}/revisions \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/parts/{part_id}/revisions",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/revisions", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `count` | integer | Total number of revisions matching the query. |
| `data` | PartRevisionPublic[] | The page of revisions matching the query. |

**Example response**

```json
{
  "count": 0,
  "data": [
    {
      "attributes": {},
      "created_at": "2026-06-01T12:00:00Z",
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "parent_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "part_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "revision_code": "string",
      "updated_at": "2026-06-01T12:00:00Z"
    }
  ]
}
```

## Create Revision

`POST /api/v1/parts/{part_id}/revisions`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |

**Request body** (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `attributes` | object | no | Free-form key/value metadata for the revision. |
| `change_request_id` | string | no | ID of the change request that triggered this revision, if any. |
| `parent_revision_id` | string | no | ID of the revision this one was derived from, recording its lineage. |
| `revision_code` | string | yes | Customer-facing revision label (A, B, 01, 02, …). |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/parts/{part_id}/revisions \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "revision_code": "string"
  }'
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/parts/{part_id}/revisions",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "revision_code": "string"
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/revisions", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "revision_code": "string"
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `201` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `201`

| Field | Type | Description |
| --- | --- | --- |
| `attributes` | object | Free-form key/value metadata for the revision. |
| `created_at` | string | Timestamp when the revision was created (UTC, ISO 8601). |
| `id` | string | Unique identifier of the revision. |
| `parent_revision_id` | string | ID of the revision this one was derived from, recording its lineage. |
| `part_id` | string | ID of the part this revision belongs to. |
| `revision_code` | string | Customer-facing revision label (A, B, 01, 02, …). |
| `updated_at` | string | Timestamp when the revision was last updated (UTC, ISO 8601). |

**Example response**

```json
{
  "attributes": {},
  "created_at": "2026-06-01T12:00:00Z",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "parent_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "part_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "revision_code": "string",
  "updated_at": "2026-06-01T12:00:00Z"
}
```

## Delete Revision

`DELETE /api/v1/parts/{part_id}/revisions/{revision_id}`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |
| `revision_id` | path | string | yes | Identifier of the revision. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X DELETE https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id} \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.delete(
    "https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}", {
  method: "DELETE",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `message` | string | Human-readable confirmation that the resource was deleted. |

**Example response**

```json
{
  "message": "string"
}
```

## Get Revision

`GET /api/v1/parts/{part_id}/revisions/{revision_id}`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |
| `revision_id` | path | string | yes | Identifier of the revision. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id} \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `attributes` | object | Free-form key/value metadata for the revision. |
| `created_at` | string | Timestamp when the revision was created (UTC, ISO 8601). |
| `id` | string | Unique identifier of the revision. |
| `parent_revision_id` | string | ID of the revision this one was derived from, recording its lineage. |
| `part_id` | string | ID of the part this revision belongs to. |
| `revision_code` | string | Customer-facing revision label (A, B, 01, 02, …). |
| `updated_at` | string | Timestamp when the revision was last updated (UTC, ISO 8601). |

**Example response**

```json
{
  "attributes": {},
  "created_at": "2026-06-01T12:00:00Z",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "parent_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "part_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "revision_code": "string",
  "updated_at": "2026-06-01T12:00:00Z"
}
```

## Update Revision

`PATCH /api/v1/parts/{part_id}/revisions/{revision_id}`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |
| `revision_id` | path | string | yes | Identifier of the revision. |

**Request body** (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `attributes` | object | no | Free-form metadata to merge onto the revision, as a JSON object. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X PATCH https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id} \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "attributes": {}
  }'
```

```python title="Python"
import requests

resp = requests.patch(
    "https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "attributes": {}
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}", {
  method: "PATCH",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "attributes": {}
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `attributes` | object | Free-form key/value metadata for the revision. |
| `created_at` | string | Timestamp when the revision was created (UTC, ISO 8601). |
| `id` | string | Unique identifier of the revision. |
| `parent_revision_id` | string | ID of the revision this one was derived from, recording its lineage. |
| `part_id` | string | ID of the part this revision belongs to. |
| `revision_code` | string | Customer-facing revision label (A, B, 01, 02, …). |
| `updated_at` | string | Timestamp when the revision was last updated (UTC, ISO 8601). |

**Example response**

```json
{
  "attributes": {},
  "created_at": "2026-06-01T12:00:00Z",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "parent_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "part_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "revision_code": "string",
  "updated_at": "2026-06-01T12:00:00Z"
}
```

## List Datasets

`GET /api/v1/parts/{part_id}/revisions/{revision_id}/datasets`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |
| `revision_id` | path | string | yes | Identifier of the revision. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


## Attach Dataset

`POST /api/v1/parts/{part_id}/revisions/{revision_id}/datasets`

Attach an existing DataSource row to a revision.

For a fresh file upload, prefer ``POST .../datasets/upload`` which
creates the DataSource + link in one shot.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |
| `revision_id` | path | string | yes | Identifier of the revision. |

**Request body** (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `attributes` | object | no | Free-form metadata for the attachment, as a JSON object. |
| `data_source_id` | string | yes | Identifier of the uploaded data source to attach to the revision. |
| `is_primary` | boolean | no | Whether this dataset is the primary one for the revision. |
| `role` | string | no | Role of the file on the revision (e.g. cad_3d for a 3D CAD model, drawing_2d for a 2D drawing). |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `201` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `201`

| Field | Type | Description |
| --- | --- | --- |
| `attributes` | object | Free-form key/value metadata for the file attachment. |
| `content_type` | string | MIME type of the uploaded file (e.g. application/pdf). |
| `created_at` | string | Timestamp when the file was attached (UTC, ISO 8601). |
| `data_source_id` | string | ID of the underlying stored file this attachment points to. |
| `filename` | string | Original filename of the uploaded file. |
| `id` | string | Unique identifier of the uploaded file attached to the revision. |
| `is_active` | boolean | Whether this is the current file; superseded re-uploads are marked inactive. |
| `is_primary` | boolean | Whether this is the primary CAD file used for analysis on the revision. |
| `part_revision_id` | string | ID of the revision this file is attached to. |
| `role` | string | The file's role on the revision (e.g. 3D CAD, 2D drawing, spec). |
| `size_bytes` | integer | Size of the uploaded file in bytes. |

**Example response**

```json
{
  "attributes": {},
  "content_type": "string",
  "created_at": "2026-06-01T12:00:00Z",
  "data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "filename": "bracket.step",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "is_active": true,
  "is_primary": true,
  "part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "role": "primary",
  "size_bytes": 204800
}
```

## Detach Dataset

`DELETE /api/v1/parts/{part_id}/revisions/{revision_id}/datasets/{dataset_link_id}`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |
| `revision_id` | path | string | yes | Identifier of the revision. |
| `dataset_link_id` | path | string | yes | Identifier of the dataset link. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X DELETE https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets/{dataset_link_id} \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.delete(
    "https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets/{dataset_link_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets/{dataset_link_id}", {
  method: "DELETE",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `message` | string | Human-readable confirmation that the dataset was detached. |

**Example response**

```json
{
  "message": "string"
}
```

## Update Dataset

`PATCH /api/v1/parts/{part_id}/revisions/{revision_id}/datasets/{dataset_link_id}`

Edit a dataset link.

Currently supports two fields:
  - ``filename``: rename the underlying ``DataSource``.
  - ``is_primary``: promote/demote within the parent revision. The
    "exactly one primary" invariant is maintained server-side: when
    promoting, every other link on the same revision is demoted in
    the same transaction so concurrent primaries can't co-exist.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |
| `revision_id` | path | string | yes | Identifier of the revision. |
| `dataset_link_id` | path | string | yes | Identifier of the dataset link. |

**Request body** (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `filename` | string | no | New original filename for the uploaded file. |
| `is_primary` | boolean | no | Set true to make this the primary CAD file for analysis on the revision. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X PATCH https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets/{dataset_link_id} \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "bracket.step",
    "is_primary": true
  }'
```

```python title="Python"
import requests

resp = requests.patch(
    "https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets/{dataset_link_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "filename": "bracket.step",
        "is_primary": True
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets/{dataset_link_id}", {
  method: "PATCH",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "filename": "bracket.step",
    "is_primary": true
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `attributes` | object | Free-form key/value metadata for the file attachment. |
| `content_type` | string | MIME type of the uploaded file (e.g. application/pdf). |
| `created_at` | string | Timestamp when the file was attached (UTC, ISO 8601). |
| `data_source_id` | string | ID of the underlying stored file this attachment points to. |
| `filename` | string | Original filename of the uploaded file. |
| `id` | string | Unique identifier of the uploaded file attached to the revision. |
| `is_active` | boolean | Whether this is the current file; superseded re-uploads are marked inactive. |
| `is_primary` | boolean | Whether this is the primary CAD file used for analysis on the revision. |
| `part_revision_id` | string | ID of the revision this file is attached to. |
| `role` | string | The file's role on the revision (e.g. 3D CAD, 2D drawing, spec). |
| `size_bytes` | integer | Size of the uploaded file in bytes. |

**Example response**

```json
{
  "attributes": {},
  "content_type": "string",
  "created_at": "2026-06-01T12:00:00Z",
  "data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "filename": "bracket.step",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "is_active": true,
  "is_primary": true,
  "part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "role": "primary",
  "size_bytes": 204800
}
```

## Upload Dataset

`POST /api/v1/parts/{part_id}/revisions/{revision_id}/datasets/upload`

Upload a file directly onto a revision, in one shot.

Stores the file and links it to the revision. No calculation is
enqueued — use ``/parts/calculations/upload`` for the
calculate-on-upload flow.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `part_id` | path | string | yes | Identifier of the part. |
| `revision_id` | path | string | yes | Identifier of the revision. |
| `role` | query | string | no | Role of the file on the revision (e.g. cad_3d for a 3D model, drawing_2d for a 2D drawing). |
| `is_primary` | query | boolean | no | Whether this dataset becomes the primary one for its role on the revision. |

**Request body** (`multipart/form-data`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `file` | string | yes | The CAD model or drawing file to attach to the revision. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets/upload \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -F "file=@file.bin"
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets/upload",
    headers={"X-API-Key": "YOUR_API_KEY"},
    files={
        "file": open("file.bin", "rb"),
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const form = new FormData()
form.append("file", file) // a File or Blob

const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets/upload", {
  method: "POST",
  headers: { "X-API-Key": process.env.ARCNM_API_KEY! },
  body: form,
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `201` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `201`

| Field | Type | Description |
| --- | --- | --- |
| `attributes` | object | Free-form key/value metadata for the file attachment. |
| `content_type` | string | MIME type of the uploaded file (e.g. application/pdf). |
| `created_at` | string | Timestamp when the file was attached (UTC, ISO 8601). |
| `data_source_id` | string | ID of the underlying stored file this attachment points to. |
| `filename` | string | Original filename of the uploaded file. |
| `id` | string | Unique identifier of the uploaded file attached to the revision. |
| `is_active` | boolean | Whether this is the current file; superseded re-uploads are marked inactive. |
| `is_primary` | boolean | Whether this is the primary CAD file used for analysis on the revision. |
| `part_revision_id` | string | ID of the revision this file is attached to. |
| `role` | string | The file's role on the revision (e.g. 3D CAD, 2D drawing, spec). |
| `size_bytes` | integer | Size of the uploaded file in bytes. |

**Example response**

```json
{
  "attributes": {},
  "content_type": "string",
  "created_at": "2026-06-01T12:00:00Z",
  "data_source_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "filename": "bracket.step",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "is_active": true,
  "is_primary": true,
  "part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "role": "primary",
  "size_bytes": 204800
}
```
