API reference
Folders
The Folders API lets you create, list, retrieve, update, and delete 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 theX-API-Keyheader (see Authentication).
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
curl -X GET https://api.arcnm.io/api/v1/folders \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog 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
{
"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
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"
}'
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())
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()
Responses
| Status | Description |
|---|---|
201 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog 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
{
"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
curl -X DELETE https://api.arcnm.io/api/v1/folders/{folder_id} \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog 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
{
"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
curl -X GET https://api.arcnm.io/api/v1/folders/{folder_id} \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog 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
{
"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
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"
}'
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())
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()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog 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
{
"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
curl -X GET https://api.arcnm.io/api/v1/folders/{folder_id}/summary \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog 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
{
"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
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"
]
}'
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())
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()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog 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
{
"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
curl -X GET https://api.arcnm.io/api/v1/folders/summary \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog 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
{
"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
}