---
title: Folders
description: The Folders API lets you create, list, retrieve, update, and delete folders.
---

# Folders

The Folders API lets you create, list, retrieve, update, and delete folders.

> **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 folders

`GET /api/v1/folders`

All folders for the org, each with its direct (non-recursive) part count.

The tree is small enough to ship flat — the client assembles parent/child
structure from ``parent_id``.

**Request**

<CodeTabs>

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

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/folders",
    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/folders", {
  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 folders. |
| `data` | PartFolderPublic[] | All folders for the org. |

**Example response**

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

## Create a folder

`POST /api/v1/folders`

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | Folder name, shown in the workspace tree. Unique among its siblings. |
| `parent_id` | string | no | ID of the parent folder; null creates a top-level folder. |

**Request**

<CodeTabs>

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

```python title="Python"
import requests

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

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/folders", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "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. |
| `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 |
| --- | --- | --- |
| `created_at` | string | Timestamp when the folder was created (UTC, ISO 8601). |
| `id` | string | Unique identifier of the folder. |
| `name` | string | Folder name, shown in the workspace tree. Unique among its siblings. |
| `parent_id` | string | ID of the parent folder; null for a top-level folder. |
| `part_count` | integer | Number of parts filed directly in this folder (excludes subfolders). |
| `updated_at` | string | Timestamp when the folder was last updated (UTC, ISO 8601). |

**Example response**

```json
{
  "created_at": "2026-06-01T12:00:00Z",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "string",
  "parent_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "part_count": 0,
  "updated_at": "2026-06-01T12:00:00Z"
}
```

## Delete a folder

`DELETE /api/v1/folders/{folder_id}`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `folder_id` | path | string | yes | Identifier of the folder. |

**Request**

<CodeTabs>

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

```python title="Python"
import requests

resp = requests.delete(
    "https://api.arcnm.io/api/v1/folders/{folder_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/folders/{folder_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 |  |

**Example response**

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

## Get a folder

`GET /api/v1/folders/{folder_id}`

Fetch a single folder.

Parts carry a ``folder_id``, so a client holding one previously had to
list the whole tree to resolve it to a name.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `folder_id` | path | string | yes | Identifier of the folder. |

**Request**

<CodeTabs>

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

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/folders/{folder_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/folders/{folder_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 |
| --- | --- | --- |
| `created_at` | string | Timestamp when the folder was created (UTC, ISO 8601). |
| `id` | string | Unique identifier of the folder. |
| `name` | string | Folder name, shown in the workspace tree. Unique among its siblings. |
| `parent_id` | string | ID of the parent folder; null for a top-level folder. |
| `part_count` | integer | Number of parts filed directly in this folder (excludes subfolders). |
| `updated_at` | string | Timestamp when the folder was last updated (UTC, ISO 8601). |

**Example response**

```json
{
  "created_at": "2026-06-01T12:00:00Z",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "string",
  "parent_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "part_count": 0,
  "updated_at": "2026-06-01T12:00:00Z"
}
```

## Rename or move a folder

`PATCH /api/v1/folders/{folder_id}`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `folder_id` | path | string | yes | Identifier of the folder. |

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no | New folder name. |
| `parent_id` | string | no | New parent folder; null moves the folder to the workspace root. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X PATCH https://api.arcnm.io/api/v1/folders/{folder_id} \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "string",
    "parent_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
```

```python title="Python"
import requests

resp = requests.patch(
    "https://api.arcnm.io/api/v1/folders/{folder_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "name": "string",
        "parent_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/folders/{folder_id}", {
  method: "PATCH",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "string",
    "parent_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }),
})
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 |
| --- | --- | --- |
| `created_at` | string | Timestamp when the folder was created (UTC, ISO 8601). |
| `id` | string | Unique identifier of the folder. |
| `name` | string | Folder name, shown in the workspace tree. Unique among its siblings. |
| `parent_id` | string | ID of the parent folder; null for a top-level folder. |
| `part_count` | integer | Number of parts filed directly in this folder (excludes subfolders). |
| `updated_at` | string | Timestamp when the folder was last updated (UTC, ISO 8601). |

**Example response**

```json
{
  "created_at": "2026-06-01T12:00:00Z",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "string",
  "parent_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "part_count": 0,
  "updated_at": "2026-06-01T12:00:00Z"
}
```

## Folder spend rollup

`GET /api/v1/folders/{folder_id}/summary`

Rollup scoped to a folder and all of its subfolders.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `folder_id` | path | string | yes | Identifier of the folder. |

**Request**

<CodeTabs>

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

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/folders/{folder_id}/summary",
    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/folders/{folder_id}/summary", {
  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 |
| --- | --- | --- |
| `calculation_count` | integer | Succeeded calculations counted in this rollup. |
| `currency` | string | Currency of the money figures. |
| `effective_annual_cost_eur` | number | Σ over scoped parts of the current TOTAL annual cost — production plus inventory holding at the current lot where known, else the production spend. The honest denominator for a savings percentage: savings include holding relief, so dividing by production spend alone can overstate the fraction. |
| `folder_id` | string | The folder this rollup is scoped to; null means the whole workspace. |
| `part_count` | integer | Parts in this folder and all its subfolders. |
| `potential_savings_eur` | number | Σ over scoped parts of the net annual saving of moving to the recommended (holding-aware) lot size, EUR — production plus inventory holding. |
| `quoted_part_count` | integer | Parts in scope that have at least one succeeded calculation. |
| `total_annual_spend_eur` | number | Σ over scoped parts of (latest should-cost unit price × annual volume), EUR. |

**Example response**

```json
{
  "calculation_count": 0,
  "currency": "EUR",
  "effective_annual_cost_eur": 0,
  "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "part_count": 0,
  "potential_savings_eur": 0,
  "quoted_part_count": 0,
  "total_annual_spend_eur": 0
}
```

## Move parts into a folder

`POST /api/v1/folders/move-parts`

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `folder_id` | string | no | Destination folder; null unfiles the parts (moves them to the root). |
| `part_ids` | string[] | yes | Parts to (re-)file. |

**Request**

<CodeTabs>

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

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/folders/move-parts",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "part_ids": [
            "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/folders/move-parts", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "part_ids": [
      "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    ]
  }),
})
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. |
| `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 |
| --- | --- | --- |
| `moved` | integer | Number of parts actually re-filed. |
| `not_found` | string[] | Ids that were skipped because no such part exists in this workspace, capped at 100 entries. Empty on a fully successful move. Compare ``moved`` against ``requested`` for the true count — that pair is never truncated. |
| `requested` | integer | Number of distinct part ids you asked about. |

**Example response**

```json
{
  "moved": 0,
  "not_found": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "requested": 0
}
```

## Workspace spend rollup

`GET /api/v1/folders/summary`

Rollup over the entire workspace (all parts, filed or not).

**Request**

<CodeTabs>

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

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/folders/summary",
    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/folders/summary", {
  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 |
| --- | --- | --- |
| `calculation_count` | integer | Succeeded calculations counted in this rollup. |
| `currency` | string | Currency of the money figures. |
| `effective_annual_cost_eur` | number | Σ over scoped parts of the current TOTAL annual cost — production plus inventory holding at the current lot where known, else the production spend. The honest denominator for a savings percentage: savings include holding relief, so dividing by production spend alone can overstate the fraction. |
| `folder_id` | string | The folder this rollup is scoped to; null means the whole workspace. |
| `part_count` | integer | Parts in this folder and all its subfolders. |
| `potential_savings_eur` | number | Σ over scoped parts of the net annual saving of moving to the recommended (holding-aware) lot size, EUR — production plus inventory holding. |
| `quoted_part_count` | integer | Parts in scope that have at least one succeeded calculation. |
| `total_annual_spend_eur` | number | Σ over scoped parts of (latest should-cost unit price × annual volume), EUR. |

**Example response**

```json
{
  "calculation_count": 0,
  "currency": "EUR",
  "effective_annual_cost_eur": 0,
  "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "part_count": 0,
  "potential_savings_eur": 0,
  "quoted_part_count": 0,
  "total_annual_spend_eur": 0
}
```
