API reference
Calculations
The Calculations API runs a should-cost calculation on a part and returns the priced result.
The Calculations API runs a should-cost calculation on a part and returns the priced result — quote a stored revision, upload-and-quote a file in one call, then list, fetch, or bulk-manage runs.
Auto-generated from the public OpenAPI spec — this page never drifts from the running API. Base URL
https://api.arcnm.io. Authenticate with theX-API-Keyheader (see Authentication).
List Calculations
GET /api/v1/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;limitcaps 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. |
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 \
-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"
}