API reference
Parts & revisions
The Parts API manages parts and their 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 theX-API-Keyheader (see Authentication).
List Parts
GET /api/v1/parts/
List the organization's parts, newest first.
count is the total across all pages. Page with cursor rather than
offset when parts may be created while you walk — offset counts
from the start of the result set, so a concurrent insert shifts every
later page and rows get skipped or repeated.
Paginated. Pass
cursor(from the previous response) to fetch the next page;limitcaps the page size.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
q |
query | string | no | Substring match |
classification_id |
query | string | no | Identifier of the classification. |
folder_id |
query | string | no | Only parts filed directly in this folder (excludes subfolders). |
unfiled |
query | boolean | no | Only parts not filed in any folder (folder_id IS NULL). |
make_or_buy |
query | string[] | no | Only parts sourced this way. Repeatable — several values are OR-ed, so passing every value is the same as passing none. |
procurement_type |
query | string[] | no | Only parts with one of these procurement types. |
quoted |
query | boolean | no | true → only parts with at least one succeeded calculation; false → only parts that have never been priced. |
material_grade_id |
query | string[] | no | Only parts whose NEWEST succeeded calculation used one of these material grades. A part carries no material of its own — the material is chosen per calculation — so this filters on the last run. Repeatable; several values are OR-ed. |
sort |
query | string | no | Ordering key. unit_cost and annual_spend rank by the newest succeeded calculation per part; parts with no successful calculation always sort last, whatever the direction. created_at, updated_at and part_number support cursor; the other three page by offset only, because their column is nullable or joined and a cursor over it cannot reach every row. |
include_facets |
query | boolean | no | Also return facets: the values available for each filter with their counts, computed over everything the OTHER active filters allow. Lets a client show counts and grey out choices that would return nothing. |
cursor |
query | string | no | Opaque position token from the previous page's next_cursor (or the Link / X-Next-Cursor response header). Omit it for the first page. Keep every other query parameter identical for the whole walk — a cursor replayed against different filters is rejected. |
limit |
query | integer | no | Maximum rows to return in one page. |
order |
query | asc | desc |
no | Sort direction over the collection's ordering key. Use asc to reconcile a batch: rows come oldest-first, so work created while you page lands after your position instead of shifting rows under it. |
created_after |
query | string | no | Only rows created at or after this instant (RFC 3339, e.g. 2026-07-20T09:00:00Z). Inclusive. |
created_before |
query | string | no | Only rows created strictly before this instant (RFC 3339). Exclusive, so an after/before pair tiles a range without overlap. |
offset |
query | integer | no | Rows to skip. Superseded by cursor, which is stable under concurrent writes; kept for existing integrations. Bounded — past the cap, page with cursor. |
Request
curl -X GET https://api.arcnm.io/api/v1/parts/ \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
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 parts matching the query. |
data |
PartListItem[] | The page of parts matching the query. |
facets |
object | Present only when include_facets is set. Maps each filter name to its selectable values with counts. |
has_more |
boolean | Whether more parts match beyond this page. |
next_cursor |
string | Position token for the next page — pass it back as cursor. Null on the last page, and null under a sort that cannot be keyset-paged (see sort). Prefer this over offset: it is stable while parts are being created. |
Example response
{
"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",
"folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"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",
"preview_status": "string",
"procurement_type": "in_house",
"updated_at": "2026-06-01T12:00:00Z"
}
],
"facets": {},
"has_more": false,
"next_cursor": "string"
}
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. |
folder_id |
string | no | ID of the workspace folder this part is filed in; null is unfiled. |
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
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"
}'
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())
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()
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 |
|---|---|---|
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. |
folder_id |
string | ID of the workspace folder this part is filed in; null is unfiled. |
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
{
"attributes": {},
"base_uom": "EA",
"classification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"created_at": "2026-06-01T12:00:00Z",
"default_currency": "EUR",
"description": "string",
"folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"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
curl -X DELETE https://api.arcnm.io/api/v1/parts/{part_id} \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
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 | Human-readable confirmation that the resource was deleted. |
Example response
{
"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
curl -X GET https://api.arcnm.io/api/v1/parts/{part_id} \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
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 |
|---|---|---|
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. |
folder_id |
string | ID of the workspace folder this part is filed in; null is unfiled. |
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
{
"attributes": {},
"base_uom": "EA",
"classification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"created_at": "2026-06-01T12:00:00Z",
"default_currency": "EUR",
"description": "string",
"folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"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. |
folder_id |
string | no | ID of the workspace folder this part is filed in; null is unfiled. |
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
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",
"folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"lead_time_days": 0,
"make_or_buy": "string",
"procurement_type": "string"
}'
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",
"folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"lead_time_days": 0,
"make_or_buy": "string",
"procurement_type": "string"
},
)
resp.raise_for_status()
print(resp.json())
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",
"folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"lead_time_days": 0,
"make_or_buy": "string",
"procurement_type": "string"
}),
})
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 |
|---|---|---|
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. |
folder_id |
string | ID of the workspace folder this part is filed in; null is unfiled. |
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
{
"attributes": {},
"base_uom": "EA",
"classification_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"created_at": "2026-06-01T12:00:00Z",
"default_currency": "EUR",
"description": "string",
"folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"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 Faces Url
GET /api/v1/parts/{part_id}/faces-url
Per-face-tagged glTF (cost-driver heatmap model) for a Part.
Same dataset-resolution + {url, status} contract as
/parts/{id}/preview-url but serves faces_glb_storage_key — the
model where every B-rep face is its own primitive tagged with
face_uuid. Only produced for STEP inputs; the frontend falls back to
the opaque preview when url is None.
part_revision_id (optional query param) pins resolution to a specific
revision so the heatmap's face_uuids line up with the cost drivers from a
calc on THAT revision. Without it, the newest revision's dataset is served
(a re-upload after the calc would otherwise blank the heatmap — the GLB's
face_uuids would derive from different STEP bytes than the drivers).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
part_id |
path | string | yes | Identifier of the part. |
part_revision_id |
query | string | no | Identifier of the part revision. |
Request
curl -X GET https://api.arcnm.io/api/v1/parts/{part_id}/faces-url \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/parts/{part_id}/faces-url",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/parts/{part_id}/faces-url", {
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 |
|---|---|---|
data_source_id |
string | Identifier of the resolved CAD file; null when no dataset was found. |
expires_at |
string | UTC ISO-8601 instant at which the presigned URL expires; null unless status is 'ok'. Refresh the URL before this moment. |
expires_in_s |
integer | Lifetime of the presigned URL in seconds; null unless status is 'ok'. |
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
{
"data_source_id": "string",
"expires_at": "string",
"expires_in_s": 0,
"status": "string",
"url": "string"
}
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
/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
curl -X GET https://api.arcnm.io/api/v1/parts/{part_id}/preview-url \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
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 |
|---|---|---|
data_source_id |
string | Identifier of the resolved CAD file; null when no dataset was found. |
expires_at |
string | UTC ISO-8601 instant at which the presigned URL expires; null unless status is 'ok'. Refresh the URL before this moment. |
expires_in_s |
integer | Lifetime of the presigned URL in seconds; null unless status is 'ok'. |
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
{
"data_source_id": "string",
"expires_at": "string",
"expires_in_s": 0,
"status": "string",
"url": "string"
}
List Revisions
GET /api/v1/parts/{part_id}/revisions
List a part's revisions, newest first.
count is the total across all pages; data is one page of it.
Paginated. Pass
cursor(from the previous response) to fetch the next page;limitcaps the page size.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
part_id |
path | string | yes | Identifier of the part. |
cursor |
query | string | no | Opaque position token from the previous page's next_cursor (or the Link / X-Next-Cursor response header). Omit it for the first page. Keep every other query parameter identical for the whole walk — a cursor replayed against different filters is rejected. |
limit |
query | integer | no | Maximum rows to return in one page. |
order |
query | asc | desc |
no | Sort direction over the collection's ordering key. Use asc to reconcile a batch: rows come oldest-first, so work created while you page lands after your position instead of shifting rows under it. |
created_after |
query | string | no | Only rows created at or after this instant (RFC 3339, e.g. 2026-07-20T09:00:00Z). Inclusive. |
created_before |
query | string | no | Only rows created strictly before this instant (RFC 3339). Exclusive, so an after/before pair tiles a range without overlap. |
Request
curl -X GET https://api.arcnm.io/api/v1/parts/{part_id}/revisions \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
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 |
|---|---|---|
count |
integer | Total number of revisions matching the query. |
data |
PartRevisionPublic[] | The page of revisions matching the query. |
has_more |
boolean | Whether more revisions match beyond this page. |
next_cursor |
string | Position token for the next page — pass it back as cursor. Null on the last page. |
Example response
{
"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"
}
],
"has_more": false,
"next_cursor": "string"
}
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
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"
}'
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())
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()
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. |
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
{
"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
curl -X DELETE https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id} \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
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 | Human-readable confirmation that the resource was deleted. |
Example response
{
"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
curl -X GET https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id} \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
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 |
|---|---|---|
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
{
"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
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": {}
}'
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())
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()
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 |
|---|---|---|
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
{
"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
curl -X GET https://api.arcnm.io/api/v1/parts/{part_id}/revisions/{revision_id}/datasets \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
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. |
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
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"
}'
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())
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()
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. |
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
{
"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
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"
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())
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()
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 | Human-readable confirmation that the dataset was detached. |
Example response
{
"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 underlyingDataSource.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
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
}'
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())
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()
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 |
|---|---|---|
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
{
"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 /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
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 "[email protected]"
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())
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()
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. |
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
{
"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
}
Part Preview Urls Batch
POST /api/v1/parts/preview-urls
Presign the previews for many parts in ONE request.
A list view needs a thumbnail per row, and the per-part endpoint made that one HTTP call per row: a 50-row page fired 35 of them, which on the Free tier (60 weighted calls/minute) exhausted the whole budget on a single page load — the tail 429'd, the thumbnails broke, and the client's expired-URL retry turned that into a loop. Measured on production 2026-07-28.
Same payload per part as GET /{part_id}/preview-url, keyed by part id.
An id the caller cannot see is simply absent from the response rather than
404-ing the batch, so one stale row in a list never blanks the rest.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
part_ids |
string[] | yes | Parts to resolve preview URLs for. Ask only for the rows you are about to render. |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/preview-urls \
-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/parts/preview-urls",
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/parts/preview-urls", {
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 |
|---|---|---|
previews |
PartPreviewUrlBatchItem[] | One entry per resolvable part, in the order requested. Ids the caller cannot see are omitted rather than failing the batch. |
Example response
{
"previews": [
{
"data_source_id": "string",
"expires_at": "string",
"expires_in_s": 0,
"part_id": "string",
"status": "string",
"url": "string"
}
]
}
Which of these part numbers already exist?
POST /api/v1/parts/resolve-numbers
Read-only pre-flight for a bulk upload: given the part numbers you are about to send, says which already exist in this workspace, where each one currently lives, and what it last cost — so a conflict can be resolved by the user BEFORE the first byte is uploaded and before anything is metered. No side effects.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
folder_id |
string | no | The folder you intend to upload into, if any. |
part_numbers |
string[] | yes | Candidate part numbers, exactly as they will be sent to /calculations/upload-and-quote. They are matched verbatim — see the endpoint description for why nothing is normalised here. |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/resolve-numbers \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"part_numbers": [
"string"
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/resolve-numbers",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"part_numbers": [
"string"
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/parts/resolve-numbers", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"part_numbers": [
"string"
]
}),
})
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 |
|---|---|---|
results |
ResolvedPartNumber[] | One entry per requested number, in the order requested. |
Example response
{
"results": [
{
"already_in_requested_folder": false,
"current_folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"current_folder_name": "string",
"exists": true,
"last_calculated_at": "2026-06-01T12:00:00Z",
"last_currency": "string",
"last_unit_cost": 0,
"part_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"part_number": "BRACKET-001"
}
]
}