ARCNM

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 the X-API-Key header (see Authentication).

List Calculations

GET /api/v1/calculations

List calculations for the tenant.

Paged newest-first by default with a slim projection — no analytics blob, to keep the list response cheap. Walk the whole collection by following next_cursor (or the Link header) until has_more is false; count is this page's size, never the total.

To reconcile a bulk run against your own records, page with order=asc and a created_after bound: rows come oldest-first, so calculations submitted while you are still walking land after your position instead of shifting rows under it.

Optional part_id narrows the result to a single part. Optional ids turns this into the bulk polling surface: poll one request per sweep, not one per calculation.

Paginated. Pass cursor (from the previous response) to fetch the next page; limit caps the page size.

Parameters

Name In Type Required Description
status_filter query string[] no Filter by lifecycle status (e.g. queued, running, succeeded, failed, cancelled). Repeatable; several values are OR-ed.
part_id query string no Identifier of the part.
batch_id query string no Narrow to one batch's calculations — e.g. every cell of a multi-environment comparison grid. Pair with the batch comparison endpoint, which returns the pivoted matrix.
material_grade_id query string[] no Only runs priced against one of these material grades. Repeatable; several values are OR-ed, so passing every value is the same as passing none.
sort query string no Ordering key. Rows with no value yet (an in-flight run has no unit cost) sort last in both directions. Ignored when ids is set. created_at, lot_size and annual_volume support cursor; unit_cost and finished_at are null until a run settles, so a cursor over them cannot reach every row and is rejected — those two return the first page only.
include_facets query boolean no Also return facets: status and material values with counts, each computed over everything the OTHER active filters allow.
ids query string no Comma-separated calculation IDs (max 500). THE polling surface for bulk clients: one weight-1 sweep returns the status of every listed calculation, instead of N weight-1 detail requests that exhaust the per-org rate budget (self-DoS). When set, limit and ordering are ignored and every matching row is returned.
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.
include_total query boolean no Also return the total number of rows matching the query, across all pages. Off by default because it costs an extra scan; has_more is the cheap way to know whether to keep paging.

Request

curl -X GET https://api.arcnm.io/api/v1/calculations \
  -H "X-API-Key: $ARCNM_API_KEY"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/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/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 on THIS page.
facets object Present only when include_facets is set. Maps each filter name to its selectable values with counts.
has_more boolean Whether more rows match beyond this page. A full page with has_more: false is the end of the collection.
items CalculationListItem[] The calculations on this page, newest first.
next_cursor string Position token for the next page — pass it back as cursor. Null on the last page.
total integer Total rows matching the query across all pages. Null unless include_total=true was requested.

Example response

{
  "count": 0,
  "facets": {},
  "has_more": false,
  "items": [
    {
      "attempt_count": 0,
      "batch_id": "string",
      "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",
      "max_attempts": 0,
      "name": "string",
      "part_id": "string",
      "started_at": "string",
      "status": "string",
      "unit_cost": 12.84
    }
  ],
  "next_cursor": "string",
  "total": 0
}

Create a calculation (queued)

POST /api/v1/calculations

Create a calculation for a part revision in a costing environment. Does NOT enqueue — call POST /calculations/{id}/run to price it.

One endpoint covers every part. An attached 2D drawing is read automatically — there is nothing to configure.

Creates exactly one calculation: costing_environment_ids with more than one entry is rejected here — fan a comparison out via POST /calculations/batch, /quote or /upload-and-quote.

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 no UUID of the costing environment (machine rates, region) to price against. Omit to use your organization's default environment (the auto-provisioned default baseline).
costing_environment_ids string[] no Price this part in several environments at once, in comparison order. Each environment becomes one calculation and counts as one against the plan quota. Mutually exclusive with costing_environment_id; duplicates are collapsed preserving first occurrence. The response then carries batch_id and environment_runs.
currency string no ISO 4217 currency code for the quote; defaults to the costing environment's currency when omitted.
engine string no Pricing engine selector; retained for back-compat and always normalized to the sole engine.
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_is_provided boolean no True when the customer supplies the raw material (beigestellt) — the material cost line is set to zero; machining, setup and overheads are billed normally.
material_ref string no Free-form material reference (URN, Werkstoffnummer, AISI/SAE code, trade name, or your SKU); resolved to a material grade. Unresolvable values are rejected with 422 material_unresolved plus ranked candidates. Omit to price the material read from the part's drawing (or the environment default).
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.
provided_stock_kind none | near_net_profile | near_net_casting | sawn_blank no Shape of the provided stock. 'near_net_profile' (extruded profile) additionally scopes the process plan to the features the profile does not already provide.
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/calculations \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/calculations",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    },
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/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"
  }),
})
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
batch_id string Groups the runs of a multi-environment quote; compare them via GET /calculations/batches/{batch_id}/comparison. Null for a single-environment quote.
costing_environment_id string UUID of the costing environment this calculation is priced against — the one you supplied, or your organization's default when you omitted it. Always echoed so a caller that omitted the field can tell which rates the quote used.
coverage CalculationCoverage Populated only when status='blocked': the calc was created but NOT enqueued because the org has no included calculations left (or is payment-blocked). Explains how many parts can still run and how to resume this one. null whenever the calc was enqueued.
enqueued_task string Name of the background task enqueued for this run; null if nothing was scheduled.
environment_runs EnvironmentRunStatus[] All runs of a multi-environment quote, one per requested environment in comparison order. The top-level fields describe the first created run; poll each run (or the batch comparison) for results. Null for a single-environment quote.
error string Why the calculation failed, when this response already carries a terminal failed status — a run refused at submit time, before any worker. Null in every other case, including a normal enqueue: a run that fails LATER reports through the calculation itself, not through the response that started it.
eta_basis string How eta_seconds was derived, so the number is auditable: org_rate — divided by YOUR organization's own measured completions per second over the recent window; platform_default — your organization has too little recent history, so the estimate comes from your plan's parallelism and the platform's measured service time. Null when no ETA was computed.
eta_seconds number Estimated wait before this calculation reaches a result, derived from its queue position divided by a measured completion rate (advisory); null when not enqueued. See eta_basis for which rate was used.
id string UUID of the calculation.
job_id string Identifier of the queued background job; null when no job was enqueued.
queue_position integer 1-based queue position at enqueue time (backpressure hint); null when not enqueued. With tenant fair scheduling active this is the position among YOUR organization's own queued calculations — your parts run in parallel with other customers, so another tenant's backlog does not push you back.
status string Current lifecycle state: queued, running, succeeded, failed, cancelled, timed_out, or 'blocked' — created but NOT started because the org is out of included calculations (see coverage). A blocked calc has no worker and no job; resume it with POST /run after topping up.

Example response

{
  "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "enqueued_task": "string",
  "environment_runs": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "reason": "string",
      "status": "string"
    }
  ],
  "error": "string",
  "eta_basis": "string",
  "eta_seconds": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "job_id": "string",
  "queue_position": 0,
  "status": "string"
}

Delete Calculation

DELETE /api/v1/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/calculations/{calculation_id} \
  -H "X-API-Key: $ARCNM_API_KEY"
import requests

resp = requests.delete(
    "https://api.arcnm.io/api/v1/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/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/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/calculations/{calculation_id} \
  -H "X-API-Key: $ARCNM_API_KEY"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/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/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 PublicCalculationAnalytics Cost breakdown and value-engineering detail for a priced calculation; null until the calculation succeeds.
annual_volume integer Expected yearly quantity used to amortize setup cost.
confidence object Quote-trust signal: {state: green
costing_environment_id string UUID of the costing environment used for pricing.
coverage CalculationCoverage Populated only when status='blocked': why the calc is parked and how to resume it after topping up. null for every other state. Surfaced as a typed field (not just inside analytics) so API/MCP clients see it without the analytics allow-list stripping it.
created_at string ISO 8601 timestamp when the calculation was created.
currency string ISO 4217 currency code for the cost figures.
dataset_link_id string UUID of the dataset link (source geometry / drawing set) this calculation used. Echoed so a recalculate reuses the same dataset instead of the environment default.
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.
needs_human_review boolean True when the analysis flagged this calculation for human review (low confidence, prompt-injection suspicion, or conflicting sources); surfaced at the top level so API consumers can gate on it.
part_id string UUID of the part this calculation belongs to.
part_revision_id string UUID of the part revision that was priced.
region string Region key used to price this calc; null when the environment's default region was used. Echoed so a recalculate preserves it instead of falling back to the environment default.
review_reasons string[] Plain-language reasons the calculation was flagged for review; empty when not flagged.
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: queued, running, succeeded, failed, cancelled, timed_out, or 'blocked' (created but not started — the org is out of included calculations; see coverage).
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: one-time setup plus lot size × per-part time; 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, including per-part shares of programming and inspection but excluding the one-time lot setup; null until pricing completes.

Example response

{
  "analytics": {
    "cost_decomposition": null,
    "cost_drivers": null,
    "extraction": null,
    "lot_size_curve": null,
    "material_resolution": null,
    "optimization": null,
    "part_requirements": null,
    "pipeline_notes": null,
    "process_plan": null,
    "review": null,
    "secondary_route": null,
    "selection": null,
    "selection_failure": null,
    "time_breakdown": null,
    "unit_cost_interval": null
  },
  "annual_volume": 500,
  "confidence": {},
  "costing_environment_id": "string",
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "created_at": "string",
  "currency": "EUR",
  "dataset_link_id": "string",
  "engine": "arcanum",
  "error": "string",
  "finished_at": "string",
  "id": "string",
  "lot_size": 50,
  "material_grade_id": "string",
  "material_ref": "1.4301",
  "name": "string",
  "needs_human_review": true,
  "part_id": "string",
  "part_revision_id": "string",
  "region": "EU",
  "review_reasons": [
    "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/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/calculations/{calculation_id}/cancel \
  -H "X-API-Key: $ARCNM_API_KEY"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/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/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
batch_id string Groups the runs of a multi-environment quote; compare them via GET /calculations/batches/{batch_id}/comparison. Null for a single-environment quote.
costing_environment_id string UUID of the costing environment this calculation is priced against — the one you supplied, or your organization's default when you omitted it. Always echoed so a caller that omitted the field can tell which rates the quote used.
coverage CalculationCoverage Populated only when status='blocked': the calc was created but NOT enqueued because the org has no included calculations left (or is payment-blocked). Explains how many parts can still run and how to resume this one. null whenever the calc was enqueued.
enqueued_task string Name of the background task enqueued for this run; null if nothing was scheduled.
environment_runs EnvironmentRunStatus[] All runs of a multi-environment quote, one per requested environment in comparison order. The top-level fields describe the first created run; poll each run (or the batch comparison) for results. Null for a single-environment quote.
error string Why the calculation failed, when this response already carries a terminal failed status — a run refused at submit time, before any worker. Null in every other case, including a normal enqueue: a run that fails LATER reports through the calculation itself, not through the response that started it.
eta_basis string How eta_seconds was derived, so the number is auditable: org_rate — divided by YOUR organization's own measured completions per second over the recent window; platform_default — your organization has too little recent history, so the estimate comes from your plan's parallelism and the platform's measured service time. Null when no ETA was computed.
eta_seconds number Estimated wait before this calculation reaches a result, derived from its queue position divided by a measured completion rate (advisory); null when not enqueued. See eta_basis for which rate was used.
id string UUID of the calculation.
job_id string Identifier of the queued background job; null when no job was enqueued.
queue_position integer 1-based queue position at enqueue time (backpressure hint); null when not enqueued. With tenant fair scheduling active this is the position among YOUR organization's own queued calculations — your parts run in parallel with other customers, so another tenant's backlog does not push you back.
status string Current lifecycle state: queued, running, succeeded, failed, cancelled, timed_out, or 'blocked' — created but NOT started because the org is out of included calculations (see coverage). A blocked calc has no worker and no job; resume it with POST /run after topping up.

Example response

{
  "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "enqueued_task": "string",
  "environment_runs": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "reason": "string",
      "status": "string"
    }
  ],
  "error": "string",
  "eta_basis": "string",
  "eta_seconds": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "job_id": "string",
  "queue_position": 0,
  "status": "string"
}

Upload Inputs

POST /api/v1/calculations/{calculation_id}/inputs

Attach a 2D drawing, a mesh, or an RFQ text file to the calculation.

Accepted per role: drawing_2d → PDF, PNG, JPEG; mesh → STL, OBJ; rfq_text → plain text, Markdown, PDF. A mesh carries no B-rep, so it yields envelope, mass and surface area only — attach STEP at creation time for a fully-featured quote.

The file is stored against the calculation's part revision under the requested role. Subsequent POST /run calls auto-discover it by role and fold it into the analysis (drawing, mesh fallback, RFQ text) alongside the primary geometry.

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: a 2D drawing (PDF, PNG or JPEG), an STL/OBJ mesh, or an RFQ text file.
role string yes Role the file plays on the calculation's revision: drawing_2d, mesh, or rfq_text.

Request

curl -X POST https://api.arcnm.io/api/v1/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/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/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
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

{
  "calculation_id": "string",
  "data_source_id": "string",
  "role": "primary",
  "sha256": "9f86d081884c7d659a2feaa0c55ad015…",
  "size_bytes": 204800
}

Patch Calculation Material

PATCH /api/v1/calculations/{calculation_id}/material

Override the material on an existing calculation.

The new material_grade_id is resolved exactly the same way POST /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/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/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/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"
}

Optimization directions for a calculation

GET /api/v1/calculations/{calculation_id}/optimization

Research-grounded cost-optimization directions for one priced calculation: lot sizing, setup reduction, tolerance-cost review, material utilization, external-process benchmarking, plus agent-grade signals (inspection/programming shares, volume-bundling elasticity, DFM issues) and the live supplier-quote gap where a quote exists. Every figure derives from this calculation's own engine results.

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/calculations/{calculation_id}/optimization \
  -H "X-API-Key: $ARCNM_API_KEY"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/calculations/{calculation_id}/optimization",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calculations/{calculation_id}/optimization", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()

Responses

Status Description
200 Successful Response
422 Validation Error

Errors

Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.

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

Response body 200

Field Type Description
calculation_id string Identifier of the calculation.
currency string Currency of the money figures.
findings OptimizationDirection[] Optimization directions; empty when the calculation predates the findings engine or nothing material fired.
status string Calculation status the findings derive from.

Example response

{
  "calculation_id": "string",
  "currency": "EUR",
  "findings": [
    {
      "audience": "string",
      "confidence": "string",
      "kind": "string",
      "lever": "string",
      "method": "string",
      "params": {},
      "saving_eur_per_piece": 0,
      "saving_eur_per_year": 0
    }
  ],
  "status": "string"
}

Run Calculation

POST /api/v1/calculations/{calculation_id}/run

Enqueue the calculation onto the worker queue — subject to coverage.

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.

Coverage: before enqueueing, the org's included-calculation quota is checked. A hard-capped org with no included extraction left FAILS FAST: the calc is parked as blocked (never a queued orphan) and the request raises 402 quota_exceeded with the upgrade path (F-2) — except when the dedup cache can serve an identical prior run, which stays free even over quota. A payment-blocked (dunning) org or an over-spend-cap API key still parks with a coverage payload on a 2xx. A blocked row resumes through this same path once unblocked, without spending a retry attempt.

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/calculations/{calculation_id}/run \
  -H "X-API-Key: $ARCNM_API_KEY"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/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/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
batch_id string Groups the runs of a multi-environment quote; compare them via GET /calculations/batches/{batch_id}/comparison. Null for a single-environment quote.
costing_environment_id string UUID of the costing environment this calculation is priced against — the one you supplied, or your organization's default when you omitted it. Always echoed so a caller that omitted the field can tell which rates the quote used.
coverage CalculationCoverage Populated only when status='blocked': the calc was created but NOT enqueued because the org has no included calculations left (or is payment-blocked). Explains how many parts can still run and how to resume this one. null whenever the calc was enqueued.
enqueued_task string Name of the background task enqueued for this run; null if nothing was scheduled.
environment_runs EnvironmentRunStatus[] All runs of a multi-environment quote, one per requested environment in comparison order. The top-level fields describe the first created run; poll each run (or the batch comparison) for results. Null for a single-environment quote.
error string Why the calculation failed, when this response already carries a terminal failed status — a run refused at submit time, before any worker. Null in every other case, including a normal enqueue: a run that fails LATER reports through the calculation itself, not through the response that started it.
eta_basis string How eta_seconds was derived, so the number is auditable: org_rate — divided by YOUR organization's own measured completions per second over the recent window; platform_default — your organization has too little recent history, so the estimate comes from your plan's parallelism and the platform's measured service time. Null when no ETA was computed.
eta_seconds number Estimated wait before this calculation reaches a result, derived from its queue position divided by a measured completion rate (advisory); null when not enqueued. See eta_basis for which rate was used.
id string UUID of the calculation.
job_id string Identifier of the queued background job; null when no job was enqueued.
queue_position integer 1-based queue position at enqueue time (backpressure hint); null when not enqueued. With tenant fair scheduling active this is the position among YOUR organization's own queued calculations — your parts run in parallel with other customers, so another tenant's backlog does not push you back.
status string Current lifecycle state: queued, running, succeeded, failed, cancelled, timed_out, or 'blocked' — created but NOT started because the org is out of included calculations (see coverage). A blocked calc has no worker and no job; resume it with POST /run after topping up.

Example response

{
  "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "enqueued_task": "string",
  "environment_runs": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "reason": "string",
      "status": "string"
    }
  ],
  "error": "string",
  "eta_basis": "string",
  "eta_seconds": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "job_id": "string",
  "queue_position": 0,
  "status": "string"
}

Batch re-cost a set of part revisions

POST /api/v1/calculations/batch

Create + enqueue one calculation per revision in a single request, grouped under a CalculationBatch — the path an agent triggers when a market signal (a material price move) makes a batch re-cost worth running. Each run is metered on success against the org's subscription (v5/v6 quota + flat overage), exactly like a single /run — there is no separate per-call wallet draw. Free orgs over their included quota are rejected up front (HTTP 402).

Selection is explicit (part_revision_ids) for now; richer criteria (by material category, above-should-cost, …) layer on top of this path later.

Request body (application/json)

Field Type Required Description
annual_volume integer no
batch_name string no
costing_environment_id string no Single environment to cost against. Omit together with costing_environment_ids to use the organization's baseline environment.
costing_environment_ids string[] no Environments to cost each revision against, in comparison order. Every (revision, environment) pair becomes one calculation and counts as one against the plan quota. Mutually exclusive with costing_environment_id. Duplicates are collapsed preserving first occurrence; revisions x environments may not exceed 500.
currency string no
engine string no
engines string[] no Engines to run per revision. Today a single engine is honoured and any other value is coerced to it. The list shape is preserved for forward-compat with future engines.
language string no
lot_size integer no
material_grade_id string no
material_is_provided boolean no
material_ref string no Free-form material reference (URN, Werkstoffnummer, AISI/SAE code, trade name, or your SKU); resolved to a material grade for every calculation in the batch. Unresolvable values are rejected with 422 material_unresolved plus ranked candidates. Omit to price the material read from each part's drawing (or the environment default).
part_revision_ids string[] yes
provided_stock_kind none | near_net_profile | near_net_casting | sawn_blank no
raw_material_strategy RawMaterialStrategy no
region string no
surface_treatments string[] no Deprecated. Surface treatments (e.g. anodizing, zinc plating) do not set the priced cost — treatments are read from the part's drawing and priced on cost_decomposition.subcontract_cost — but this field is part of a calculation's cache identity, so two otherwise-identical batch runs that differ only here are priced as distinct calculations instead of one reusing the other's result. Prefer specifying treatments on the drawing.

Request

curl -X POST https://api.arcnm.io/api/v1/calculations/batch \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "part_revision_ids": [
      "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    ]
  }'
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/calculations/batch",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "part_revision_ids": [
            "3fa85f64-5717-4562-b3fc-2c963f66afa6"
        ]
    },
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calculations/batch", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "part_revision_ids": [
      "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.
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
batch_id string Groups the created calculations.
blocked integer Cells created but parked on billing (saved, waiting on a an upgrade or a raised spend cap — see coverage); never enqueued, resumable once billing allows.
costing_environment_ids string[] The environments costed against, in comparison order.
coverage CalculationCoverage Why cells were parked, when blocked > 0 — the same payload a single-run park response carries. null when nothing parked.
enqueued integer Calculations created and enqueued.
items BatchCalculationItem[]
requested integer Requested cells: distinct revisions × distinct environments.
served_from_cache integer Cells answered instantly from an identical prior run within the billing window — never billed.
skipped integer Cells skipped for structural reasons or unquotable environments (see each item).

Example response

{
  "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "blocked": 0,
  "costing_environment_ids": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "enqueued": 0,
  "items": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "reason": "string",
      "status": "string"
    }
  ],
  "requested": 0,
  "served_from_cache": 0,
  "skipped": 0
}

Cancel every non-terminal calculation in a batch

POST /api/v1/calculations/batches/{batch_id}/cancel

Convenience for a whole comparison grid: cancels the batch's queued/running members in one call — the per-id equivalent of /bulk-cancel without a round-trip to collect the ids first. Terminal members are reported skipped, exactly as the id-list route would.

Parameters

Name In Type Required Description
batch_id path string yes Identifier of the batch.

Request

curl -X POST https://api.arcnm.io/api/v1/calculations/batches/{batch_id}/cancel \
  -H "X-API-Key: $ARCNM_API_KEY"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/calculations/batches/{batch_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/calculations/batches/{batch_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
blocked string[] IDs parked as 'blocked' instead of run: the org is out of included calculations (or is payment-blocked), so they were saved but NOT enqueued. Resume them with bulk-retry after topping up. Distinct from 'skipped' (wrong state) so the UI can prompt the unblock path, not an error.
coverage CalculationCoverage The billing hold the blocked ids are parked under — how many calculations remain and how to unblock them. Present only when something parked. Mirrors the same field on a batch submit, so a client renders one 'awaiting credits' path whichever endpoint parked the work.
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.
unquotable string[] IDs failed immediately instead of run: their environment has no machine that can be evaluated, so the calculation could not succeed however long it ran. Each row carries the reason. Distinct from 'blocked' (the org's wallet, resolvable by topping up) and from 'skipped' (wrong state) — this one is resolved by fixing the environment.

Example response

{
  "blocked": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "not_found": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "skipped": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "succeeded": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "unquotable": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ]
}

Compare a batch's calculations across environments

GET /api/v1/calculations/batches/{batch_id}/comparison

Pivot one batch into a parts × environments matrix: per-cell costs and times, best environment per part (ties included), deltas vs the cheapest and vs the baseline environment, and per-environment aggregates (wins, median delta, basket total). All figures are computed server-side, so every consumer sees the same comparison. Poll while complete is false — cells fill in as their runs finish.

Parameters

Name In Type Required Description
batch_id path string yes Identifier of the batch.

Request

curl -X GET https://api.arcnm.io/api/v1/calculations/batches/{batch_id}/comparison \
  -H "X-API-Key: $ARCNM_API_KEY"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/calculations/batches/{batch_id}/comparison",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calculations/batches/{batch_id}/comparison", {
  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
baseline_environment_id string The delta anchor environment.
batch_id string
complete boolean True when every cell is terminal or parked — nothing is still computing.
environments ComparisonEnvironmentSummary[]
generated_at string
ignored_duplicates string[] Calculations dropped because a newer run of the same part in the same environment was also supplied (ad-hoc comparisons only).
mixed_calibration boolean True when the compared environments were not all priced under the same calibration — at least one column is aligned to your reported costs and another is the engine's unaligned estimate. The deltas are still real, but part of the gap may be alignment rather than the environments themselves, so a winner here is worth confirming by calibrating the other environments too. Deliberately a flag and not an incomparability: excluding uncalibrated columns would leave a tenant who has calibrated exactly one environment with no comparison at all.
mixed_currency boolean True when cells carry more than one currency; deltas, wins and basket totals are then restricted to same-currency comparisons.
not_found string[] Requested calculation ids that do not exist in your organization (ad-hoc comparisons only).
parts ComparisonPartRow[]

Example response

{
  "baseline_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "complete": false,
  "environments": [
    {
      "basket_total": 0,
      "blocked": 0,
      "calibrated_cells": 0,
      "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "currency": "EUR",
      "failed": 0,
      "median_delta_vs_baseline_pct": 0,
      "name": "string",
      "not_run": 0,
      "pending": 0,
      "stale_cells": 0,
      "succeeded": 0,
      "wins": 0
    }
  ],
  "generated_at": "2026-06-01T12:00:00Z",
  "ignored_duplicates": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "mixed_calibration": false,
  "mixed_currency": false,
  "not_found": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "parts": [
    {
      "best_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "cells": [
        {}
      ],
      "lot_size": 50,
      "part_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "part_number": "BRACKET-001",
      "part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    }
  ]
}

Bulk Cancel Calculations

POST /api/v1/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/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/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/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
blocked string[] IDs parked as 'blocked' instead of run: the org is out of included calculations (or is payment-blocked), so they were saved but NOT enqueued. Resume them with bulk-retry after topping up. Distinct from 'skipped' (wrong state) so the UI can prompt the unblock path, not an error.
coverage CalculationCoverage The billing hold the blocked ids are parked under — how many calculations remain and how to unblock them. Present only when something parked. Mirrors the same field on a batch submit, so a client renders one 'awaiting credits' path whichever endpoint parked the work.
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.
unquotable string[] IDs failed immediately instead of run: their environment has no machine that can be evaluated, so the calculation could not succeed however long it ran. Each row carries the reason. Distinct from 'blocked' (the org's wallet, resolvable by topping up) and from 'skipped' (wrong state) — this one is resolved by fixing the environment.

Example response

{
  "blocked": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "not_found": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "skipped": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "succeeded": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "unquotable": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ]
}

Bulk Delete Calculations

POST /api/v1/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/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/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/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
blocked string[] IDs parked as 'blocked' instead of run: the org is out of included calculations (or is payment-blocked), so they were saved but NOT enqueued. Resume them with bulk-retry after topping up. Distinct from 'skipped' (wrong state) so the UI can prompt the unblock path, not an error.
coverage CalculationCoverage The billing hold the blocked ids are parked under — how many calculations remain and how to unblock them. Present only when something parked. Mirrors the same field on a batch submit, so a client renders one 'awaiting credits' path whichever endpoint parked the work.
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.
unquotable string[] IDs failed immediately instead of run: their environment has no machine that can be evaluated, so the calculation could not succeed however long it ran. Each row carries the reason. Distinct from 'blocked' (the org's wallet, resolvable by topping up) and from 'skipped' (wrong state) — this one is resolved by fixing the environment.

Example response

{
  "blocked": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "not_found": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "skipped": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "succeeded": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "unquotable": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ]
}

Bulk Retry Calculations

POST /api/v1/calculations/bulk-retry

Re-enqueue eligible failed/cancelled/timed_out rows.

Each row goes through the same quota-gate + enqueue path as a direct POST /run so subscription semantics and attempt budgets are enforced identically. A row that's already running, succeeded, out of attempt budget, or over a Free org's included quota lands in skipped. A row whose environment can make nothing lands in unquotable, failed immediately rather than enqueued to fail slowly.

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/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/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/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
blocked string[] IDs parked as 'blocked' instead of run: the org is out of included calculations (or is payment-blocked), so they were saved but NOT enqueued. Resume them with bulk-retry after topping up. Distinct from 'skipped' (wrong state) so the UI can prompt the unblock path, not an error.
coverage CalculationCoverage The billing hold the blocked ids are parked under — how many calculations remain and how to unblock them. Present only when something parked. Mirrors the same field on a batch submit, so a client renders one 'awaiting credits' path whichever endpoint parked the work.
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.
unquotable string[] IDs failed immediately instead of run: their environment has no machine that can be evaluated, so the calculation could not succeed however long it ran. Each row carries the reason. Distinct from 'blocked' (the org's wallet, resolvable by topping up) and from 'skipped' (wrong state) — this one is resolved by fixing the environment.

Example response

{
  "blocked": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "not_found": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "skipped": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "succeeded": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "unquotable": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ]
}

Get Calculation Capacity

GET /api/v1/calculations/capacity

Introspect this organization's calculation capacity and queue state.

Read-only snapshot for bulk submitters: how many calculations may run in parallel (inflight_cap), the plan's pending bound and how much of it is used, and the advisory global queue depth. Poll it to pace a bulk submission instead of discovering limits through 429 responses.

Request

curl -X GET https://api.arcnm.io/api/v1/calculations/capacity \
  -H "X-API-Key: $ARCNM_API_KEY"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/calculations/capacity",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calculations/capacity", {
  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
admission_enabled boolean Whether submit-path admission control is active. When false, pending_bound is not enforced (submits always enqueue).
completion_rate_per_s number Completions per second the ETAs below were divided by — your organization's own measured rate over the recent window when eta_basis is org_rate, otherwise the plan-share estimate used in its place. Null when nothing is queued. Divide any position by this to reproduce its ETA.
eta_basis string Which rate the ETAs below were divided by: org_rate (your own measured throughput) or platform_default (a plan-share estimate for an organization with too little history). Null when nothing is queued.
fair_scheduling boolean Whether tenant-fair scheduling is active. When true, each organization's calculations are scheduled from its own queue and served in parallel with other customers — queue_position values are per-organization, and another tenant's backlog cannot delay the start of yours.
inflight_cap integer Maximum calculations this organization may have RUNNING concurrently across the worker pool (tier-weighted fairness cap). Enforced only when inflight_enabled is true; calculations over the cap stay queued and start as slots free up.
inflight_enabled boolean Whether the per-organization concurrency cap is actively enforced. When false, inflight_cap is advisory only.
inflight_now integer Calculations currently running for this organization; null when Redis is unreachable.
pending_bound integer Your plan's pending-calculation bound: the maximum queued + running calculations this organization may hold at once. Submits beyond it are rejected with HTTP 429 until earlier calculations drain.
pending_count integer This organization's calculations currently queued or running (its admitted footprint).
queue CalculationQueueEntry[] Your organization's own waiting calculations, in the order they will be served, each with its 1-based position and ETA. Empty when nothing is waiting or when fair scheduling is off (with it off there is no per-organization queue to report a position in). Capped at 200 entries; queue_pending_total is the untruncated count.
queue_depth integer Approximate total depth of the calculation queue across all tenants — advisory, feeds the queue_position / ETA hints on submit; null when Redis is unreachable.
queue_pending_total integer Total calculations waiting in your organization's queue, before the 200-entry cap applied to queue.
tier_slug string Subscription tier the caps below are derived from (e.g. free, pro, scale, enterprise); null when the org has no resolved tier (defaults apply).

Example response

{
  "admission_enabled": true,
  "completion_rate_per_s": 184,
  "eta_basis": "string",
  "fair_scheduling": false,
  "inflight_cap": 0,
  "inflight_enabled": true,
  "inflight_now": 0,
  "pending_bound": 0,
  "pending_count": 0,
  "queue": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "eta_seconds": 0,
      "position": 0
    }
  ],
  "queue_depth": 0,
  "queue_pending_total": 0,
  "tier_slug": "string"
}

Compare an ad-hoc set of calculations across environments

POST /api/v1/calculations/comparison

The same parts × environments matrix as the batch comparison, for any set of your calculation ids — e.g. re-runs of the same parts from different days or batches. A read expressed as POST only because id lists don't fit in a query string; nothing is created or modified. Cells whose configuration diverges from their row's anchor are flagged incomparable_reasons instead of being silently compared.

Request body (application/json)

Field Type Required Description
baseline_environment_id string no Delta anchor. Omit to anchor on your organization's baseline environment when the compared calculations include it, else on the first calculation's environment. Supplying an environment none of the calculations used anchors on the first calculation's environment.
calculation_ids string[] yes Calculations to compare (duplicates collapse). Ids that do not exist in your organization are reported in not_found, never guessed at.

Request

curl -X POST https://api.arcnm.io/api/v1/calculations/comparison \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "calculation_ids": [
      "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    ]
  }'
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/calculations/comparison",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "calculation_ids": [
            "3fa85f64-5717-4562-b3fc-2c963f66afa6"
        ]
    },
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calculations/comparison", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "calculation_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
baseline_environment_id string The delta anchor environment.
batch_id string
complete boolean True when every cell is terminal or parked — nothing is still computing.
environments ComparisonEnvironmentSummary[]
generated_at string
ignored_duplicates string[] Calculations dropped because a newer run of the same part in the same environment was also supplied (ad-hoc comparisons only).
mixed_calibration boolean True when the compared environments were not all priced under the same calibration — at least one column is aligned to your reported costs and another is the engine's unaligned estimate. The deltas are still real, but part of the gap may be alignment rather than the environments themselves, so a winner here is worth confirming by calibrating the other environments too. Deliberately a flag and not an incomparability: excluding uncalibrated columns would leave a tenant who has calibrated exactly one environment with no comparison at all.
mixed_currency boolean True when cells carry more than one currency; deltas, wins and basket totals are then restricted to same-currency comparisons.
not_found string[] Requested calculation ids that do not exist in your organization (ad-hoc comparisons only).
parts ComparisonPartRow[]

Example response

{
  "baseline_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "complete": false,
  "environments": [
    {
      "basket_total": 0,
      "blocked": 0,
      "calibrated_cells": 0,
      "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "currency": "EUR",
      "failed": 0,
      "median_delta_vs_baseline_pct": 0,
      "name": "string",
      "not_run": 0,
      "pending": 0,
      "stale_cells": 0,
      "succeeded": 0,
      "wins": 0
    }
  ],
  "generated_at": "2026-06-01T12:00:00Z",
  "ignored_duplicates": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "mixed_calibration": false,
  "mixed_currency": false,
  "not_found": [
    "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  ],
  "parts": [
    {
      "best_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "cells": [
        {}
      ],
      "lot_size": 50,
      "part_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "part_number": "BRACKET-001",
      "part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    }
  ]
}

Remaining calculation quota

GET /api/v1/calculations/entitlement

This organization's remaining included-calculation quota for the current window, plus its monthly overage budget. Check it before a bulk run so you can upgrade the plan (or raise the overage cap) up front instead of discovering the wall when a calc parks as blocked.

Request

curl -X GET https://api.arcnm.io/api/v1/calculations/entitlement \
  -H "X-API-Key: $ARCNM_API_KEY"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/calculations/entitlement",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calculations/entitlement", {
  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
effective integer Included plus any one-window rollover — the real ceiling this window.
guidance string What the numbers mean and what to do next.
hard_capped boolean True when the plan blocks further runs at the ceiling; false when over-quota runs bill as overage instead of blocking.
has_payment_method boolean Whether a card is on file. Required before any calculation beyond the included volume on a 'wallet' plan.
included integer Calculations included in the plan per window.
over_quota boolean True when used has reached the effective ceiling.
overage_budget_remaining_cents integer Overage spend still inside the cap, in cents; null when uncapped.
overage_cap_cents integer This organization's monthly overage spend cap in cents, or null when uncapped. Bounds what over-quota runs may cost; it does not affect included calculations.
overage_cents integer Price of one calculation beyond the included volume, in cents. Null when this plan cannot bill overage.
overage_settlement string How overage is paid: 'wallet' (prepaid balance — top up to continue) or 'invoice' (contracted, billed in arrears). Empty when the plan cannot bill overage.
overage_spend_cents integer Overage already billed this calendar month, in cents.
payment_blocked boolean True when billing is suspended (dunning) and no calculation will run.
plan string Subscription tier slug the quota applies to.
remaining integer Calculations still available this window (effective − used, floored at 0).
rollover integer Unused calculations carried in from last window.
spend_cap_blocked boolean True when the organization's overage cap is reached and set to 'pause' — further over-quota runs park as 'blocked' until the cap is raised or the month rolls over.
used integer Calculations already consumed this window.
wallet_balance_cents integer Prepaid balance in cents. Only meaningful for 'wallet' settlement.
wallet_funded_calculations integer How many more overage calculations the current balance covers. Null when settlement is not 'wallet'.
wallet_overdraft_cents integer How far the prepaid balance is below zero, as a positive number of cents. 0 for a wallet that is not overdrawn.
wallet_overdraft_floor_cents integer The hard floor on the prepaid balance, as a positive number of cents: the balance can never go below its negation. Runs pause once it is reached.
wallet_overdrawn boolean True when the prepaid balance has reached its hard floor. The wallet analogue of an exhausted hard cap: further runs park as 'blocked' and resume on a top-up, and the balance cannot be driven any further negative.
window string Quota window granularity: 'month' or 'year'.
window_start string ISO 8601 start of the current quota window.

Example response

{
  "effective": 0,
  "guidance": "string",
  "hard_capped": true,
  "has_payment_method": false,
  "included": 0,
  "over_quota": true,
  "overage_budget_remaining_cents": 0,
  "overage_cap_cents": 0,
  "overage_cents": 0,
  "overage_settlement": "",
  "overage_spend_cents": 0,
  "payment_blocked": true,
  "plan": "string",
  "remaining": 0,
  "rollover": 0,
  "spend_cap_blocked": false,
  "used": 0,
  "wallet_balance_cents": 0,
  "wallet_funded_calculations": 0,
  "wallet_overdraft_cents": 0,
  "wallet_overdraft_floor_cents": 0,
  "wallet_overdrawn": false,
  "window": "string",
  "window_start": "string"
}

Quote

POST /api/v1/calculations/quote

Convenience: create + enqueue in one round-trip.

Equivalent to POST / + POST /{id}/run. With costing_environment_ids the part is priced in every requested environment at once (one calculation per environment, grouped under batch_id for comparison).

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 no UUID of the costing environment (machine rates, region) to price against. Omit to use your organization's default environment (the auto-provisioned default baseline).
costing_environment_ids string[] no Price this part in several environments at once, in comparison order. Each environment becomes one calculation and counts as one against the plan quota. Mutually exclusive with costing_environment_id; duplicates are collapsed preserving first occurrence. The response then carries batch_id and environment_runs.
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.
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_is_provided boolean no True when the customer supplies the raw material (beigestellt) — the material cost line is set to zero; machining, setup and overheads are billed normally.
material_ref string no Free-form material reference (URN, Werkstoffnummer, AISI/SAE code, trade name, or your SKU); resolved to a material grade. Unresolvable values are rejected with 422 material_unresolved plus ranked candidates. Omit to price the material read from the part's drawing (or the environment default).
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.
provided_stock_kind none | near_net_profile | near_net_casting | sawn_blank no Shape of the provided stock. 'near_net_profile' (extruded profile) additionally scopes the process plan to the features the profile does not already provide.
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/calculations/quote \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/calculations/quote",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    },
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/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"
  }),
})
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
batch_id string Groups the runs of a multi-environment quote; compare them via GET /calculations/batches/{batch_id}/comparison. Null for a single-environment quote.
costing_environment_id string UUID of the costing environment this calculation is priced against — the one you supplied, or your organization's default when you omitted it. Always echoed so a caller that omitted the field can tell which rates the quote used.
coverage CalculationCoverage Populated only when status='blocked': the calc was created but NOT enqueued because the org has no included calculations left (or is payment-blocked). Explains how many parts can still run and how to resume this one. null whenever the calc was enqueued.
enqueued_task string Name of the background task enqueued for this run; null if nothing was scheduled.
environment_runs EnvironmentRunStatus[] All runs of a multi-environment quote, one per requested environment in comparison order. The top-level fields describe the first created run; poll each run (or the batch comparison) for results. Null for a single-environment quote.
error string Why the calculation failed, when this response already carries a terminal failed status — a run refused at submit time, before any worker. Null in every other case, including a normal enqueue: a run that fails LATER reports through the calculation itself, not through the response that started it.
eta_basis string How eta_seconds was derived, so the number is auditable: org_rate — divided by YOUR organization's own measured completions per second over the recent window; platform_default — your organization has too little recent history, so the estimate comes from your plan's parallelism and the platform's measured service time. Null when no ETA was computed.
eta_seconds number Estimated wait before this calculation reaches a result, derived from its queue position divided by a measured completion rate (advisory); null when not enqueued. See eta_basis for which rate was used.
id string UUID of the calculation.
job_id string Identifier of the queued background job; null when no job was enqueued.
queue_position integer 1-based queue position at enqueue time (backpressure hint); null when not enqueued. With tenant fair scheduling active this is the position among YOUR organization's own queued calculations — your parts run in parallel with other customers, so another tenant's backlog does not push you back.
status string Current lifecycle state: queued, running, succeeded, failed, cancelled, timed_out, or 'blocked' — created but NOT started because the org is out of included calculations (see coverage). A blocked calc has no worker and no job; resume it with POST /run after topping up.

Example response

{
  "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "enqueued_task": "string",
  "environment_runs": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "reason": "string",
      "status": "string"
    }
  ],
  "error": "string",
  "eta_basis": "string",
  "eta_seconds": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "job_id": "string",
  "queue_position": 0,
  "status": "string"
}

Upload And Quote

POST /api/v1/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, on_conflict decides what happens, and filing in the response says what did.

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 STEP file (.step/.stp) to price; required — STEP is the only 3D format accepted. Export as STEP AP203/AP214/AP242.
costing_environment_id string no UUID of the costing environment (machine rates, region) to price against. Omit to use your organization's default environment (the auto-provisioned default baseline).
costing_environment_ids string[] no Price the part in several environments at once — repeat the field once per environment id, in comparison order. The file is uploaded once; each environment becomes one calculation and counts as one against the plan quota. The response then carries batch_id and environment_runs. Mutually exclusive with costing_environment_id; at most 16.
currency string no ISO 4217 currency code for the quote; defaults to the costing environment's currency when omitted.
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.
folder_id string no Optional workspace folder to file the created part into (bulk upload into a folder).
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_is_provided boolean no True when the customer supplies the raw material (beigestellt) — the material cost line is set to zero.
material_ref string no Free-form material reference (URN, Werkstoffnummer, AISI/SAE code, trade name, or your SKU). Unresolvable values are rejected with 422 material_unresolved plus ranked candidates. Omit to price the material read from the part's drawing (or the environment default).
on_conflict keep_folder | move | new_part | skip no How to resolve a part_number that already exists in this workspace. keep_folder — add a revision, leave the part where it is (the historical behaviour, now stated rather than assumed); move — add a revision AND re-file the part into folder_id; new_part — create a separate part under a distinct number (977 → 977-2); skip — upload nothing, create no calculation, meter nothing. Whatever happens is reported in filing.
part_number string yes Natural-key part number; reused to attach a new revision if the part already exists.
provided_stock_kind none | near_net_profile | near_net_casting | sawn_blank no Shape of the provided stock; 'near_net_profile' scopes the plan to the features the profile does not already provide.
region string no Pricing region override; defaults to the costing environment's region when omitted.
rfq_file string no Optional RFQ text file with requirements for the part.

Request

curl -X POST https://api.arcnm.io/api/v1/calculations/upload-and-quote \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -F "part_number=BRACKET-001" \
  -F "[email protected]"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/calculations/upload-and-quote",
    headers={"X-API-Key": "YOUR_API_KEY"},
    files={
        "cad_file": open("part.step", "rb"),
    },
    data={
        "part_number": "BRACKET-001",
    },
)
resp.raise_for_status()
print(resp.json())
const form = new FormData()
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/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
batch_id string Groups the runs of a multi-environment quote; compare them via GET /calculations/batches/{batch_id}/comparison. Null for a single-environment quote.
costing_environment_id string UUID of the costing environment this calculation is priced against — the one you supplied, or your organization's default when you omitted it. Always echoed so a caller that omitted the field can tell which rates the quote used.
coverage CalculationCoverage Populated only when status='blocked': the calc was created but NOT enqueued because the org has no included calculations left (or is payment-blocked). Explains how many parts can still run and how to resume this one. null whenever the calc was enqueued.
enqueued_task string Name of the background task enqueued for this run; null if nothing was scheduled.
environment_runs EnvironmentRunStatus[] All runs of a multi-environment quote, one per requested environment in comparison order. The top-level fields describe the first created run; poll each run (or the batch comparison) for results. Null for a single-environment quote.
error string Why the calculation failed, when this response already carries a terminal failed status — a run refused at submit time, before any worker. Null in every other case, including a normal enqueue: a run that fails LATER reports through the calculation itself, not through the response that started it.
eta_basis string How eta_seconds was derived, so the number is auditable: org_rate — divided by YOUR organization's own measured completions per second over the recent window; platform_default — your organization has too little recent history, so the estimate comes from your plan's parallelism and the platform's measured service time. Null when no ETA was computed.
eta_seconds number Estimated wait before this calculation reaches a result, derived from its queue position divided by a measured completion rate (advisory); null when not enqueued. See eta_basis for which rate was used.
filing PartFilingOutcome What happened to the part and its folder. Always present on the upload routes. Check folder_applied before telling a user their files were filed where they asked.
id string UUID of the calculation. Null only when filing.action_taken is skipped — you asked for the conflict to be skipped, so no calculation was created.
job_id string Identifier of the queued background job; null when no job was enqueued.
queue_position integer 1-based queue position at enqueue time (backpressure hint); null when not enqueued. With tenant fair scheduling active this is the position among YOUR organization's own queued calculations — your parts run in parallel with other customers, so another tenant's backlog does not push you back.
status string Current lifecycle state: queued, running, succeeded, failed, cancelled, timed_out, or 'blocked' — created but NOT started because the org is out of included calculations (see coverage). A blocked calc has no worker and no job; resume it with POST /run after topping up.

Example response

{
  "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "enqueued_task": "string",
  "environment_runs": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "reason": "string",
      "status": "string"
    }
  ],
  "error": "string",
  "eta_basis": "string",
  "eta_seconds": 0,
  "filing": {
    "action_taken": "string",
    "collided_with_part_number": "string",
    "existing_part": true,
    "folder_applied": true,
    "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "part_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "part_number": "BRACKET-001",
    "previous_folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "requested_folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  },
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "job_id": "string",
  "queue_position": 0,
  "status": "string"
}

Upload (base64 JSON) + quote in one call

POST /api/v1/calculations/upload-and-quote-json

JSON-body twin of /upload-and-quote for clients that can only pass strings (MCP): the CAD file — and optional drawing / RFQ — ride as base64 (or data: URI) strings. Best for small single-part STEP files; base64 inflates ~33% and rides inside the call, so use the presign flow for large assemblies. Same find-or-create-by-part_number idempotency, billing, and enqueue semantics as the multipart route.

Request body (application/json)

Field Type Required Description
annual_volume integer no Expected yearly quantity used to amortize setup cost (1 to 1,000,000,000).
cad_content_type string no MIME type of the CAD file, when known.
cad_file_b64 string yes Base64 (or a data: URI) of the 3D CAD file (e.g. STEP). Required.
cad_filename string yes CAD filename including extension, e.g. bracket.step — used for format detection.
costing_environment_id string no UUID of the costing environment to price against. Omit to use your organization's default environment.
costing_environment_ids string[] no Price the part in several environments at once, in comparison order. The file is uploaded once; each environment becomes one calculation and counts as one against the plan quota. The response then carries batch_id and environment_runs. Mutually exclusive with costing_environment_id.
currency string no ISO 4217 currency code for the quote; defaults to the costing environment's currency when omitted.
drawing_content_type string no MIME type of the drawing file, when known.
drawing_file_b64 string no Optional base64 (or data: URI) of a 2D drawing (PDF/PNG/JPEG).
drawing_filename string no Drawing filename including extension.
engine string no Pricing engine selector; retained for back-compat and always normalized to the sole engine.
folder_id string no Optional workspace folder to file the created part into.
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_is_provided boolean no True when the customer supplies the raw material (beigestellt).
material_ref string no Free-form material reference (URN, Werkstoffnummer, AISI/SAE code, trade name, or your SKU). Unresolvable values are rejected with 422 material_unresolved plus ranked candidates. Omit to price the material read from the part's drawing (or the environment default).
on_conflict keep_folder | move | new_part | skip no How to resolve a part_number that already exists: keep_folder (add a revision, leave the part where it is — the historical behaviour), move (add a revision and re-file into folder_id), new_part (create a separate part under a distinct number), or skip (upload nothing, create no calculation, meter nothing). The outcome is always reported in filing.
part_number string yes Natural-key part number; reused to attach a new revision if the part already exists.
provided_stock_kind none | near_net_profile | near_net_casting | sawn_blank no Shape of the provided stock; 'near_net_profile' scopes the plan.
region string no Pricing region override; defaults to the costing environment's region when omitted.
rfq_content_type string no MIME type of the RFQ file, when known.
rfq_file_b64 string no Optional base64 (or data: URI) of an RFQ text file.
rfq_filename string no RFQ filename including extension.

Request

curl -X POST https://api.arcnm.io/api/v1/calculations/upload-and-quote-json \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "part_number": "BRACKET-001",
    "cad_file_b64": "string",
    "cad_filename": "bracket.step"
  }'
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/calculations/upload-and-quote-json",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "part_number": "BRACKET-001",
        "cad_file_b64": "string",
        "cad_filename": "bracket.step"
    },
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calculations/upload-and-quote-json", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "part_number": "BRACKET-001",
    "cad_file_b64": "string",
    "cad_filename": "bracket.step"
  }),
})
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.
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
batch_id string Groups the runs of a multi-environment quote; compare them via GET /calculations/batches/{batch_id}/comparison. Null for a single-environment quote.
costing_environment_id string UUID of the costing environment this calculation is priced against — the one you supplied, or your organization's default when you omitted it. Always echoed so a caller that omitted the field can tell which rates the quote used.
coverage CalculationCoverage Populated only when status='blocked': the calc was created but NOT enqueued because the org has no included calculations left (or is payment-blocked). Explains how many parts can still run and how to resume this one. null whenever the calc was enqueued.
enqueued_task string Name of the background task enqueued for this run; null if nothing was scheduled.
environment_runs EnvironmentRunStatus[] All runs of a multi-environment quote, one per requested environment in comparison order. The top-level fields describe the first created run; poll each run (or the batch comparison) for results. Null for a single-environment quote.
error string Why the calculation failed, when this response already carries a terminal failed status — a run refused at submit time, before any worker. Null in every other case, including a normal enqueue: a run that fails LATER reports through the calculation itself, not through the response that started it.
eta_basis string How eta_seconds was derived, so the number is auditable: org_rate — divided by YOUR organization's own measured completions per second over the recent window; platform_default — your organization has too little recent history, so the estimate comes from your plan's parallelism and the platform's measured service time. Null when no ETA was computed.
eta_seconds number Estimated wait before this calculation reaches a result, derived from its queue position divided by a measured completion rate (advisory); null when not enqueued. See eta_basis for which rate was used.
filing PartFilingOutcome What happened to the part and its folder. Always present on the upload routes. Check folder_applied before telling a user their files were filed where they asked.
id string UUID of the calculation. Null only when filing.action_taken is skipped — you asked for the conflict to be skipped, so no calculation was created.
job_id string Identifier of the queued background job; null when no job was enqueued.
queue_position integer 1-based queue position at enqueue time (backpressure hint); null when not enqueued. With tenant fair scheduling active this is the position among YOUR organization's own queued calculations — your parts run in parallel with other customers, so another tenant's backlog does not push you back.
status string Current lifecycle state: queued, running, succeeded, failed, cancelled, timed_out, or 'blocked' — created but NOT started because the org is out of included calculations (see coverage). A blocked calc has no worker and no job; resume it with POST /run after topping up.

Example response

{
  "batch_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "coverage": {
    "included": 0,
    "message": "string",
    "reason": "string",
    "remaining": 0,
    "tier_slug": "string",
    "upgrade_url": "string",
    "used": 0,
    "window": "string"
  },
  "enqueued_task": "string",
  "environment_runs": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "costing_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "reason": "string",
      "status": "string"
    }
  ],
  "error": "string",
  "eta_basis": "string",
  "eta_seconds": 0,
  "filing": {
    "action_taken": "string",
    "collided_with_part_number": "string",
    "existing_part": true,
    "folder_applied": true,
    "folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "part_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "part_number": "BRACKET-001",
    "previous_folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "requested_folder_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  },
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "job_id": "string",
  "queue_position": 0,
  "status": "string"
}