API reference
Calculations
The Calculations API runs a should-cost calculation on a part and returns the priced result.
The Calculations API runs a should-cost calculation on a part and returns the priced result — quote a stored revision, upload-and-quote a file in one call, then list, fetch, or bulk-manage runs.
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 Calculations
GET /api/v1/parts/calculations
List recent calculations for the tenant.
Returns the most recent limit rows ordered by created_at
descending with a slim projection — no analytics blob, to keep the
list response cheap.
Optional part_id narrows the result to a single part.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | Maximum number of results to return. |
status_filter |
query | string | no | Filter by lifecycle status (e.g. queued, running, succeeded, failed, cancelled). |
part_id |
query | string | no | Identifier of the part. |
Request
curl -X GET https://api.arcnm.io/api/v1/parts/calculations \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/parts/calculations",
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/calculations", {
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 | Number of calculations returned in items (this page's size). |
items |
CalculationListItem[] | The calculations on this page, newest first. |
Example response
{
"count": 0,
"items": [
{
"created_at": "string",
"currency": "EUR",
"engine": "arcanum",
"finished_at": "string",
"id": "string",
"lot_size": 50,
"material_grade_id": "string",
"material_ref": "1.4301",
"name": "string",
"started_at": "string",
"status": "string",
"unit_cost": 12.84
}
]
}
Create a calculation (queued)
POST /api/v1/parts/calculations
Create a new Calculation row tied to (revision × env × dataset). Does NOT enqueue — call POST /parts/calculations/{id}/run to schedule.
One engine (ARCNM), one endpoint. The platform chooses the right drawing-understanding depth per calculation automatically. The optional extraction_tier field lets you override that depth — auto (default), lite (faster), or full (deepest); most callers should leave it unset.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
annual_volume |
integer | no | Expected yearly quantity, used for amortizing setup over the run (>= 1). |
costing_environment_id |
string | yes | UUID of the costing environment (machine rates, region) to price against. |
currency |
string | no | ISO 4217 currency code for the quote; defaults to the costing environment's currency when omitted. |
dataset_link_id |
string | no | UUID of a specific dataset link to use; omit to use the revision's active primary CAD. |
engine |
string | no | Pricing engine selector; retained for back-compat and always normalized to the sole engine. |
extraction_tier |
auto | lite | full |
no | Drawing-understanding depth. auto (default) lets the platform choose the right depth per drawing; lite is faster and lighter; full is the deepest. Most callers should leave this unset. |
lot_size |
integer | no | Number of identical parts produced per batch (>= 1). |
material_grade_id |
string | no | UUID of a resolved material grade; takes precedence over material_ref when both are supplied. |
material_ref |
string | no | Free-form material reference (URN, Werkstoffnummer, AISI/SAE code, trade name, or your SKU); resolved to a material grade. |
name |
string | no | Optional human-readable label; defaults to a timestamped name when omitted. |
part_revision_id |
string | yes | UUID of the part revision to price. |
region |
string | no | Pricing region override; defaults to the costing environment's region when omitted. |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/calculations \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/calculations",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/parts/calculations", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"costing_environment_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. |
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 |
|---|---|---|
enqueued_task |
string | Name of the background task enqueued for this run; null if nothing was scheduled. |
id |
string | UUID of the calculation. |
job_id |
string | Identifier of the queued background job; null when no job was enqueued. |
status |
string | Current lifecycle state (e.g. queued, running, succeeded, failed, cancelled). |
Example response
{
"enqueued_task": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"job_id": "string",
"status": "string"
}
Delete Calculation
DELETE /api/v1/parts/calculations/{calculation_id}
Hard-delete a calculation row.
A non-terminal row is first cancelled (which releases the wallet hold) so the worker can no longer transition it; the row itself is then removed. Use Cancel if you want the audit trail to persist.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
calculation_id |
path | string | yes | Identifier of the calculation. |
Request
curl -X DELETE https://api.arcnm.io/api/v1/parts/calculations/{calculation_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.delete(
"https://api.arcnm.io/api/v1/parts/calculations/{calculation_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/calculations/{calculation_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 calculation was deleted. |
Example response
{
"message": "string"
}
Get Calculation
GET /api/v1/parts/calculations/{calculation_id}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
calculation_id |
path | string | yes | Identifier of the calculation. |
Request
curl -X GET https://api.arcnm.io/api/v1/parts/calculations/{calculation_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/parts/calculations/{calculation_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/calculations/{calculation_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 |
|---|---|---|
analytics |
object | Free-form engine-output object carrying the cost breakdown and audit detail; its internal keys may evolve. |
annual_volume |
integer | Expected yearly quantity used to amortize setup cost. |
costing_environment_id |
string | UUID of the costing environment used for pricing. |
created_at |
string | ISO 8601 timestamp when the calculation was created. |
currency |
string | ISO 4217 currency code for the cost figures. |
engine |
string | Pricing engine used for this calculation. |
error |
string | Failure message; null unless the run failed. |
finished_at |
string | ISO 8601 timestamp when the run finished; null before it completes. |
id |
string | UUID of the calculation. |
lot_size |
integer | Number of identical parts produced per batch. |
material_grade_id |
string | UUID of the linked material grade; null when no grade is set. |
material_ref |
string | Material reference (URN) of the linked grade; null when no grade is set. |
name |
string | Human-readable label for the calculation. |
part_id |
string | UUID of the part this calculation belongs to. |
part_revision_id |
string | UUID of the part revision that was priced. |
setup_cost |
number | One-time setup cost in the quote's currency; null until pricing completes. |
started_at |
string | ISO 8601 timestamp when the run started; null before it begins. |
status |
string | Current lifecycle state (e.g. queued, running, succeeded, failed, cancelled). |
total_cost |
number | Total cost for the full lot in the quote's currency; null until pricing completes. |
total_time_s |
number | Total production time for the lot in seconds; null until pricing completes. |
unit_cost |
number | Cost per part in the quote's currency; null until pricing completes. |
unit_time_s |
number | Production time per part in seconds; null until pricing completes. |
Example response
{
"analytics": {},
"annual_volume": 500,
"costing_environment_id": "string",
"created_at": "string",
"currency": "EUR",
"engine": "arcanum",
"error": "string",
"finished_at": "string",
"id": "string",
"lot_size": 50,
"material_grade_id": "string",
"material_ref": "1.4301",
"name": "string",
"part_id": "string",
"part_revision_id": "string",
"setup_cost": 52.5,
"started_at": "string",
"status": "string",
"total_cost": 642,
"total_time_s": 184,
"unit_cost": 12.84,
"unit_time_s": 184
}
Cancel Calculation
POST /api/v1/parts/calculations/{calculation_id}/cancel
Cancel a queued / running / polling calculation.
Idempotent: a terminal row is returned untouched. The wallet hold is released as part of the transition so a cancelled calc never debits the org's balance.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
calculation_id |
path | string | yes | Identifier of the calculation. |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/cancel \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/cancel",
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/calculations/{calculation_id}/cancel", {
method: "POST",
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 |
|---|---|---|
enqueued_task |
string | Name of the background task enqueued for this run; null if nothing was scheduled. |
id |
string | UUID of the calculation. |
job_id |
string | Identifier of the queued background job; null when no job was enqueued. |
status |
string | Current lifecycle state (e.g. queued, running, succeeded, failed, cancelled). |
Example response
{
"enqueued_task": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"job_id": "string",
"status": "string"
}
Upload Inputs
POST /api/v1/parts/calculations/{calculation_id}/inputs
Attach a 2D drawing PDF / STL mesh / RFQ text file to the calculation.
The file is stored against the calculation's part revision under the
requested role. Subsequent POST /run calls auto-discover it
by role so the pipeline can fan out additional extractors (drawing,
mesh fallback, RFQ text) alongside the primary geometry pass.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
calculation_id |
path | string | yes | Identifier of the calculation. |
Request body (multipart/form-data)
| Field | Type | Required | Description |
|---|---|---|---|
file |
string | yes | The file to attach (2D drawing PDF, STL mesh, or RFQ text). |
role |
string | yes | Role the file plays on the calculation's revision (e.g. drawing_2d, mesh_3d, rfq_text). |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/inputs \
-H "X-API-Key: $ARCNM_API_KEY" \
-F "role=primary" \
-F "[email protected]"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/inputs",
headers={"X-API-Key": "YOUR_API_KEY"},
files={
"file": open("file.bin", "rb"),
},
data={
"role": "primary",
},
)
resp.raise_for_status()
print(resp.json())
const form = new FormData()
form.append("role", "primary")
form.append("file", file) // a File or Blob
const resp = await fetch("https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/inputs", {
method: "POST",
headers: { "X-API-Key": process.env.ARCNM_API_KEY! },
body: form,
})
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 |
|---|---|---|
billing |
object | Free-form billing object describing the upload charge; its internal keys may evolve. |
calculation_id |
string | UUID of the calculation the file was attached to. |
data_source_id |
string | UUID of the stored data source created for the uploaded file. |
role |
string | Role the file was attached under (e.g. drawing_2d, mesh, rfq_text). |
sha256 |
string | Hex-encoded SHA-256 digest of the uploaded bytes. |
size_bytes |
integer | Size of the uploaded file in bytes. |
Example response
{
"billing": {},
"calculation_id": "string",
"data_source_id": "string",
"role": "primary",
"sha256": "9f86d081884c7d659a2feaa0c55ad015…",
"size_bytes": 204800
}
Patch Calculation Material
PATCH /api/v1/parts/calculations/{calculation_id}/material
Override the material on an existing calculation.
The new material_grade_id is resolved exactly the same way
POST /parts/calculations resolves it on create, so the result is
consistent with the create-time logic.
The calculation isn't re-priced here — follow up with POST /run
if a re-quote is desired. The explicit two-step flow lets you review
the override before paying for another pipeline pass.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
calculation_id |
path | string | yes | Identifier of the calculation. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
material_grade_id |
string | no | UUID of the material grade to link. Provide this or material_ref. |
material_ref |
string | no | Material reference (URN, Werkstoffnummer, or trade name) to resolve and link. Provide this or material_grade_id. |
Request
curl -X PATCH https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/material \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"material_grade_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"material_ref": "1.4301"
}'
import requests
resp = requests.patch(
"https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/material",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"material_grade_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"material_ref": "1.4301"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/material", {
method: "PATCH",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"material_grade_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"material_ref": "1.4301"
}),
})
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 |
|---|---|---|
id |
string | UUID of the calculation that was updated. |
material_grade_id |
string | UUID of the newly assigned material grade. |
material_ref |
string | Material reference (URN) of the newly assigned grade; null when unavailable. |
previous_material_grade_id |
string | UUID of the material grade before this change; null if none was set. |
resolved_via |
string | How the grade was resolved: a directly supplied grade id, or a free-form reference lookup. |
Example response
{
"id": "string",
"material_grade_id": "string",
"material_ref": "1.4301",
"previous_material_grade_id": "string",
"resolved_via": "string"
}
Run Calculation
POST /api/v1/parts/calculations/{calculation_id}/run
Enqueue the calculation onto the worker queue.
Idempotent: a row in a non-terminal state (queued/running/polling)
is not re-enqueued; a row in succeeded is returned untouched;
a row in failed/cancelled/timed_out is reset to
queued and re-enqueued so the user can retry.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
calculation_id |
path | string | yes | Identifier of the calculation. |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/run \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/run",
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/calculations/{calculation_id}/run", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
202 |
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. |
402 |
insufficient_funds |
The pre-authorised wallet hold for the run exceeds your balance. |
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 202
| Field | Type | Description |
|---|---|---|
enqueued_task |
string | Name of the background task enqueued for this run; null if nothing was scheduled. |
id |
string | UUID of the calculation. |
job_id |
string | Identifier of the queued background job; null when no job was enqueued. |
status |
string | Current lifecycle state (e.g. queued, running, succeeded, failed, cancelled). |
Example response
{
"enqueued_task": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"job_id": "string",
"status": "string"
}
Select Machine
POST /api/v1/parts/calculations/{calculation_id}/select-machine
Run geometry extraction and machine selection only, returning the rationale.
Cheaper than a full calculation (skips planning, physics, and economics); typical latency 200–800 ms for a 10-machine fleet.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
calculation_id |
path | string | yes | Identifier of the calculation. |
X-Request-ID |
header | string | no | Optional client-supplied request ID; reused as an idempotency key for the metered charge. |
Idempotency-Key |
header | string | no | Optional key that makes this request safely retryable — see Idempotency. |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/select-machine \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/calculations/{calculation_id}/select-machine",
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/calculations/{calculation_id}/select-machine", {
method: "POST",
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 |
|---|---|---|
calculation_id |
string | Identifier of the calculation the selection was run for. |
candidates |
SelectionCandidatePayload[] | All machines evaluated, with their feasibility and scoring. |
chosen_machine_id |
string | Identifier of the selected machine, or null if none is feasible. |
chosen_machine_name |
string | Name of the selected machine, or null if none is feasible. |
rationale_text |
string | Human-readable explanation of why the machine was or was not selected. |
Example response
{
"calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"candidates": [
{
"blocking_violations": [],
"capability_score": 0,
"feasible": true,
"machine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"machine_name": "string",
"provisional_unit_cost_eur": 0
}
],
"chosen_machine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"chosen_machine_name": "string",
"rationale_text": "string"
}
Bulk Cancel Calculations
POST /api/v1/parts/calculations/bulk-cancel
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
ids |
string[] | yes | Calculation IDs to act on (1–200). Duplicates collapse silently. |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/calculations/bulk-cancel \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/calculations/bulk-cancel",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/parts/calculations/bulk-cancel", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"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 |
|---|---|---|
not_found |
string[] | IDs that did not match a calculation for this tenant. |
skipped |
string[] | IDs skipped because their state didn't allow the action. |
succeeded |
string[] | IDs of calculations the bulk action applied to successfully. |
Example response
{
"not_found": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
],
"skipped": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
],
"succeeded": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}
Bulk Delete Calculations
POST /api/v1/parts/calculations/bulk-delete
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
ids |
string[] | yes | Calculation IDs to act on (1–200). Duplicates collapse silently. |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/calculations/bulk-delete \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/calculations/bulk-delete",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/parts/calculations/bulk-delete", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"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 |
|---|---|---|
not_found |
string[] | IDs that did not match a calculation for this tenant. |
skipped |
string[] | IDs skipped because their state didn't allow the action. |
succeeded |
string[] | IDs of calculations the bulk action applied to successfully. |
Example response
{
"not_found": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
],
"skipped": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
],
"succeeded": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}
Bulk Retry Calculations
POST /api/v1/parts/calculations/bulk-retry
Re-enqueue eligible failed/cancelled/timed_out rows.
Each row goes through the same place-hold + enqueue path as a
direct POST /run so wallet semantics and attempt budgets are
enforced identically. A row that's already running, succeeded,
or out of attempt budget lands in skipped.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
ids |
string[] | yes | Calculation IDs to act on (1–200). Duplicates collapse silently. |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/calculations/bulk-retry \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/calculations/bulk-retry",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"ids": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/parts/calculations/bulk-retry", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"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 |
|---|---|---|
not_found |
string[] | IDs that did not match a calculation for this tenant. |
skipped |
string[] | IDs skipped because their state didn't allow the action. |
succeeded |
string[] | IDs of calculations the bulk action applied to successfully. |
Example response
{
"not_found": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
],
"skipped": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
],
"succeeded": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}
Quote
POST /api/v1/parts/calculations/quote
Convenience: create + enqueue in one round-trip.
Equivalent to POST / + POST /{id}/run.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
annual_volume |
integer | no | Expected yearly quantity, used for amortizing setup over the run (>= 1). |
costing_environment_id |
string | yes | UUID of the costing environment (machine rates, region) to price against. |
currency |
string | no | ISO 4217 currency code for the quote; defaults to the costing environment's currency when omitted. |
dataset_link_id |
string | no | UUID of a specific dataset link to use; omit to use the revision's active primary CAD. |
engine |
string | no | Pricing engine selector; retained for back-compat and always normalized to the sole engine. |
extraction_tier |
auto | lite | full |
no | Drawing-understanding depth. auto (default) lets the platform choose the right depth per drawing; lite is faster and lighter; full is the deepest. Most callers should leave this unset. |
lot_size |
integer | no | Number of identical parts produced per batch (>= 1). |
material_grade_id |
string | no | UUID of a resolved material grade; takes precedence over material_ref when both are supplied. |
material_ref |
string | no | Free-form material reference (URN, Werkstoffnummer, AISI/SAE code, trade name, or your SKU); resolved to a material grade. |
name |
string | no | Optional human-readable label; defaults to a timestamped name when omitted. |
part_revision_id |
string | yes | UUID of the part revision to price. |
region |
string | no | Pricing region override; defaults to the costing environment's region when omitted. |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/calculations/quote \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/calculations/quote",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/parts/calculations/quote", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
202 |
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. |
402 |
insufficient_funds |
The pre-authorised wallet hold for the run exceeds your balance. |
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 202
| Field | Type | Description |
|---|---|---|
enqueued_task |
string | Name of the background task enqueued for this run; null if nothing was scheduled. |
id |
string | UUID of the calculation. |
job_id |
string | Identifier of the queued background job; null when no job was enqueued. |
status |
string | Current lifecycle state (e.g. queued, running, succeeded, failed, cancelled). |
Example response
{
"enqueued_task": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"job_id": "string",
"status": "string"
}
Upload And Quote
POST /api/v1/parts/calculations/upload-and-quote
One-shot upload + new-calculation flow.
Takes a STEP file (+ optional 2D drawing PDF + RFQ text), creates the underlying Part / PartRevision / dataset rows, and enqueues the calculation. This is the simplest end-to-end UX — the user goes from "I have a CAD file" to "I'll see a quote in 30 s" with one HTTP call.
Idempotency: part_number is the natural key — if a Part with
the same number already exists for this tenant, we attach a new
revision rather than re-creating the Part.
Request body (multipart/form-data)
| Field | Type | Required | Description |
|---|---|---|---|
annual_volume |
integer | no | Expected yearly quantity used to amortize setup cost (1 to 1,000,000,000). |
cad_file |
string | yes | 3D CAD file (e.g. STEP) to price; required. |
costing_environment_id |
string | yes | UUID of the costing environment (machine rates, region) to price against. |
drawing_file |
string | no | Optional 2D drawing (PDF/PNG/JPEG) for the part. |
engine |
string | no | Pricing engine selector; retained for back-compat and always normalized to the sole engine. |
extraction_tier |
string | no | Drawing-understanding depth override: auto (default), lite (faster), or full (deepest). Most callers should leave this unset. |
lot_size |
integer | no | Number of identical parts produced per batch (1 to 1,000,000,000). |
material_grade_id |
string | no | UUID of a resolved material grade; takes precedence over material_ref when both are supplied. |
material_ref |
string | no | Free-form material reference (URN, Werkstoffnummer, AISI/SAE code, trade name, or your SKU). |
part_number |
string | yes | Natural-key part number; reused to attach a new revision if the part already exists. |
rfq_file |
string | no | Optional RFQ text file with requirements for the part. |
Request
curl -X POST https://api.arcnm.io/api/v1/parts/calculations/upload-and-quote \
-H "X-API-Key: $ARCNM_API_KEY" \
-F "costing_environment_id=3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-F "part_number=BRACKET-001" \
-F "[email protected]"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/calculations/upload-and-quote",
headers={"X-API-Key": "YOUR_API_KEY"},
files={
"cad_file": open("part.step", "rb"),
},
data={
"costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"part_number": "BRACKET-001",
},
)
resp.raise_for_status()
print(resp.json())
const form = new FormData()
form.append("costing_environment_id", "3fa85f64-5717-4562-b3fc-2c963f66afa6")
form.append("part_number", "BRACKET-001")
form.append("cad_file", file) // a File or Blob
const resp = await fetch("https://api.arcnm.io/api/v1/parts/calculations/upload-and-quote", {
method: "POST",
headers: { "X-API-Key": process.env.ARCNM_API_KEY! },
body: form,
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
202 |
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. |
402 |
insufficient_funds |
The pre-authorised wallet hold for the run exceeds your balance. |
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 202
| Field | Type | Description |
|---|---|---|
enqueued_task |
string | Name of the background task enqueued for this run; null if nothing was scheduled. |
id |
string | UUID of the calculation. |
job_id |
string | Identifier of the queued background job; null when no job was enqueued. |
status |
string | Current lifecycle state (e.g. queued, running, succeeded, failed, cancelled). |
Example response
{
"enqueued_task": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"job_id": "string",
"status": "string"
}